Browse Source

feat(permission): add dynamic permission managemnent support.

pull/891/head
colin 2 years ago
parent
commit
8378bf3ad7
  1. 40
      apps/vue/src/api/permission-management/definitions/groups/index.ts
  2. 19
      apps/vue/src/api/permission-management/definitions/groups/model/index.ts
  3. 40
      apps/vue/src/api/permission-management/definitions/permissions/index.ts
  4. 36
      apps/vue/src/api/permission-management/definitions/permissions/model/index.ts
  5. 11
      apps/vue/src/api/permission-management/permissions/index.ts
  6. 2
      apps/vue/src/api/permission-management/permissions/model/index.ts
  7. 90
      apps/vue/src/components/Abp/ExtraPropertyDictionary/ExtraPropertyDictionary.vue
  8. 305
      apps/vue/src/components/Abp/SimpleStateChecking/SimpleStateChecking.vue
  9. 6
      apps/vue/src/components/Abp/SimpleStateChecking/index.ts
  10. 151
      apps/vue/src/components/Abp/SimpleStateChecking/src/features/RequireFeaturesSimpleStateChecker.vue
  11. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/features/index.ts
  12. 90
      apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue
  13. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/index.ts
  14. 155
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/RequirePermissionsSimpleStateChecker.vue
  15. 7
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/index.ts
  16. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/typing.ts
  17. 3
      apps/vue/src/components/Abp/SimpleStateChecking/typing.ts
  18. 31
      apps/vue/src/components/Abp/StringValueType/StringValueTypeInput.vue
  19. 10
      apps/vue/src/components/Abp/StringValueType/validator.ts
  20. 1
      apps/vue/src/components/Abp/index.ts
  21. 4
      apps/vue/src/components/Permission/src/hooks/usePermissions.ts
  22. 28
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker.ts
  23. 41
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts
  24. 41
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts
  25. 82
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequirePermissionsSimpleStateChecker.ts
  26. 11
      apps/vue/src/hooks/abp/useAuthorization.ts
  27. 4
      apps/vue/src/hooks/abp/useFeatures.ts
  28. 48
      apps/vue/src/hooks/abp/useGlobalFeatures.ts
  29. 100
      apps/vue/src/hooks/abp/useSimpleStateCheck.ts
  30. 36
      apps/vue/src/locales/lang/en/component.ts
  31. 36
      apps/vue/src/locales/lang/zh-CN/component.ts
  32. 2
      apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionModal.vue
  33. 4
      apps/vue/src/views/feature-management/definitions/groups/datas/TableData.ts
  34. 199
      apps/vue/src/views/permission-management/definitions/groups/components/GroupDefinitionModal.vue
  35. 159
      apps/vue/src/views/permission-management/definitions/groups/components/GroupDefinitionTable.vue
  36. 15
      apps/vue/src/views/permission-management/definitions/groups/datas/ModalData.ts
  37. 35
      apps/vue/src/views/permission-management/definitions/groups/datas/TableData.ts
  38. 16
      apps/vue/src/views/permission-management/definitions/groups/index.vue
  39. 346
      apps/vue/src/views/permission-management/definitions/permissions/components/PermissionDefinitionModal.vue
  40. 262
      apps/vue/src/views/permission-management/definitions/permissions/components/PermissionDefinitionTable.vue
  41. 22
      apps/vue/src/views/permission-management/definitions/permissions/datas/ModalData.ts
  42. 60
      apps/vue/src/views/permission-management/definitions/permissions/datas/TableData.ts
  43. 16
      apps/vue/src/views/permission-management/definitions/permissions/index.vue
  44. 30
      apps/vue/src/views/permission-management/definitions/typing/index.ts
  45. 4
      apps/vue/src/views/settings-management/definitions/datas/TableData.ts
  46. 32
      apps/vue/src/views/webhooks/definitions/webhooks/components/WebhookDefinitionModal.vue
  47. 44
      apps/vue/types/abp.d.ts
  48. 1
      aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application.Contracts/LINGYUN/Abp/FeatureManagement/Localization/Application/Contracts/en.json
  49. 1
      aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application.Contracts/LINGYUN/Abp/FeatureManagement/Localization/Application/Contracts/zh-Hans.json
  50. 32
      aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application/LINGYUN/Abp/FeatureManagement/Definitions/FeatureDefinitionAppService.cs
  51. 11
      aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.HttpApi/LINGYUN/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs
  52. 5
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionCreateOrUpdateDto.cs
  53. 8
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionDto.cs
  54. 6
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionGetListInput.cs
  55. 4
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionGroupDefinitionDto.cs
  56. 6
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionGroupDefinitionGetListInput.cs
  57. 2
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/IPermissionDefinitionAppService.cs
  58. 2
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/IPermissionGroupDefinitionAppService.cs
  59. 29
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Localization/Application/Contracts/en.json
  60. 30
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Localization/Application/Contracts/zh-Hans.json
  61. 6
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/PermissionManagementErrorCodes.cs
  62. 246
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/Definitions/PermissionDefinitionAppService.cs
  63. 143
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/Definitions/PermissionGroupDefinitionAppService.cs
  64. 11
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/AbpPermissionManagementHttpApiModule.cs
  65. 2
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/Definitions/PermissionDefinitionController.cs
  66. 2
      aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/Definitions/PermissionGroupDefinitionController.cs
  67. 3
      aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingDefinitionAppService.cs
  68. 2
      aspnet-core/services/LY.MicroService.Applications.Single/Properties/PublishProfiles/FolderProfile1.pubxml.user

40
apps/vue/src/api/permission-management/definitions/groups/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
PermissionGroupDefinitionDto,
PermissionGroupDefinitionCreateDto,
PermissionGroupDefinitionUpdateDto,
PermissionGroupDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: PermissionGroupDefinitionCreateDto) => {
return defHttp.post<PermissionGroupDefinitionDto>({
url: '/api/permission-management/definitions/groups',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/permission-management/definitions/groups/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<PermissionGroupDefinitionDto>({
url: `/api/permission-management/definitions/groups/${name}`,
});
};
export const GetListAsyncByInput = (input: PermissionGroupDefinitionGetListInput) => {
return defHttp.get<ListResultDto<PermissionGroupDefinitionDto>>({
url: '/api/permission-management/definitions/groups',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: PermissionGroupDefinitionUpdateDto) => {
return defHttp.put<PermissionGroupDefinitionDto>({
url: `/api/permission-management/definitions/groups/${name}`,
data: input,
});
};

19
apps/vue/src/api/permission-management/definitions/groups/model/index.ts

@ -0,0 +1,19 @@
interface PermissionGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
}
export interface PermissionGroupDefinitionCreateDto extends PermissionGroupDefinitionCreateOrUpdateDto {
name: string;
}
export interface PermissionGroupDefinitionDto extends IHasExtraProperties {
name: string;
displayName: string;
isStatic: boolean;
}
export interface PermissionGroupDefinitionGetListInput {
filter?: string;
}
export type PermissionGroupDefinitionUpdateDto = PermissionGroupDefinitionCreateOrUpdateDto;

40
apps/vue/src/api/permission-management/definitions/permissions/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
PermissionDefinitionDto,
PermissionDefinitionCreateDto,
PermissionDefinitionUpdateDto,
PermissionDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: PermissionDefinitionCreateDto) => {
return defHttp.post<PermissionDefinitionDto>({
url: '/api/permission-management/definitions',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/permission-management/definitions/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<PermissionDefinitionDto>({
url: `/api/permission-management/definitions/${name}`,
});
};
export const GetListAsyncByInput = (input: PermissionDefinitionGetListInput) => {
return defHttp.get<ListResultDto<PermissionDefinitionDto>>({
url: '/api/permission-management/definitions',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: PermissionDefinitionUpdateDto) => {
return defHttp.put<PermissionDefinitionDto>({
url: `/api/permission-management/definitions/${name}`,
data: input,
});
};

36
apps/vue/src/api/permission-management/definitions/permissions/model/index.ts

@ -0,0 +1,36 @@
export enum MultiTenancySides {
Tenant = 0x1,
Host = 0x2,
Both = 0x3,
}
interface PermissionDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
parentName?: string;
isEnabled: boolean;
providers: string[];
stateCheckers: string;
}
export interface PermissionDefinitionCreateDto extends PermissionDefinitionCreateOrUpdateDto {
groupName: string;
name: string;
}
export interface PermissionDefinitionDto extends IHasExtraProperties {
groupName: string;
name: string;
displayName: string;
parentName?: string;
isEnabled: boolean;
isStatic: boolean;
providers: string[];
stateCheckers: string;
}
export interface PermissionDefinitionGetListInput {
filter?: string;
groupName?: string;
}
export type PermissionDefinitionUpdateDto = PermissionDefinitionCreateOrUpdateDto;

11
apps/vue/src/api/permission-management/permission.ts → apps/vue/src/api/permission-management/permissions/index.ts

@ -1,21 +1,16 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { PermissionProvider, PermissionResult, UpdatePermissions } from './model/permissionModel';
enum Api {
Get = '/api/permission-management/permissions',
Update = '/api/permission-management/permissions',
}
import { PermissionProvider, PermissionResult, UpdatePermissions } from './model';
export const get = (provider: PermissionProvider) => {
return defAbpHttp.get<PermissionResult>({
url: Api.Get,
url: '/api/permission-management/permissions',
params: provider,
});
};
export const update = (provider: PermissionProvider, input: UpdatePermissions) => {
return defAbpHttp.put<void>({
url: Api.Update,
url: '/api/permission-management/permissions',
data: input,
params: provider,
});

2
apps/vue/src/api/permission-management/model/permissionModel.ts → apps/vue/src/api/permission-management/permissions/model/index.ts

@ -1,4 +1,4 @@
import { IPermission } from '../../model/baseModel';
import { IPermission } from '/@/api/model/baseModel';
export class PermissionProvider {
providerName!: string;

90
apps/vue/src/components/Abp/ExtraPropertyDictionary/ExtraPropertyDictionary.vue

@ -5,7 +5,7 @@
<Button v-if="props.allowDelete" danger @click="handleClean">{{ t('component.extra_property_dictionary.actions.clean') }}</Button>
</div>
<Card :title="t('component.extra_property_dictionary.title')">
<Table v-bind="state.table">
<Table sticky rowKey="key" :columns="getTableColumns" :data-source="state.table.dataSource" :scroll="{ x: 1500 }">
<template v-if="!props.disabled" #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<div :class="`${prefixCls}__action`">
@ -47,11 +47,11 @@
</template>
<script lang="ts" setup>
import type { TableProps } from 'ant-design-vue';
import type { RuleObject } from 'ant-design-vue/lib/form';
import type { ColumnsType } from 'ant-design-vue/lib/table/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, ref, unref, watch } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { nextTick, reactive, ref, unref, watch } from 'vue';
import { Button, Card, Divider, Form, Input, Table, Modal } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
@ -59,9 +59,12 @@
const FormItem = Form.Item;
interface DataSource {
key: string;
value: string;
}
interface State {
editFlag: boolean,
table: TableProps,
modal: {
title?: string,
visible?: boolean,
@ -75,6 +78,9 @@
model: any,
rules?: Dictionary<string, RuleObject>,
},
table: {
dataSource: DataSource[],
},
}
const emits = defineEmits(['change', 'update:value']);
@ -90,6 +96,32 @@
const { prefixCls } = useDesign('extra-property-dictionary');
const { t } = useI18n();
const formRef = ref<any>();
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.extra_property_dictionary.key'),
dataIndex: 'key',
align: 'left',
fixed: 'left',
width: 180,
},
{
title: t('component.extra_property_dictionary.value'),
dataIndex: 'value',
align: 'left',
fixed: 'left',
width: 'auto',
}];
return columns.concat(props.disabled
? []
: [{
width: 220,
title: t('component.extra_property_dictionary.actions.title'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}]);
});
const state = reactive<State>({
editFlag: false,
modal: {
@ -114,29 +146,8 @@
},
},
table: {
sticky: true,
rowKey: "key",
columns: [
{
title: t('component.extra_property_dictionary.key'),
dataIndex: 'key',
align: 'left',
fixed: 'left',
width: 180,
},
{
title: t('component.extra_property_dictionary.value'),
dataIndex: 'value',
align: 'left',
fixed: 'left',
width: 'auto',
}
],
dataSource: [],
scroll: {
x: 1500,
},
}
},
});
watch(
() => props.value,
@ -156,33 +167,6 @@
immediate: true,
}
);
watch(
() => [props.allowEdit, props.allowDelete],
([allowEdit, allowDelete]) => {
if (allowEdit || allowDelete) {
nextTick(() => {
state.table.columns!.push({
width: 220,
title: t('component.extra_property_dictionary.actions.title'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
});
});
} else {
nextTick(() => {
const columns = state.table.columns ?? [];
const findIndex = columns.findIndex(x => x.key === 'action');
columns.splice(findIndex, 1);
state.table.columns = columns;
});
}
},
{
immediate: true,
}
)
function handleAddNew() {
state.form.model = {};

305
apps/vue/src/components/Abp/SimpleStateChecking/SimpleStateChecking.vue

@ -0,0 +1,305 @@
<template>
<div :class="`${prefixCls}__container`">
<div class="card">
<Card>
<template #title>
<Row>
<Col :span="12">
<span>{{ t('component.simple_state_checking.title') }}</span>
</Col>
<Col :span="12">
<div class="toolbar" v-if="!props.disabled">
<Button type="primary" @click="handleAddNew">{{ t('component.simple_state_checking.actions.create') }}</Button>
<Button danger @click="handleClean">{{ t('component.simple_state_checking.actions.clean') }}</Button>
</div>
</Col>
</Row>
</template>
<Table :columns="getTableColumns" :data-source="getSimpleCheckers">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<span>{{ simpleCheckerMap[record.name] }}</span>
</template>
<template v-else-if="column.key === 'properties'">
<div v-if="record.name === 'F'">
<Tag v-for="feature in record.featureNames">{{ feature }}</Tag>
</div>
<div v-else-if="record.name === 'G'">
<Tag v-for="feature in record.globalFeatureNames">{{ feature }}</Tag>
</div>
<div v-else-if="record.name === 'P'">
<Tag v-for="permission in record.model.permissions">{{ permission }}</Tag>
</div>
<div v-else-if="record.name === 'A'">
<span>{{ t('component.simple_state_checking.requireAuthenticated.title') }}</span>
</div>
</template>
<template v-else-if="column.key === 'action'">
<div :class="`${prefixCls}__action`">
<Button v-if="props.allowEdit" type="link" @click="() => handleEdit(record)">
<template #icon>
<EditOutlined />
</template>
{{ t('component.simple_state_checking.actions.update') }}
</Button>
<Divider v-if="props.allowEdit && props.allowDelete" type="vertical" />
<Button v-if="props.allowDelete" type="link" @click="() => handleDelete(record)" class="ant-btn-error">
<template #icon>
<DeleteOutlined />
</template>
{{ t('component.simple_state_checking.actions.delete') }}
</Button>
</div>
</template>
</template>
</Table>
</Card>
</div>
<Modal :class="`${prefixCls}__modal`" v-bind="state.modal">
<Form
ref="formRef"
class="form"
v-bind="state.form"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16}"
>
<FormItem name="name" required :label="t('component.simple_state_checking.form.name')">
<Select
:disabled="state.form.editFlag"
:options="getStateCheckerOptions"
v-model:value="state.form.model.name"
@change="handleStateCheckerChange"
/>
</FormItem>
<component :is="componentsRef[state.form.editComponent]" v-model:value="state.form.model.stateChecker" />
</Form>
</Modal>
</div>
</template>
<script setup lang="ts">
import type { RuleObject } from 'ant-design-vue/lib/form';
import type { ColumnsType } from 'ant-design-vue/lib/table';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, ref, unref, shallowRef, shallowReactive } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, Card, Col, Divider, Empty, Form, Modal, Row, Select, Table, Tag } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { isNullOrUnDef } from '/@/utils/is';
import { SimplaCheckStateBase } from './typing';
import { useSimpleStateCheck } from '/@/hooks/abp/useSimpleStateCheck';
import { propTypes } from '/@/utils/propTypes';
import RequireGlobalFeaturesSimpleStateChecker from './src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue';
import RequirePermissionsSimpleStateChecker from './src/permissions/RequirePermissionsSimpleStateChecker.vue';
import RequireFeaturesSimpleStateChecker from './src/features/RequireFeaturesSimpleStateChecker.vue';
const FormItem = Form.Item;
interface State {
modal: {
title?: string,
visible?: boolean,
maskClosable?: boolean,
width?: number,
minHeight?: number,
onOk?: (e: MouseEvent) => void,
onCancel?: (e: MouseEvent) => void,
},
form: {
model: any,
editFlag: boolean,
editComponent: string,
rules?: Dictionary<string, RuleObject>,
},
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: propTypes.string,
state: {
type: Object as PropType<SimplaCheckStateBase>,
required: true,
},
allowEdit: propTypes.bool.def(false),
allowDelete: propTypes.bool.def(false),
disabled: propTypes.bool.def(false),
});
const { t } = useI18n();
const { serializer } = useSimpleStateCheck();
const { prefixCls } = useDesign('simple-state-checking');
const formRef = ref<any>();
const componentsRef = shallowRef({
'Empty': Empty,
'F': RequireFeaturesSimpleStateChecker,
'P': RequirePermissionsSimpleStateChecker,
'G': RequireGlobalFeaturesSimpleStateChecker,
});
const simpleCheckerMap = shallowReactive({
'F': t('component.simple_state_checking.requireFeatures.title'),
'G': t('component.simple_state_checking.requireGlobalFeatures.title'),
'P': t('component.simple_state_checking.requirePermissions.title'),
'A': t('component.simple_state_checking.requireAuthenticated.title')
});
const state = reactive<State>({
modal: {
width: 600,
minHeight: 400,
visible: false,
maskClosable: false,
onOk: handleSubmit,
onCancel: handleCancel,
},
form: {
model: {},
editFlag: false,
editComponent: 'Empty',
},
});
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.simple_state_checking.table.name'),
dataIndex: 'name',
key: 'name',
align: 'left',
fixed: 'left',
width: 130,
},
{
title: t('component.simple_state_checking.table.properties'),
dataIndex: 'properties',
key: 'properties',
align: 'left',
fixed: 'left',
width: 200,
}];
return columns.concat(props.disabled
? []
: [{
width: 230,
title: t('component.simple_state_checking.table.actions'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}]);
});
const getSimpleCheckers = computed(() => {
if (isNullOrUnDef(props.value) || props.value.length === 0) {
return [];
}
const simpleCheckers = serializer.deserializeArray(props.value, props.state);
return simpleCheckers;
});
const getStateCheckerOptions = computed(() => {
const stateCheckers = unref(getSimpleCheckers);
return Object.keys(simpleCheckerMap).map((key) => {
return {
label: simpleCheckerMap[key],
disabled: stateCheckers.some(x => (x as any).name === key),
value: key,
}
});
});
function handleAddNew() {
state.form.editFlag = false;
state.form.editComponent = 'Empty';
state.form.model = {};
state.modal.title = t('component.simple_state_checking.title');
state.modal.visible = true;
}
function handleStateCheckerChange(value: string) {
const stateChecker = serializer.deserialize({
T: value,
A: true,
N: [],
}, props.state);
state.form.model.stateChecker = stateChecker;
state.form.editComponent = value;
}
function handleEdit(record) {
state.form.editFlag = true;
state.form.editComponent = record.name;
state.form.model = {
name: record.name,
stateChecker: cloneDeep(record),
};
state.modal.title = simpleCheckerMap[record.name];
state.modal.visible = true;
}
function handleDelete(record) {
const stateCheckers = unref(getSimpleCheckers);
const filtedStateCheckers = stateCheckers.filter(x => (x as any).name !== record.name);
if (filtedStateCheckers.length === 0) {
handleClean();
return;
}
const serializedCheckers = serializer.serializeArray(filtedStateCheckers);
emits('change', serializedCheckers);
emits('update:value', serializedCheckers);
}
function handleClean() {
emits('change', undefined);
emits('update:value', undefined);
}
function handleCancel() {
state.form.model = {};
state.form.editFlag = false;
state.form.editComponent = 'Empty';
state.modal.visible = false;
}
function handleSubmit() {
const form = unref(formRef);
form?.validate().then(() => {
const input = cloneDeep(state.form.model.stateChecker);
const stateCheckers = cloneDeep(unref(getSimpleCheckers));
const inputIndex = stateCheckers.findIndex(x => (x as any).name === input.name);
if (inputIndex >= 0) {
stateCheckers[inputIndex] = input;
} else {
stateCheckers.push(input);
}
const updateValue = serializer.serializeArray(stateCheckers);
emits('change', updateValue);
emits('update:value', updateValue);
form?.resetFields();
state.modal.visible = false;
});
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-simple-state-checking';
.@{prefix-cls} {
&__container {
width: 100%;
.card {
.toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
> * {
margin-right: 8px;
}
}
}
}
&__modal {
.form {
margin: 20px;
}
}
}
</style>

6
apps/vue/src/components/Abp/SimpleStateChecking/index.ts

@ -0,0 +1,6 @@
import SimpleStateChecking from './SimpleStateChecking.vue';
import { SimplaCheckStateBase } from './typing';
import { PermissionState } from './src/permissions';
export { SimplaCheckStateBase, PermissionState };
export { SimpleStateChecking };

151
apps/vue/src/components/Abp/SimpleStateChecking/src/features/RequireFeaturesSimpleStateChecker.vue

@ -0,0 +1,151 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'requiresAll']"
:label="t('component.simple_state_checking.requireFeatures.requiresAll')"
:extra="t('component.simple_state_checking.requireFeatures.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem :name="['stateChecker', 'featureNames']" required :label="t('component.simple_state_checking.requireFeatures.featureNames')">
<TreeSelect
allow-clear
tree-checkable
tree-check-strictly
:tree-data="state.treeData"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
:value="getRequiredFeatures"
@change="handleChangeFeatures"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted, watchEffect } from 'vue';
import { Checkbox, Form, TreeSelect } from 'ant-design-vue';
import { GetListAsyncByInput } from '/@/api/feature-management/definitions/features';
import { FeatureDefinitionDto } from '/@/api/feature-management/definitions/features/model';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useI18n } from '/@/hooks/web/useI18n';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
import { valueTypeSerializer } from '../../../StringValueType/valueType';
const FormItem = Form.Item;
interface FeatureTreeData {
name: string;
groupName: string;
displayName: string;
children: FeatureTreeData[];
}
interface StateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
interface State {
features: FeatureDefinitionDto[];
treeData: FeatureTreeData[];
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const state = reactive<State>({
treeData: [],
features: [],
stateChecker: {
name: 'F',
requiresAll: true,
featureNames: [],
}
});
const getRequiredFeatures = computed(() => {
return state.features
.filter((feature) => state.stateChecker.featureNames.includes(feature.name))
.map((feature) => {
return {
label: feature.displayName,
value: feature.name,
};
});
});
watchEffect(() => {
state.stateChecker = props.value;
});
onMounted(fetchFeatures);
function fetchFeatures() {
GetListAsyncByInput({}).then((res) => {
state.features = res.items;
formatDisplayName(state.features);
const featureGroup = groupBy(cloneDeep(res.items), 'groupName');
const featureTreeData: FeatureTreeData[] = [];
Object.keys(featureGroup).forEach((gk) => {
const featuresByGroup = featureGroup[gk].filter(feature => {
const valueType = valueTypeSerializer.deserialize(feature.valueType);
return valueType.validator.name === 'BOOLEAN';
});
const featureTree = listToTree(featuresByGroup, {
id: 'name',
pid: 'parentName',
});
featureTreeData.push(...featureTree);
});
state.treeData = featureTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangeFeatures(value: any[]) {
state.stateChecker.featureNames = value.map((val) => val.value);
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

3
apps/vue/src/components/Abp/SimpleStateChecking/src/features/index.ts

@ -0,0 +1,3 @@
import RequireFeaturesSimpleStateChecker from './RequireFeaturesSimpleStateChecker.vue';
export { RequireFeaturesSimpleStateChecker };

90
apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue

@ -0,0 +1,90 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'requiresAll']"
:label="t('component.simple_state_checking.requireFeatures.requiresAll')"
:extra="t('component.simple_state_checking.requireFeatures.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem
:name="['stateChecker', 'featureNames']"
required
:label="t('component.simple_state_checking.requireGlobalFeatures.featureNames')"
>
<TextArea
allow-clear
:value="getRequiredFeatures"
@change="(e) => handleChangeFeatures(e.target.value)"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { computed, reactive, watchEffect } from 'vue';
import { Checkbox, Form, Input } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
const FormItem = Form.Item;
const TextArea = Input.TextArea;
interface StateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
interface State {
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const state = reactive<State>({
stateChecker: {
name: 'G',
requiresAll: true,
featureNames: [],
}
});
const getRequiredFeatures = computed(() => {
let features = state.stateChecker.featureNames.join(',');
return features.length > 0 ? features.substring(0, features.length - 1) : features;
});
watchEffect(() => {
state.stateChecker = props.value;
});
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangeFeatures(value?: string) {
if (!value) {
state.stateChecker.featureNames = [];
return;
}
state.stateChecker.featureNames = value.split(',');
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

3
apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/index.ts

@ -0,0 +1,3 @@
import RequireGlobalFeaturesSimpleStateChecker from './RequireGlobalFeaturesSimpleStateChecker.vue';
export { RequireGlobalFeaturesSimpleStateChecker };

155
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/RequirePermissionsSimpleStateChecker.vue

@ -0,0 +1,155 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'model', 'featureNames']"
:label="t('component.simple_state_checking.requirePermissions.requiresAll')"
:extra="t('component.simple_state_checking.requirePermissions.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.model.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requirePermissions.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem
:name="['stateChecker', 'model', 'permissions']"
required
:label="t('component.simple_state_checking.requirePermissions.permissions')"
>
<TreeSelect
allow-clear
tree-checkable
tree-check-strictly
:tree-data="state.treeData"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
:value="getRequiredPermissions"
@change="handleChangePermissions"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted, watchEffect } from 'vue';
import { Checkbox, Form, TreeSelect } from 'ant-design-vue';
import { GetListAsyncByInput } from '/@/api/permission-management/definitions/permissions';
import { PermissionDefinitionDto } from '/@/api/permission-management/definitions/permissions/model';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useI18n } from '/@/hooks/web/useI18n';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
const FormItem = Form.Item;
interface TreeData {
name: string;
groupName: string;
displayName: string;
children: TreeData[];
}
interface StateCheckerModel {
requiresAll: boolean;
permissions: string[];
}
interface StateChecker {
name: string;
model: StateCheckerModel;
}
interface State {
permissions: PermissionDefinitionDto[];
treeData: TreeData[];
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const state = reactive<State>({
treeData: [],
permissions: [],
stateChecker: {
name: 'P',
model: {
requiresAll: true,
permissions: [],
},
}
});
const getRequiredPermissions = computed(() => {
return state.permissions
.filter((permission) => state.stateChecker.model.permissions.includes(permission.name))
.map((permission) => {
return {
label: permission.displayName,
value: permission.name,
};
});
});
watchEffect(() => {
state.stateChecker = props.value;
});
onMounted(fetchFeatures);
function fetchFeatures() {
GetListAsyncByInput({}).then((res) => {
state.permissions = res.items;
formatDisplayName(state.permissions);
const permissionGroup = groupBy(cloneDeep(res.items), 'groupName');
const permissionGroupTreeData: TreeData[] = [];
Object.keys(permissionGroup).forEach((gk) => {
const featureTree = listToTree(permissionGroup[gk], {
id: 'name',
pid: 'parentName',
});
permissionGroupTreeData.push(...featureTree);
});
state.treeData = permissionGroupTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.model.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangePermissions(value: any[]) {
state.stateChecker.model.permissions = value.map((val) => val.value);
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

7
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/index.ts

@ -0,0 +1,7 @@
import RequirePermissionsSimpleStateChecker from './RequirePermissionsSimpleStateChecker.vue';
import { PermissionState } from './typing';
export {
RequirePermissionsSimpleStateChecker,
PermissionState,
};

3
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/typing.ts

@ -0,0 +1,3 @@
export class PermissionState implements IHasSimpleStateCheckers<PermissionState> {
stateCheckers: ISimpleStateChecker<PermissionState>[] = [];
}

3
apps/vue/src/components/Abp/SimpleStateChecking/typing.ts

@ -0,0 +1,3 @@
export class SimplaCheckStateBase implements IHasSimpleStateCheckers<SimplaCheckStateBase> {
stateCheckers: ISimpleStateChecker<SimplaCheckStateBase>[] = [];
}

31
apps/vue/src/components/Abp/StringValueType/StringValueTypeInput.vue

@ -17,7 +17,7 @@
</Row>
<Row>
<Col :span="11">
<Select :value="state.valueType.name" @change="handleValueTypeChange">
<Select :disabled="props.disabled" :value="state.valueType.name" @change="handleValueTypeChange">
<Option value="FreeTextStringValueType">{{ t('component.value_type_nput.type.FREE_TEXT.name') }}</Option>
<Option value="ToggleStringValueType">{{ t('component.value_type_nput.type.TOGGLE.name') }}</Option>
<Option value="SelectionStringValueType">{{ t('component.value_type_nput.type.SELECTION.name') }}</Option>
@ -27,7 +27,7 @@
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<Select :value="state.valueType.validator.name" @change="handleValidatorChange">
<Select :disabled="props.disabled" :value="state.valueType.validator.name" @change="handleValidatorChange">
<Option value="NULL">{{ t('component.value_type_nput.validator.NULL.name') }}</Option>
<Option value="BOOLEAN" :disabled="state.valueType.name !== 'ToggleStringValueType'">
{{ t('component.value_type_nput.validator.BOOLEAN.name') }}
@ -62,6 +62,7 @@
<Row>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as NumericValueValidator).minValue"
/>
@ -71,6 +72,7 @@
</Col>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as NumericValueValidator).maxValue"
/>
@ -81,6 +83,7 @@
<Row style="margin-top: 10px;">
<Col :span="24">
<Checkbox
:disabled="props.disabled"
style="width: 100%"
v-model:checked="(state.valueType.validator as StringValueValidator).allowNull"
>
@ -96,6 +99,7 @@
<Row>
<Col :span="24">
<Input
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).regularExpression"
/>
@ -115,6 +119,7 @@
<Row>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).minLength"
/>
@ -124,6 +129,7 @@
</Col>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).maxLength"
/>
@ -148,7 +154,7 @@
</Col>
</Row>
</template>
<Table :columns="tableColumns" :data-source="(state.valueType as SelectionStringValueType).itemSource.items">
<Table :columns="getTableColumns" :data-source="(state.valueType as SelectionStringValueType).itemSource.items">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayText'">
<span>{{ getDisplayName(record.displayText) }}</span>
@ -188,10 +194,10 @@
:wrapper-col="{ span: 18}"
>
<FormItem name="displayText" required :label="t('component.value_type_nput.type.SELECTION.displayText')">
<LocalizableInput :disabled="state.selection.form.editFlag" v-model:value="state.selection.form.model.displayText" />
<LocalizableInput :disabled="props.disabled || state.selection.form.editFlag" v-model:value="state.selection.form.model.displayText" />
</FormItem>
<FormItem name="value" required :label="t('component.value_type_nput.type.SELECTION.value')">
<Input v-model:value="state.selection.form.model.value" />
<Input :disabled="props.disabled" v-model:value="state.selection.form.model.value" />
</FormItem>
</Form>
</Modal>
@ -201,7 +207,7 @@
<script setup lang="ts">
import type { ColumnsType } from 'ant-design-vue/lib/table';
import type { RuleObject } from 'ant-design-vue/lib/form';
import { reactive, ref, unref, watch } from 'vue';
import { computed, reactive, ref, unref, watch } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import {
Button,
@ -317,7 +323,8 @@
},
},
});
const tableColumns = reactive<ColumnsType>([{
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.value_type_nput.type.SELECTION.displayText'),
dataIndex: 'displayText',
key: 'displayText',
@ -332,16 +339,18 @@
align: 'left',
fixed: 'left',
width: 200,
},
{
}];
return columns.concat(props.disabled
? []
: [{
width: 180,
title: t('component.value_type_nput.type.SELECTION.actions.title'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}
]);
}]);
});
watch(
() => props.value,
(value) => {

10
apps/vue/src/components/Abp/StringValueType/validator.ts

@ -26,7 +26,7 @@ export class BooleanValueValidator implements ValueValidator {
this.properties = {};
}
isValid(value?: any): boolean {
if (value === undefined) return true;
if (isNullOrUnDef(value)) return true;
if (isBoolean(value)) return true;
const bolString = String(value).toLowerCase();
if (bolString === 'true' || bolString === 'false') return true;
@ -58,7 +58,7 @@ export class NumericValueValidator implements ValueValidator {
}
isValid(value?: any): boolean {
if (!value) return true;
if (isNullOrUnDef(value)) return true;
if (isNumber(value)) return this._isValidInternal(value);
const numString = String(value);
if (!isNullOrUnDef(numString)) {
@ -115,15 +115,9 @@ export class StringValueValidator implements ValueValidator {
}
isValid(value?: any): boolean {
console.log(value);
console.log(this.allowNull);
if (!this.allowNull && isNullOrUnDef(value)) return false;
const valueString = String(value);
console.log(valueString);
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false;
console.log(valueString.length);
console.log(this.maxLength);
console.log(this.maxLength);
if (this.minLength && this.minLength > 0 && valueString.length < this.minLength) return false;
if (this.maxLength && this.maxLength > 0 && valueString.length > this.maxLength) return false;
if (!isNullOrWhiteSpace(this.regularExpression)) {

1
apps/vue/src/components/Abp/index.ts

@ -1,4 +1,5 @@
export * from './ExtraPropertyDictionary';
export * from './FeatureModal';
export * from './LocalizableInput';
export * from './SimpleStateChecking';
export * from './StringValueType';

4
apps/vue/src/components/Permission/src/hooks/usePermissions.ts

@ -3,8 +3,8 @@ import type { PermissionProps, PermissionTree } from '../types/permission';
import { computed, watch, unref, ref } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { get, update } from '/@/api/permission-management/permission';
import { PermissionProvider } from '/@/api/permission-management/model/permissionModel';
import { get, update } from '/@/api/permission-management/permissions';
import { PermissionProvider } from '/@/api/permission-management/permissions/model';
import {
generatePermissionTree,
getGrantedPermissionKeys,

28
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker.ts

@ -0,0 +1,28 @@
import { useAbpStoreWithOut } from '/@/store/modules/abp';
export interface RequireAuthenticatedStateChecker {
name: string;
}
export class RequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireAuthenticatedStateChecker, ISimpleStateChecker<TState> {
name = "A";
_currentUser: CurrentUser;
constructor(currentUser: CurrentUser) {
this._currentUser = currentUser;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._currentUser.isAuthenticated;
}
serialize(): string {
return JSON.stringify({
"T": this.name,
});
}
}
export function useRequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(): ISimpleStateChecker<TState> {
const abpStore = useAbpStoreWithOut();
const { currentUser } = abpStore.getApplication;
return new RequireAuthenticatedSimpleStateChecker<TState>(currentUser);
}

41
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts

@ -0,0 +1,41 @@
import { IFeatureChecker, useFeatures } from '/@/hooks/abp/useFeatures';
export interface RequireFeaturesStateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
export class RequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireFeaturesStateChecker, ISimpleStateChecker<TState> {
name: string = 'F';
_featureChecker: IFeatureChecker;
featureNames: string[];
requiresAll: boolean;
constructor(
featureChecker: IFeatureChecker,
featureNames: string[],
requiresAll: boolean = false) {
this._featureChecker = featureChecker;
this.featureNames = featureNames;
this.requiresAll = requiresAll;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._featureChecker.isEnabled(this.featureNames, this.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.requiresAll,
"N": this.featureNames,
});
}
}
export function useRequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
featureNames: string[],
requiresAll: boolean = false,
): ISimpleStateChecker<TState> {
const { featureChecker } = useFeatures();
return new RequireFeaturesSimpleStateChecker(featureChecker, featureNames, requiresAll);
}

41
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts

@ -0,0 +1,41 @@
import { IGlobalFeatureChecker, useGlobalFeatures } from '/@/hooks/abp/useGlobalFeatures';
export interface RequireGlobalFeaturesStateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
export class RequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireGlobalFeaturesStateChecker, ISimpleStateChecker<TState> {
name: string = 'G';
_globalFeatureChecker: IGlobalFeatureChecker;
featureNames: string[];
requiresAll: boolean;
constructor(
globalFeatureChecker: IGlobalFeatureChecker,
featureNames: string[],
requiresAll: boolean = false) {
this._globalFeatureChecker = globalFeatureChecker;
this.featureNames = featureNames;
this.requiresAll = requiresAll;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._globalFeatureChecker.isEnabled(this.featureNames, this.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.requiresAll,
"N": this.featureNames,
});
}
}
export function useRequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
featureNames: string[],
requiresAll: boolean = false,
): ISimpleStateChecker<TState> {
const globalFeatureChecker = useGlobalFeatures();
return new RequireGlobalFeaturesSimpleStateChecker(globalFeatureChecker, featureNames, requiresAll);
}

82
apps/vue/src/hooks/abp/SimpleStateChecking/useRequirePermissionsSimpleStateChecker.ts

@ -0,0 +1,82 @@
import { PermissionChecker, useAuthorization } from '/@/hooks/abp/useAuthorization';
export interface RequirePermissionsStateChecker<TState extends IHasSimpleStateCheckers<TState>> {
name: string;
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
}
export class RequirePermissionsSimpleBatchStateCheckerModel<TState extends IHasSimpleStateCheckers<TState>> {
state: TState;
requiresAll: boolean;
permissions: string[];
constructor(
state: TState,
permissions: string[],
requiresAll: boolean = true,
) {
this.state = state;
this.permissions = permissions;
this.requiresAll = requiresAll;
}
}
export class RequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequirePermissionsStateChecker<TState>, ISimpleStateChecker<TState> {
name: string = 'P';
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
_permissionChecker: PermissionChecker;
constructor(
permissionChecker: PermissionChecker,
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>) {
this.model = model;
this._permissionChecker = permissionChecker;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._permissionChecker.isGranted(this.model.permissions, this.model.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.model.requiresAll,
"N": this.model.permissions,
});
}
}
export class RequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements ISimpleBatchStateChecker<TState> {
name: string = 'P';
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[];
_permissionChecker: PermissionChecker;
constructor(
permissionChecker: PermissionChecker,
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]) {
this.models = models;
this._permissionChecker = permissionChecker;
}
isEnabled(context: SimpleBatchStateCheckerContext<TState>) {
const result: SimpleStateCheckerResult<TState> = {};
context.states.forEach((state) => {
const model = this.models.find(x => x.state === state);
if (model) {
result[model.state as TState] = this._permissionChecker.isGranted(model.permissions, model.requiresAll);
}
});
return result;
}
serialize(): string | undefined {
return undefined;
}
}
export function useRequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>): ISimpleStateChecker<TState> {
const permissionChecker = useAuthorization();
return new RequirePermissionsSimpleStateChecker<TState>(permissionChecker, model);
}
export function useRequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]): ISimpleBatchStateChecker<TState> {
const permissionChecker = useAuthorization();
return new RequirePermissionsSimpleBatchStateChecker<TState>(permissionChecker, models);
}

11
apps/vue/src/hooks/abp/useAuthorization.ts

@ -1,8 +1,8 @@
import { computed } from 'vue';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
interface PermissionChecker {
isGranted(name: string | string[]): boolean;
export interface PermissionChecker {
isGranted(name: string | string[], requiresAll?: boolean): boolean;
authorize(name: string | string[]): void;
}
@ -12,10 +12,13 @@ export function useAuthorization(): PermissionChecker {
return abpStore.getApplication.auth.grantedPolicies ?? {};
});
function isGranted(name: string | string[]): boolean {
function isGranted(name: string | string[], requiresAll?: boolean): boolean {
const grantedPolicies = getGrantedPolicies.value;
if (Array.isArray(name)) {
return name.every((name) => grantedPolicies[name]);
if (requiresAll === undefined || requiresAll === true) {
return name.every((name) => grantedPolicies[name]);
}
return name.some((name) => grantedPolicies[name]);
}
return grantedPolicies[name]
}

4
apps/vue/src/hooks/abp/useFeatures.ts

@ -6,7 +6,7 @@ type FeatureValue = NameValue<string>;
/**
*
*/
interface IFeatureChecker {
export interface IFeatureChecker {
/**
*
* @param featureNames
@ -51,7 +51,7 @@ export function useFeatures() {
isEnabled(featureNames: string | string[], requiresAll?: boolean) {
if (Array.isArray(featureNames)) {
if (featureNames.length === 0) return true;
if (!requiresAll || requiresAll === true) {
if (requiresAll === undefined || requiresAll === true) {
for (let index = 0; index < featureNames.length; index++) {
if (!_isEnabled(featureNames[index])) return false;
}

48
apps/vue/src/hooks/abp/useGlobalFeatures.ts

@ -0,0 +1,48 @@
import { computed } from 'vue';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { isNullOrWhiteSpace } from '/@/utils/strings';
export interface IGlobalFeatureChecker {
isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean;
}
export function useGlobalFeatures() {
const getGlobalFeatures = computed(() => {
const abpStore = useAbpStoreWithOut();
const enabledFeatures = abpStore.getApplication.globalFeatures.enabledFeatures ?? [];
return enabledFeatures;
});
function get(name: string): string | undefined {
return getGlobalFeatures.value.find((feature) => name === feature);
}
function _isEnabled(name: string): boolean {
var feature = get(name);
return !isNullOrWhiteSpace(feature);
}
function isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean {
if (Array.isArray(featureNames)) {
if (featureNames.length === 0) return true;
if (requiresAll === undefined || requiresAll === true) {
for (let index = 0; index < featureNames.length; index++) {
if (!_isEnabled(featureNames[index])) return false;
}
return true;
}
for (let index = 0; index < featureNames.length; index++) {
if (_isEnabled(featureNames[index])) return true;
}
} else {
return _isEnabled(featureNames);
}
return false;
}
return {
isEnabled,
};
}

100
apps/vue/src/hooks/abp/useSimpleStateCheck.ts

@ -0,0 +1,100 @@
import {
useRequireAuthenticatedSimpleStateChecker
} from "./SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker";
import {
useRequirePermissionsSimpleStateChecker
} from "./SimpleStateChecking/useRequirePermissionsSimpleStateChecker";
import {
useRequireFeaturesSimpleStateChecker
} from "./SimpleStateChecking/useRequireFeaturesSimpleStateChecker";
import {
useRequireGlobalFeaturesSimpleStateChecker
} from "./SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker";
import { isNullOrUnDef } from "/@/utils/is";
import { isNullOrWhiteSpace } from "/@/utils/strings";
class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
serialize<TState extends IHasSimpleStateCheckers<TState>>(checker: ISimpleStateChecker<TState>): string | undefined {
return checker.serialize();
}
serializeArray<TState extends IHasSimpleStateCheckers<TState>>(stateCheckers: ISimpleStateChecker<TState>[]): string | undefined {
if (stateCheckers.length === 0) return undefined;
if (stateCheckers.length === 1) {
const single = stateCheckers[0].serialize();
if (isNullOrUnDef(single)) return undefined;
return `[${single}]`;
}
let serializedCheckers: string = '';
stateCheckers.forEach((checker) => {
const serializedChecker = checker.serialize();
if (!isNullOrUnDef(serializedChecker)) {
serializedCheckers += serializedChecker + ',';
}
});
if (serializedCheckers.endsWith(',')) {
serializedCheckers = serializedCheckers.substring(0, serializedCheckers.length - 1);
}
return serializedCheckers.length > 0 ? `[${serializedCheckers}]` : undefined;
}
deserialize<TState extends IHasSimpleStateCheckers<TState>>(jsonObject: any, state: TState): ISimpleStateChecker<TState> | undefined {
if (isNullOrUnDef(jsonObject) || !Reflect.has(jsonObject, 'T')) {
return undefined;
}
switch (String(jsonObject['T'])) {
case 'A':
return useRequireAuthenticatedSimpleStateChecker();
case 'P':
const permissions = jsonObject['N'] as string[];
if (permissions === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequirePermissionsSimpleStateChecker({
permissions: permissions,
requiresAll: jsonObject['A'] === true,
state,
});
case 'F':
const features = jsonObject['N'] as string[];
if (features === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequireFeaturesSimpleStateChecker(
features,
jsonObject['A'] === true,
);
case 'G':
const globalFeatures = jsonObject['N'] as string[];
if (globalFeatures === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequireGlobalFeaturesSimpleStateChecker(
globalFeatures,
jsonObject['A'] === true,
);
default: return undefined;
}
}
deserializeArray<TState extends IHasSimpleStateCheckers<TState>>(value: string, state: TState): ISimpleStateChecker<TState>[] {
if (isNullOrWhiteSpace(value)) return [];
const jsonObject = JSON.parse(value);
if (isNullOrUnDef(jsonObject)) return [];
if (Array.isArray(jsonObject)) {
if (jsonObject.length === 0) return [];
return jsonObject.map((json) => this.deserialize(json, state))
.filter(checker => !isNullOrUnDef(checker))
.map(checker => checker!);
}
const stateChecker = this.deserialize(jsonObject, state);
if (!stateChecker) return [];
return [stateChecker];
}
}
export function useSimpleStateCheck<TState extends IHasSimpleStateCheckers<TState>>() {
return {
serializer: new SimpleStateCheckerSerializer(),
};
}

36
apps/vue/src/locales/lang/en/component.ts

@ -236,5 +236,41 @@ export default {
regularExpression: 'Regular Expression',
},
},
},
simple_state_checking: {
title: 'State checking',
actions: {
create: 'Add',
update: 'Edit',
delete: 'Delete',
clean: 'Clean',
},
table: {
name: 'Name',
properties: 'Properties',
actions: 'Actions',
},
form: {
name: 'State checking',
},
requireAuthenticated: {
title: 'Require Authenticated',
},
requireFeatures: {
title: 'Require Features',
requiresAll: 'Requires All',
requiresAllDesc: 'If checked, all selected features need to be enabled.',
featureNames: 'Required features',
},
requireGlobalFeatures: {
title: 'Require Global Features',
featureNames: 'Required Global Features',
},
requirePermissions: {
title: 'Require Permissions',
requiresAll: 'Requires All',
requiresAllDesc: 'If checked, you need to have all the selected permissions.',
permissions: 'Required Permissions',
},
}
};

36
apps/vue/src/locales/lang/zh-CN/component.ts

@ -239,5 +239,41 @@ export default {
regularExpression: '正则表达式',
},
},
},
simple_state_checking: {
title: '状态检查',
actions: {
create: '新增',
update: '编辑',
delete: '删除',
clean: '清除'
},
table: {
name: '名称',
properties: '属性',
actions: '操作',
},
form: {
name: '状态检查器',
},
requireAuthenticated: {
title: '需要用户认证',
},
requireFeatures: {
title: '检查所需功能',
requiresAll: '要求所有',
requiresAllDesc: '如果勾选,则需要启用所有选择的功能.',
featureNames: '需要的功能',
},
requireGlobalFeatures: {
title: '检查全局功能',
featureNames: '需要的全局功能',
},
requirePermissions: {
title: '检查所需权限',
requiresAll: '要求所有',
requiresAllDesc: '如果勾选,则需要拥有所有选择的权限.',
permissions: '需要的权限',
},
}
};

2
apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionModal.vue

@ -224,7 +224,7 @@
},
}),
defaultValue: ruleCreator.defineValidator({
trigger: 'blur',
trigger: 'change',
validator(_rule, value) {
const valueType = unref(valueTypeRef);
if (valueType) {

4
apps/vue/src/views/feature-management/definitions/groups/datas/TableData.ts

@ -10,7 +10,7 @@ export function getDataColumns(): BasicColumn[] {
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 350,
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
@ -18,7 +18,7 @@ export function getDataColumns(): BasicColumn[] {
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
width: 350,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},

199
apps/vue/src/views/permission-management/definitions/groups/components/GroupDefinitionModal.vue

@ -0,0 +1,199 @@
<template>
<BasicModal
@register="registerModal"
:title="L('GroupDefinitions')"
:can-fullscreen="false"
:width="800"
:height="500"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')">
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es';
import { ref, reactive, unref, nextTick, watch } from 'vue';
import { Form, Input, Tabs } from 'ant-design-vue';
import { ExtraPropertyDictionary } from '/@/components/Abp';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput } from '/@/components/Abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput
} from '/@/api/permission-management/definitions/groups';
import {
PermissionGroupDefinitionUpdateDto,
PermissionGroupDefinitionCreateDto
} from '/@/api/permission-management/definitions/groups/model';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['AbpPermissionManagement', 'AbpUi']);
const formRef = ref<any>();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
entityRules: {
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'AbpPermissionManagement',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:Description')]));
}
return Promise.resolve();
},
}),
},
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((record) => {
nextTick(() => fetch(record.name));
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => state.entityChanged = false);
});
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.allowedChange = false;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, cloneDeep(state.entity) as PermissionGroupDefinitionUpdateDto)
: CreateAsyncByInput(cloneDeep(state.entity) as PermissionGroupDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

159
apps/vue/src/views/permission-management/definitions/groups/components/GroupDefinitionTable.vue

@ -0,0 +1,159 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<Button
v-auth="['PermissionManagement.GroupDefinitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('GroupDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'PermissionManagement.GroupDefinitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'PermissionManagement.GroupDefinitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
auth: 'PermissionManagement.Definitions.Create',
label: L('PermissionDefinitions:AddNew'),
icon: 'ant-design:edit-outlined',
onClick: handleAddFeature.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
<GroupDefinitionModal @register="registerModal" @change="fetch" />
<PermissionDefinitionModal @register="registerPermissionModal" />
</div>
</template>
<script lang="ts" setup>
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted } from 'vue';
import { Button } from 'ant-design-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { PermissionGroupDefinitionDto } from '/@/api/permission-management/definitions/groups/model';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/permission-management/definitions/groups';
import { getSearchFormSchemas } from '../datas/ModalData';
import GroupDefinitionModal from './GroupDefinitionModal.vue';
import PermissionDefinitionModal from '../../permissions/components/PermissionDefinitionModal.vue';
interface State {
groups: PermissionGroupDefinitionDto[];
}
const state = reactive<State>({
groups: [],
});
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['AbpPermissionManagement', 'AbpUi']);
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerPermissionModal, { openModal: openPermissionModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('GroupDefinitions'),
columns: getDataColumns(),
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
actionColumn: {
width: 120,
title: L('Actions'),
dataIndex: 'action',
},
});
const getDisplayName = computed(() => {
return (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
});
onMounted(fetch);
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
state.groups = [];
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
state.groups = res.items;
}).finally(() => {
setTableData(state.groups);
setLoading(false);
});
});
}
function handleAddNew() {
openModal(true, {});
}
function handleEdit(record) {
openModal(true, record);
}
function handleAddFeature(record) {
openPermissionModal(true, {
groupName: record.name,
groups: cloneDeep(state.groups),
});
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
</script>

15
apps/vue/src/views/permission-management/definitions/groups/datas/ModalData.ts

@ -0,0 +1,15 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 24 },
},
];
}

35
apps/vue/src/views/permission-management/definitions/groups/datas/TableData.ts

@ -0,0 +1,35 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['AbpPermissionManagement']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 350,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

16
apps/vue/src/views/permission-management/definitions/groups/index.vue

@ -0,0 +1,16 @@
<template>
<GroupDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import GroupDefinitionTable from './components/GroupDefinitionTable.vue';
export default defineComponent({
name: 'PermissionsGroupDefinitions',
components: {
GroupDefinitionTable,
},
setup() {},
});
</script>

346
apps/vue/src/views/permission-management/definitions/permissions/components/PermissionDefinitionModal.vue

@ -0,0 +1,346 @@
<template>
<BasicModal
@register="registerModal"
:title="L('PermissionDefinitions')"
:can-fullscreen="false"
:width="800"
:height="500"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="isEnabled" :label="L('DisplayName:IsEnabled')">
<Checkbox
:disabled="!state.allowedChange"
v-model:checked="state.entity.isEnabled"
>{{ L('DisplayName:IsEnabled') }}
</Checkbox>
</FormItem>
<FormItem name="groupName" :label="L('DisplayName:GroupName')">
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.groupName"
:options="getGroupOptions"
@change="handleGroupChange"
/>
</FormItem>
<FormItem name="parentName" :label="L('DisplayName:ParentName')">
<TreeSelect
:disabled="!state.allowedChange"
:allow-clear="true"
:tree-data="state.availablePermissions"
v-model:value="state.entity.parentName"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
@change="handleParentChange"
/>
</FormItem>
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
<FormItem name="multiTenancySide" :label="L('DisplayName:MultiTenancySide')">
<Select
:disabled="!state.allowedChange"
v-model:value="state.entity.multiTenancySide"
:options="multiTenancySides"
/>
</FormItem>
<FormItem name="providers" :label="L('DisplayName:Providers')">
<Select
:disabled="!state.allowedChange"
mode="multiple"
:allow-clear="true"
v-model:value="state.entity.providers"
:options="providers"
/>
</FormItem>
</TabPane>
<TabPane key="stateCheckers" :tab="L('StateCheckers')" force-render>
<FormItem name="stateCheckers" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<SimpleStateChecking
:allow-delete="true"
:allow-edit="true"
:disabled="!state.allowedChange"
v-model:value="state.entity.stateCheckers"
:state="state.simpleCheckState"
/>
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')" force-render>
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { SimplaCheckStateBase, PermissionState } from '/@/components/Abp';
import { computed, ref, reactive, unref, nextTick, watch } from 'vue';
import { Checkbox, Form, Input, Select, Tabs, TreeSelect } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput, ExtraPropertyDictionary, SimpleStateChecking } from '/@/components/Abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput,
GetListAsyncByInput,
} from '/@/api/permission-management/definitions/permissions';
import {
MultiTenancySides,
PermissionDefinitionUpdateDto,
PermissionDefinitionCreateDto,
} from '/@/api/permission-management/definitions/permissions/model';
import { PermissionGroupDefinitionDto } from '/@/api/permission-management/definitions/groups/model';
import { multiTenancySides, providers } from '../../typing';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface PermissionSelectData {
label: string;
value: string;
}
interface PermissionTreeData {
name: string;
groupName: string;
displayName: string;
children: PermissionTreeData[];
}
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
defaultGroup?: string,
availablePermissions: PermissionTreeData[],
availableGroups: PermissionGroupDefinitionDto[],
selectionDataSource: PermissionSelectData[],
simpleCheckState: SimplaCheckStateBase,
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { deserialize, validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L, Lr } = useLocalization(['AbpPermissionManagement', 'AbpUi']);
const formRef = ref<any>();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
availablePermissions: [],
availableGroups: [],
selectionDataSource: [],
simpleCheckState: new PermissionState(),
entityRules: {
groupName: ruleCreator.fieldRequired({
name: 'GroupName',
prefix: 'DisplayName',
resourceName: 'AbpPermissionManagement',
trigger: 'blur',
}),
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'AbpPermissionManagement',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
},
});
const getGroupOptions = computed(() => {
return state.availableGroups.map((group) => {
const info = deserialize(group.displayName);
return {
label: Lr(info.resourceName, info.name),
value: group.name,
};
});
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((data) => {
state.defaultGroup = data.groupName;
state.availableGroups = data.groups;
nextTick(() => {
fetchFeatures(state.defaultGroup);
fetch(data.record?.name);
});
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {
isEnabled: true,
groupName: state.defaultGroup,
multiTenancySide: MultiTenancySides.Both,
providers: [],
extraProperties: {},
};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => {
state.entityChanged = false;
});
});
}
function fetchFeatures(groupName?: string) {
GetListAsyncByInput({
groupName: groupName,
}).then((res) => {
const permissionGroup = groupBy(res.items, 'groupName');
const permissionTreeData: PermissionTreeData[] = [];
Object.keys(permissionGroup).forEach((gk) => {
const permissionTree = listToTree(permissionGroup[gk], {
id: 'name',
pid: 'parentName',
});
formatDisplayName(permissionTree);
permissionTreeData.push(...permissionTree);
});
state.availablePermissions = permissionTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleGroupChange(value?: string) {
state.entity.parentName = undefined;
fetchFeatures(value);
}
function handleParentChange(value?: string) {
if (!value) return;
GetAsyncByName(value).then((res) => {
state.entity.groupName = res.groupName;
const form = unref(formRef);
form?.clearValidate(['groupName']);
});
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
state.allowedChange = false;
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.defaultGroup = undefined;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, state.entity as PermissionDefinitionUpdateDto)
: CreateAsyncByInput(state.entity as PermissionDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

262
apps/vue/src/views/permission-management/definitions/permissions/components/PermissionDefinitionTable.vue

@ -0,0 +1,262 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #form-groupName="{ model, field }">
<FormItem name="groupName">
<Select v-model:value="model[field]" :options="getGroupOptions" />
</FormItem>
</template>
<template #toolbar>
<Button
v-auth="['PermissionManagement.Definitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('PermissionDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getGroupDisplayName(record.displayName) }}</span>
</template>
</template>
<template #expandedRowRender="{ record }">
<BasicTable @register="registerSubTable" :data-source="record.permissions">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'groupName'">
<span>{{ getGroupDisplayName(record.groupName) }}</span>
</template>
<template v-else-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'multiTenancySide'">
<Tag color="blue">{{ multiTenancySidesMap[record.multiTenancySide] }}</Tag>
</template>
<template v-else-if="column.key === 'providers'">
<Tag v-for="provider in record.providers" color="blue" style="margin: 5px;">{{ providersMap[provider] }}</Tag>
</template>
<template v-else-if="column.key === 'isEnabled'">
<CheckOutlined v-if="record.isEnabled" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'isStatic'">
<CheckOutlined v-if="record.isStatic" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'PermissionManagement.Definitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'PermissionManagement.Definitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</template>
</BasicTable>
<PermissionDefinitionModal @register="registerModal" @change="fetch" />
</div>
</template>
<script lang="ts" setup>
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted } from 'vue';
import { Button, Form, Select, Tag } from 'ant-design-vue';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { GetListAsyncByInput as getGroupDefinitions } from '/@/api/permission-management/definitions/groups';
import { PermissionGroupDefinitionDto } from '/@/api/permission-management/definitions/groups/model';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/permission-management/definitions/permissions';
import { multiTenancySidesMap, providersMap } from '../../typing';
import { getSearchFormSchemas } from '../datas/ModalData';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
import { sorter } from '/@/utils/table';
import PermissionDefinitionModal from './PermissionDefinitionModal.vue';
const FormItem = Form.Item;
interface Permission {
groupName: string;
name: string;
displayName: string;
parentName?: string;
isEnabled: boolean;
isStatic: boolean;
providers: string[];
stateCheckers: string[];
children: Permission[];
}
interface PermissionGroup {
name: string;
displayName: string;
permissions: Permission[];
}
interface State {
groups: PermissionGroupDefinitionDto[];
}
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['AbpPermissionManagement', 'AbpUi']);
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('PermissionDefinitions'),
columns: [
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
],
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
});
const [registerSubTable] = useTable({
rowKey: 'name',
columns: getDataColumns(),
pagination: false,
striped: false,
useSearchForm: false,
showIndexColumn: true,
immediate: false,
scroll: { x: 2000, y: 900 },
actionColumn: {
width: 100,
title: L('Actions'),
dataIndex: 'action',
},
});
const state = reactive<State>({
groups: [],
});
const getGroupOptions = computed(() => {
return state.groups.map((group) => {
const info = deserialize(group.displayName);
return {
label: Lr(info.resourceName, info.name),
value: group.name,
};
});
});
const getGroupDisplayName = computed(() => {
return (groupName: string) => {
const group = state.groups.find(x => x.name === groupName);
if (!group) return groupName;
const info = deserialize(group.displayName);
return Lr(info.resourceName, info.name);
};
});
const getDisplayName = computed(() => {
return (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
});
onMounted(() => {
fetch();
fetchGroups();
});
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
setTableData([]);
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
const permissionGroup = groupBy(res.items, 'groupName');
const permissionGroupData: PermissionGroup[] = [];
Object.keys(permissionGroup).forEach((gk) => {
const groupData: PermissionGroup = {
name: gk,
displayName: gk,
permissions: [],
};
const permissionTree = listToTree(permissionGroup[gk], {
id: 'name',
pid: 'parentName',
});
permissionTree.forEach((tk) => {
groupData.permissions.push(tk);
});
permissionGroupData.push(groupData);
});
setTableData(permissionGroupData);
}).finally(() => {
setLoading(false);
});
});
}
function fetchGroups() {
getGroupDefinitions({}).then((res) => {
state.groups = res.items;
});
}
function handleAddNew() {
openModal(true, {
groups: cloneDeep(state.groups),
});
}
function handleEdit(record) {
openModal(true, {
record: record,
groups: cloneDeep(state.groups),
});
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
</script>

22
apps/vue/src/views/permission-management/definitions/permissions/datas/ModalData.ts

@ -0,0 +1,22 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['AbpPermissionManagement', 'AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'groupName',
component: 'ApiSelect',
label: L('DisplayName:GroupName'),
colProps: { span: 6 },
slot: 'groupName',
},
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 18 },
},
];
}

60
apps/vue/src/views/permission-management/definitions/permissions/datas/TableData.ts

@ -0,0 +1,60 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['AbpPermissionManagement']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:IsEnabled'),
dataIndex: 'isEnabled',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isEnabled'),
},
{
title: L('DisplayName:MultiTenancySide'),
dataIndex: 'multiTenancySide',
align: 'left',
width: 80,
resizable: true,
sorter: (a, b) => sorter(a, b, 'multiTenancySide'),
},
{
title: L('DisplayName:Providers'),
dataIndex: 'providers',
align: 'left',
width: 80,
resizable: true,
sorter: (a, b) => sorter(a, b, 'providers'),
},
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

16
apps/vue/src/views/permission-management/definitions/permissions/index.vue

@ -0,0 +1,16 @@
<template>
<PermissionDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import PermissionDefinitionTable from './components/PermissionDefinitionTable.vue';
export default defineComponent({
name: 'PermissionsDefinitions',
components: {
PermissionDefinitionTable,
},
setup() {},
});
</script>

30
apps/vue/src/views/permission-management/definitions/typing/index.ts

@ -0,0 +1,30 @@
import { MultiTenancySides } from '/@/api/permission-management/definitions/permissions/model';
import { useLocalization } from '/@/hooks/abp/useLocalization';
const { L } = useLocalization(['AbpPermissionManagement']);
export const multiTenancySidesMap = {
[MultiTenancySides.Tenant]: L('MultiTenancySides:Tenant'),
[MultiTenancySides.Host]: L('MultiTenancySides:Host'),
[MultiTenancySides.Both]: L('MultiTenancySides:Both'),
};
export const multiTenancySides = [
{ label: multiTenancySidesMap[MultiTenancySides.Tenant], value: MultiTenancySides.Tenant },
{ label: multiTenancySidesMap[MultiTenancySides.Host], value: MultiTenancySides.Host },
{ label: multiTenancySidesMap[MultiTenancySides.Both], value: MultiTenancySides.Both },
];
export const providersMap = {
'R': L('Providers:Role'),
'U': L('Providers:User'),
'O': L('Providers:OrganizationUnit'),
'C': L('Providers:Client'),
};
export const providers = [
{ label: providersMap['R'], value: 'R' },
{ label: providersMap['U'], value: 'U' },
{ label: providersMap['O'], value: 'O' },
{ label: providersMap['C'], value: 'C' },
];

4
apps/vue/src/views/settings-management/definitions/datas/TableData.ts

@ -10,7 +10,7 @@ export function getDataColumns(): BasicColumn[] {
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 350,
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
@ -18,7 +18,7 @@ export function getDataColumns(): BasicColumn[] {
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
width: 300,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},

32
apps/vue/src/views/webhooks/definitions/webhooks/components/WebhookDefinitionModal.vue

@ -48,7 +48,7 @@
:tree-checkable="true"
:tree-check-strictly="true"
:tree-data="state.availableFeatures"
:value="state.entity.requiredFeatures"
:value="getRequiredFeatures"
:field-names="{
label: 'displayName',
value: 'name',
@ -80,6 +80,7 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { GetListAsyncByInput as getAvailableFeatures } from '/@/api/feature-management/definitions/features';
import { FeatureDefinitionDto } from '/@/api/feature-management/definitions/features/model';
import {
GetAsyncByName,
CreateAsyncByInput,
@ -94,6 +95,7 @@
import { GetListAsyncByInput as getGroupDefinitions } from '/@/api/webhooks/definitions/groups';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
import { isNullOrUnDef } from '/@/utils/is';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
@ -110,6 +112,7 @@
entityChanged: boolean,
entityEditFlag: boolean,
defaultGroup?: string,
features: FeatureDefinitionDto[];
availableFeatures: FeatureTreeData[],
availableGroups: WebhookGroupDefinitionDto[],
}
@ -132,6 +135,7 @@
entityEditFlag: false,
availableFeatures: [],
availableGroups: [],
features: [],
entityRules: {
groupName: ruleCreator.fieldRequired({
name: 'GroupName',
@ -166,6 +170,17 @@
}),
},
});
const getRequiredFeatures = computed(() => {
if (isNullOrUnDef(state.entity.requiredFeatures)) return [];
return state.features
.filter((feature) => state.entity.requiredFeatures.includes(feature.name))
.map((feature) => {
return {
label: feature.displayName,
value: feature.name,
};
});
});
const getGroupOptions = computed(() => {
return state.availableGroups.map((group) => {
const info = deserialize(group.displayName);
@ -236,23 +251,16 @@
function fetchFeatures() {
getAvailableFeatures({}).then((res) => {
const featureGroup = groupBy(res.items, 'groupName');
state.features = res.items;
formatDisplayName(state.features);
const featureGroup = groupBy(cloneDeep(res.items), 'groupName');
const featureGroupTree: FeatureTreeData[] = [];
Object.keys(featureGroup).forEach((gk) => {
const treeData: FeatureTreeData = {
name: gk,
displayName: gk,
children: [],
};
const featureTree = listToTree(featureGroup[gk], {
id: 'name',
pid: 'parentName',
});
formatDisplayName(featureTree);
featureTree.forEach((tk) => {
treeData.children.push(tk);
});
featureGroupTree.push(treeData);
featureGroupTree.push(...featureTree);
});
state.availableFeatures = featureGroupTree;
});

44
apps/vue/types/abp.d.ts

@ -111,3 +111,47 @@ declare interface CurrentUser {
phoneNumberVerified: boolean;
roles: string[];
}
type SimpleStateCheckerResult<TState extends IHasSimpleStateCheckers<TState>> = Recordable<
TState,
boolean
>;
declare interface SimpleStateCheckerContext<TState extends IHasSimpleStateCheckers<TState>> {
state: TState;
}
declare interface SimpleBatchStateCheckerContext<TState extends IHasSimpleStateCheckers<TState>> {
states: TState[];
}
declare interface IHasSimpleStateCheckers<TState extends IHasSimpleStateCheckers<TState>> {
stateCheckers: ISimpleStateChecker<TState>[];
}
declare interface ISimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> {
isEnabled(context: SimpleStateCheckerContext<TState>): boolean;
serialize(): string | undefined;
}
declare interface ISimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>>
extends ISimpleStateChecker<TState> {
isEnabled(context: SimpleBatchStateCheckerContext<TState>): SimpleStateCheckerResult<TState>;
}
declare interface ISimpleStateCheckerSerializer {
serialize<TState extends IHasSimpleStateCheckers<TState>>(
checker: ISimpleStateChecker<TState>,
): string | undefined;
serializeArray<TState extends IHasSimpleStateCheckers<TState>>(
stateCheckers: ISimpleStateChecker<TState>[],
): string | undefined;
deserialize<TState extends IHasSimpleStateCheckers<TState>>(
jsonObject: any,
state: TState,
): ISimpleStateChecker<TState> | undefined;
deserializeArray<TState extends IHasSimpleStateCheckers<TState>>(
value: string,
state: TState,
): ISimpleStateChecker<TState>[];
}

1
aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application.Contracts/LINGYUN/Abp/FeatureManagement/Localization/Application/Contracts/en.json

@ -20,6 +20,7 @@
"DisplayName:ParentName": "Parent",
"DisplayName:Description": "Description",
"DisplayName:DefaultValue": "Default Value",
"DisplayName:ValueType": "Value Validator Type",
"DisplayName:IsVisibleToClients": "Visible To Clients",
"Description:IsVisibleToClients": "Can clients see this feature and it's value.",
"DisplayName:IsAvailableToHost": "Available To Host",

1
aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application.Contracts/LINGYUN/Abp/FeatureManagement/Localization/Application/Contracts/zh-Hans.json

@ -20,6 +20,7 @@
"DisplayName:ParentName": "所属功能",
"DisplayName:Description": "描述",
"DisplayName:DefaultValue": "默认值",
"DisplayName:ValueType": "值验证类型",
"DisplayName:IsVisibleToClients": "对客户端可见",
"Description:IsVisibleToClients": "客户端是否可以看到这个功能.",
"DisplayName:IsAvailableToHost": "宿主可用功能",

32
aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.Application/LINGYUN/Abp/FeatureManagement/Definitions/FeatureDefinitionAppService.cs

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
@ -13,6 +14,7 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.FeatureManagement.Definitions;
@ -48,7 +50,13 @@ public class FeatureDefinitionAppService : FeatureManagementAppServiceBase, IFea
[Authorize(FeatureManagementPermissionNames.Definition.Create)]
public async virtual Task<FeatureDefinitionDto> CreateAsync(FeatureDefinitionCreateDto input)
{
if (await _featureDefinitionManager.GetOrNullAsync(input.Name) != null)
if (await _staticFeatureDefinitionStore.GetOrNullAsync(input.Name) != null)
{
throw new BusinessException(FeatureManagementErrorCodes.Definition.AlreayNameExists)
.WithData(nameof(FeatureDefinitionRecord.Name), input.Name);
}
if (await _definitionRepository.FindByNameAsync(input.Name) != null)
{
throw new BusinessException(FeatureManagementErrorCodes.Definition.AlreayNameExists)
.WithData(nameof(FeatureDefinitionRecord.Name), input.Name);
@ -202,10 +210,6 @@ public class FeatureDefinitionAppService : FeatureManagementAppServiceBase, IFea
{
record.DefaultValue = input.DefaultValue;
}
if (!string.Equals(record.ValueType, input.ValueType, StringComparison.InvariantCultureIgnoreCase))
{
record.ValueType = input.ValueType;
}
string allowedProviders = null;
if (!input.AllowedProviders.IsNullOrEmpty())
{
@ -220,6 +224,24 @@ public class FeatureDefinitionAppService : FeatureManagementAppServiceBase, IFea
{
record.SetProperty(property.Key, property.Value);
}
try
{
if (!string.Equals(record.ValueType, input.ValueType, StringComparison.InvariantCultureIgnoreCase))
{
var _ = _stringValueTypeSerializer.Deserialize(input.ValueType);
record.ValueType = input.ValueType;
}
}
catch
{
throw new AbpValidationException(
new List<ValidationResult>
{
new ValidationResult(
L["The field {0} is invalid", L["DisplayName:ValueType"]],
new string[1] { nameof(input.ValueType) })
});
}
}
protected async virtual Task<FeatureDefinitionRecord> FindRecordByNameAsync(string name)

11
aspnet-core/modules/feature-management/LINGYUN.Abp.FeatureManagement.HttpApi/LINGYUN/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs

@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.FeatureManagement.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Validation.Localization;
using VoloAbpFeatureManagementHttpApiModule = Volo.Abp.FeatureManagement.AbpFeatureManagementHttpApiModule;
namespace LINGYUN.Abp.FeatureManagement.HttpApi;
@ -25,4 +27,13 @@ public class AbpFeatureManagementHttpApiModule : AbpModule
typeof(AbpFeatureManagementApplicationContractsModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLocalizationOptions>(options =>
{
options.Resources.Get<AbpFeatureManagementResource>()
.AddBaseTypes(typeof(AbpValidationResource));
});
}
}

5
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionCreateOrUpdateDto.cs

@ -18,11 +18,12 @@ public abstract class PermissionDefinitionCreateOrUpdateDto : IHasExtraPropertie
public bool IsEnabled { get; set; }
public MultiTenancySides? MultiTenancySide { get; set; }
public MultiTenancySides MultiTenancySide { get; set; } = MultiTenancySides.Both;
public List<string> Providers { get; set; } = new List<string>();
public List<string> StateCheckers { get; set; } = new List<string>();
[DynamicStringLength(typeof(PermissionDefinitionRecordConsts), nameof(PermissionDefinitionRecordConsts.MaxStateCheckersLength))]
public string StateCheckers { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; } = new ExtraPropertyDictionary();
}

8
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionDto.cs

@ -16,11 +16,13 @@ public class PermissionDefinitionDto : IHasExtraProperties
public bool IsEnabled { get; set; }
public MultiTenancySides? MultiTenancySide { get; set; }
public bool IsStatic { get; set; }
public MultiTenancySides MultiTenancySide { get; set; }
public List<string> Providers { get; set; } = new List<string>();
public List<string> StateCheckers { get; set; } = new List<string>();
public string StateCheckers { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; } = new ExtraPropertyDictionary();
}

6
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionDefinitionGetListInput.cs

@ -1,7 +1,5 @@
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.PermissionManagement.Definitions;
public class PermissionDefinitionGetListInput : PagedAndSortedResultRequestDto
namespace LINGYUN.Abp.PermissionManagement.Definitions;
public class PermissionDefinitionGetListInput
{
public string Filter { get; set; }

4
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionGroupDefinitionDto.cs

@ -8,5 +8,7 @@ public class PermissionGroupDefinitionDto : IHasExtraProperties
public string DisplayName { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; }
public bool IsStatic { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; set; } = new ExtraPropertyDictionary();
}

6
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/Dto/PermissionGroupDefinitionGetListInput.cs

@ -1,7 +1,5 @@
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.PermissionManagement.Definitions;
public class PermissionGroupDefinitionGetListInput : PagedAndSortedResultRequestDto
namespace LINGYUN.Abp.PermissionManagement.Definitions;
public class PermissionGroupDefinitionGetListInput
{
public string Filter { get; set; }
}

2
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/IPermissionDefinitionAppService.cs

@ -14,5 +14,5 @@ public interface IPermissionDefinitionAppService : IApplicationService
Task<PermissionDefinitionDto> UpdateAsync(string name, PermissionDefinitionUpdateDto input);
Task<PagedResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input);
Task<ListResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input);
}

2
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Definitions/IPermissionGroupDefinitionAppService.cs

@ -14,5 +14,5 @@ public interface IPermissionGroupDefinitionAppService : IApplicationService
Task<PermissionGroupDefinitionDto> UpdateAsync(string name, PermissionGroupDefinitionUpdateDto input);
Task<PagedResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input);
Task<ListResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input);
}

29
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Localization/Application/Contracts/en.json

@ -8,9 +8,36 @@
"Permission:Edit": "Edit",
"Permission:Delete": "Delete",
"PermissionManagement:001100": "Permission group {Name} already exists!",
"PermissionManagement:001010": "Static permission group {Name} is not allowed to change!",
"PermissionManagement:001404": "Permission group named {Name} not found!",
"PermissionManagement:002100": "Permission {Name} already exists!",
"PermissionManagement:002010": "Static permission {Name} is not allowed to change!",
"PermissionManagement:002101": "The group definition of permission {Name} could not be retrieved!",
"PermissionManagement:0021404": "Permission named {Name} not found!"
"PermissionManagement:002404": "Permission named {Name} not found!",
"DisplayName:IsEnabled": "Enabled",
"DisplayName:IsStatic": "Static",
"DisplayName:MultiTenancySide": "MultiTenancySide",
"Description:MultiTenancySide": "The way permissions apply to multi-tenancy.",
"DisplayName:Name": "Name",
"DisplayName:DisplayName": "Display Name",
"DisplayName:GroupName": "Group Name",
"DisplayName:ParentName": "Parent",
"DisplayName:Providers": "Providers",
"Description:Providers": "A list of allowed providers to get/set value of this permission,An empty list indicates that all providers are allowed.",
"DisplayName:StateCheckers": "State Checkers",
"MultiTenancySides:Tenant": "Tenant",
"MultiTenancySides:Host": "Host",
"MultiTenancySides:Both": "Both",
"Providers:Role": "Role",
"Providers:User": "User",
"Providers:OrganizationUnit": "Organization Unit",
"Providers:Client": "Client",
"GroupDefinitions": "Group Definitions",
"GroupDefinitions:AddNew": "Add New",
"PermissionDefinitions": "Permission Definitions",
"PermissionDefinitions:AddNew": "Add New",
"BasicInfo": "Basic",
"Properties": "Properties",
"StateCheckers": "State Checkers"
}
}

30
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/Localization/Application/Contracts/zh-Hans.json

@ -8,9 +8,37 @@
"Permission:Edit": "修改",
"Permission:Delete": "删除",
"PermissionManagement:001100": "权限分组 {Name} 已经存在!",
"PermissionManagement:001010": "静态权限分组 {Name} 不允许变更!",
"PermissionManagement:001404": "没有找到名为 {Name} 的权限分组!",
"PermissionManagement:002100": "权限 {Name} 已经存在!",
"PermissionManagement:002010": "静态权限 {Name} 不允许变更!",
"PermissionManagement:002101": "未能检索到权限 {Name} 所在分组定义!",
"PermissionManagement:0021404": "没有找到名为 {Name} 的权限定义!"
"PermissionManagement:002400": "无效的StateCheckers格式",
"PermissionManagement:002404": "没有找到名为 {Name} 的权限定义!",
"DisplayName:IsEnabled": "启用",
"DisplayName:IsStatic": "静态",
"DisplayName:MultiTenancySide": "多租户",
"Description:MultiTenancySide": "权限应用到多租户的方式.",
"DisplayName:Name": "名称",
"DisplayName:DisplayName": "显示名称",
"DisplayName:GroupName": "分组名称",
"DisplayName:ParentName": "上级权限",
"DisplayName:Providers": "权限提供者",
"Description:Providers": "允许提供程序获取/设置此权限值的列表。空列表表示允许所有提供程序.",
"DisplayName:StateCheckers": "状态检查",
"MultiTenancySides:Tenant": "租户",
"MultiTenancySides:Host": "主机",
"MultiTenancySides:Both": "两者",
"Providers:Role": "角色",
"Providers:User": "用户",
"Providers:OrganizationUnit": "组织机构",
"Providers:Client": "客户端",
"GroupDefinitions": "分组定义",
"GroupDefinitions:AddNew": "添加分组",
"PermissionDefinitions": "权限定义",
"PermissionDefinitions:AddNew": "添加权限",
"BasicInfo": "基本信息",
"Properties": "属性",
"StateCheckers": "状态检查"
}
}

6
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application.Contracts/LINGYUN/Abp/PermissionManagement/PermissionManagementErrorCodes.cs

@ -7,6 +7,8 @@ public static class PermissionManagementErrorCodes
{
private const string Prefix = Namespace + ":001";
public const string StaticGroupNotAllowedChanged = Prefix + "010";
public const string AlreayNameExists = Prefix + "100";
public const string NameNotFount = Prefix + "404";
@ -16,10 +18,14 @@ public static class PermissionManagementErrorCodes
{
private const string Prefix = Namespace + ":002";
public const string StaticPermissionNotAllowedChanged = Prefix + "010";
public const string AlreayNameExists = Prefix + "100";
public const string FailedGetGroup = Prefix + "101";
public const string NameNotFount = Prefix + "404";
public const string InvalidStateCheckers = Prefix + "400";
}
}

246
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/Definitions/PermissionDefinitionAppService.cs

@ -2,9 +2,12 @@
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using System.Xml.Linq;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization.Permissions;
@ -13,6 +16,7 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.Localization;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SimpleStateChecking;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.PermissionManagement.Definitions;
@ -22,19 +26,24 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
private readonly ISimpleStateCheckerSerializer _simpleStateCheckerSerializer;
private readonly ILocalizableStringSerializer _localizableStringSerializer;
private readonly IPermissionDefinitionManager _permissionDefinitionManager;
private readonly IStaticPermissionDefinitionStore _staticPermissionDefinitionStore;
private readonly IDynamicPermissionDefinitionStore _dynamicPermissionDefinitionStore;
private readonly IPermissionDefinitionRecordRepository _definitionRepository;
private readonly IRepository<PermissionDefinitionRecord, Guid> _definitionBasicRepository;
public PermissionDefinitionAppService(
ILocalizableStringSerializer localizableStringSerializer,
IPermissionDefinitionManager permissionDefinitionManager,
IStaticPermissionDefinitionStore staticPermissionDefinitionStore,
IDynamicPermissionDefinitionStore dynamicPermissionDefinitionStore,
ISimpleStateCheckerSerializer simpleStateCheckerSerializer,
IPermissionDefinitionRecordRepository definitionRepository,
IRepository<PermissionDefinitionRecord, Guid> definitionBasicRepository)
{
_localizableStringSerializer = localizableStringSerializer;
_permissionDefinitionManager = permissionDefinitionManager;
_staticPermissionDefinitionStore = staticPermissionDefinitionStore;
_dynamicPermissionDefinitionStore = dynamicPermissionDefinitionStore;
_simpleStateCheckerSerializer = simpleStateCheckerSerializer;
_definitionRepository = definitionRepository;
_definitionBasicRepository = definitionBasicRepository;
@ -64,33 +73,13 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
input.DisplayName,
input.IsEnabled);
if (input.MultiTenancySide.HasValue)
{
definitionRecord.MultiTenancySide = input.MultiTenancySide.Value;
}
if (input.Providers.Any())
{
definitionRecord.Providers = input.Providers.JoinAsString(",");
}
if (input.StateCheckers.Any())
{
definitionRecord.StateCheckers = input.StateCheckers.JoinAsString(",");
}
await UpdateByInput(definitionRecord, input);
foreach (var property in input.ExtraProperties)
{
definitionRecord.SetProperty(property.Key, property.Value);
}
await _definitionRepository.InsertAsync(definitionRecord);
definitionRecord = await _definitionRepository.InsertAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
var dto = await DefinitionRecordToDto(definitionRecord);
return dto;
return DefinitionRecordToDto(definitionRecord);
}
[Authorize(PermissionManagementPermissionNames.Definition.Delete)]
@ -98,80 +87,69 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
{
var definitionRecord = await FindByNameAsync(name);
if (definitionRecord == null)
if (definitionRecord != null)
{
return;
}
await _definitionRepository.DeleteAsync(definitionRecord);
await _definitionRepository.DeleteAsync(definitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
}
}
public async virtual Task<PermissionDefinitionDto> GetAsync(string name)
{
var definition = await _permissionDefinitionManager.GetOrNullAsync(name);
if (definition == null)
var definition = await _staticPermissionDefinitionStore.GetOrNullAsync(name);
if (definition != null)
{
throw new BusinessException(PermissionManagementErrorCodes.Definition.NameNotFount)
.WithData(nameof(PermissionDefinitionRecord.Name), name);
return DefinitionToDto(await GetGroupDefinition(definition), definition, true);
}
var groupDefinition = await GetGroupDefinition(definition);
var dto = await DefinitionToDto(groupDefinition, definition);
return dto;
definition = await _dynamicPermissionDefinitionStore.GetOrNullAsync(name);
return DefinitionToDto(await GetGroupDefinition(definition), definition);
}
public async virtual Task<PagedResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input)
public async virtual Task<ListResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input)
{
var permissions = new List<PermissionDefinitionDto>();
var permissionDtoList = new List<PermissionDefinitionDto>();
IReadOnlyList<PermissionDefinition> definitionPermissions;
var staticPermissions = new List<PermissionDefinition>();
if (!input.GroupName.IsNullOrWhiteSpace())
var staticGroups = await _staticPermissionDefinitionStore.GetGroupsAsync();
var staticGroupNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
foreach (var group in staticGroups.WhereIf(!input.GroupName.IsNullOrWhiteSpace(), x => x.Name == input.GroupName))
{
var group = await _permissionDefinitionManager.GetGroupOrNullAsync(input.GroupName);
if (group == null)
{
return new PagedResultDto<PermissionDefinitionDto>(0, permissions);
}
definitionPermissions = group.GetPermissionsWithChildren();
}
else
{
definitionPermissions = await _permissionDefinitionManager.GetPermissionsAsync();
var permissions = group.GetPermissionsWithChildren();
staticPermissions.AddRange(permissions);
permissionDtoList.AddRange(permissions.Select(f => DefinitionToDto(group, f, true)));
}
var definitionFilter = definitionPermissions.AsQueryable()
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter) ||
(x.Parent != null && x.Parent.Name.Contains(input.Filter)));
var sorting = input.Sorting;
if (sorting.IsNullOrWhiteSpace())
{
sorting = nameof(PermissionDefinitionRecord.Name);
}
var filterDefinitionCount = definitionFilter.Count();
var filterDefinitions = definitionFilter
.OrderBy(sorting)
.PageBy(input.SkipCount, input.MaxResultCount);
foreach (var definition in filterDefinitions)
var staticPermissionNames = staticPermissions
.Select(p => p.Name)
.ToImmutableHashSet();
var dynamicGroups = await _dynamicPermissionDefinitionStore.GetGroupsAsync();
foreach (var group in dynamicGroups
.Where(d => !staticGroupNames.Contains(d.Name))
.WhereIf(!input.GroupName.IsNullOrWhiteSpace(), x => x.Name == input.GroupName))
{
var groupDefinition = await GetGroupDefinition(definition);
var Dto = await DefinitionToDto(groupDefinition, definition);
permissions.Add(Dto);
var permissions = group.GetPermissionsWithChildren();
permissionDtoList.AddRange(permissions
.Where(d => !staticPermissionNames.Contains(d.Name))
.Select(f => DefinitionToDto(group, f)));
}
return new PagedResultDto<PermissionDefinitionDto>(filterDefinitionCount, permissions);
return new ListResultDto<PermissionDefinitionDto>(permissionDtoList
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter) || x.DisplayName.Contains(input.Filter))
.ToList());
}
[Authorize(PermissionManagementPermissionNames.Definition.Update)]
public async virtual Task<PermissionDefinitionDto> UpdateAsync(string name, PermissionDefinitionUpdateDto input)
{
if (await _staticPermissionDefinitionStore.GetOrNullAsync(name) != null)
{
throw new BusinessException(PermissionManagementErrorCodes.Definition.StaticPermissionNotAllowedChanged)
.WithData("Name", name);
}
var definition = await _permissionDefinitionManager.GetOrNullAsync(name);
var definitionRecord = await FindByNameAsync(name);
@ -186,64 +164,71 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
input.DisplayName,
input.IsEnabled);
if (input.MultiTenancySide.HasValue)
{
definitionRecord.MultiTenancySide = input.MultiTenancySide.Value;
}
if (input.Providers.Any())
{
definitionRecord.Providers = input.Providers.JoinAsString(",");
}
if (input.StateCheckers.Any())
{
definitionRecord.StateCheckers = input.StateCheckers.JoinAsString(",");
}
foreach (var property in input.ExtraProperties)
{
definitionRecord.SetProperty(property.Key, property.Value);
}
await UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionBasicRepository.InsertAsync(definitionRecord);
}
else
{
definitionRecord.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
definitionRecord.SetProperty(property.Key, property.Value);
}
if (input.MultiTenancySide.HasValue)
{
definitionRecord.MultiTenancySide = input.MultiTenancySide.Value;
}
if (input.Providers.Any())
{
definitionRecord.Providers = input.Providers.JoinAsString(",");
}
if (input.StateCheckers.Any())
{
definitionRecord.StateCheckers = input.StateCheckers.JoinAsString(",");
}
if (!string.Equals(definitionRecord.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
definitionRecord.DisplayName = input.DisplayName;
}
await UpdateByInput(definitionRecord, input);
definitionRecord = await _definitionBasicRepository.UpdateAsync(definitionRecord);
}
await CurrentUnitOfWork.SaveChangesAsync();
var dto = await DefinitionRecordToDto(definitionRecord);
return DefinitionRecordToDto(definitionRecord);
}
return dto;
protected async virtual Task UpdateByInput(PermissionDefinitionRecord record, PermissionDefinitionCreateOrUpdateDto input)
{
record.IsEnabled = input.IsEnabled;
record.MultiTenancySide = input.MultiTenancySide;
if (!string.Equals(record.ParentName, input.ParentName, StringComparison.InvariantCultureIgnoreCase))
{
record.ParentName = input.ParentName;
}
if (!string.Equals(record.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
record.DisplayName = input.DisplayName;
}
string providers = null;
if (!input.Providers.IsNullOrEmpty())
{
providers = input.Providers.JoinAsString(",");
}
if (!string.Equals(record.Providers, providers, StringComparison.InvariantCultureIgnoreCase))
{
record.Providers = providers;
}
record.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
record.SetProperty(property.Key, property.Value);
}
try
{
if (!string.Equals(record.StateCheckers, input.StateCheckers, StringComparison.InvariantCultureIgnoreCase))
{
// 校验格式
var permissionDefinition = await _staticPermissionDefinitionStore.GetOrNullAsync(PermissionManagementPermissionNames.Definition.Default);
var _ = _simpleStateCheckerSerializer.DeserializeArray(input.StateCheckers, permissionDefinition);
record.StateCheckers = input.StateCheckers;
}
}
catch
{
throw new AbpValidationException(
new List<ValidationResult>
{
new ValidationResult(
L["The field {0} is invalid", L["DisplayName:StateCheckers"]],
new string[1] { nameof(input.StateCheckers) })
});
}
}
protected async virtual Task<PermissionDefinitionRecord> FindByNameAsync(string name)
@ -272,18 +257,20 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
.WithData(nameof(PermissionDefinitionRecord.Name), definition.Name);
}
protected virtual Task<PermissionDefinitionDto> DefinitionRecordToDto(PermissionDefinitionRecord definitionRecord)
protected virtual PermissionDefinitionDto DefinitionRecordToDto(PermissionDefinitionRecord definitionRecord)
{
var dto = new PermissionDefinitionDto
{
IsStatic = false,
Name = definitionRecord.Name,
GroupName = definitionRecord.GroupName,
ParentName = definitionRecord.ParentName,
IsEnabled = definitionRecord.IsEnabled,
DisplayName = definitionRecord.DisplayName,
Providers = definitionRecord.Providers.Split(',').ToList(),
StateCheckers = definitionRecord.StateCheckers.Split(',').ToList(),
Providers = definitionRecord.Providers?.Split(',').ToList(),
StateCheckers = definitionRecord.StateCheckers,
MultiTenancySide = definitionRecord.MultiTenancySide,
ExtraProperties = new ExtraPropertyDictionary(),
};
foreach (var property in definitionRecord.ExtraProperties)
@ -291,13 +278,14 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
dto.SetProperty(property.Key, property.Value);
}
return Task.FromResult(dto);
return dto;
}
protected virtual Task<PermissionDefinitionDto> DefinitionToDto(PermissionGroupDefinition groupDefinition, PermissionDefinition definition)
protected virtual PermissionDefinitionDto DefinitionToDto(PermissionGroupDefinition groupDefinition, PermissionDefinition definition, bool isStatic = false)
{
var dto = new PermissionDefinitionDto
{
IsStatic = isStatic,
Name = definition.Name,
GroupName = groupDefinition.Name,
ParentName = definition.Parent?.Name,
@ -305,12 +293,12 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
Providers = definition.Providers,
MultiTenancySide = definition.MultiTenancySide,
DisplayName = _localizableStringSerializer.Serialize(definition.DisplayName),
ExtraProperties = new ExtraPropertyDictionary(),
};
if (definition.StateCheckers.Any())
{
var stateCheckers = _simpleStateCheckerSerializer.Serialize(definition.StateCheckers);
dto.StateCheckers = stateCheckers.Split(',').ToList();
dto.StateCheckers = _simpleStateCheckerSerializer.Serialize(definition.StateCheckers);
}
foreach (var property in definition.Properties)
@ -318,6 +306,6 @@ public class PermissionDefinitionAppService : PermissionManagementAppServiceBase
dto.SetProperty(property.Key, property.Value);
}
return Task.FromResult(dto);
return dto;
}
}

143
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/Definitions/PermissionGroupDefinitionAppService.cs

@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
@ -20,18 +21,23 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
{
private readonly ILocalizableStringSerializer _localizableStringSerializer;
private readonly IPermissionDefinitionManager _permissionDefinitionManager;
private readonly IStaticPermissionDefinitionStore _staticPermissionDefinitionStore;
private readonly IDynamicPermissionDefinitionStore _dynamicPermissionDefinitionStore;
private readonly IPermissionGroupDefinitionRecordRepository _groupDefinitionRepository;
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> _groupDefinitionBasicRepository;
public PermissionGroupDefinitionAppService(
ILocalizableStringSerializer localizableStringSerializer,
IPermissionDefinitionManager permissionDefinitionManager,
IStaticPermissionDefinitionStore staticPermissionDefinitionStore,
IDynamicPermissionDefinitionStore dynamicPermissionDefinitionStore,
IPermissionGroupDefinitionRecordRepository groupDefinitionRepository,
IRepository<PermissionGroupDefinitionRecord, Guid> groupDefinitionBasicRepository)
{
_localizableStringSerializer = localizableStringSerializer;
_permissionDefinitionManager = permissionDefinitionManager;
_staticPermissionDefinitionStore = staticPermissionDefinitionStore;
_dynamicPermissionDefinitionStore = dynamicPermissionDefinitionStore;
_groupDefinitionRepository = groupDefinitionRepository;
_groupDefinitionBasicRepository = groupDefinitionBasicRepository;
}
@ -45,7 +51,14 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
.WithData(nameof(PermissionGroupDefinitionRecord.Name), input.Name);
}
var groupDefinitionRecord = new PermissionGroupDefinitionRecord(
var groupDefinitionRecord = await _groupDefinitionBasicRepository.FindAsync(x => x.Name == input.Name);
if (groupDefinitionRecord != null)
{
throw new BusinessException(PermissionManagementErrorCodes.GroupDefinition.AlreayNameExists)
.WithData(nameof(PermissionGroupDefinitionRecord.Name), input.Name);
}
groupDefinitionRecord = new PermissionGroupDefinitionRecord(
GuidGenerator.Create(),
input.Name,
input.DisplayName);
@ -55,13 +68,11 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
groupDefinitionRecord.SetProperty(property.Key, property.Value);
}
await _groupDefinitionRepository.InsertAsync(groupDefinitionRecord);
groupDefinitionRecord = await _groupDefinitionRepository.InsertAsync(groupDefinitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
var dto = await GroupDefinitionRecordToDto(groupDefinitionRecord);
return dto;
return DefinitionRecordToDto(groupDefinitionRecord);
}
[Authorize(PermissionManagementPermissionNames.GroupDefinition.Delete)]
@ -69,62 +80,66 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
{
var groupDefinitionRecord = await FindByNameAsync(name);
if (groupDefinitionRecord == null)
if (groupDefinitionRecord != null)
{
return;
}
await _groupDefinitionRepository.DeleteAsync(groupDefinitionRecord);
await _groupDefinitionRepository.DeleteAsync(groupDefinitionRecord);
await CurrentUnitOfWork.SaveChangesAsync();
}
}
public async virtual Task<PermissionGroupDefinitionDto> GetAsync(string name)
{
var groupDefinition = await _permissionDefinitionManager.GetGroupOrNullAsync(name);
var staticGroups = await _staticPermissionDefinitionStore.GetGroupsAsync();
var groupDefinition = staticGroups.FirstOrDefault(x => x.Name == name);
if (groupDefinition != null)
{
return DefinitionToDto(groupDefinition, true);
}
var dynamicGroups = await _dynamicPermissionDefinitionStore.GetGroupsAsync();
groupDefinition = dynamicGroups.FirstOrDefault(x => x.Name == name);
if (groupDefinition == null)
{
throw new BusinessException(PermissionManagementErrorCodes.GroupDefinition.NameNotFount)
.WithData(nameof(PermissionGroupDefinitionRecord.Name), name);
}
var dto = await GroupDefinitionToDto(groupDefinition);
return dto;
return DefinitionToDto(groupDefinition);
}
public async virtual Task<PagedResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input)
public async virtual Task<ListResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input)
{
var groups = new List<PermissionGroupDefinitionDto>();
var groupDefinitions = await _permissionDefinitionManager.GetGroupsAsync();
var groupDefinitionFilter = groupDefinitions.AsQueryable()
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter));
var sorting = input.Sorting;
if (sorting.IsNullOrWhiteSpace())
{
sorting = nameof(PermissionDefinitionRecord.Name);
}
var filterGroupDefinitionCount = groupDefinitionFilter.Count();
var filterGroupDefinitions = groupDefinitionFilter
.OrderBy(sorting)
.PageBy(input.SkipCount, input.MaxResultCount);
foreach (var groupDefinition in filterGroupDefinitions)
{
var groupDto = await GroupDefinitionToDto(groupDefinition);
groups.Add(groupDto);
}
return new PagedResultDto<PermissionGroupDefinitionDto>(filterGroupDefinitionCount, groups);
var groupDtoList = new List<PermissionGroupDefinitionDto>();
var staticGroups = await _staticPermissionDefinitionStore.GetGroupsAsync();
var staticGroupsNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
groupDtoList.AddRange(staticGroups.Select(d => DefinitionToDto(d, true)));
var dynamicGroups = await _dynamicPermissionDefinitionStore.GetGroupsAsync();
groupDtoList.AddRange(dynamicGroups
.Where(d => !staticGroupsNames.Contains(d.Name))
.Select(d => DefinitionToDto(d)));
return new ListResultDto<PermissionGroupDefinitionDto>(
groupDtoList
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(input.Filter))
.ToList());
}
[Authorize(PermissionManagementPermissionNames.GroupDefinition.Update)]
public async virtual Task<PermissionGroupDefinitionDto> UpdateAsync(string name, PermissionGroupDefinitionUpdateDto input)
{
var groupDefinition = await _permissionDefinitionManager.GetGroupOrNullAsync(name);
if (groupDefinition != null)
{
throw new BusinessException(PermissionManagementErrorCodes.GroupDefinition.StaticGroupNotAllowedChanged)
.WithData("Name", name);
}
var groupDefinitionRecord = await FindByNameAsync(name);
if (groupDefinitionRecord == null)
@ -133,35 +148,20 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
GuidGenerator.Create(),
name,
input.DisplayName);
foreach (var property in input.ExtraProperties)
{
groupDefinitionRecord.SetProperty(property.Key, property.Value);
}
UpdateByInput(groupDefinitionRecord, input);
groupDefinitionRecord = await _groupDefinitionBasicRepository.InsertAsync(groupDefinitionRecord);
}
else
{
groupDefinitionRecord.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
groupDefinitionRecord.SetProperty(property.Key, property.Value);
}
if (!string.Equals(groupDefinitionRecord.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
groupDefinitionRecord.DisplayName = input.DisplayName;
}
UpdateByInput(groupDefinitionRecord, input);
groupDefinitionRecord = await _groupDefinitionBasicRepository.UpdateAsync(groupDefinitionRecord);
}
await CurrentUnitOfWork.SaveChangesAsync();
var dto = await GroupDefinitionRecordToDto(groupDefinitionRecord);
return dto;
return DefinitionRecordToDto(groupDefinitionRecord);
}
protected async virtual Task<PermissionGroupDefinitionRecord> FindByNameAsync(string name)
@ -174,12 +174,27 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
return groupDefinitionRecord;
}
protected virtual Task<PermissionGroupDefinitionDto> GroupDefinitionRecordToDto(PermissionGroupDefinitionRecord groupDefinitionRecord)
protected virtual void UpdateByInput(PermissionGroupDefinitionRecord record, PermissionGroupDefinitionCreateOrUpdateDto input)
{
record.ExtraProperties.Clear();
foreach (var property in input.ExtraProperties)
{
record.SetProperty(property.Key, property.Value);
}
if (!string.Equals(record.DisplayName, input.DisplayName, StringComparison.InvariantCultureIgnoreCase))
{
record.DisplayName = input.DisplayName;
}
}
protected virtual PermissionGroupDefinitionDto DefinitionRecordToDto(PermissionGroupDefinitionRecord groupDefinitionRecord)
{
var groupDto = new PermissionGroupDefinitionDto
{
IsStatic = false,
Name = groupDefinitionRecord.Name,
DisplayName = groupDefinitionRecord.DisplayName,
ExtraProperties = new ExtraPropertyDictionary(),
};
foreach (var property in groupDefinitionRecord.ExtraProperties)
@ -187,15 +202,17 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
groupDto.SetProperty(property.Key, property.Value);
}
return Task.FromResult(groupDto);
return groupDto;
}
protected virtual Task<PermissionGroupDefinitionDto> GroupDefinitionToDto(PermissionGroupDefinition groupDefinition)
protected virtual PermissionGroupDefinitionDto DefinitionToDto(PermissionGroupDefinition groupDefinition, bool isStatic = false)
{
var groupDto = new PermissionGroupDefinitionDto
{
IsStatic = isStatic,
Name = groupDefinition.Name,
DisplayName = _localizableStringSerializer.Serialize(groupDefinition.DisplayName),
ExtraProperties = new ExtraPropertyDictionary(),
};
foreach (var property in groupDefinition.Properties)
@ -203,6 +220,6 @@ public class PermissionGroupDefinitionAppService : PermissionManagementAppServic
groupDto.SetProperty(property.Key, property.Value);
}
return Task.FromResult(groupDto);
return groupDto;
}
}

11
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/AbpPermissionManagementHttpApiModule.cs

@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.Localization;
using Volo.Abp.Validation.Localization;
using VoloAbpPermissionManagementHttpApiModule = Volo.Abp.PermissionManagement.HttpApi.AbpPermissionManagementHttpApiModule;
namespace LINGYUN.Abp.PermissionManagement.HttpApi;
@ -25,4 +27,13 @@ public class AbpPermissionManagementHttpApiModule : AbpModule
typeof(AbpPermissionManagementApplicationContractsModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLocalizationOptions>(options =>
{
options.Resources.Get<AbpPermissionManagementResource>()
.AddBaseTypes(typeof(AbpValidationResource));
});
}
}

2
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/Definitions/PermissionDefinitionController.cs

@ -47,7 +47,7 @@ public class PermissionDefinitionController : PermissionManagementControllerBase
}
[HttpGet]
public virtual Task<PagedResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input)
public virtual Task<ListResultDto<PermissionDefinitionDto>> GetListAsync(PermissionDefinitionGetListInput input)
{
return _service.GetListAsync(input);
}

2
aspnet-core/modules/permissions-management/LINGYUN.Abp.PermissionManagement.HttpApi/LINGYUN/Abp/PermissionManagement/HttpApi/Definitions/PermissionGroupDefinitionController.cs

@ -47,7 +47,7 @@ public class PermissionGroupDefinitionController : PermissionManagementControlle
}
[HttpGet]
public virtual Task<PagedResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input)
public virtual Task<ListResultDto<PermissionGroupDefinitionDto>> GetListAsync(PermissionGroupDefinitionGetListInput input)
{
return _service.GetListAsync(input);
}

3
aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingDefinitionAppService.cs

@ -44,8 +44,7 @@ public class SettingDefinitionAppService : SettingManagementAppServiceBase, ISet
[Authorize(SettingManagementPermissions.Definition.Create)]
public async virtual Task<SettingDefinitionDto> CreateAsync(SettingDefinitionCreateDto input)
{
var settingDefinition = await _settingDefinitionManager.GetOrNullAsync(input.Name);
if (settingDefinition != null)
if (await _staticSettingDefinitionStore.GetOrNullAsync(input.Name) != null)
{
throw new BusinessException(SettingManagementErrorCodes.Definition.DuplicateName)
.WithData("Name", input.Name);

2
aspnet-core/services/LY.MicroService.Applications.Single/Properties/PublishProfiles/FolderProfile1.pubxml.user

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<_PublishTargetUrl>D:\C Sharp\Open-Sources\abp-next-admin\aspnet-core\services\LY.MicroService.Applications.Single\bin\Release\net7.0\publish\</_PublishTargetUrl>
<History>True|2023-10-18T09:35:46.8173884Z;True|2023-10-18T08:20:25.4724000+08:00;True|2023-10-17T20:41:39.8166265+08:00;True|2023-10-17T20:32:05.6551486+08:00;True|2023-10-17T20:29:19.7472269+08:00;True|2023-10-17T18:19:43.6344087+08:00;True|2023-10-17T17:20:20.0797294+08:00;True|2023-10-17T09:00:40.0972772+08:00;True|2023-10-17T08:55:45.9955777+08:00;True|2023-10-17T08:24:44.7012068+08:00;True|2023-10-16T18:10:03.1232748+08:00;True|2023-10-16T17:52:29.6719862+08:00;True|2023-10-16T16:19:59.2730598+08:00;True|2023-10-16T15:43:02.1945039+08:00;True|2023-10-16T08:02:18.1704809+08:00;True|2023-10-14T16:45:12.9838816+08:00;False|2023-10-14T16:43:45.3839438+08:00;True|2023-10-14T15:48:46.0226669+08:00;True|2023-10-14T09:19:00.9482903+08:00;True|2023-10-13T16:17:49.1884589+08:00;False|2023-10-13T16:14:57.9204224+08:00;False|2023-10-13T16:12:31.6792705+08:00;False|2023-10-13T16:09:52.4764354+08:00;True|2023-10-12T11:06:51.5823669+08:00;</History>
<History>True|2023-10-24T01:18:20.5030667Z;True|2023-10-23T17:44:35.6749808+08:00;True|2023-10-18T17:35:46.8173884+08:00;True|2023-10-18T08:20:25.4724000+08:00;True|2023-10-17T20:41:39.8166265+08:00;True|2023-10-17T20:32:05.6551486+08:00;True|2023-10-17T20:29:19.7472269+08:00;True|2023-10-17T18:19:43.6344087+08:00;True|2023-10-17T17:20:20.0797294+08:00;True|2023-10-17T09:00:40.0972772+08:00;True|2023-10-17T08:55:45.9955777+08:00;True|2023-10-17T08:24:44.7012068+08:00;True|2023-10-16T18:10:03.1232748+08:00;True|2023-10-16T17:52:29.6719862+08:00;True|2023-10-16T16:19:59.2730598+08:00;True|2023-10-16T15:43:02.1945039+08:00;True|2023-10-16T08:02:18.1704809+08:00;True|2023-10-14T16:45:12.9838816+08:00;False|2023-10-14T16:43:45.3839438+08:00;True|2023-10-14T15:48:46.0226669+08:00;True|2023-10-14T09:19:00.9482903+08:00;True|2023-10-13T16:17:49.1884589+08:00;False|2023-10-13T16:14:57.9204224+08:00;False|2023-10-13T16:12:31.6792705+08:00;False|2023-10-13T16:09:52.4764354+08:00;True|2023-10-12T11:06:51.5823669+08:00;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>
Loading…
Cancel
Save