Browse Source

Merge pull request #1056 from colinin/openiddict

Openiddict
pull/1067/head
yx lin 1 year ago
committed by GitHub
parent
commit
5644547dff
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      apps/vben5/apps/app-antd/package.json
  2. 7
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  3. 7
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  4. 48
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  5. 15
      apps/vben5/apps/app-antd/src/views/openiddict/applications/index.vue
  6. 15
      apps/vben5/apps/app-antd/src/views/openiddict/authorizations/index.vue
  7. 15
      apps/vben5/apps/app-antd/src/views/openiddict/scopes/index.vue
  8. 15
      apps/vben5/apps/app-antd/src/views/openiddict/tokens/index.vue
  9. 7
      apps/vben5/apps/app-antd/vite.config.mts
  10. 1
      apps/vben5/packages/@abp/core/src/types/index.ts
  11. 28
      apps/vben5/packages/@abp/core/src/types/openid.ts
  12. 1
      apps/vben5/packages/@abp/identity/src/api/index.ts
  13. 55
      apps/vben5/packages/@abp/identity/src/api/users-lookup.ts
  14. 8
      apps/vben5/packages/@abp/identity/src/constants/permissions.ts
  15. 1
      apps/vben5/packages/@abp/identity/src/index.ts
  16. 10
      apps/vben5/packages/@abp/identity/src/types/users.ts
  17. 43
      apps/vben5/packages/@abp/openiddict/package.json
  18. 74
      apps/vben5/packages/@abp/openiddict/src/api/applications.ts
  19. 43
      apps/vben5/packages/@abp/openiddict/src/api/authorizations.ts
  20. 4
      apps/vben5/packages/@abp/openiddict/src/api/index.ts
  21. 13
      apps/vben5/packages/@abp/openiddict/src/api/openid.ts
  22. 72
      apps/vben5/packages/@abp/openiddict/src/api/scopes.ts
  23. 41
      apps/vben5/packages/@abp/openiddict/src/api/tokens.ts
  24. 449
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationModal.vue
  25. 82
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationSecretModal.vue
  26. 287
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue
  27. 154
      apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationModal.vue
  28. 273
      apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationTable.vue
  29. 81
      apps/vben5/packages/@abp/openiddict/src/components/display-names/DisplayNameModal.vue
  30. 103
      apps/vben5/packages/@abp/openiddict/src/components/display-names/DisplayNameTable.vue
  31. 11
      apps/vben5/packages/@abp/openiddict/src/components/display-names/types.ts
  32. 4
      apps/vben5/packages/@abp/openiddict/src/components/index.ts
  33. 66
      apps/vben5/packages/@abp/openiddict/src/components/properties/PropertyModal.vue
  34. 101
      apps/vben5/packages/@abp/openiddict/src/components/properties/PropertyTable.vue
  35. 11
      apps/vben5/packages/@abp/openiddict/src/components/properties/types.ts
  36. 212
      apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeModal.vue
  37. 199
      apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue
  38. 165
      apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenModal.vue
  39. 297
      apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenTable.vue
  40. 52
      apps/vben5/packages/@abp/openiddict/src/components/uris/UriModal.vue
  41. 106
      apps/vben5/packages/@abp/openiddict/src/components/uris/UriTable.vue
  42. 1
      apps/vben5/packages/@abp/openiddict/src/constants/index.ts
  43. 42
      apps/vben5/packages/@abp/openiddict/src/constants/permissions.ts
  44. 4
      apps/vben5/packages/@abp/openiddict/src/index.ts
  45. 62
      apps/vben5/packages/@abp/openiddict/src/types/applications.ts
  46. 29
      apps/vben5/packages/@abp/openiddict/src/types/authorizations.ts
  47. 4
      apps/vben5/packages/@abp/openiddict/src/types/index.ts
  48. 41
      apps/vben5/packages/@abp/openiddict/src/types/scopes.ts
  49. 34
      apps/vben5/packages/@abp/openiddict/src/types/tokens.ts
  50. 6
      apps/vben5/packages/@abp/openiddict/tsconfig.json
  51. 3
      apps/vben5/packages/@abp/permission/src/components/permissions/PermissionModal.vue
  52. 2
      apps/vben5/pnpm-workspace.yaml

1
apps/vben5/apps/app-antd/package.json

@ -30,6 +30,7 @@
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*",
"@abp/identity": "workspace:*",
"@abp/openiddict": "workspace:*",
"@abp/request": "workspace:*",
"@abp/ui": "workspace:*",
"@vben/access": "workspace:*",

7
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -11,5 +11,12 @@
"organizationUnits": "Organization Units",
"auditLogs": "Audit Logs"
}
},
"openiddict": {
"title": "OpenIddict",
"applications": "Applications",
"authorizations": "Authorizations",
"scopes": "Scopes",
"tokens": "Tokens"
}
}

7
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -11,5 +11,12 @@
"organizationUnits": "组织机构",
"auditLogs": "审计日志"
}
},
"openiddict": {
"title": "OpenIddict",
"applications": "应用管理",
"authorizations": "授权管理",
"scopes": "范围管理",
"tokens": "授权令牌"
}
}

48
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -92,6 +92,54 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
meta: {
title: $t('abp.openiddict.title'),
icon: 'mdi:openid',
},
name: 'OpenIddict',
path: '/openiddict',
children: [
{
meta: {
title: $t('abp.openiddict.applications'),
icon: 'carbon:application',
},
name: 'OpenIddictApplications',
path: '/openiddict/applications',
component: () =>
import('#/views/openiddict/applications/index.vue'),
},
{
meta: {
title: $t('abp.openiddict.authorizations'),
icon: 'arcticons:ente-authenticator',
},
name: 'OpenIddictAuthorizations',
path: '/openiddict/authorizations',
component: () =>
import('#/views/openiddict/authorizations/index.vue'),
},
{
meta: {
title: $t('abp.openiddict.scopes'),
icon: 'et:scope',
},
name: 'OpenIddictScopes',
path: '/openiddict/scopes',
component: () => import('#/views/openiddict/scopes/index.vue'),
},
{
meta: {
title: $t('abp.openiddict.tokens'),
icon: 'oui:token-key',
},
name: 'OpenIddictTokens',
path: '/openiddict/tokens',
component: () => import('#/views/openiddict/tokens/index.vue'),
},
],
},
],
},
];

15
apps/vben5/apps/app-antd/src/views/openiddict/applications/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { ApplicationTable } from '@abp/openiddict';
defineOptions({
name: 'OpenIddictApplications',
});
</script>
<template>
<Page>
<ApplicationTable />
</Page>
</template>

15
apps/vben5/apps/app-antd/src/views/openiddict/authorizations/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { AuthorizationTable } from '@abp/openiddict';
defineOptions({
name: 'OpenIddictAuthorizations',
});
</script>
<template>
<Page>
<AuthorizationTable />
</Page>
</template>

15
apps/vben5/apps/app-antd/src/views/openiddict/scopes/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { ScopeTable } from '@abp/openiddict';
defineOptions({
name: 'OpenIddictScopes',
});
</script>
<template>
<Page>
<ScopeTable />
</Page>
</template>

15
apps/vben5/apps/app-antd/src/views/openiddict/tokens/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { TokenTable } from '@abp/openiddict';
defineOptions({
name: 'OpenIddictTokens',
});
</script>
<template>
<Page>
<TokenTable />
</Page>
</template>

7
apps/vben5/apps/app-antd/vite.config.mts

@ -6,6 +6,13 @@ export default defineConfig(async () => {
vite: {
server: {
proxy: {
'/.well-known': {
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),
// mock代理目标地址
target: 'http://127.0.0.1:30001/',
ws: true,
},
'/api': {
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),

1
apps/vben5/packages/@abp/core/src/types/index.ts

@ -1,6 +1,7 @@
export * from './dto';
export * from './global';
export * from './localization';
export * from './openid';
export * from './rules';
export * from './settings';
export * from './table';

28
apps/vben5/packages/@abp/core/src/types/openid.ts

@ -0,0 +1,28 @@
interface OpenIdConfiguration {
authorization_endpoint: string;
backchannel_logout_session_supported: boolean;
backchannel_logout_supported: boolean;
check_session_iframe: string;
claims_supported: string[];
code_challenge_methods_supported: string[];
device_authorization_endpoint: string;
end_session_endpoint: string;
frontchannel_logout_session_supported: boolean;
frontchannel_logout_supported: boolean;
grant_types_supported: string[];
id_token_signing_alg_values_supported: string[];
introspection_endpoint: string;
issuer: string;
jwks_uri: string;
request_parameter_supported: boolean;
response_modes_supported: string[];
response_types_supported: string[];
revocation_endpoint: string;
scopes_supported: string[];
subject_types_supported: string[];
token_endpoint: string;
token_endpoint_auth_methods_supported: string[];
userinfo_endpoint: string;
}
export type { OpenIdConfiguration };

1
apps/vben5/packages/@abp/identity/src/api/index.ts

@ -0,0 +1 @@
export * as userLookupApi from './users-lookup';

55
apps/vben5/packages/@abp/identity/src/api/users-lookup.ts

@ -0,0 +1,55 @@
import type { ListResultDto } from '@abp/core';
import type {
IdentityUserDto,
UserLookupCountInput,
UserLookupSearchInput,
} from '../types/users';
import { requestClient } from '@abp/request';
/**
* id查询用户
* @param id id
* @returns
*/
export function findByIdApi(id: string): Promise<IdentityUserDto> {
return requestClient.get<IdentityUserDto>(`/api/identity/users/lookup/${id}`);
}
/**
*
* @param userName
* @returns
*/
export function findByUserNameApi(userName: string): Promise<IdentityUserDto> {
return requestClient.get<IdentityUserDto>(
`/api/identity/users/lookup/by-username/${userName}`,
);
}
/**
*
* @param input
* @returns
*/
export function searchApi(
input?: UserLookupSearchInput,
): Promise<ListResultDto<IdentityUserDto>> {
return requestClient.get<ListResultDto<IdentityUserDto>>(
`/api/identity/users/lookup/search`,
{
params: input,
},
);
}
/**
*
* @param input
*/
export function countApi(input?: UserLookupCountInput): Promise<number> {
return requestClient.get<number>(`/api/identity/users/lookup/count`, {
params: input,
});
}

8
apps/vben5/packages/@abp/identity/src/constants/permissions.ts

@ -45,3 +45,11 @@ export const SecurityLogPermissions = {
/** 删除 */
Delete: 'AbpAuditing.SecurityLog.Delete',
};
/**
*
* @deprecated 使.
* @todo abp框架权限定义
*/
export const UserLookupPermissions = {
Default: 'AbpIdentity.UserLookup',
};

1
apps/vben5/packages/@abp/identity/src/index.ts

@ -1,3 +1,4 @@
export * from './api';
export * from './components';
export * from './constants';
export * from './types';

10
apps/vben5/packages/@abp/identity/src/types/users.ts

@ -90,6 +90,14 @@ interface GetUserPagedListInput extends PagedAndSortedResultRequestDto {
type IdentityUserCreateDto = IdentityUserCreateOrUpdateDto;
type IdentityUserUpdateDto = IdentityUserCreateOrUpdateDto;
interface UserLookupCountInput {
filter?: string;
}
interface UserLookupSearchInput
extends UserLookupCountInput,
PagedAndSortedResultRequestDto {}
export type {
ChangeMyPasswordInput,
ChangeUserPasswordInput,
@ -98,4 +106,6 @@ export type {
IdentityUserDto,
IdentityUserOrganizationUnitUpdateDto,
IdentityUserUpdateDto,
UserLookupCountInput,
UserLookupSearchInput,
};

43
apps/vben5/packages/@abp/openiddict/package.json

@ -0,0 +1,43 @@
{
"name": "@abp/openiddict",
"version": "8.3.2",
"homepage": "https://github.com/colinin/abp-next-admin",
"bugs": "https://github.com/colinin/abp-next-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/colinin/abp-next-admin.git",
"directory": "packages/@abp/openiddict"
},
"license": "MIT",
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"dependencies": {
"@abp/core": "workspace:*",
"@abp/identity": "workspace:*",
"@abp/permission": "workspace:*",
"@abp/request": "workspace:*",
"@abp/ui": "workspace:*",
"@ant-design/icons-vue": "catalog:",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"ant-design-vue": "catalog:",
"lodash.debounce": "catalog:",
"vue": "catalog:*",
"vxe-table": "catalog:"
},
"devDependencies": {
"@types/lodash.debounce": "catalog:"
}
}

74
apps/vben5/packages/@abp/openiddict/src/api/applications.ts

@ -0,0 +1,74 @@
import type { PagedResultDto } from '@abp/core';
import type {
OpenIddictApplicationCreateDto,
OpenIddictApplicationDto,
OpenIddictApplicationGetListInput,
OpenIddictApplicationUpdateDto,
} from '../types/applications';
import { requestClient } from '@abp/request';
/**
*
* @param input
* @returns
*/
export function createApi(
input: OpenIddictApplicationCreateDto,
): Promise<OpenIddictApplicationDto> {
return requestClient.post<OpenIddictApplicationDto>(
'/api/openiddict/applications',
input,
);
}
/**
*
* @param id id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/openiddict/applications/${id}`);
}
/**
*
* @param id id
* @returns
*/
export function getApi(id: string): Promise<OpenIddictApplicationDto> {
return requestClient.get<OpenIddictApplicationDto>(
`/api/openiddict/applications/${id}`,
);
}
/**
*
* @param id id
* @returns
*/
export function updateApi(
id: string,
input: OpenIddictApplicationUpdateDto,
): Promise<OpenIddictApplicationDto> {
return requestClient.put<OpenIddictApplicationDto>(
`/api/openiddict/applications/${id}`,
input,
);
}
/**
*
* @param input
* @returns
*/
export function getPagedListApi(
input?: OpenIddictApplicationGetListInput,
): Promise<PagedResultDto<OpenIddictApplicationDto>> {
return requestClient.get<PagedResultDto<OpenIddictApplicationDto>>(
`/api/openiddict/applications`,
{
params: input,
},
);
}

43
apps/vben5/packages/@abp/openiddict/src/api/authorizations.ts

@ -0,0 +1,43 @@
import type { PagedResultDto } from '@abp/core';
import type {
OpenIddictAuthorizationDto,
OpenIddictAuthorizationGetListInput,
} from '../types/authorizations';
import { requestClient } from '@abp/request';
/**
*
* @param id id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/openiddict/authorizations/${id}`);
}
/**
*
* @param id id
* @returns
*/
export function getApi(id: string): Promise<OpenIddictAuthorizationDto> {
return requestClient.get<OpenIddictAuthorizationDto>(
`/api/openiddict/authorizations/${id}`,
);
}
/**
*
* @param input
* @returns
*/
export function getPagedListApi(
input?: OpenIddictAuthorizationGetListInput,
): Promise<PagedResultDto<OpenIddictAuthorizationDto>> {
return requestClient.get<PagedResultDto<OpenIddictAuthorizationDto>>(
`/api/openiddict/authorizations`,
{
params: input,
},
);
}

4
apps/vben5/packages/@abp/openiddict/src/api/index.ts

@ -0,0 +1,4 @@
export * as applicationsApi from './applications';
export * as authorizationsApi from './authorizations';
export * as scopesApi from './scopes';
export * as tokensApi from './tokens';

13
apps/vben5/packages/@abp/openiddict/src/api/openid.ts

@ -0,0 +1,13 @@
import type { OpenIdConfiguration } from '@abp/core';
import { requestClient } from '@abp/request';
/**
* openid发现端点
* @returns OpenId配置数据
*/
export function discoveryApi(): Promise<OpenIdConfiguration> {
return requestClient.get<OpenIdConfiguration>(
'/.well-known/openid-configuration',
);
}

72
apps/vben5/packages/@abp/openiddict/src/api/scopes.ts

@ -0,0 +1,72 @@
import type { PagedResultDto } from '@abp/core';
import type {
OpenIddictScopeCreateDto,
OpenIddictScopeDto,
OpenIddictScopeGetListInput,
OpenIddictScopeUpdateDto,
} from '../types/scopes';
import { requestClient } from '@abp/request';
/**
*
* @param input
* @returns
*/
export function createApi(
input: OpenIddictScopeCreateDto,
): Promise<OpenIddictScopeDto> {
return requestClient.post<OpenIddictScopeDto>(
'/api/openiddict/scopes',
input,
);
}
/**
*
* @param id id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/openiddict/scopes/${id}`);
}
/**
*
* @param id id
* @returns
*/
export function getApi(id: string): Promise<OpenIddictScopeDto> {
return requestClient.get<OpenIddictScopeDto>(`/api/openiddict/scopes/${id}`);
}
/**
*
* @param id id
* @returns
*/
export function updateApi(
id: string,
input: OpenIddictScopeUpdateDto,
): Promise<OpenIddictScopeDto> {
return requestClient.put<OpenIddictScopeDto>(
`/api/openiddict/scopes/${id}`,
input,
);
}
/**
*
* @param input
* @returns
*/
export function getPagedListApi(
input?: OpenIddictScopeGetListInput,
): Promise<PagedResultDto<OpenIddictScopeDto>> {
return requestClient.get<PagedResultDto<OpenIddictScopeDto>>(
`/api/openiddict/scopes`,
{
params: input,
},
);
}

41
apps/vben5/packages/@abp/openiddict/src/api/tokens.ts

@ -0,0 +1,41 @@
import type { PagedResultDto } from '@abp/core';
import type {
OpenIddictTokenDto,
OpenIddictTokenGetListInput,
} from '../types/tokens';
import { requestClient } from '@abp/request';
/**
*
* @param id id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/openiddict/tokens/${id}`);
}
/**
*
* @param id id
* @returns
*/
export function getApi(id: string): Promise<OpenIddictTokenDto> {
return requestClient.get<OpenIddictTokenDto>(`/api/openiddict/tokens/${id}`);
}
/**
*
* @param input
* @returns
*/
export function getPagedListApi(
input?: OpenIddictTokenGetListInput,
): Promise<PagedResultDto<OpenIddictTokenDto>> {
return requestClient.get<PagedResultDto<OpenIddictTokenDto>>(
`/api/openiddict/tokens`,
{
params: input,
},
);
}

449
apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationModal.vue

@ -0,0 +1,449 @@
<script setup lang="ts">
import type { OpenIdConfiguration } from '@abp/core';
import type { FormInstance } from 'ant-design-vue';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { DefaultOptionType } from 'ant-design-vue/es/select';
import type { TransferItem } from 'ant-design-vue/es/transfer';
import type { OpenIddictApplicationDto } from '../../types/applications';
import type { DisplayNameInfo } from '../display-names/types';
import type { PropertyInfo } from '../properties/types';
import {
type Component,
computed,
defineAsyncComponent,
defineEmits,
defineOptions,
reactive,
ref,
shallowRef,
toValue,
} from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DownOutlined } from '@ant-design/icons-vue';
import {
Dropdown,
Form,
Input,
InputPassword,
Menu,
message,
Select,
Tabs,
Transfer,
} from 'ant-design-vue';
import { createApi, getApi, updateApi } from '../../api/applications';
import { discoveryApi } from '../../api/openid';
import DisplayNameTable from '../display-names/DisplayNameTable.vue';
import PropertyTable from '../properties/PropertyTable.vue';
defineOptions({
name: 'ApplicationModal',
});
const emits = defineEmits<{
(event: 'change', data: OpenIddictApplicationDto): void;
}>();
const FormItem = Form.Item;
const MenuItem = Menu.Item;
const TabPane = Tabs.TabPane;
type TabKeys =
| 'authorize'
| 'basic'
| 'dispalyName'
| 'endpoint'
| 'props'
| 'scope';
const defaultModel = {
applicationType: 'web',
clientType: 'public',
consentType: 'explicit',
} as OpenIddictApplicationDto;
const form = ref<FormInstance>();
const formModel = ref<OpenIddictApplicationDto>({ ...defaultModel });
const openIdConfiguration = ref<OpenIdConfiguration>();
const activeTab = ref<TabKeys>('basic');
const uriComponentState = reactive<{
component: string;
title: string;
uris?: string[];
}>({
component: 'RedirectUris',
title: $t('AbpOpenIddict.DisplayName:RedirectUris'),
uris: [],
});
const uriComponentMap = shallowRef<{
[key: string]: Component;
}>({
PostLogoutRedirectUris: defineAsyncComponent(
() => import('../uris/UriTable.vue'),
),
RedirectUris: defineAsyncComponent(() => import('../uris/UriTable.vue')),
});
const clientTypes = reactive<DefaultOptionType[]>([
{ label: 'public', value: 'public' },
{ label: 'confidential', value: 'confidential' },
]);
const applicationTypes = reactive<DefaultOptionType[]>([
{ label: 'Web', value: 'web' },
{ label: 'Native', value: 'native' },
]);
const consentTypes = reactive<DefaultOptionType[]>([
{ label: 'explicit', value: 'explicit' },
{ label: 'external', value: 'external' },
{ label: 'implicit', value: 'implicit' },
{ label: 'systematic', value: 'systematic' },
]);
const endpoints = reactive<DefaultOptionType[]>([
{ label: 'authorization', value: 'authorization' },
{ label: 'token', value: 'token' },
{ label: 'logout', value: 'logout' },
{ label: 'device', value: 'device' },
{ label: 'revocation', value: 'revocation' },
{ label: 'introspection', value: 'introspection' },
]);
const getGrantTypes = computed(() => {
const types = openIdConfiguration.value?.grant_types_supported ?? [];
return types.map((type) => {
return {
label: type,
value: type,
};
});
});
const getResponseTypes = computed(() => {
const types = openIdConfiguration.value?.response_types_supported ?? [];
return types.map((type) => {
return {
label: type,
value: type,
};
});
});
const getSupportScopes = computed((): TransferItem[] => {
const types = openIdConfiguration.value?.scopes_supported ?? [];
return types.map((type) => {
return {
key: type,
title: type,
};
});
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await form.value?.validate();
const api = formModel.value.id
? updateApi(formModel.value.id, toValue(formModel))
: createApi(toValue(formModel));
modalApi.setState({ confirmLoading: true, loading: true });
api
.then((res) => {
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', res);
modalApi.close();
})
.finally(() => {
modalApi.setState({ confirmLoading: false, loading: false });
});
},
onOpenChange: async (isOpen: boolean) => {
if (isOpen) {
activeTab.value = 'basic';
formModel.value = { ...defaultModel };
uriComponentState.uris = [];
uriComponentState.component = 'RedirectUris';
modalApi.setState({
title: $t('AbpOpenIddict.Applications:AddNew'),
});
try {
modalApi.setState({ loading: true });
await onDiscovery();
const claimTypeDto = modalApi.getData<OpenIddictApplicationDto>();
if (claimTypeDto?.id) {
const dto = await getApi(claimTypeDto.id);
formModel.value = dto;
uriComponentState.uris = dto.redirectUris;
modalApi.setState({
title: `${$t('AbpOpenIddict.Applications')} - ${dto.clientId}`,
});
}
} finally {
modalApi.setState({ loading: false });
}
}
},
title: 'ClaimType',
});
async function onDiscovery() {
openIdConfiguration.value = await discoveryApi();
}
function onDisplayNameChange(displayName: DisplayNameInfo) {
formModel.value.displayNames ??= {};
formModel.value.displayNames[displayName.culture] = displayName.displayName;
}
function onDisplayNameDelete(displayName: DisplayNameInfo) {
formModel.value.displayNames ??= {};
delete formModel.value.displayNames[displayName.culture];
}
function onPropChange(prop: PropertyInfo) {
formModel.value.properties ??= {};
formModel.value.properties[prop.key] = prop.value;
}
function onPropDelete(prop: PropertyInfo) {
formModel.value.properties ??= {};
delete formModel.value.properties[prop.key];
}
function onSwitchUri(info: MenuInfo) {
activeTab.value = 'endpoint';
const eventKey = String(info.key);
switch (eventKey) {
case 'PostLogoutRedirectUris': {
uriComponentState.uris = formModel.value.postLogoutRedirectUris;
uriComponentState.title = $t(
'AbpOpenIddict.DisplayName:PostLogoutRedirectUris',
);
break;
}
case 'RedirectUris': {
uriComponentState.uris = formModel.value.redirectUris;
uriComponentState.title = $t('AbpOpenIddict.DisplayName:RedirectUris');
break;
}
}
uriComponentState.component = eventKey;
}
function onUriChange(uri: string) {
switch (uriComponentState.component) {
case 'PostLogoutRedirectUris': {
formModel.value.postLogoutRedirectUris ??= [];
formModel.value.postLogoutRedirectUris.push(uri);
break;
}
case 'RedirectUris': {
formModel.value.redirectUris ??= [];
formModel.value.redirectUris.push(uri);
break;
}
}
}
function onUriDelete(uri: string) {
switch (uriComponentState.component) {
case 'PostLogoutRedirectUris': {
formModel.value.postLogoutRedirectUris ??= [];
formModel.value.postLogoutRedirectUris =
formModel.value.postLogoutRedirectUris.filter((item) => item !== uri);
uriComponentState.uris = formModel.value.postLogoutRedirectUris;
break;
}
case 'RedirectUris': {
formModel.value.redirectUris ??= [];
formModel.value.redirectUris = formModel.value.redirectUris.filter(
(item) => item !== uri,
);
uriComponentState.uris = formModel.value.redirectUris;
break;
}
}
}
</script>
<template>
<Modal>
<Form
ref="form"
:label-col="{ span: 6 }"
:model="formModel"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="activeTab">
<!-- 基本信息 -->
<TabPane key="basic" :tab="$t('AbpOpenIddict.BasicInfo')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ApplicationType')"
name="applicationType"
required
>
<Select
v-model:value="formModel.applicationType"
:options="applicationTypes"
/>
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ClientId')"
name="clientId"
required
>
<Input v-model:value="formModel.clientId" autocomplete="off" />
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ClientType')"
name="clientType"
>
<Select
v-model:value="formModel.clientType"
:options="clientTypes"
/>
</FormItem>
<FormItem
v-if="!formModel.id && formModel.clientType === 'confidential'"
:label="$t('AbpOpenIddict.DisplayName:ClientSecret')"
name="clientSecret"
>
<InputPassword
v-model:value="formModel.clientSecret"
autocomplete="off"
/>
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ClientUri')"
name="clientUri"
>
<Input v-model:value="formModel.clientUri" />
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:LogoUri')"
name="logoUri"
>
<Input v-model:value="formModel.logoUri" autocomplete="off" />
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ConsentType')"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
name="consentType"
>
<Select
v-model:value="formModel.consentType"
:options="consentTypes"
default-value="explicit"
/>
</FormItem>
</TabPane>
<!-- 显示名称 -->
<TabPane key="dispalyName" :tab="$t('AbpOpenIddict.DisplayNames')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:DefaultDisplayName')"
name="displayName"
>
<Input v-model:value="formModel.displayName" autocomplete="off" />
</FormItem>
<DisplayNameTable
:data="formModel.displayNames"
@change="onDisplayNameChange"
@delete="onDisplayNameDelete"
/>
</TabPane>
<!-- 端点 -->
<TabPane key="endpoint">
<template #tab>
<Dropdown>
<span>
{{ $t('AbpOpenIddict.Endpoints') }}
<DownOutlined />
</span>
<template #overlay>
<Menu @click="onSwitchUri">
<MenuItem key="RedirectUris">
{{ $t('AbpOpenIddict.DisplayName:RedirectUris') }}
</MenuItem>
<MenuItem key="PostLogoutRedirectUris">
{{ $t('AbpOpenIddict.DisplayName:PostLogoutRedirectUris') }}
</MenuItem>
</Menu>
</template>
</Dropdown>
</template>
<component
:is="uriComponentMap[uriComponentState.component]"
:title="uriComponentState.title"
:uris="uriComponentState.uris"
@change="onUriChange"
@delete="onUriDelete"
/>
</TabPane>
<!-- 范围 -->
<TabPane key="scope" :tab="$t('AbpOpenIddict.Scopes')">
<Transfer
v-model:target-keys="formModel.scopes"
:data-source="getSupportScopes"
:list-style="{
width: '47%',
height: '338px',
}"
:render="(item) => item.title"
:titles="[
$t('AbpOpenIddict.Assigned'),
$t('AbpOpenIddict.Available'),
]"
class="tree-transfer"
/>
</TabPane>
<!-- 授权 -->
<TabPane key="authorize" :tab="$t('AbpOpenIddict.Authorizations')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:Endpoints')"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
name="endpoints"
>
<Select
v-model:value="formModel.endpoints"
:options="endpoints"
mode="tags"
/>
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:GrantTypes')"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
name="grantTypes"
>
<Select
v-model:value="formModel.grantTypes"
:options="getGrantTypes"
mode="tags"
/>
</FormItem>
<FormItem
:label="$t('AbpOpenIddict.DisplayName:ResponseTypes')"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
name="responseTypes"
>
<Select
v-model:value="formModel.responseTypes"
:options="getResponseTypes"
mode="tags"
/>
</FormItem>
</TabPane>
<!-- 属性 -->
<TabPane key="props" :tab="$t('AbpOpenIddict.Propertites')">
<PropertyTable
:data="formModel.properties"
@change="onPropChange"
@delete="onPropDelete"
/>
</TabPane>
</Tabs>
</Form>
</Modal>
</template>
<style scoped></style>

82
apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationSecretModal.vue

@ -0,0 +1,82 @@
<script setup lang="ts">
import type { OpenIddictApplicationDto } from '../../types';
import { ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
import { getApi, updateApi } from '../../api/applications';
defineOptions({
name: 'ApplicationSecretModal',
});
const emits = defineEmits<{
(event: 'change', data: OpenIddictApplicationDto): void;
}>();
const applicationModel = ref<OpenIddictApplicationDto>();
const [Form, formApi] = useVbenForm({
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
handleSubmit: onSubmit,
schema: [
{
component: 'InputPassword',
fieldName: 'clientSecret',
label: $t('AbpOpenIddict.DisplayName:ClientSecret'),
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
},
onOpenChange: async (isOpen) => {
if (isOpen) {
try {
modalApi.setState({ loading: true });
const { id } = modalApi.getData<OpenIddictApplicationDto>();
await onGet(id);
} finally {
modalApi.setState({ loading: false });
}
}
},
title: $t('AbpOpenIddict.ManageSecret'),
});
async function onGet(id: string) {
const dto = await getApi(id);
applicationModel.value = dto;
}
async function onSubmit(input: Record<string, any>) {
const dto = await updateApi(applicationModel.value!.id, {
...applicationModel.value!,
clientSecret: input.clientSecret,
});
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', dto);
modalApi.close();
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>
<style scoped></style>

287
apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue

@ -0,0 +1,287 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { OpenIddictApplicationDto } from '../../types/applications';
import { defineAsyncComponent, h } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { PermissionModal } from '@abp/permission';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
EditOutlined,
EllipsisOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue';
import { deleteApi, getPagedListApi } from '../../api/applications';
import { ApplicationsPermissions } from '../../constants/permissions';
defineOptions({
name: 'ApplicationTable',
});
const MenuItem = Menu.Item;
const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const SecretIcon = createIconifyIcon('codicon:gist-secret');
const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions');
const { hasAccessByCodes } = useAccess();
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<OpenIddictApplicationDto> = {
columns: [
{
align: 'left',
field: 'clientId',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:ClientId'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'consentType',
title: $t('AbpOpenIddict.DisplayName:ConsentType'),
width: 200,
},
{
align: 'center',
field: 'clientType',
title: $t('AbpOpenIddict.DisplayName:ClientType'),
width: 120,
},
{
align: 'center',
field: 'applicationType',
title: $t('AbpOpenIddict.DisplayName:ApplicationType'),
width: 150,
},
{
align: 'left',
field: 'clientUri',
title: $t('AbpOpenIddict.DisplayName:ClientUri'),
width: 150,
},
{
align: 'left',
field: 'logoUri',
title: $t('AbpOpenIddict.DisplayName:LogoUri'),
width: 120,
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([
ApplicationsPermissions.Update,
ApplicationsPermissions.Delete,
]),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const [ApplicationModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./ApplicationModal.vue'),
),
});
const [ApplicationSecretModal, secretModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./ApplicationSecretModal.vue'),
),
});
const [ApplicationPermissionModal, permissionModalApi] = useVbenModal({
connectedComponent: PermissionModal,
});
const [Grid, { query }] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const onCreate = () => {
modalApi.setData({});
modalApi.open();
};
const onUpdate = (row: OpenIddictApplicationDto) => {
modalApi.setData(row);
modalApi.open();
};
const onDelete = (row: OpenIddictApplicationDto) => {
Modal.confirm({
centered: true,
content: `${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.clientId])}`,
onOk: () => {
return deleteApi(row.id).then(() => {
message.success($t('AbpUi.SuccessfullyDeleted'));
query();
});
},
title: $t('AbpUi.AreYouSure'),
});
};
const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => {
switch (info.key) {
case 'permissions': {
permissionModalApi.setData({
displayName: row.clientId,
providerKey: row.clientId,
providerName: 'C',
});
permissionModalApi.open();
break;
}
case 'secret': {
secretModalApi.setData(row);
secretModalApi.open();
break;
}
}
};
</script>
<template>
<Grid :table-title="$t('AbpOpenIddict.Applications')">
<template #toolbar-tools>
<Button
:icon="h(PlusOutlined)"
type="primary"
v-access:code="[ApplicationsPermissions.Create]"
@click="onCreate"
>
{{ $t('AbpOpenIddict.Applications:AddNew') }}
</Button>
</template>
<template #required="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.required" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #static="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.isStatic" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<div class="basis-1/3">
<Button
:icon="h(EditOutlined)"
block
type="link"
v-access:code="[ApplicationsPermissions.Update]"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
</div>
<div class="basis-1/3">
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[ApplicationsPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
</div>
<div class="basis-1/3">
<Dropdown>
<template #overlay>
<Menu @click="(info) => onMenuClick(row, info)">
<MenuItem
v-if="
row.clientType === 'confidential' &&
hasAccessByCodes([ApplicationsPermissions.ManageSecret])
"
key="secret"
:icon="h(SecretIcon)"
>
{{ $t('AbpOpenIddict.GenerateSecret') }}
</MenuItem>
<MenuItem
v-if="
hasAccessByCodes([
ApplicationsPermissions.ManagePermissions,
])
"
key="permissions"
:icon="h(PermissionsOutlined)"
>
{{ $t('AbpOpenIddict.ManagePermissions') }}
</MenuItem>
</Menu>
</template>
<Button :icon="h(EllipsisOutlined)" type="link" />
</Dropdown>
</div>
</div>
</template>
</Grid>
<ApplicationModal @change="() => query()" />
<ApplicationSecretModal @change="() => query()" />
<ApplicationPermissionModal />
</template>
<style lang="scss" scoped></style>

154
apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationModal.vue

@ -0,0 +1,154 @@
<script setup lang="ts">
import type { OpenIddictAuthorizationDto } from '../../types';
import { useAccess } from '@vben/access';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
type IdentityUserDto,
userLookupApi,
UserLookupPermissions,
} from '@abp/identity';
import { CodeEditor } from '@abp/ui';
import { Select } from 'ant-design-vue';
import { getApi as getApplication } from '../../api/applications';
import { getApi as getAuthorization } from '../../api/authorizations';
defineOptions({
name: 'AuthorizationModal',
});
const Option = Select.Option;
const { hasAccessByCodes } = useAccess();
const [Form, formApi] = useVbenForm({
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
schema: [
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'applicationId',
label: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'subject',
label: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'type',
label: $t('AbpOpenIddict.DisplayName:Type'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'creationDate',
label: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'status',
label: $t('AbpOpenIddict.DisplayName:Status'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'scopes',
label: $t('AbpOpenIddict.DisplayName:Scopes'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'properties',
label: $t('AbpOpenIddict.DisplayName:Properties'),
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
async onOpenChange(isOpen) {
if (isOpen) {
try {
modalApi.setState({ loading: true });
const { id } = modalApi.getData<OpenIddictAuthorizationDto>();
await onGet(id);
} finally {
modalApi.setState({ loading: false });
}
}
},
showConfirmButton: false,
title: $t('AbpOpenIddict.Authorizations'),
});
async function onGet(id: string) {
const authorization = await getAuthorization(id);
const application = await getApplication(authorization.applicationId!);
let subjectInfo: IdentityUserDto | undefined;
if (hasAccessByCodes([UserLookupPermissions.Default])) {
subjectInfo = await userLookupApi.findByIdApi(authorization.subject!);
}
formApi.setValues({
...authorization,
applicationId: `${application.clientId}(${authorization.applicationId})`,
subject: subjectInfo?.userName
? `${subjectInfo.userName}(${authorization.subject})`
: authorization.subject,
});
}
</script>
<template>
<Modal>
<Form>
<template #scopes="{ modelValue }">
<Select :disabled="true" :value="modelValue" mode="tags">
<Option
v-for="scope in modelValue"
:key="scope"
:title="scope"
:value="scope"
/>
</Select>
</template>
<template #properties="{ modelValue }">
<CodeEditor :value="modelValue" readonly />
</template>
</Form>
</Modal>
</template>
<style scoped></style>

273
apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationTable.vue

@ -0,0 +1,273 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridProps } from '@abp/ui';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { OpenIddictApplicationDto } from '../../types';
import type { OpenIddictAuthorizationDto } from '../../types/authorizations';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, message, Modal, Select } from 'ant-design-vue';
import debounce from 'lodash.debounce';
import { getPagedListApi as getApplications } from '../../api/applications';
import { deleteApi, getPagedListApi } from '../../api/authorizations';
import { AuthorizationsPermissions } from '../../constants/permissions';
defineOptions({
name: 'AuthorizationTable',
});
const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const { hasAccessByCodes } = useAccess();
const applications = ref<OpenIddictApplicationDto[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: true,
collapsedRows: 2,
//
commonConfig: {
// label
colon: true,
//
componentProps: {
class: 'w-full',
},
},
fieldMappingTime: [
['creationTime', ['beginCreationTime', 'endCreationTime'], 'YYYY-MM-DD'],
],
handleReset: onFormReset,
schema: [
{
component: 'Select',
fieldName: 'clientId',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:ClientId'),
},
{
component: 'RangePicker',
fieldName: 'creationTime',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpOpenIddict.DisplayName:CreationTime'),
},
{
component: 'Input',
fieldName: 'subject',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
component: 'Input',
fieldName: 'status',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Status'),
},
{
component: 'Input',
fieldName: 'type',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Type'),
},
{
component: 'Input',
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<OpenIddictAuthorizationDto> = {
columns: [
{
align: 'left',
field: 'applicationId',
minWidth: 300,
title: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
align: 'left',
field: 'subject',
minWidth: 300,
title: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
align: 'left',
field: 'type',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:Type'),
},
{
align: 'left',
field: 'status',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:Status'),
},
{
align: 'left',
field: 'creationDate',
formatter: ({ cellValue }) => {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([AuthorizationsPermissions.Delete]),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const [AuthorizationModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./AuthorizationModal.vue'),
),
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const onSearchClient = debounce(async (filter?: string) => {
const { items } = await getApplications({
filter,
maxResultCount: 25,
});
applications.value = items;
}, 500);
function onChangeClient(value?: SelectValue) {
gridApi.formApi.setFieldValue('clientId', value);
}
function onUpdate(row: OpenIddictAuthorizationDto) {
modalApi.setData(row);
modalApi.open();
}
function onDelete(row: OpenIddictAuthorizationDto) {
Modal.confirm({
centered: true,
content: `${$t('AbpUi.ItemWillBeDeletedMessage')}`,
onOk: () => {
return deleteApi(row.id).then(() => {
message.success($t('AbpUi.SuccessfullyDeleted'));
gridApi.query();
});
},
title: $t('AbpUi.AreYouSure'),
});
}
function onFormReset() {
gridApi.formApi.resetForm();
gridApi.formApi.submitForm();
}
onMounted(onSearchClient);
</script>
<template>
<Grid :table-title="$t('AbpOpenIddict.Authorizations')">
<template #form-clientId="{ modelValue }">
<Select
:default-active-first-option="false"
:field-names="{ label: 'clientId', value: 'id' }"
:filter-option="false"
:options="applications"
:placeholder="$t('ui.placeholder.select')"
:value="modelValue"
allow-clear
class="w-full"
show-search
@change="onChangeClient"
@search="onSearchClient"
/>
</template>
<template #required="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.required" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #static="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.isStatic" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<div class="basis-1/2">
<Button
:icon="h(EditOutlined)"
block
type="link"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
</div>
<div class="basis-1/2">
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[AuthorizationsPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
</div>
</div>
</template>
</Grid>
<AuthorizationModal @change="() => gridApi.query()" />
</template>
<style lang="scss" scoped></style>

81
apps/vben5/packages/@abp/openiddict/src/components/display-names/DisplayNameModal.vue

@ -0,0 +1,81 @@
<script setup lang="ts">
import type { DisplayNameInfo } from './types';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
defineOptions({
name: 'DisplayNameModal',
});
const emits = defineEmits<{
(event: 'change', data: DisplayNameInfo): void;
}>();
const { application } = useAbpStore();
const [Form, formApi] = useVbenForm({
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
handleSubmit: onSubmit,
schema: [
{
component: 'Select',
componentProps: {
autocomplete: 'off',
fieldNames: {
label: 'displayName',
value: 'cultureName',
},
options: application?.localization.languages,
},
fieldName: 'culture',
label: $t('AbpOpenIddict.DisplayName:CultureName'),
rules: 'required',
},
{
component: 'Input',
componentProps: {
autocomplete: 'off',
},
fieldName: 'displayName',
label: $t('AbpOpenIddict.DisplayName:DisplayName'),
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
},
title: $t('AbpOpenIddict.DisplayName.DisplayNames'),
});
function onSubmit(input: Record<string, any>) {
emits('change', {
culture: input.culture,
displayName: input.displayName,
});
modalApi.close();
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>
<style scoped></style>

103
apps/vben5/packages/@abp/openiddict/src/components/display-names/DisplayNameTable.vue

@ -0,0 +1,103 @@
<script setup lang="ts">
import type { VxeGridPropTypes } from 'vxe-table';
import type { DisplayNameInfo, DisplayNameProps } from './types';
import { computed, defineAsyncComponent, h, reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { Button, Popconfirm } from 'ant-design-vue';
import { VxeGrid } from 'vxe-table';
defineOptions({
name: 'DisplayNameTable',
});
const props = defineProps<DisplayNameProps>();
const emits = defineEmits<{
(event: 'change', data: DisplayNameInfo): void;
(event: 'delete', data: DisplayNameInfo): void;
}>();
const getDataResource = computed((): DisplayNameInfo[] => {
if (!props.data) return [];
return Object.keys(props.data).map((item) => {
return {
culture: item,
displayName: props.data![item]!,
};
});
});
const columnsConfig = reactive<VxeGridPropTypes.Columns<DisplayNameInfo>>([
{
align: 'left',
field: 'culture',
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:CultureName'),
},
{
align: 'left',
field: 'displayName',
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:DisplayName'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
]);
const [DisplayNameModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./DisplayNameModal.vue'),
),
});
function onCreate() {
modalApi.open();
}
function onDelete(prop: DisplayNameInfo) {
emits('delete', prop);
}
function onChange(prop: DisplayNameInfo) {
emits('change', prop);
}
</script>
<template>
<VxeGrid
:columns="columnsConfig"
:data="getDataResource"
:toolbar-config="{ slots: { tools: 'toolbar_tools' } }"
>
<template #toolbar_tools>
<Button :icon="h(PlusOutlined)" type="primary" @click="onCreate">
{{ $t('AbpOpenIddict.DisplayName:AddNew') }}
</Button>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Popconfirm
:title="`${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.culture])}`"
@confirm="onDelete(row)"
>
<Button :icon="h(DeleteOutlined)" block danger type="link">
{{ $t('AbpUi.Delete') }}
</Button>
</Popconfirm>
</div>
</template>
</VxeGrid>
<DisplayNameModal @change="onChange" />
</template>
<style scoped></style>

11
apps/vben5/packages/@abp/openiddict/src/components/display-names/types.ts

@ -0,0 +1,11 @@
import type { Dictionary } from '@abp/core';
interface DisplayNameInfo {
culture: string;
displayName: string;
}
interface DisplayNameProps {
data?: Dictionary<string, string>;
}
export type { DisplayNameInfo, DisplayNameProps };

4
apps/vben5/packages/@abp/openiddict/src/components/index.ts

@ -0,0 +1,4 @@
export { default as ApplicationTable } from './applications/ApplicationTable.vue';
export { default as AuthorizationTable } from './authorizations/AuthorizationTable.vue';
export { default as ScopeTable } from './scopes/ScopeTable.vue';
export { default as TokenTable } from './tokens/TokenTable.vue';

66
apps/vben5/packages/@abp/openiddict/src/components/properties/PropertyModal.vue

@ -0,0 +1,66 @@
<script setup lang="ts">
import type { PropertyInfo } from './types';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
defineOptions({
name: 'PropertyModal',
});
const emits = defineEmits<{
(event: 'change', data: PropertyInfo): void;
}>();
const [Form, formApi] = useVbenForm({
handleSubmit: onSubmit,
schema: [
{
component: 'Input',
componentProps: {
autocomplete: 'off',
},
fieldName: 'key',
label: $t('AbpOpenIddict.Propertites:Key'),
rules: 'required',
},
{
component: 'Input',
componentProps: {
autocomplete: 'off',
},
fieldName: 'value',
label: $t('AbpOpenIddict.Propertites:Value'),
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
},
title: $t('AbpOpenIddict.Propertites'),
});
function onSubmit(input: Record<string, any>) {
emits('change', {
key: input.key,
value: input.value,
});
modalApi.close();
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>
<style scoped></style>

101
apps/vben5/packages/@abp/openiddict/src/components/properties/PropertyTable.vue

@ -0,0 +1,101 @@
<script setup lang="ts">
import type { VxeGridPropTypes } from 'vxe-table';
import type { PropertyInfo, PropertyProps } from './types';
import { computed, defineAsyncComponent, h, reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { Button, Popconfirm } from 'ant-design-vue';
import { VxeGrid } from 'vxe-table';
defineOptions({
name: 'PropertyTable',
});
const props = defineProps<PropertyProps>();
const emits = defineEmits<{
(event: 'change', data: PropertyInfo): void;
(event: 'delete', data: PropertyInfo): void;
}>();
const getDataResource = computed((): PropertyInfo[] => {
if (!props.data) return [];
return Object.keys(props.data).map((item) => {
return {
key: item,
value: props.data![item]!,
};
});
});
const columnsConfig = reactive<VxeGridPropTypes.Columns<PropertyInfo>>([
{
align: 'left',
field: 'key',
minWidth: 200,
title: $t('AbpOpenIddict.Propertites:Key'),
},
{
align: 'left',
field: 'value',
minWidth: 200,
title: $t('AbpOpenIddict.Propertites:Value'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
]);
const [PropertyModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./PropertyModal.vue')),
});
function onCreate() {
modalApi.open();
}
function onDelete(prop: PropertyInfo) {
emits('delete', prop);
}
function onChange(prop: PropertyInfo) {
emits('change', prop);
}
</script>
<template>
<VxeGrid
:columns="columnsConfig"
:data="getDataResource"
:toolbar-config="{ slots: { tools: 'toolbar_tools' } }"
>
<template #toolbar_tools>
<Button :icon="h(PlusOutlined)" type="primary" @click="onCreate">
{{ $t('AbpOpenIddict.Propertites:New') }}
</Button>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Popconfirm
:title="`${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.key])}`"
@confirm="onDelete(row)"
>
<Button :icon="h(DeleteOutlined)" block danger type="link">
{{ $t('AbpUi.Delete') }}
</Button>
</Popconfirm>
</div>
</template>
</VxeGrid>
<PropertyModal @change="onChange" />
</template>
<style scoped></style>

11
apps/vben5/packages/@abp/openiddict/src/components/properties/types.ts

@ -0,0 +1,11 @@
import type { Dictionary } from '@abp/core';
interface PropertyInfo {
key: string;
value: string;
}
interface PropertyProps {
data?: Dictionary<string, string>;
}
export type { PropertyInfo, PropertyProps };

212
apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeModal.vue

@ -0,0 +1,212 @@
<script setup lang="ts">
import type { OpenIdConfiguration } from '@abp/core';
import type { FormInstance } from 'ant-design-vue';
import type { TransferItem } from 'ant-design-vue/es/transfer';
import type { OpenIddictScopeDto } from '../../types/scopes';
import type { DisplayNameInfo } from '../display-names/types';
import type { PropertyInfo } from '../properties/types';
import { computed, defineEmits, defineOptions, ref, toValue } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { Form, Input, message, Tabs, Transfer } from 'ant-design-vue';
import { discoveryApi } from '../../api/openid';
import { createApi, getApi, updateApi } from '../../api/scopes';
import DisplayNameTable from '../display-names/DisplayNameTable.vue';
import PropertyTable from '../properties/PropertyTable.vue';
defineOptions({
name: 'ScopeModal',
});
const emits = defineEmits<{
(event: 'change', data: OpenIddictScopeDto): void;
}>();
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
type TabKeys =
| 'authorize'
| 'basic'
| 'description'
| 'dispalyName'
| 'props'
| 'resource';
const defaultModel = {} as OpenIddictScopeDto;
const form = ref<FormInstance>();
const formModel = ref<OpenIddictScopeDto>({ ...defaultModel });
const openIdConfiguration = ref<OpenIdConfiguration>();
const activeTab = ref<TabKeys>('basic');
const getSupportClaims = computed((): TransferItem[] => {
const types = openIdConfiguration.value?.claims_supported ?? [];
return types.map((type) => {
return {
key: type,
title: type,
};
});
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await form.value?.validate();
const api = formModel.value.id
? updateApi(formModel.value.id, toValue(formModel))
: createApi(toValue(formModel));
modalApi.setState({ confirmLoading: true, loading: true });
api
.then((res) => {
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', res);
modalApi.close();
})
.finally(() => {
modalApi.setState({ confirmLoading: false, loading: false });
});
},
onOpenChange: async (isOpen: boolean) => {
if (isOpen) {
activeTab.value = 'basic';
formModel.value = { ...defaultModel };
modalApi.setState({
title: $t('AbpOpenIddict.Scopes:AddNew'),
});
try {
modalApi.setState({ loading: true });
await onDiscovery();
const { id } = modalApi.getData<OpenIddictScopeDto>();
id && (await onGet(id));
} finally {
modalApi.setState({ loading: false });
}
}
},
title: $t('AbpOpenIddict.Scopes:AddNew'),
});
async function onGet(id: string) {
const dto = await getApi(id);
formModel.value = dto;
modalApi.setState({
title: `${$t('AbpOpenIddict.Scopes')} - ${dto.name}`,
});
}
async function onDiscovery() {
openIdConfiguration.value = await discoveryApi();
}
function onDescriptionChange(displayName: DisplayNameInfo) {
formModel.value.descriptions ??= {};
formModel.value.descriptions[displayName.culture] = displayName.displayName;
}
function onDescriptionDelete(displayName: DisplayNameInfo) {
formModel.value.descriptions ??= {};
delete formModel.value.descriptions[displayName.culture];
}
function onDisplayNameChange(displayName: DisplayNameInfo) {
formModel.value.displayNames ??= {};
formModel.value.displayNames[displayName.culture] = displayName.displayName;
}
function onDisplayNameDelete(displayName: DisplayNameInfo) {
formModel.value.displayNames ??= {};
delete formModel.value.displayNames[displayName.culture];
}
function onPropChange(prop: PropertyInfo) {
formModel.value.properties ??= {};
formModel.value.properties[prop.key] = prop.value;
}
function onPropDelete(prop: PropertyInfo) {
formModel.value.properties ??= {};
delete formModel.value.properties[prop.key];
}
</script>
<template>
<Modal>
<Form
ref="form"
:label-col="{ span: 6 }"
:model="formModel"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="activeTab">
<!-- 基本信息 -->
<TabPane key="basic" :tab="$t('AbpOpenIddict.BasicInfo')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:Name')"
name="name"
required
>
<Input v-model:value="formModel.name" autocomplete="off" />
</FormItem>
</TabPane>
<!-- 显示名称 -->
<TabPane key="dispalyName" :tab="$t('AbpOpenIddict.DisplayNames')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:DefaultDisplayName')"
name="displayName"
>
<Input v-model:value="formModel.displayName" autocomplete="off" />
</FormItem>
<DisplayNameTable
:data="formModel.displayNames"
@change="onDisplayNameChange"
@delete="onDisplayNameDelete"
/>
</TabPane>
<!-- 描述 -->
<TabPane key="description" :tab="$t('AbpOpenIddict.Descriptions')">
<FormItem
:label="$t('AbpOpenIddict.DisplayName:DefaultDescription')"
name="description"
>
<Input v-model:value="formModel.description" autocomplete="off" />
</FormItem>
<DisplayNameTable
:data="formModel.descriptions"
@change="onDescriptionChange"
@delete="onDescriptionDelete"
/>
</TabPane>
<!-- 资源 -->
<TabPane key="resource" :tab="$t('AbpOpenIddict.Resources')">
<Transfer
v-model:target-keys="formModel.resources"
:data-source="getSupportClaims"
:list-style="{
width: '47%',
height: '338px',
}"
:render="(item) => item.title"
:titles="[
$t('AbpOpenIddict.Assigned'),
$t('AbpOpenIddict.Available'),
]"
class="tree-transfer"
/>
</TabPane>
<!-- 属性 -->
<TabPane key="props" :tab="$t('AbpOpenIddict.Propertites')">
<PropertyTable
:data="formModel.properties"
@change="onPropChange"
@delete="onPropDelete"
/>
</TabPane>
</Tabs>
</Form>
</Modal>
</template>
<style scoped></style>

199
apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue

@ -0,0 +1,199 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridProps } from '@abp/ui';
import type { OpenIddictScopeDto } from '../../types/scopes';
import { defineAsyncComponent, h } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { Button, message, Modal } from 'ant-design-vue';
import { deleteApi, getPagedListApi } from '../../api/scopes';
import { ScopesPermissions } from '../../constants/permissions';
defineOptions({
name: 'ScopeTable',
});
const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Input',
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
columns: [
{
align: 'left',
field: 'name',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'description',
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:Description'),
},
{
align: 'center',
field: 'creationTime',
formatter: ({ cellValue }) => {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 120,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const [ScopeModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./ScopeModal.vue')),
});
const [Grid, { query }] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const onCreate = () => {
modalApi.setData({});
modalApi.open();
};
const onUpdate = (row: OpenIddictScopeDto) => {
modalApi.setData(row);
modalApi.open();
};
const onDelete = (row: OpenIddictScopeDto) => {
Modal.confirm({
centered: true,
content: `${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.name])}`,
onOk: () => {
return deleteApi(row.id).then(() => {
message.success($t('AbpUi.SuccessfullyDeleted'));
query();
});
},
title: $t('AbpUi.AreYouSure'),
});
};
</script>
<template>
<Grid :table-title="$t('AbpOpenIddict.Scopes')">
<template #toolbar-tools>
<Button
:icon="h(PlusOutlined)"
type="primary"
v-access:code="[ScopesPermissions.Create]"
@click="onCreate"
>
{{ $t('AbpOpenIddict.Scopes:AddNew') }}
</Button>
</template>
<template #required="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.required" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #static="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.isStatic" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<div class="basis-1/3">
<Button
:icon="h(EditOutlined)"
block
type="link"
v-access:code="[ScopesPermissions.Update]"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
</div>
<div class="basis-1/3">
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[ScopesPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
</div>
</div>
</template>
</Grid>
<ScopeModal @change="() => query()" />
</template>
<style lang="scss" scoped></style>

165
apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenModal.vue

@ -0,0 +1,165 @@
<script setup lang="ts">
import type { OpenIddictTokenDto } from '../../types/tokens';
import { useAccess } from '@vben/access';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
type IdentityUserDto,
userLookupApi,
UserLookupPermissions,
} from '@abp/identity';
import { CodeEditor } from '@abp/ui';
import { getApi as getApplication } from '../../api/applications';
import { getApi as getAuthorization } from '../../api/tokens';
defineOptions({
name: 'TokenModal',
});
const { hasAccessByCodes } = useAccess();
const [Form, formApi] = useVbenForm({
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
schema: [
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'applicationId',
label: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'authorizationId',
label: $t('AbpOpenIddict.DisplayName:AuthorizationId'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'subject',
label: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'type',
label: $t('AbpOpenIddict.DisplayName:Type'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'status',
label: $t('AbpOpenIddict.DisplayName:Status'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'creationDate',
label: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'expirationDate',
label: $t('AbpOpenIddict.DisplayName:ExpirationDate'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'redemptionDate',
label: $t('AbpOpenIddict.DisplayName:RedemptionDate'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'referenceId',
label: $t('AbpOpenIddict.DisplayName:ReferenceId'),
},
{
component: 'Input',
componentProps: {
readonly: true,
},
fieldName: 'payload',
label: $t('AbpOpenIddict.DisplayName:Payload'),
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
async onOpenChange(isOpen) {
if (isOpen) {
try {
modalApi.setState({ loading: true });
const { id } = modalApi.getData<OpenIddictTokenDto>();
await onGet(id);
} finally {
modalApi.setState({ loading: false });
}
}
},
showConfirmButton: false,
title: $t('AbpOpenIddict.Tokens'),
});
async function onGet(id: string) {
const authorization = await getAuthorization(id);
const application = await getApplication(authorization.applicationId!);
let subjectInfo: IdentityUserDto | undefined;
if (hasAccessByCodes([UserLookupPermissions.Default])) {
subjectInfo = await userLookupApi.findByIdApi(authorization.subject!);
}
formApi.setValues({
...authorization,
applicationId: `${application.clientId}(${authorization.applicationId})`,
subject: subjectInfo?.userName
? `${subjectInfo.userName}(${authorization.subject})`
: authorization.subject,
});
}
</script>
<template>
<Modal>
<Form>
<template #payload="{ modelValue }">
<CodeEditor :value="modelValue" readonly />
</template>
</Form>
</Modal>
</template>
<style scoped></style>

297
apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenTable.vue

@ -0,0 +1,297 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridProps } from '@abp/ui';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { OpenIddictApplicationDto } from '../../types';
import type { OpenIddictTokenDto } from '../../types/tokens';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, message, Modal, Select } from 'ant-design-vue';
import debounce from 'lodash.debounce';
import { getPagedListApi as getApplications } from '../../api/applications';
import { deleteApi, getPagedListApi } from '../../api/tokens';
import { TokensPermissions } from '../../constants/permissions';
defineOptions({
name: 'TokenTable',
});
const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const { hasAccessByCodes } = useAccess();
const applications = ref<OpenIddictApplicationDto[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: true,
collapsedRows: 2,
//
commonConfig: {
// label
colon: true,
//
componentProps: {
class: 'w-full',
},
},
fieldMappingTime: [
['creationTime', ['beginCreationTime', 'endCreationTime'], 'YYYY-MM-DD'],
[
'expirationDate',
['beginExpirationDate', 'endExpirationDate'],
'YYYY-MM-DD',
],
],
handleReset: onFormReset,
schema: [
{
component: 'Select',
fieldName: 'clientId',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:ClientId'),
},
{
component: 'RangePicker',
fieldName: 'creationTime',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpOpenIddict.DisplayName:CreationTime'),
},
{
component: 'RangePicker',
fieldName: 'expirationDate',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpOpenIddict.DisplayName:ExpirationDate'),
},
{
component: 'Input',
fieldName: 'subject',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
component: 'Input',
fieldName: 'status',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Status'),
},
{
component: 'Input',
fieldName: 'type',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:Type'),
},
{
component: 'Input',
fieldName: 'referenceId',
formItemClass: 'col-span-1/3 items-baseline',
label: $t('AbpOpenIddict.DisplayName:ReferenceId'),
},
{
component: 'Input',
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
columns: [
{
align: 'left',
field: 'applicationId',
minWidth: 300,
title: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
align: 'left',
field: 'subject',
minWidth: 300,
title: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
align: 'left',
field: 'type',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:Type'),
},
{
align: 'left',
field: 'status',
minWidth: 150,
title: $t('AbpOpenIddict.DisplayName:Status'),
},
{
align: 'left',
field: 'creationDate',
formatter: ({ cellValue }) => {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
align: 'left',
field: 'expirationDate',
formatter: ({ cellValue }) => {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
title: $t('AbpOpenIddict.DisplayName:ExpirationDate'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([TokensPermissions.Delete]),
width: 220,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const [TokenModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./TokenModal.vue')),
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const onSearchClient = debounce(async (filter?: string) => {
const { items } = await getApplications({
filter,
maxResultCount: 25,
});
applications.value = items;
}, 500);
function onChangeClient(value?: SelectValue) {
gridApi.formApi.setFieldValue('clientId', value);
}
function onUpdate(row: OpenIddictTokenDto) {
modalApi.setData(row);
modalApi.open();
}
function onDelete(row: OpenIddictTokenDto) {
Modal.confirm({
centered: true,
content: `${$t('AbpUi.ItemWillBeDeletedMessage')}`,
onOk: () => {
return deleteApi(row.id).then(() => {
message.success($t('AbpUi.SuccessfullyDeleted'));
gridApi.query();
});
},
title: $t('AbpUi.AreYouSure'),
});
}
function onFormReset() {
gridApi.formApi.resetForm();
gridApi.formApi.submitForm();
}
onMounted(onSearchClient);
</script>
<template>
<Grid :table-title="$t('AbpOpenIddict.Tokens')">
<template #form-clientId="{ modelValue }">
<Select
:default-active-first-option="false"
:field-names="{ label: 'clientId', value: 'id' }"
:filter-option="false"
:options="applications"
:placeholder="$t('ui.placeholder.select')"
:value="modelValue"
allow-clear
class="w-full"
show-search
@change="onChangeClient"
@search="onSearchClient"
/>
</template>
<template #required="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.required" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #static="{ row }">
<div class="flex flex-row justify-center">
<CheckIcon v-if="row.isStatic" class="text-green-500" />
<CloseIcon v-else class="text-red-500" />
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<div class="basis-1/2">
<Button
:icon="h(EditOutlined)"
block
type="link"
@click="onUpdate(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
</div>
<div class="basis-1/2">
<Button
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[TokensPermissions.Delete]"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
</div>
</div>
</template>
</Grid>
<TokenModal @change="() => gridApi.query()" />
</template>
<style lang="scss" scoped></style>

52
apps/vben5/packages/@abp/openiddict/src/components/uris/UriModal.vue

@ -0,0 +1,52 @@
<script setup lang="ts">
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
defineOptions({
name: 'UriModal',
});
const emits = defineEmits<{
(event: 'change', data: string): void;
}>();
const [Form, formApi] = useVbenForm({
handleSubmit: onSubmit,
schema: [
{
component: 'Input',
componentProps: {
autocomplete: 'off',
},
fieldName: 'uri',
label: 'Uri',
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
draggable: true,
fullscreenButton: false,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
await formApi.validateAndSubmitForm();
},
title: $t('AbpOpenIddict.Uri:AddNew'),
});
function onSubmit(input: Record<string, any>) {
emits('change', input.uri);
modalApi.close();
}
</script>
<template>
<Modal>
<Form />
</Modal>
</template>
<style scoped></style>

106
apps/vben5/packages/@abp/openiddict/src/components/uris/UriTable.vue

@ -0,0 +1,106 @@
<script setup lang="ts">
import type { VxeGridPropTypes } from 'vxe-table';
import { computed, defineAsyncComponent, h, reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { Button, Popconfirm } from 'ant-design-vue';
import { VxeGrid } from 'vxe-table';
defineOptions({
name: 'UriTable',
});
const props = defineProps<{
title: string;
uris?: string[];
}>();
const emits = defineEmits<{
(event: 'change', uri: string): void;
(event: 'delete', uri: string): void;
}>();
interface Uri {
uri: string;
}
const getDataResource = computed((): Uri[] => {
if (!props.uris) return [];
return props.uris.map((uri) => {
return { uri };
});
});
const columnsConfig = reactive<VxeGridPropTypes.Columns<Uri>>([
{
align: 'left',
field: 'uri',
title: 'Uri',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
]);
const toolbarConfig = reactive<VxeGridPropTypes.ToolbarConfig>({
slots: {
buttons: 'toolbar_buttons',
tools: 'toolbar_tools',
},
});
const [UriModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./UriModal.vue')),
});
function onCreate() {
modalApi.open();
}
function onDelete(uri: Uri) {
emits('delete', uri.uri);
}
function onChange(uri: string) {
emits('change', uri);
}
</script>
<template>
<VxeGrid
:columns="columnsConfig"
:data="getDataResource"
:toolbar-config="toolbarConfig"
>
<template #toolbar_buttons>
<h3>{{ title }}</h3>
</template>
<template #toolbar_tools>
<Button :icon="h(PlusOutlined)" type="primary" @click="onCreate">
{{ $t('AbpOpenIddict.Uri:AddNew') }}
</Button>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Popconfirm
:title="`${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.uri])}`"
@confirm="onDelete(row)"
>
<Button :icon="h(DeleteOutlined)" block danger type="link">
{{ $t('AbpUi.Delete') }}
</Button>
</Popconfirm>
</div>
</template>
</VxeGrid>
<UriModal @change="onChange" />
</template>
<style scoped></style>

1
apps/vben5/packages/@abp/openiddict/src/constants/index.ts

@ -0,0 +1 @@
export * from './permissions';

42
apps/vben5/packages/@abp/openiddict/src/constants/permissions.ts

@ -0,0 +1,42 @@
/** 应用权限 */
export const ApplicationsPermissions = {
/** 新增 */
Create: 'AbpOpenIddict.Applications.Create',
Default: 'AbpOpenIddict.Applications',
/** 删除 */
Delete: 'AbpOpenIddict.Applications.Delete',
/** 管理功能 */
ManageFeatures: 'AbpOpenIddict.Applications.ManageFeatures',
/** 管理权限 */
ManagePermissions: 'AbpOpenIddict.Applications.ManagePermissions',
/** 管理密钥 */
ManageSecret: 'AbpOpenIddict.Applications.ManageSecret',
/** 更新 */
Update: 'AbpOpenIddict.Applications.Update',
};
/** 授权权限 */
export const AuthorizationsPermissions = {
/** 新增 */
Create: 'AbpOpenIddict.Authorizations.Create',
Default: 'AbpOpenIddict.Authorizations',
/** 删除 */
Delete: 'AbpOpenIddict.Authorizations.Delete',
};
/** 范围权限 */
export const ScopesPermissions = {
/** 新增 */
Create: 'AbpOpenIddict.Scopes.Create',
Default: 'AbpOpenIddict.Scopes',
/** 删除 */
Delete: 'AbpOpenIddict.Scopes.Delete',
/** 更新 */
Update: 'AbpOpenIddict.Scopes.Update',
};
/** 授权令牌权限 */
export const TokensPermissions = {
/** 新增 */
Create: 'AbpOpenIddict.Tokens.Create',
Default: 'AbpOpenIddict.Tokens',
/** 删除 */
Delete: 'AbpOpenIddict.Tokens.Delete',
};

4
apps/vben5/packages/@abp/openiddict/src/index.ts

@ -0,0 +1,4 @@
export * from './api';
export * from './components';
export * from './constants';
export * from './types';

62
apps/vben5/packages/@abp/openiddict/src/types/applications.ts

@ -0,0 +1,62 @@
import type {
Dictionary,
ExtensibleAuditedEntityDto,
ExtensibleObject,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface OpenIddictApplicationGetListInput
extends PagedAndSortedResultRequestDto {
filter?: string;
}
interface OpenIddictApplicationCreateOrUpdateDto extends ExtensibleObject {
applicationType?: string;
clientId: string;
clientSecret?: string;
clientType?: string;
clientUri?: string;
consentType?: string;
displayName?: string;
displayNames?: Dictionary<string, string>;
endpoints?: string[];
grantTypes?: string[];
logoUri?: string;
postLogoutRedirectUris?: string[];
properties?: Dictionary<string, string>;
redirectUris?: string[];
requirements?: string[];
responseTypes?: string[];
scopes?: string[];
}
type OpenIddictApplicationCreateDto = OpenIddictApplicationCreateOrUpdateDto;
type OpenIddictApplicationUpdateDto = OpenIddictApplicationCreateOrUpdateDto;
interface OpenIddictApplicationDto extends ExtensibleAuditedEntityDto<string> {
applicationType?: string;
clientId: string;
clientSecret?: string;
clientType?: string;
clientUri?: string;
consentType?: string;
displayName?: string;
displayNames?: Dictionary<string, string>;
endpoints?: string[];
grantTypes?: string[];
logoUri?: string;
postLogoutRedirectUris?: string[];
properties?: Dictionary<string, string>;
redirectUris?: string[];
requirements?: string[];
responseTypes?: string[];
scopes?: string[];
}
export type {
OpenIddictApplicationCreateDto,
OpenIddictApplicationDto,
OpenIddictApplicationGetListInput,
OpenIddictApplicationUpdateDto,
};

29
apps/vben5/packages/@abp/openiddict/src/types/authorizations.ts

@ -0,0 +1,29 @@
import type {
Dictionary,
ExtensibleAuditedEntityDto,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface OpenIddictAuthorizationDto
extends ExtensibleAuditedEntityDto<string> {
applicationId?: string;
creationDate?: string;
properties?: Dictionary<string, string>;
scopes?: string[];
status?: string;
subject?: string;
type?: string;
}
interface OpenIddictAuthorizationGetListInput
extends PagedAndSortedResultRequestDto {
beginCreationTime?: string;
clientId?: string;
endCreationTime?: string;
filter?: string;
status?: string;
subject?: string;
type?: string;
}
export type { OpenIddictAuthorizationDto, OpenIddictAuthorizationGetListInput };

4
apps/vben5/packages/@abp/openiddict/src/types/index.ts

@ -0,0 +1,4 @@
export * from './applications';
export * from './authorizations';
export * from './scopes';
export * from './tokens';

41
apps/vben5/packages/@abp/openiddict/src/types/scopes.ts

@ -0,0 +1,41 @@
import type {
Dictionary,
ExtensibleAuditedEntityDto,
ExtensibleObject,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface OpenIddictScopeCreateOrUpdateDto extends ExtensibleObject {
description?: string;
descriptions?: Dictionary<string, string>;
displayName?: string;
displayNames?: Dictionary<string, string>;
name: string;
properties?: Dictionary<string, string>;
resources?: string[];
}
type OpenIddictScopeCreateDto = OpenIddictScopeCreateOrUpdateDto;
interface OpenIddictScopeGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}
type OpenIddictScopeUpdateDto = OpenIddictScopeCreateOrUpdateDto;
interface OpenIddictScopeDto extends ExtensibleAuditedEntityDto<string> {
description?: string;
descriptions?: Dictionary<string, string>;
displayName?: string;
displayNames?: Dictionary<string, string>;
name: string;
properties?: Dictionary<string, string>;
resources?: string[];
}
export type {
OpenIddictScopeCreateDto,
OpenIddictScopeDto,
OpenIddictScopeGetListInput,
OpenIddictScopeUpdateDto,
};

34
apps/vben5/packages/@abp/openiddict/src/types/tokens.ts

@ -0,0 +1,34 @@
import type {
ExtensibleAuditedEntityDto,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface OpenIddictTokenGetListInput extends PagedAndSortedResultRequestDto {
authorizationId?: string;
beginCreationTime?: string;
beginExpirationDate?: string;
clientId?: string;
endCreationTime?: string;
endExpirationDate?: string;
filter?: string;
referenceId?: string;
status?: string;
subject?: string;
type?: string;
}
interface OpenIddictTokenDto extends ExtensibleAuditedEntityDto<string> {
applicationId?: string;
authorizationId?: string;
creationDate?: string;
expirationDate?: string;
payload?: string;
properties?: string;
redemptionDate?: string;
referenceId?: string;
status?: string;
subject?: string;
type?: string;
}
export type { OpenIddictTokenDto, OpenIddictTokenGetListInput };

6
apps/vben5/packages/@abp/openiddict/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

3
apps/vben5/packages/@abp/permission/src/components/permissions/PermissionModal.vue

@ -48,7 +48,8 @@ const permissionTree = ref<PermissionTree[]>([]);
const getPermissionTab = computed(() => {
return (tree: PermissionTree) => {
const grantCount = getGrantPermissionCount(tree);
return `${tree.displayName} (${grantCount})`;
const permissionCount = getPermissionCount(tree);
return `${tree.displayName} (${grantCount}/${permissionCount})`;
};
});

2
apps/vben5/pnpm-workspace.yaml

@ -46,6 +46,7 @@ catalog:
'@types/html-minifier-terser': ^7.0.2
'@types/jsonwebtoken': ^9.0.7
'@types/lodash.clonedeep': ^4.5.9
'@types/lodash.debounce': ^4.0.9
'@types/lodash.get': ^4.4.9
'@types/lodash.isequal': ^4.5.8
'@types/lodash.merge': ^4.6.9
@ -119,6 +120,7 @@ catalog:
jsonwebtoken: ^9.0.2
lint-staged: ^15.2.11
lodash.clonedeep: ^4.5.0
lodash.debounce: ^4.0.8
lodash.get: ^4.4.2
lodash.isequal: ^4.5.0
lodash.merge: ^4.6.2

Loading…
Cancel
Save