Browse Source

update react-admin

pull/1431/head
ksdaylight 1 month ago
parent
commit
a523919d78
  1. 17
      apps/react-admin/.env.development
  2. 1
      apps/react-admin/.gitignore
  3. 3
      apps/react-admin/biome.json
  4. 9
      apps/react-admin/package.json
  5. 8
      apps/react-admin/src/api/account/account.ts
  6. 37
      apps/react-admin/src/api/account/profile.ts
  7. 20
      apps/react-admin/src/api/account/scan-Qrcode.ts
  8. 58
      apps/react-admin/src/api/account/token.ts
  9. 25
      apps/react-admin/src/api/management/auditing/loggings.ts
  10. 57
      apps/react-admin/src/api/management/features/feature-definitions.ts
  11. 59
      apps/react-admin/src/api/management/features/feature-group-definitions.ts
  12. 37
      apps/react-admin/src/api/management/features/features.ts
  13. 57
      apps/react-admin/src/api/management/localization/languages.ts
  14. 16
      apps/react-admin/src/api/management/localization/localizations.ts
  15. 57
      apps/react-admin/src/api/management/localization/resources.ts
  16. 41
      apps/react-admin/src/api/management/localization/texts.ts
  17. 31
      apps/react-admin/src/api/management/notifications/my-subscribes.ts
  18. 59
      apps/react-admin/src/api/management/notifications/notification-definitions.ts
  19. 62
      apps/react-admin/src/api/management/notifications/notification-group-definitions.ts
  20. 14
      apps/react-admin/src/api/management/notifications/notifications.ts
  21. 33
      apps/react-admin/src/api/oss/containes.ts
  22. 32
      apps/react-admin/src/api/oss/objects.ts
  23. 59
      apps/react-admin/src/api/platform/data-dictionaries.ts
  24. 43
      apps/react-admin/src/api/platform/email-messages.ts
  25. 27
      apps/react-admin/src/api/platform/layouts.ts
  26. 27
      apps/react-admin/src/api/platform/menus.ts
  27. 33
      apps/react-admin/src/api/platform/my-favorite-menus.ts
  28. 11
      apps/react-admin/src/api/platform/my-menus.ts
  29. 19
      apps/react-admin/src/api/platform/role-menus.ts
  30. 43
      apps/react-admin/src/api/platform/sms-messages.ts
  31. 19
      apps/react-admin/src/api/platform/user-menus.ts
  32. 9
      apps/react-admin/src/api/request.ts
  33. 53
      apps/react-admin/src/api/saas/editions.ts
  34. 10
      apps/react-admin/src/api/saas/multi-tenancy.ts
  35. 111
      apps/react-admin/src/api/saas/tenants.ts
  36. 86
      apps/react-admin/src/api/tasks/job-infos.ts
  37. 19
      apps/react-admin/src/api/tasks/job-logs.ts
  38. 39
      apps/react-admin/src/api/text-templating/template-contents.ts
  39. 58
      apps/react-admin/src/api/text-templating/template-definitions.ts
  40. 64
      apps/react-admin/src/api/webhooks/send-attempts.ts
  41. 79
      apps/react-admin/src/api/webhooks/subscriptions.ts
  42. 57
      apps/react-admin/src/api/webhooks/webhook-definitions.ts
  43. 59
      apps/react-admin/src/api/webhooks/webhook-group-definitions.ts
  44. 110
      apps/react-admin/src/components/abp/account/change-password-modal.tsx
  45. 112
      apps/react-admin/src/components/abp/account/change-phone-number-modal.tsx
  46. 130
      apps/react-admin/src/components/abp/account/notice-settings.tsx
  47. 2
      apps/react-admin/src/components/abp/account/security-settings.tsx
  48. 2
      apps/react-admin/src/components/abp/account/session-settings.tsx
  49. 87
      apps/react-admin/src/components/abp/adapter/api-select.tsx
  50. 73
      apps/react-admin/src/components/abp/adapter/api-tree-select.tsx
  51. 39
      apps/react-admin/src/components/abp/adapter/icon-picker.tsx
  52. 36
      apps/react-admin/src/components/abp/claims/claim-table.tsx
  53. 27
      apps/react-admin/src/components/abp/display-names/display-name-table.tsx
  54. 333
      apps/react-admin/src/components/abp/features/feature-modal.tsx
  55. 149
      apps/react-admin/src/components/abp/features/state-check/feature-state-check.tsx
  56. 53
      apps/react-admin/src/components/abp/features/state-check/global-feature-state-check.tsx
  57. 139
      apps/react-admin/src/components/abp/permissions/state-check/permission-state-check.tsx
  58. 5
      apps/react-admin/src/components/abp/simple-state-checking/interface.ts
  59. 208
      apps/react-admin/src/components/abp/simple-state-checking/simple-state-checking-modal.tsx
  60. 214
      apps/react-admin/src/components/abp/simple-state-checking/simple-state-checking.tsx
  61. 4
      apps/react-admin/src/components/abp/string-value-type/index.ts
  62. 3
      apps/react-admin/src/components/abp/string-value-type/interface.ts
  63. 540
      apps/react-admin/src/components/abp/string-value-type/string-value-type-input.tsx
  64. 130
      apps/react-admin/src/components/abp/string-value-type/validator.ts
  65. 119
      apps/react-admin/src/components/abp/string-value-type/value-type.ts
  66. 4
      apps/react-admin/src/constants/management/auditing/permissions.ts
  67. 1
      apps/react-admin/src/constants/management/features/index.ts
  68. 20
      apps/react-admin/src/constants/management/features/permissions.ts
  69. 1
      apps/react-admin/src/constants/management/localization/index.ts
  70. 30
      apps/react-admin/src/constants/management/localization/permissions.ts
  71. 28
      apps/react-admin/src/constants/notifications/permissions.ts
  72. 18
      apps/react-admin/src/constants/oss/permissions.ts
  73. 59
      apps/react-admin/src/constants/platform/permissions.ts
  74. 44
      apps/react-admin/src/constants/request/http-status.ts
  75. 28
      apps/react-admin/src/constants/saas/permissions.ts
  76. 24
      apps/react-admin/src/constants/tasks/permissions.ts
  77. 10
      apps/react-admin/src/constants/text-templating/permissions.ts
  78. 41
      apps/react-admin/src/constants/webhooks/permissions.ts
  79. 2
      apps/react-admin/src/hooks/abp/fake-hooks/readme.md
  80. 37
      apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-authenticated-simple-state-checker.ts
  81. 42
      apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-features-simple-state-checker.ts
  82. 43
      apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-global-features-simple-state-checker.ts
  83. 93
      apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-permissions-simple-state-checker.ts
  84. 33
      apps/react-admin/src/hooks/abp/fake-hooks/use-abp-authorization.ts
  85. 55
      apps/react-admin/src/hooks/abp/fake-hooks/use-abp-feature.ts
  86. 43
      apps/react-admin/src/hooks/abp/fake-hooks/use-abp-global-feature.ts
  87. 71
      apps/react-admin/src/hooks/abp/fake-hooks/use-http-status-code-map.ts
  88. 107
      apps/react-admin/src/hooks/abp/fake-hooks/use-simple-state-check.ts
  89. 69
      apps/react-admin/src/hooks/abp/identity/usePasswordValidator.ts
  90. 65
      apps/react-admin/src/hooks/abp/identity/useRandomPassword.ts
  91. 63
      apps/react-admin/src/hooks/abp/use-Job-enums-map.ts
  92. 1
      apps/react-admin/src/hooks/abp/use-localization.ts
  93. 346
      apps/react-admin/src/hooks/abp/use-validation.ts
  94. 79
      apps/react-admin/src/locales/lang/en_US/abp.json
  95. 58
      apps/react-admin/src/locales/lang/en_US/authentication.json
  96. 3
      apps/react-admin/src/locales/lang/en_US/component.json
  97. 4
      apps/react-admin/src/locales/lang/en_US/index.ts
  98. 39
      apps/react-admin/src/locales/lang/en_US/workbench.json
  99. 79
      apps/react-admin/src/locales/lang/zh_CN/abp.json
  100. 58
      apps/react-admin/src/locales/lang/zh_CN/authentication.json

17
apps/react-admin/.env.development

@ -1,12 +1,25 @@
VITE_APP_BASE_API= VITE_APP_BASE_API=
VITE_APP_HOMEPAGE=/dashboard/workbench VITE_APP_HOMEPAGE=/dashboard/workbench
VITE_APP_BASE_PATH=/ VITE_APP_BASE_PATH=/
# -----------------------
# 1
# VITE_PROXY_API=http://192.168.31.24:30001 # milo
# VITE_GLOB_CLIENT_ID=react-admin-client # VITE_GLOB_CLIENT_ID=react-admin-client
# VITE_GLOB_CLIENT_SECRET='' # VITE_GLOB_CLIENT_SECRET=''
# VITE_GLOB_SCOPE="openid email address phone profile offline_access miwen-abp-application" # VITE_GLOB_SCOPE="openid email address phone profile offline_access miwen-abp-application"
# VITE_PROXY_API=http://192.168.31.246:30001
# 2
# VITE_PROXY_API=http://192.168.31.24:30000 # collin
VITE_PROXY_API=http://124.223.5.95:30001/ #yun
VITE_GLOB_CLIENT_ID=vue-admin-client VITE_GLOB_CLIENT_ID=vue-admin-client
VITE_GLOB_CLIENT_SECRET=1q2w3e* VITE_GLOB_CLIENT_SECRET=1q2w3e*
VITE_GLOB_SCOPE="openid email address phone profile offline_access lingyun-abp-application" VITE_GLOB_SCOPE="openid email address phone profile offline_access lingyun-abp-application"
VITE_PROXY_API=http://124.223.5.95:30001
# -----------------------
VITE_EXTERNAL_LOGIN_ADDRESS=http://localhost:30001/connect/external/login
VITE_REGISTER_ADDRESS=http://localhost:3100/

1
apps/react-admin/.gitignore

@ -24,5 +24,6 @@ dist-ssr
# vite 打包分析产物 # vite 打包分析产物
stats.html stats.html
# 取消上层忽略 # 取消上层忽略
!.vscode/ !.vscode/

3
apps/react-admin/biome.json

@ -23,7 +23,8 @@
"rules": { "rules": {
"recommended": true, "recommended": true,
"suspicious": { "suspicious": {
"noExplicitAny": "off" "noExplicitAny": "off",
"noAssignInExpressions": "off"
}, },
"a11y": { "a11y": {
"useKeyWithClickEvents": "off" "useKeyWithClickEvents": "off"

9
apps/react-admin/package.json

@ -46,6 +46,10 @@
"i18next": "^23.5.1", "i18next": "^23.5.1",
"i18next-browser-languagedetector": "^7.1.0", "i18next-browser-languagedetector": "^7.1.0",
"json-edit-react": "^1.19.2", "json-edit-react": "^1.19.2",
"lodash.debounce": "^4.0.8",
"lodash.isdate": "^4.0.1",
"lodash.isnumber": "^3.0.3",
"lodash.orderby": "^4.6.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"ramda": "^0.29.1", "ramda": "^0.29.1",
@ -78,9 +82,12 @@
"@commitlint/cli": "^17.7.2", "@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"@faker-js/faker": "^8.1.0", "@faker-js/faker": "^8.1.0",
"@hey-api/openapi-ts": "^0.59.1",
"@types/autosuggest-highlight": "^3.2.0", "@types/autosuggest-highlight": "^3.2.0",
"@types/color": "^3.0.4", "@types/color": "^3.0.4",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.isdate": "^4.0.9",
"@types/lodash.isnumber": "^3.0.9",
"@types/lodash.orderby": "^4.6.9",
"@types/nprogress": "^0.2.1", "@types/nprogress": "^0.2.1",
"@types/numeral": "^2.0.3", "@types/numeral": "^2.0.3",
"@types/ramda": "^0.29.6", "@types/ramda": "^0.29.6",

8
apps/react-admin/src/api/account/account.ts

@ -4,6 +4,8 @@ import type {
TwoFactorProvider, TwoFactorProvider,
SendEmailSigninCodeDto, SendEmailSigninCodeDto,
SendPhoneSigninCodeDto, SendPhoneSigninCodeDto,
ExternalSignUpApiDto,
PhoneResetPasswordDto,
} from "#/account/account"; } from "#/account/account";
import requestClient from "@/api/request"; import requestClient from "@/api/request";
@ -26,3 +28,9 @@ export const sendEmailSigninCodeApi = (input: SendEmailSigninCodeDto) =>
*/ */
export const sendPhoneSigninCodeApi = (input: SendPhoneSigninCodeDto) => export const sendPhoneSigninCodeApi = (input: SendPhoneSigninCodeDto) =>
requestClient.post("/api/account/phone/send-signin-code", input); requestClient.post("/api/account/phone/send-signin-code", input);
export const externalSignUpApi = (input: ExternalSignUpApiDto) =>
requestClient.post("/api/account/external/register", input, { withCredentials: true });
export const resetPasswordApi = (input: PhoneResetPasswordDto) =>
requestClient.put("/api/account/phone/reset-password", input);

37
apps/react-admin/src/api/account/profile.ts

@ -8,6 +8,9 @@ import type {
AuthenticatorRecoveryCodeDto, AuthenticatorRecoveryCodeDto,
SendEmailConfirmCodeDto, SendEmailConfirmCodeDto,
ConfirmEmailInput, ConfirmEmailInput,
SendChangePhoneNumberCodeInput,
ChangePhoneNumberInput,
ChangePictureInput,
} from "#/account/profile"; } from "#/account/profile";
import requestClient from "@/api/request"; import requestClient from "@/api/request";
@ -27,6 +30,40 @@ export const updateApi = (input: UpdateProfileDto) => requestClient.put<ProfileD
export const changePasswordApi = (input: ChangePasswordInput) => export const changePasswordApi = (input: ChangePasswordInput) =>
requestClient.post("/api/account/my-profile/change-password", input); requestClient.post("/api/account/my-profile/change-password", input);
/**
*
* @param input
*/
export const sendChangePhoneNumberCodeApi = (input: SendChangePhoneNumberCodeInput) =>
requestClient.post("/api/account/my-profile/send-phone-number-change-code", input);
/**
*
* @param input
*/
export const changePhoneNumberApi = (input: ChangePhoneNumberInput) =>
requestClient.put("/api/account/my-profile/change-phone-number", input);
/**
*
* @param input
*/
export const changePictureApi = (input: ChangePictureInput) => {
requestClient.post("/api/account/my-profile/picture", input, {
headers: {
"Content-Type": "multipart/form-data",
},
});
};
/**
*
* @returns
*/
export const getPictureApi = () =>
requestClient.get("/api/account/my-profile/picture", {
responseType: "blob",
});
/** /**
* Get two-factor authentication status * Get two-factor authentication status
*/ */

20
apps/react-admin/src/api/account/scan-Qrcode.ts

@ -0,0 +1,20 @@
import type { GenerateQrCodeResult, QrCodeUserInfoResult } from "#/account/qrcode";
import requestClient from "@/api/request";
/**
*
* @returns
*/
export function generateApi(): Promise<GenerateQrCodeResult> {
return requestClient.post<GenerateQrCodeResult>("/api/account/qrcode/generate");
}
/**
*
* @param key Key
* @returns
*/
export function checkCodeApi(key: string): Promise<QrCodeUserInfoResult> {
return requestClient.get<QrCodeUserInfoResult>(`/api/account/qrcode/${key}/check`);
}

58
apps/react-admin/src/api/account/token.ts

@ -1,4 +1,10 @@
import type { OAuthTokenResult, PasswordTokenRequestModel, RefreshTokenRequestModel, TokenResult } from "#/account"; import type {
OAuthTokenResult,
PasswordTokenRequestModel,
RefreshTokenRequestModel,
SignInRedirectResult,
TokenResult,
} from "#/account";
import requestClient from "../request"; import requestClient from "../request";
/** /**
@ -35,6 +41,11 @@ export async function loginApi(request: PasswordTokenRequestModel): Promise<Toke
}; };
} }
/**
* token
* @param request
* @returns
*/
export async function refreshToken(request: RefreshTokenRequestModel): Promise<TokenResult> { export async function refreshToken(request: RefreshTokenRequestModel): Promise<TokenResult> {
const clientId = import.meta.env.VITE_GLOB_CLIENT_ID; const clientId = import.meta.env.VITE_GLOB_CLIENT_ID;
const clientSecret = import.meta.env.VITE_GLOB_CLIENT_SECRET; const clientSecret = import.meta.env.VITE_GLOB_CLIENT_SECRET;
@ -61,3 +72,48 @@ export async function refreshToken(request: RefreshTokenRequestModel): Promise<T
tokenType: result.token_type, tokenType: result.token_type,
}; };
} }
/**
* token
* @param request
* @returns
*/
export async function externalLoginApi(): Promise<TokenResult | SignInRedirectResult> {
const clientId = import.meta.env.VITE_GLOB_CLIENT_ID;
const clientSecret = import.meta.env.VITE_GLOB_CLIENT_SECRET;
const scope = import.meta.env.VITE_GLOB_SCOPE;
const registerAddress = import.meta.env.VITE_REGISTER_ADDRESS;
//import: https://stackoverflow.com/questions/61345366/axios-302-responses
const res = await fetch("/connect/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
credentials: "include", // 让请求带上 Cookie
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
grant_type: "ExternalLogin",
scope: scope,
register_address: registerAddress,
}),
});
if (res.redirected) {
const redirectUrl = new URL(res.url);
const searchParams = new URLSearchParams(redirectUrl.search);
const isExternalLogin = searchParams.get("isExternalLogin") === "true";
const needRegister = searchParams.get("needRegister") === "true";
return {
isExternalLogin,
needRegister,
// redirectUrl //这个react项目这里不需要
};
}
const result = await res.json();
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}

25
apps/react-admin/src/api/management/auditing/loggings.ts

@ -0,0 +1,25 @@
import type { PagedResultDto } from "#/abp-core";
import type { LogDto, LogGetListInput } from "#/management/auditing";
import requestClient from "../../request";
/**
*
* @param id id
*/
export function getApi(id: string): Promise<LogDto> {
return requestClient.get<LogDto>(`/api/auditing/logging/${id}`, {
method: "GET",
});
}
/**
*
* @param input
*/
export function getPagedListApi(input: LogGetListInput): Promise<PagedResultDto<LogDto>> {
return requestClient.get<PagedResultDto<LogDto>>("/api/auditing/logging", {
params: input,
});
}

57
apps/react-admin/src/api/management/features/feature-definitions.ts

@ -0,0 +1,57 @@
import type { ListResultDto } from "#/abp-core";
import type {
FeatureDefinitionCreateDto,
FeatureDefinitionDto,
FeatureDefinitionGetListInput,
FeatureDefinitionUpdateDto,
} from "#/management/features/definitions";
import requestClient from "../../request";
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/feature-management/definitions/${name}`);
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<FeatureDefinitionDto> {
return requestClient.get<FeatureDefinitionDto>(`/api/feature-management/definitions/${name}`);
}
/**
*
* @param input
* @returns
*/
export function getListApi(input?: FeatureDefinitionGetListInput): Promise<ListResultDto<FeatureDefinitionDto>> {
return requestClient.get<ListResultDto<FeatureDefinitionDto>>("/api/feature-management/definitions", {
params: input,
});
}
/**
*
* @param input
* @returns
*/
export function createApi(input: FeatureDefinitionCreateDto): Promise<FeatureDefinitionDto> {
return requestClient.post<FeatureDefinitionDto>("/api/feature-management/definitions", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: FeatureDefinitionUpdateDto): Promise<FeatureDefinitionDto> {
return requestClient.put<FeatureDefinitionDto>(`/api/feature-management/definitions/${name}`, input);
}

59
apps/react-admin/src/api/management/features/feature-group-definitions.ts

@ -0,0 +1,59 @@
import type { ListResultDto } from "#/abp-core";
import type {
FeatureGroupDefinitionCreateDto,
FeatureGroupDefinitionDto,
FeatureGroupDefinitionGetListInput,
FeatureGroupDefinitionUpdateDto,
} from "#/management/features/groups";
import requestClient from "../../request";
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/feature-management/definitions/groups/${name}`);
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<FeatureGroupDefinitionDto> {
return requestClient.get<FeatureGroupDefinitionDto>(`/api/feature-management/definitions/groups/${name}`);
}
/**
*
* @param input
* @returns
*/
export function getListApi(
input?: FeatureGroupDefinitionGetListInput,
): Promise<ListResultDto<FeatureGroupDefinitionDto>> {
return requestClient.get<ListResultDto<FeatureGroupDefinitionDto>>("/api/feature-management/definitions/groups", {
params: input,
});
}
/**
*
* @param input
* @returns
*/
export function createApi(input: FeatureGroupDefinitionCreateDto): Promise<FeatureGroupDefinitionDto> {
return requestClient.post<FeatureGroupDefinitionDto>("/api/feature-management/definitions/groups", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: FeatureGroupDefinitionUpdateDto): Promise<FeatureGroupDefinitionDto> {
return requestClient.put<FeatureGroupDefinitionDto>(`/api/feature-management/definitions/groups/${name}`, input);
}

37
apps/react-admin/src/api/management/features/features.ts

@ -0,0 +1,37 @@
import type { FeatureProvider, GetFeatureListResultDto, UpdateFeaturesDto } from "#/management/features/features";
import requestClient from "../../request";
/**
*
* @param {FeatureProvider} provider
* @returns {Promise<void>}
*/
export function deleteApi(provider: FeatureProvider): Promise<void> {
return requestClient.delete("/api/feature-management/features", {
params: provider,
});
}
/**
*
* @param {FeatureProvider} provider
* @returns {Promise<GetFeatureListResultDto>}
*/
export function getApi(provider: FeatureProvider): Promise<GetFeatureListResultDto> {
return requestClient.get<GetFeatureListResultDto>("/api/feature-management/features", {
params: provider,
});
}
/**
*
* @param {FeatureProvider} provider
* @param {UpdateFeaturesDto} input
* @returns {Promise<void>}
*/
export function updateApi(provider: FeatureProvider, input: UpdateFeaturesDto): Promise<void> {
return requestClient.put("/api/feature-management/features", input, {
params: provider,
});
}

57
apps/react-admin/src/api/management/localization/languages.ts

@ -0,0 +1,57 @@
import type { ListResultDto } from "#/abp-core";
import type {
LanguageCreateDto,
LanguageDto,
LanguageGetListInput,
LanguageUpdateDto,
} from "#/management/localization/languages";
import requestClient from "../../request";
/**
*
* @param input
* @returns
*/
export function getListApi(input?: LanguageGetListInput): Promise<ListResultDto<LanguageDto>> {
return requestClient.get<ListResultDto<LanguageDto>>("/api/abp/localization/languages", {
params: input,
});
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<LanguageDto> {
return requestClient.get<LanguageDto>(`/api/localization/languages/${name}`);
}
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/localization/languages/${name}`);
}
/**
*
* @param input
* @returns
*/
export function createApi(input: LanguageCreateDto): Promise<LanguageDto> {
return requestClient.post<LanguageDto>("/api/localization/languagesr", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: LanguageUpdateDto): Promise<LanguageDto> {
return requestClient.put<LanguageDto>(`/api/localization/languages/${name}`, input);
}

16
apps/react-admin/src/api/management/localization/localizations.ts

@ -0,0 +1,16 @@
import type { ApplicationLocalizationDto } from "#/abp-core";
import requestClient from "../../request";
/**
*
* @returns
*/
export function getLocalizationApi(options: {
cultureName: string;
onlyDynamics?: boolean;
}): Promise<ApplicationLocalizationDto> {
return requestClient.get<ApplicationLocalizationDto>("/api/abp/application-localization", {
params: options,
});
}

57
apps/react-admin/src/api/management/localization/resources.ts

@ -0,0 +1,57 @@
import type { ListResultDto } from "#/abp-core";
import type {
ResourceCreateDto,
ResourceDto,
ResourceGetListInput,
ResourceUpdateDto,
} from "#/management/localization/resources";
import requestClient from "../../request";
/**
*
* @param input
* @returns
*/
export function getListApi(input?: ResourceGetListInput): Promise<ListResultDto<ResourceDto>> {
return requestClient.get<ListResultDto<ResourceDto>>("/api/abp/localization/resources", {
params: input,
});
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<ResourceDto> {
return requestClient.get<ResourceDto>(`/api/localization/resources/${name}`);
}
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/localization/resources/${name}`);
}
/**
*
* @param input
* @returns
*/
export function createApi(input: ResourceCreateDto): Promise<ResourceDto> {
return requestClient.post<ResourceDto>("/api/localization/resources", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: ResourceUpdateDto): Promise<ResourceDto> {
return requestClient.put<ResourceDto>(`/api/localization/resources/${name}`, input);
}

41
apps/react-admin/src/api/management/localization/texts.ts

@ -0,0 +1,41 @@
import type { ListResultDto } from "#/abp-core";
import type {
GetTextByKeyInput,
GetTextsInput,
SetTextInput,
TextDifferenceDto,
TextDto,
} from "#/management/localization/texts";
import requestClient from "../../request";
/**
*
* @param input
* @returns
*/
export function getListApi(input: GetTextsInput): Promise<ListResultDto<TextDifferenceDto>> {
return requestClient.get<ListResultDto<TextDifferenceDto>>("/api/abp/localization/texts", {
params: input,
});
}
/**
*
* @param input
* @returns
*/
export function getApi(input: GetTextByKeyInput): Promise<TextDto> {
return requestClient.get<TextDto>("/api/abp/localization/texts/by-culture-key", {
params: input,
});
}
/**
*
* @param input
*/
export function setApi(input: SetTextInput): Promise<void> {
return requestClient.put("/api/localization/texts", input);
}

31
apps/react-admin/src/api/management/notifications/my-subscribes.ts

@ -0,0 +1,31 @@
import type { ListResultDto } from "#/abp-core";
import type { UserSubscreNotification } from "#/notifications/subscribes";
import requestClient from "../../request";
/**
*
* @returns
*/
export function getMySubscribesApi(): Promise<ListResultDto<UserSubscreNotification>> {
return requestClient.get<ListResultDto<UserSubscreNotification>>("/api/notifications/my-subscribes/all");
}
/**
*
* @param name
*/
export function subscribeApi(name: string): Promise<void> {
return requestClient.post("/api/notifications/my-subscribes", {
name,
});
}
/**
*
* @param name
*/
export function unSubscribeApi(name: string): Promise<void> {
return requestClient.delete(`/api/notifications/my-subscribes?name=${name}`);
}

59
apps/react-admin/src/api/management/notifications/notification-definitions.ts

@ -0,0 +1,59 @@
import type { ListResultDto } from "#/abp-core";
import type {
NotificationDefinitionCreateDto,
NotificationDefinitionDto,
NotificationDefinitionGetListInput,
NotificationDefinitionUpdateDto,
} from "#/notifications/definitions";
import requestClient from "../../request";
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/notifications/definitions/notifications/${name}`);
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<NotificationDefinitionDto> {
return requestClient.get<NotificationDefinitionDto>(`/api/notifications/definitions/notifications/${name}`);
}
/**
*
* @param input
* @returns
*/
export function getListApi(
input?: NotificationDefinitionGetListInput,
): Promise<ListResultDto<NotificationDefinitionDto>> {
return requestClient.get<ListResultDto<NotificationDefinitionDto>>("/api/notifications/definitions/notifications", {
params: input,
});
}
/**
*
* @param input
* @returns
*/
export function createApi(input: NotificationDefinitionCreateDto): Promise<NotificationDefinitionDto> {
return requestClient.post<NotificationDefinitionDto>("/api/notifications/definitions/notifications", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: NotificationDefinitionUpdateDto): Promise<NotificationDefinitionDto> {
return requestClient.put<NotificationDefinitionDto>(`/api/notifications/definitions/notifications/${name}`, input);
}

62
apps/react-admin/src/api/management/notifications/notification-group-definitions.ts

@ -0,0 +1,62 @@
import type { ListResultDto } from "#/abp-core";
import type {
NotificationGroupDefinitionCreateDto,
NotificationGroupDefinitionDto,
NotificationGroupDefinitionGetListInput,
NotificationGroupDefinitionUpdateDto,
} from "#/notifications/groups";
import requestClient from "../../request";
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/notifications/definitions/groups/${name}`);
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<NotificationGroupDefinitionDto> {
return requestClient.get<NotificationGroupDefinitionDto>(`/api/notifications/definitions/groups/${name}`);
}
/**
*
* @param input
* @returns
*/
export function getListApi(
input?: NotificationGroupDefinitionGetListInput,
): Promise<ListResultDto<NotificationGroupDefinitionDto>> {
return requestClient.get<ListResultDto<NotificationGroupDefinitionDto>>("/api/notifications/definitions/groups", {
params: input,
});
}
/**
*
* @param input
* @returns
*/
export function createApi(input: NotificationGroupDefinitionCreateDto): Promise<NotificationGroupDefinitionDto> {
return requestClient.post<NotificationGroupDefinitionDto>("/api/notifications/definitions/groups", input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(
name: string,
input: NotificationGroupDefinitionUpdateDto,
): Promise<NotificationGroupDefinitionDto> {
return requestClient.put<NotificationGroupDefinitionDto>(`/api/notifications/definitions/groups/${name}`, input);
}

14
apps/react-admin/src/api/management/notifications/notifications.ts

@ -1,10 +1,22 @@
import type { ListResultDto } from "#/abp-core"; import type { ListResultDto } from "#/abp-core";
import type { NotificationGroupDto, NotificationTemplateDto } from "#/notifications/definitions"; import type {
NotificationGroupDto,
NotificationProviderDto,
NotificationTemplateDto,
} from "#/notifications/definitions";
import type { NotificationSendInput, NotificationTemplateSendInput } from "#/notifications/notifications"; import type { NotificationSendInput, NotificationTemplateSendInput } from "#/notifications/notifications";
import requestClient from "@/api/request"; import requestClient from "@/api/request";
/**
*
* @returns {Promise<ListResultDto<NotificationProviderDto>>}
*/
export function getAssignableProvidersApi(): Promise<ListResultDto<NotificationProviderDto>> {
return requestClient.get<ListResultDto<NotificationProviderDto>>("/api/notifications/assignable-providers");
}
/** /**
* *
* @returns {Promise<ListResultDto<NotificationGroupDto>>} * @returns {Promise<ListResultDto<NotificationGroupDto>>}

33
apps/react-admin/src/api/oss/containes.ts

@ -0,0 +1,33 @@
import type {
GetOssContainersInput,
GetOssObjectsInput,
OssContainerDto,
OssContainersResultDto,
} from "#/oss/containes";
import type { OssObjectsResultDto } from "#/oss/objects";
import requestClient from "../request";
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/oss-management/containes/${name}`);
}
export function getApi(name: string): Promise<OssContainerDto> {
return requestClient.get<OssContainerDto>(`/api/oss-management/containes/${name}`);
}
export function getListApi(input?: GetOssContainersInput): Promise<OssContainersResultDto> {
return requestClient.get<OssContainersResultDto>("/api/oss-management/containes", {
params: input,
});
}
export function getObjectsApi(input: GetOssObjectsInput): Promise<OssObjectsResultDto> {
return requestClient.get<OssObjectsResultDto>("/api/oss-management/containes/objects", {
params: input,
});
}
export function createApi(name: string): Promise<OssContainerDto> {
return requestClient.post<OssContainerDto>(`/api/oss-management/containes/${name}`);
}

32
apps/react-admin/src/api/oss/objects.ts

@ -0,0 +1,32 @@
import type { BulkDeleteOssObjectInput, CreateOssObjectInput, GetOssObjectInput, OssObjectDto } from "#/oss/objects";
import requestClient from "../request";
export function createApi(input: CreateOssObjectInput): Promise<OssObjectDto> {
const formData = new window.FormData();
formData.append("bucket", input.bucket);
formData.append("fileName", input.fileName);
formData.append("overwrite", String(input.overwrite));
input.expirationTime && formData.append("expirationTime", input.expirationTime.toString());
input.path && formData.append("path", input.path);
input.file && formData.append("file", input.file);
return requestClient.post<OssObjectDto>("/api/oss-management/objects", formData, {
headers: {
"Content-Type": "multipart/form-data;charset=utf-8",
},
});
}
export function generateUrlApi(input: GetOssObjectInput): Promise<string> {
// return requestClient.get<string>("/api/oss-management/objects/generate-url", { TODO update
return requestClient.get<string>("/api/oss-management/objects/download", {
params: input,
});
}
export function deleteApi(input: BulkDeleteOssObjectInput): Promise<void> {
return requestClient.delete("/api/oss-management/objects", {
params: input,
});
}

59
apps/react-admin/src/api/platform/data-dictionaries.ts

@ -0,0 +1,59 @@
import type { ListResultDto, PagedResultDto } from "#/abp-core";
import type {
DataCreateDto,
DataDto,
DataItemCreateDto,
DataItemUpdateDto,
DataMoveDto,
DataUpdateDto,
GetDataListInput,
} from "#/platform/data-dictionaries";
import requestClient from "../request";
export function createApi(input: DataCreateDto): Promise<DataDto> {
return requestClient.post<DataDto>("/api/platform/datas", input);
}
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/datas/${id}`);
}
export function createItemApi(id: string, input: DataItemCreateDto): Promise<void> {
return requestClient.post(`/api/platform/datas/${id}/items`, input);
}
export function deleteItemApi(id: string, name: string): Promise<void> {
return requestClient.delete(`/api/platform/datas/${id}/items/${name}`);
}
export function getApi(id: string): Promise<DataDto> {
return requestClient.get<DataDto>(`/api/platform/datas/${id}`);
}
export function getByNameApi(name: string): Promise<DataDto> {
return requestClient.get<DataDto>(`/api/platform/datas/by-name/${name}`);
}
export function getAllApi(): Promise<ListResultDto<DataDto>> {
return requestClient.get<ListResultDto<DataDto>>("/api/platform/datas/all");
}
export function getPagedListApi(input?: GetDataListInput): Promise<PagedResultDto<DataDto>> {
return requestClient.get<PagedResultDto<DataDto>>("/api/platform/datas", {
params: input,
});
}
export function moveApi(id: string, input: DataMoveDto): Promise<DataDto> {
return requestClient.put<DataDto>(`/api/platform/datas/${id}/move`, input);
}
export function updateApi(id: string, input: DataUpdateDto): Promise<DataDto> {
return requestClient.put<DataDto>(`/api/platform/datas/${id}`, input);
}
export function updateItemApi(id: string, name: string, input: DataItemUpdateDto): Promise<void> {
return requestClient.put(`/api/platform/datas/${id}/items/${name}`, input);
}

43
apps/react-admin/src/api/platform/email-messages.ts

@ -0,0 +1,43 @@
import type { PagedResultDto } from "#/abp-core";
import type { EmailMessageDto, EmailMessageGetListInput } from "#/platform/messages";
import requestClient from "../request";
/**
*
* @param {EmailMessageGetListInput} input
* @returns {Promise<PagedResultDto<EmailMessageDto>>}
*/
export function getPagedListApi(input?: EmailMessageGetListInput): Promise<PagedResultDto<EmailMessageDto>> {
return requestClient.get<PagedResultDto<EmailMessageDto>>("/api/platform/messages/email", {
params: input,
});
}
/**
*
* @param id Id
* @returns {EmailMessageDto}
*/
export function getApi(id: string): Promise<EmailMessageDto> {
return requestClient.get<EmailMessageDto>(`/api/platform/messages/email/${id}`);
}
/**
*
* @param id Id
* @returns {void}
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/messages/email/${id}`);
}
/**
*
* @param id Id
* @returns {void}
*/
export function sendApi(id: string): Promise<void> {
return requestClient.post(`/api/platform/messages/email/${id}/send`);
}

27
apps/react-admin/src/api/platform/layouts.ts

@ -0,0 +1,27 @@
import type { PagedResultDto } from "#/abp-core";
import type { LayoutCreateDto, LayoutDto, LayoutGetPagedListInput, LayoutUpdateDto } from "#/platform/layouts";
import requestClient from "../request";
export function createApi(input: LayoutCreateDto): Promise<LayoutDto> {
return requestClient.post<LayoutDto>("/api/platform/layouts", input);
}
export function getPagedListApi(input?: LayoutGetPagedListInput): Promise<PagedResultDto<LayoutDto>> {
return requestClient.get<PagedResultDto<LayoutDto>>("/api/platform/layouts", {
params: input,
});
}
export function getApi(id: string): Promise<LayoutDto> {
return requestClient.get<LayoutDto>(`/api/platform/layouts/${id}`);
}
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/layouts/${id}`);
}
export function updateApi(id: string, input: LayoutUpdateDto): Promise<LayoutDto> {
return requestClient.put<LayoutDto>(`/api/platform/layouts/${id}`, input);
}

27
apps/react-admin/src/api/platform/menus.ts

@ -0,0 +1,27 @@
import type { ListResultDto } from "#/abp-core";
import type { MenuCreateDto, MenuDto, MenuGetAllInput, MenuUpdateDto } from "#/platform/menus";
import requestClient from "../request";
export function createApi(input: MenuCreateDto): Promise<MenuDto> {
return requestClient.post<MenuDto>("/api/platform/menus", input);
}
export function getAllApi(input?: MenuGetAllInput): Promise<ListResultDto<MenuDto>> {
return requestClient.get<ListResultDto<MenuDto>>("/api/platform/menus/all", {
params: input,
});
}
export function getApi(id: string): Promise<MenuDto> {
return requestClient.get<MenuDto>(`/api/platform/menus/${id}`);
}
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/menus/${id}`);
}
export function updateApi(id: string, input: MenuUpdateDto): Promise<MenuDto> {
return requestClient.put<MenuDto>(`/api/platform/menus/${id}`, input);
}

33
apps/react-admin/src/api/platform/my-favorite-menus.ts

@ -0,0 +1,33 @@
import type { ListResultDto } from "#/abp-core";
import type { UserFavoriteMenuCreateDto, UserFavoriteMenuDto } from "#/platform/favorites";
import requestClient from "../request";
/**
*
* @param input
* @returns
*/
export function createApi(input: UserFavoriteMenuCreateDto): Promise<UserFavoriteMenuDto> {
return requestClient.post<UserFavoriteMenuDto>("/api/platform/menus/favorites/my-favorite-menus", input);
}
/**
*
* @param id Id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/menus/favorites/my-favorite-menus/${id}`);
}
/**
*
* @param framework ui框架
* @returns
*/
export function getListApi(framework?: string): Promise<ListResultDto<UserFavoriteMenuDto>> {
return requestClient.get<ListResultDto<UserFavoriteMenuDto>>("/api/platform/menus/favorites/my-favorite-menus", {
params: { framework },
});
}

11
apps/react-admin/src/api/platform/my-menus.ts

@ -0,0 +1,11 @@
import type { ListResultDto } from "#/abp-core";
import type { MenuDto, MenuGetInput } from "#/platform/menus";
import requestClient from "../request";
export function getAllApi(input?: MenuGetInput): Promise<ListResultDto<MenuDto>> {
return requestClient.get<ListResultDto<MenuDto>>("/api/platform/menus/by-current-user", {
params: input,
});
}

19
apps/react-admin/src/api/platform/role-menus.ts

@ -0,0 +1,19 @@
import type { ListResultDto } from "#/abp-core";
import type { MenuDto, MenuGetByRoleInput, SetRoleMenuInput, SetRoleMenuStartupInput } from "#/platform/menus";
import requestClient from "../request";
export function getAllApi(input: MenuGetByRoleInput): Promise<ListResultDto<MenuDto>> {
return requestClient.get<ListResultDto<MenuDto>>(`/api/platform/menus/by-role/${input.role}/${input.framework}`, {
params: input,
});
}
export function setMenusApi(input: SetRoleMenuInput): Promise<void> {
return requestClient.put("/api/platform/menus/by-role", input);
}
export function setStartupMenuApi(meudId: string, input: SetRoleMenuStartupInput): Promise<void> {
return requestClient.put(`/api/platform/menus/startup/${meudId}/by-role`, input);
}

43
apps/react-admin/src/api/platform/sms-messages.ts

@ -0,0 +1,43 @@
import type { PagedResultDto } from "#/abp-core";
import type { SmsMessageDto, SmsMessageGetListInput } from "#/platform/messages";
import requestClient from "../request";
/**
*
* @param {EmailMessageGetListInput} input
* @returns {Promise<PagedResultDto<EmailMessageDto>>}
*/
export function getPagedListApi(input?: SmsMessageGetListInput): Promise<PagedResultDto<SmsMessageDto>> {
return requestClient.get<PagedResultDto<SmsMessageDto>>("/api/platform/messages/sms", {
params: input,
});
}
/**
*
* @param id Id
* @returns {SmsMessageDto}
*/
export function getApi(id: string): Promise<SmsMessageDto> {
return requestClient.get<SmsMessageDto>(`/api/platform/messages/sms/${id}`);
}
/**
*
* @param id Id
* @returns {void}
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/platform/messages/sms/${id}`);
}
/**
*
* @param id Id
* @returns {void}
*/
export function sendApi(id: string): Promise<void> {
return requestClient.post(`/api/platform/messages/sms/${id}/send`);
}

19
apps/react-admin/src/api/platform/user-menus.ts

@ -0,0 +1,19 @@
import type { ListResultDto } from "#/abp-core";
import type { MenuDto, MenuGetByUserInput, SetUserMenuInput, SetUserMenuStartupInput } from "#/platform/menus";
import requestClient from "../request";
export function getAllApi(input: MenuGetByUserInput): Promise<ListResultDto<MenuDto>> {
return requestClient.get<ListResultDto<MenuDto>>(`/api/platform/menus/by-user/${input.userId}/${input.framework}`, {
params: input,
});
}
export function setMenusApi(input: SetUserMenuInput): Promise<void> {
return requestClient.put("/api/platform/menus/by-user", input);
}
export function setStartupMenuApi(meudId: string, input: SetUserMenuStartupInput): Promise<void> {
return requestClient.put(`/api/platform/menus/startup/${meudId}/by-user`, input);
}

9
apps/react-admin/src/api/request.ts

@ -7,6 +7,7 @@ import { toast } from "sonner";
import { refreshToken } from "./account/token"; import { refreshToken } from "./account/token";
import { wrapperResult } from "@/utils/abp/request"; import { wrapperResult } from "@/utils/abp/request";
import { handleOAuthError } from "@/utils/abp/handleOAuthError"; import { handleOAuthError } from "@/utils/abp/handleOAuthError";
import useAbpStore from "@/store/abpCoreStore";
const requestClient = new RequestClient({ const requestClient = new RequestClient({
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
@ -64,12 +65,18 @@ function formatToken(token: null | string) {
requestClient.addRequestInterceptor({ requestClient.addRequestInterceptor({
fulfilled: async (config) => { fulfilled: async (config) => {
const { userToken } = useUserStore.getState(); const { userToken } = useUserStore.getState();
const { tenantId, xsrfToken } = useAbpStore.getState();
if (userToken.accessToken) { if (userToken.accessToken) {
config.headers.Authorization = `${userToken.accessToken}`; config.headers.Authorization = `${userToken.accessToken}`;
} }
const { locale } = useLocaleStore.getState(); const { locale } = useLocaleStore.getState();
config.headers["Accept-Language"] = mapLocaleToAbpLanguageFormat(locale); config.headers["Accept-Language"] = mapLocaleToAbpLanguageFormat(locale);
config.headers["X-Request-From"] = "slash-admin"; // config.headers["X-Request-From"] = "slash-admin";
config.headers["X-Request-From"] = "vben";
config.headers.__tenant = tenantId;
config.headers.RequestVerificationToken = xsrfToken;
return config; return config;
}, },
}); });

53
apps/react-admin/src/api/saas/editions.ts

@ -0,0 +1,53 @@
import type { PagedResultDto } from "#/abp-core";
import type { EditionCreateDto, EditionDto, EditionUpdateDto, GetEditionPagedListInput } from "#/saas/editions";
import requestClient from "../request";
/**
*
* @param {EditionCreateDto} input
* @returns
*/
export function createApi(input: EditionCreateDto): Promise<EditionDto> {
return requestClient.post<EditionDto>("/api/saas/editions", input);
}
/**
*
* @param {string} id
* @param {EditionUpdateDto} input
* @returns
*/
export function updateApi(id: string, input: EditionUpdateDto): Promise<EditionDto> {
return requestClient.put<EditionDto>(`/api/saas/editions/${id}`, input);
}
/**
*
* @param {string} id Id
* @returns
*/
export function getApi(id: string): Promise<EditionDto> {
return requestClient.get<EditionDto>(`/api/saas/editions/${id}`);
}
/**
*
* @param {string} id Id
* @returns {void}
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/saas/editions/${id}`);
}
/**
*
* @param {GetEditionPagedListInput} input
* @returns {void}
*/
export function getPagedListApi(input?: GetEditionPagedListInput): Promise<PagedResultDto<EditionDto>> {
return requestClient.get<PagedResultDto<EditionDto>>("/api/saas/editions", {
params: input,
});
}

10
apps/react-admin/src/api/saas/multi-tenancy.ts

@ -0,0 +1,10 @@
import type { FindTenantResultDto } from "#/saas";
import requestClient from "../request";
export function findTenantByNameApi(name: string): Promise<FindTenantResultDto> {
return requestClient.get<FindTenantResultDto>(`/api/abp/multi-tenancy/tenants/by-name/${name}`);
}
export function findTenantByIdApi(id: string): Promise<FindTenantResultDto> {
return requestClient.get<FindTenantResultDto>(`/api/abp/multi-tenancy/tenants/by-id/${id}`);
}

111
apps/react-admin/src/api/saas/tenants.ts

@ -0,0 +1,111 @@
import type { ListResultDto, PagedResultDto } from "#/abp-core";
import type {
GetTenantPagedListInput,
TenantConnectionStringCheckInput,
TenantConnectionStringDto,
TenantConnectionStringSetInput,
TenantCreateDto,
TenantDto,
TenantUpdateDto,
} from "#/saas/tenants";
import requestClient from "../request";
/**
*
* @param {TenantCreateDto} input
* @returns
*/
export function createApi(input: TenantCreateDto): Promise<TenantDto> {
return requestClient.post<TenantDto>("/api/saas/tenants", input);
}
/**
*
* @param {string} id
* @param {TenantUpdateDto} input
* @returns
*/
export function updateApi(id: string, input: TenantUpdateDto): Promise<TenantDto> {
return requestClient.put<TenantDto>(`/api/saas/tenants/${id}`, input);
}
/**
*
* @param {string} id Id
* @returns
*/
export function getApi(id: string): Promise<TenantDto> {
return requestClient.get<TenantDto>(`/api/saas/tenants/${id}`);
}
/**
*
* @param {string} id Id
* @returns {void}
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/saas/tenants/${id}`);
}
/**
*
* @param {GetTenantPagedListInput} input
* @returns {void}
*/
export function getPagedListApi(input?: GetTenantPagedListInput): Promise<PagedResultDto<TenantDto>> {
return requestClient.get<PagedResultDto<TenantDto>>("/api/saas/tenants", {
params: input,
});
}
/**
*
* @param {string} id Id
* @param {TenantConnectionStringSetInput} input
* @returns
*/
export function setConnectionStringApi(
id: string,
input: TenantConnectionStringSetInput,
): Promise<TenantConnectionStringDto> {
return requestClient.put<TenantConnectionStringDto>(`/api/saas/tenants/${id}/connection-string`, input);
}
/**
*
* @param {string} id Id
* @param {string} name
* @returns
*/
export function getConnectionStringApi(id: string, name: string): Promise<TenantConnectionStringDto> {
return requestClient.get<TenantConnectionStringDto>(`/api/saas/tenants/${id}/connection-string/${name}`);
}
/**
*
* @param {string} id Id
* @returns
*/
export function getConnectionStringsApi(id: string): Promise<ListResultDto<TenantConnectionStringDto>> {
return requestClient.get<ListResultDto<TenantConnectionStringDto>>(`/api/saas/tenants/${id}/connection-string`);
}
/**
*
* @param {string} id Id
* @param {string} name
* @returns {void}
*/
export function deleteConnectionStringApi(id: string, name: string): Promise<void> {
return requestClient.delete(`/api/saas/tenants/${id}/connection-string/${name}`);
}
/**
*
* @param input
*/
export function checkConnectionString(input: TenantConnectionStringCheckInput): Promise<void> {
return requestClient.post("/api/saas/tenants/connection-string/check", input);
}

86
apps/react-admin/src/api/tasks/job-infos.ts

@ -0,0 +1,86 @@
import type { ListResultDto, PagedResultDto } from "#/abp-core";
import type {
BackgroundJobDefinitionDto,
BackgroundJobInfoBatchInput,
BackgroundJobInfoCreateDto,
BackgroundJobInfoDto,
BackgroundJobInfoGetListInput,
BackgroundJobInfoUpdateDto,
} from "#/tasks/job-infos";
import requestClient from "../request";
export function createApi(input: BackgroundJobInfoCreateDto): Promise<BackgroundJobInfoDto> {
return requestClient.post<BackgroundJobInfoDto>("/api/task-management/background-jobs", input);
}
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/task-management/background-jobs/${id}`);
}
export function bulkDeleteApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.delete("/api/task-management/background-jobs/bulk-delete", {
data: input,
});
}
export function getApi(id: string): Promise<BackgroundJobInfoDto> {
return requestClient.get<BackgroundJobInfoDto>(`/api/task-management/background-jobs/${id}`);
}
export function getPagedListApi(input?: BackgroundJobInfoGetListInput): Promise<PagedResultDto<BackgroundJobInfoDto>> {
return requestClient.get<PagedResultDto<BackgroundJobInfoDto>>("/api/task-management/background-jobs", {
params: input,
});
}
export function pauseApi(id: string): Promise<void> {
return requestClient.put(`/api/task-management/background-jobs/${id}/pause`);
}
export function bulkPauseApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.put("/api/task-management/background-jobs/bulk-pause", input);
}
export function resumeApi(id: string): Promise<void> {
return requestClient.put(`/api/task-management/background-jobs/${id}/resume`);
}
export function bulkResumeApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.put("/api/task-management/background-jobs/bulk-resume", input);
}
export function triggerApi(id: string): Promise<void> {
return requestClient.put(`/api/task-management/background-jobs/${id}/trigger`);
}
export function bulkTriggerApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.put("/api/task-management/background-jobs/bulk-trigger", input);
}
export function stopApi(id: string): Promise<void> {
return requestClient.put(`/api/task-management/background-jobs/${id}/stop`);
}
export function bulkStopApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.put("/api/task-management/background-jobs/bulk-stop", input);
}
export function startApi(id: string): Promise<void> {
return requestClient.put(`/api/task-management/background-jobs/${id}/start`);
}
export function bulkStartApi(input: BackgroundJobInfoBatchInput): Promise<void> {
return requestClient.put("/api/task-management/background-jobs/bulk-start", input);
}
export function updateApi(id: string, input: BackgroundJobInfoUpdateDto): Promise<BackgroundJobInfoDto> {
return requestClient.put<BackgroundJobInfoDto>(`/api/task-management/background-jobs/${id}`, input);
}
export function getDefinitionsApi(): Promise<ListResultDto<BackgroundJobDefinitionDto>> {
return requestClient.get<ListResultDto<BackgroundJobDefinitionDto>>(
"/api/task-management/background-jobs/definitions",
);
}

19
apps/react-admin/src/api/tasks/job-logs.ts

@ -0,0 +1,19 @@
import type { PagedResultDto } from "#/abp-core";
import type { BackgroundJobLogDto, BackgroundJobLogGetListInput } from "#/tasks/job-logs";
import requestClient from "../request";
export function getApi(id: string): Promise<BackgroundJobLogDto> {
return requestClient.get<BackgroundJobLogDto>(`/api/task-management/background-jobs/logs/${id}`);
}
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/task-management/background-jobs/logs/${id}`);
}
export function getPagedListApi(input?: BackgroundJobLogGetListInput): Promise<PagedResultDto<BackgroundJobLogDto>> {
return requestClient.get<PagedResultDto<BackgroundJobLogDto>>("/api/task-management/background-jobs/logs", {
params: input,
});
}

39
apps/react-admin/src/api/text-templating/template-contents.ts

@ -0,0 +1,39 @@
import type {
TextTemplateContentDto,
TextTemplateContentGetInput,
TextTemplateContentUpdateDto,
TextTemplateRestoreInput,
} from "#/text-templating/contents";
import requestClient from "../request";
/**
*
* @param input
* @returns
*/
export function getApi(input: TextTemplateContentGetInput): Promise<TextTemplateContentDto> {
let url = "/api/text-templating/templates/content";
url += input.culture ? `/${input.culture}/${input.name}` : `/${input.name}`;
return requestClient.get<TextTemplateContentDto>(url);
}
/**
*
* @param name
* @param input
* @returns
*/
export function restoreToDefaultApi(name: string, input: TextTemplateRestoreInput): Promise<void> {
return requestClient.put(`/api/text-templating/templates/content/${name}/restore-to-default`, input);
}
/**
*
* @param name
* @param input
* @returns
*/
export function updateApi(name: string, input: TextTemplateContentUpdateDto): Promise<TextTemplateContentDto> {
return requestClient.put<TextTemplateContentDto>(`/api/text-templating/templates/content/${name}`, input);
}

58
apps/react-admin/src/api/text-templating/template-definitions.ts

@ -0,0 +1,58 @@
import type { ListResultDto } from "#/abp-core";
import type {
TextTemplateDefinitionCreateDto,
TextTemplateDefinitionDto,
TextTemplateDefinitionGetListInput,
TextTemplateDefinitionUpdateDto,
} from "#/text-templating/definitions";
import requestClient from "../request";
/**
*
* @param input
* @returns
*/
export function createApi(input: TextTemplateDefinitionCreateDto): Promise<TextTemplateDefinitionDto> {
return requestClient.post<TextTemplateDefinitionDto>("/api/text-templating/template/definitions", input);
}
/**
*
* @param name
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/text-templating/template/definitions/${name}`);
}
/**
*
* @param name
* @returns
*/
export function getApi(name: string): Promise<TextTemplateDefinitionDto> {
return requestClient.get<TextTemplateDefinitionDto>(`/api/text-templating/template/definitions/${name}`);
}
/**
*
* @param input
* @returns
*/
export function getListApi(
input?: TextTemplateDefinitionGetListInput,
): Promise<ListResultDto<TextTemplateDefinitionDto>> {
return requestClient.get<ListResultDto<TextTemplateDefinitionDto>>("/api/text-templating/template/definitions", {
params: input,
});
}
/**
*
* @param name
* @returns
*/
export function updateApi(name: string, input: TextTemplateDefinitionUpdateDto): Promise<TextTemplateDefinitionDto> {
return requestClient.put<TextTemplateDefinitionDto>(`/api/text-templating/template/definitions/${name}`, input);
}

64
apps/react-admin/src/api/webhooks/send-attempts.ts

@ -0,0 +1,64 @@
import type { PagedResultDto } from "#/abp-core";
import type {
WebhookSendRecordDeleteManyInput,
WebhookSendRecordDto,
WebhookSendRecordGetListInput,
WebhookSendRecordResendManyInput,
} from "#/webhooks/send-attempts";
import requestClient from "../request";
/**
*
* @param id Id
* @returns Dto
*/
export function getApi(id: string): Promise<WebhookSendRecordDto> {
return requestClient.get<WebhookSendRecordDto>(`/api/webhooks/send-attempts/${id}`);
}
/**
*
* @param id Id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/webhooks/send-attempts/${id}`);
}
/**
*
* @param input
*/
export function bulkDeleteApi(input: WebhookSendRecordDeleteManyInput): Promise<void> {
return requestClient.delete("/api/webhooks/send-attempts/delete-many", {
data: input,
});
}
/**
*
* @param input
* @returns Dto分页列表
*/
export function getPagedListApi(input: WebhookSendRecordGetListInput): Promise<PagedResultDto<WebhookSendRecordDto>> {
return requestClient.get<PagedResultDto<WebhookSendRecordDto>>("/api/webhooks/send-attempts", {
params: input,
});
}
/**
*
* @param id Id
*/
export function reSendApi(id: string): Promise<void> {
return requestClient.post(`/api/webhooks/send-attempts/${id}/resend`);
}
/**
*
* @param input
*/
export function bulkReSendApi(input: WebhookSendRecordResendManyInput): Promise<void> {
return requestClient.post("/api/webhooks/send-attempts/resend-many", input);
}

79
apps/react-admin/src/api/webhooks/subscriptions.ts

@ -0,0 +1,79 @@
import type { ListResultDto, PagedResultDto } from "#/abp-core";
import type {
WebhookAvailableGroupDto,
WebhookSubscriptionCreateDto,
WebhookSubscriptionDeleteManyInput,
WebhookSubscriptionDto,
WebhookSubscriptionGetListInput,
WebhookSubscriptionUpdateDto,
} from "#/webhooks/subscriptions";
import requestClient from "../request";
/**
*
* @param input
* @returns Dto
*/
export function createApi(input: WebhookSubscriptionCreateDto): Promise<WebhookSubscriptionDto> {
return requestClient.post<WebhookSubscriptionDto>("/api/webhooks/subscriptions", input);
}
/**
*
* @param id Id
*/
export function deleteApi(id: string): Promise<void> {
return requestClient.delete(`/api/webhooks/subscriptions/${id}`);
}
/**
*
* @param input
*/
export function bulkDeleteApi(input: WebhookSubscriptionDeleteManyInput): Promise<void> {
return requestClient.delete("/api/webhooks/subscriptions/delete-many", {
data: input,
});
}
/**
* Webhook分组列表
* @returns Webhook分组列表
*/
export function getAllAvailableWebhooksApi(): Promise<ListResultDto<WebhookAvailableGroupDto>> {
return requestClient.get<ListResultDto<WebhookAvailableGroupDto>>("/api/webhooks/subscriptions/availables");
}
/**
*
* @param id Id
* @returns Dto
*/
export function getApi(id: string): Promise<WebhookSubscriptionDto> {
return requestClient.get<WebhookSubscriptionDto>(`/api/webhooks/subscriptions/${id}`);
}
/**
*
* @param input
* @returns Dto列表
*/
export function getPagedListApi(
input: WebhookSubscriptionGetListInput,
): Promise<PagedResultDto<WebhookSubscriptionDto>> {
return requestClient.get<PagedResultDto<WebhookSubscriptionDto>>("/api/webhooks/subscriptions", {
params: input,
});
}
/**
*
* @param id Id
* @param input
* @returns Dto
*/
export function updateApi(id: string, input: WebhookSubscriptionUpdateDto): Promise<WebhookSubscriptionDto> {
return requestClient.put<WebhookSubscriptionDto>(`/api/webhooks/subscriptions/${id}`, input);
}

57
apps/react-admin/src/api/webhooks/webhook-definitions.ts

@ -0,0 +1,57 @@
import type { ListResultDto } from "#/abp-core";
import type {
WebhookDefinitionCreateDto,
WebhookDefinitionDto,
WebhookDefinitionGetListInput,
WebhookDefinitionUpdateDto,
} from "#/webhooks/definitions";
import requestClient from "../request";
/**
* Webhook定义
* @param name Webhook名称
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/webhooks/definitions/${name}`);
}
/**
* Webhook定义
* @param name Webhook名称
* @returns Webhook定义数据传输对象
*/
export function getApi(name: string): Promise<WebhookDefinitionDto> {
return requestClient.get<WebhookDefinitionDto>(`/api/webhooks/definitions/${name}`);
}
/**
* Webhook定义列表
* @param input Webhook过滤条件
* @returns Webhook定义数据传输对象列表
*/
export function getListApi(input?: WebhookDefinitionGetListInput): Promise<ListResultDto<WebhookDefinitionDto>> {
return requestClient.get<ListResultDto<WebhookDefinitionDto>>("/api/webhooks/definitions", {
params: input,
});
}
/**
* Webhook定义
* @param input Webhook定义参数
* @returns Webhook定义数据传输对象
*/
export function createApi(input: WebhookDefinitionCreateDto): Promise<WebhookDefinitionDto> {
return requestClient.post<WebhookDefinitionDto>("/api/webhooks/definitions", input);
}
/**
* Webhook定义
* @param name Webhook名称
* @param input Webhook定义参数
* @returns Webhook定义数据传输对象
*/
export function updateApi(name: string, input: WebhookDefinitionUpdateDto): Promise<WebhookDefinitionDto> {
return requestClient.put<WebhookDefinitionDto>(`/api/webhooks/definitions/${name}`, input);
}

59
apps/react-admin/src/api/webhooks/webhook-group-definitions.ts

@ -0,0 +1,59 @@
import type { ListResultDto } from "#/abp-core";
import type {
WebhookGroupDefinitionCreateDto,
WebhookGroupDefinitionDto,
WebhookGroupDefinitionGetListInput,
WebhookGroupDefinitionUpdateDto,
} from "#/webhooks/groups";
import requestClient from "../request";
/**
* Webhook分组定义
* @param name Webhook分组名称
*/
export function deleteApi(name: string): Promise<void> {
return requestClient.delete(`/api/webhooks/definitions/groups/${name}`);
}
/**
* Webhook分组定义
* @param name Webhook分组名称
* @returns Webhook分组定义数据传输对象
*/
export function getApi(name: string): Promise<WebhookGroupDefinitionDto> {
return requestClient.get<WebhookGroupDefinitionDto>(`/api/webhooks/definitions/groups/${name}`);
}
/**
* Webhook分组定义列表
* @param input Webhook分组过滤条件
* @returns Webhook分组定义数据传输对象列表
*/
export function getListApi(
input?: WebhookGroupDefinitionGetListInput,
): Promise<ListResultDto<WebhookGroupDefinitionDto>> {
return requestClient.get<ListResultDto<WebhookGroupDefinitionDto>>("/api/webhooks/definitions/groups", {
params: input,
});
}
/**
* Webhook分组定义
* @param input Webhook分组定义参数
* @returns Webhook分组定义数据传输对象
*/
export function createApi(input: WebhookGroupDefinitionCreateDto): Promise<WebhookGroupDefinitionDto> {
return requestClient.post<WebhookGroupDefinitionDto>("/api/webhooks/definitions/groups", input);
}
/**
* Webhook分组定义
* @param name Webhook分组名称
* @param input Webhook分组定义参数
* @returns Webhook分组定义数据传输对象
*/
export function updateApi(name: string, input: WebhookGroupDefinitionUpdateDto): Promise<WebhookGroupDefinitionDto> {
return requestClient.put<WebhookGroupDefinitionDto>(`/api/webhooks/definitions/groups/${name}`, input);
}

110
apps/react-admin/src/components/abp/account/change-password-modal.tsx

@ -0,0 +1,110 @@
import type React from "react";
import { useState } from "react";
import { Modal, Form, Input } from "antd";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useMutation } from "@tanstack/react-query";
import { changePasswordApi } from "@/api/account/profile";
import { usePasswordValidator } from "@/hooks/abp/identity/usePasswordValidator";
interface Props {
visible: boolean;
onClose: () => void;
}
const ChangePasswordModal: React.FC<Props> = ({ visible, onClose }) => {
const { t: $t } = useTranslation();
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const { validate } = usePasswordValidator();
const { mutateAsync: changePassword } = useMutation({
mutationFn: changePasswordApi,
onSuccess: () => {
toast.success($t("AbpIdentity.PasswordChangedMessage"));
onClose();
form.resetFields();
},
});
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setSubmitting(true);
await changePassword({
currentPassword: values.currentPassword,
newPassword: values.newPassword,
});
} catch (error) {
console.error(error);
} finally {
setSubmitting(false);
}
};
return (
<Modal
title={$t("AbpAccount.ResetMyPassword")}
open={visible}
onCancel={onClose}
onOk={handleSubmit}
confirmLoading={submitting}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item
name="currentPassword"
label={$t("AbpAccount.DisplayName:CurrentPassword")}
rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]}
>
<Input.Password />
</Form.Item>
<Form.Item
name="newPassword"
label={$t("AbpAccount.DisplayName:NewPassword")}
rules={[
{ required: true, message: $t("AbpUi.ThisFieldIsRequired") },
({ getFieldValue }) => ({
validator: async (_, value) => {
if (!value) return Promise.resolve();
if (value === getFieldValue("currentPassword")) {
return Promise.reject(new Error($t("AbpAccount.NewPasswordSameAsOld")));
}
try {
await validate(value);
return Promise.resolve();
} catch (error: any) {
return Promise.reject(error); // Validation error from hook
}
},
}),
]}
>
<Input.Password />
</Form.Item>
<Form.Item
name="newPasswordConfirm"
label={$t("AbpAccount.DisplayName:NewPasswordConfirm")}
dependencies={["newPassword"]}
rules={[
{ required: true, message: $t("AbpUi.ThisFieldIsRequired") },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("newPassword") === value) {
return Promise.resolve();
}
return Promise.reject(new Error($t("AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed")));
},
}),
]}
>
<Input.Password />
</Form.Item>
</Form>
</Modal>
);
};
export default ChangePasswordModal;

112
apps/react-admin/src/components/abp/account/change-phone-number-modal.tsx

@ -0,0 +1,112 @@
import type React from "react";
import { useState, useEffect } from "react";
import { Modal, Form, Input, Button, Col, Row } from "antd";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useMutation } from "@tanstack/react-query";
import { changePhoneNumberApi, sendChangePhoneNumberCodeApi } from "@/api/account/profile";
interface Props {
visible: boolean;
onClose: () => void;
onChange: (phoneNumber: string) => void;
}
const ChangePhoneNumberModal: React.FC<Props> = ({ visible, onClose, onChange }) => {
const { t: $t } = useTranslation();
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [countdown, setCountdown] = useState(0);
// Custom countdown logic using useEffect
useEffect(() => {
let timer: NodeJS.Timeout;
if (countdown > 0) {
timer = setInterval(() => {
setCountdown((prev) => (prev > 0 ? prev - 1 : 0));
}, 1000);
}
return () => clearInterval(timer);
}, [countdown]);
const { mutateAsync: sendCode } = useMutation({
mutationFn: sendChangePhoneNumberCodeApi,
onSuccess: () => {
setCountdown(60); // Set countdown to 60 seconds
toast.success($t("AbpUi.SavedSuccessfully"));
},
});
const { mutateAsync: changePhone } = useMutation({
mutationFn: changePhoneNumberApi,
onSuccess: (_, variables) => {
toast.success($t("AbpAccount.PhoneNumberChangedMessage"));
onChange(variables.newPhoneNumber);
onClose();
form.resetFields();
},
});
const handleSendCode = async () => {
try {
await form.validateFields(["newPhoneNumber"]);
const newPhone = form.getFieldValue("newPhoneNumber");
await sendCode({ newPhoneNumber: newPhone });
} catch (error) {
console.error(error);
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setSubmitting(true);
await changePhone({
code: values.code,
newPhoneNumber: values.newPhoneNumber,
});
} catch (error) {
console.error(error);
} finally {
setSubmitting(false);
}
};
return (
<Modal
title={$t("AbpIdentity.PhoneNumber")}
open={visible}
onCancel={onClose}
onOk={handleSubmit}
confirmLoading={submitting}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item
name="newPhoneNumber"
label={$t("AbpIdentity.DisplayName:NewPhoneNumber")}
rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]}
>
<Input />
</Form.Item>
<Form.Item label={$t("AbpIdentity.DisplayName:SmsVerifyCode")}>
<Row gutter={8}>
<Col span={16}>
<Form.Item name="code" noStyle rules={[{ required: true, message: $t("AbpUi.ThisFieldIsRequired") }]}>
<Input />
</Form.Item>
</Col>
<Col span={8}>
<Button block disabled={countdown > 0} onClick={handleSendCode}>
{countdown === 0 ? $t("authentication.sendCode") : `${countdown}s`}
</Button>
</Col>
</Row>
</Form.Item>
</Form>
</Modal>
);
};
export default ChangePhoneNumberModal;

130
apps/react-admin/src/components/abp/account/notice-settings.tsx

@ -1,14 +1,134 @@
import { Card, Empty } from "antd"; import type React from "react";
import { useEffect, useState } from "react";
import { Card, Collapse, List, Switch, Skeleton } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { getMySubscribesApi, subscribeApi, unSubscribeApi } from "@/api/management/notifications/my-subscribes";
import { getAssignableNotifiersApi } from "@/api/management/notifications/notifications";
const BindSettings: React.FC = () => { interface NotificationItem {
description?: string;
displayName: string;
isSubscribe: boolean;
loading: boolean;
name: string;
}
interface NotificationGroup {
displayName: string;
name: string;
notifications: NotificationItem[];
}
const NotificationSettings: React.FC = () => {
const { t: $t } = useTranslation(); const { t: $t } = useTranslation();
const [loading, setLoading] = useState(false);
const [notificationGroups, setNotificationGroups] = useState<NotificationGroup[]>([]);
useEffect(() => {
initData();
}, []);
const initData = async () => {
try {
setLoading(true);
const [subRes, notifierRes] = await Promise.all([getMySubscribesApi(), getAssignableNotifiersApi()]);
const groups: NotificationGroup[] = notifierRes.items.map((group) => {
const notifications: NotificationItem[] = group.notifications.map((notification) => ({
description: notification.description,
displayName: notification.displayName,
// Check if the user is already subscribed
isSubscribe: subRes.items.some((x) => x.name === notification.name),
loading: false,
name: notification.name,
}));
return {
displayName: group.displayName,
name: group.name,
notifications,
};
});
setNotificationGroups(groups);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleSubscribeChange = async (checked: boolean, groupIndex: number, itemIndex: number) => {
const targetGroup = notificationGroups[groupIndex];
const targetItem = targetGroup.notifications[itemIndex];
// Optimistic Update / Loading State
const updateState = (isLoading: boolean, isSubscribed: boolean) => {
setNotificationGroups((prev) => {
const next = [...prev];
const group = { ...next[groupIndex] };
const items = [...group.notifications];
items[itemIndex] = { ...items[itemIndex], loading: isLoading, isSubscribe: isSubscribed };
group.notifications = items;
next[groupIndex] = group;
return next;
});
};
updateState(true, checked);
try {
if (checked) {
await subscribeApi(targetItem.name);
} else {
await unSubscribeApi(targetItem.name);
}
toast.success($t("AbpUi.SavedSuccessfully"));
updateState(false, checked);
} catch (error) {
console.error(error);
// Revert on error
updateState(false, !checked);
}
};
if (loading && notificationGroups.length === 0) {
return (
<Card title={$t("abp.account.settings.noticeSettings")} bordered={false}>
<Skeleton active paragraph={{ rows: 6 }} />
</Card>
);
}
return ( return (
<Card bordered={false} title={$t("abp.account.settings.noticeSettings")}> <Card title={$t("abp.account.settings.noticeSettings")} bordered={false}>
<Empty /> <Collapse defaultActiveKey={notificationGroups.map((g) => g.name)}>
{notificationGroups.map((group, groupIndex) => (
<Collapse.Panel header={group.displayName} key={group.name}>
<List
itemLayout="horizontal"
dataSource={group.notifications}
renderItem={(item, itemIndex) => (
<List.Item
actions={[
<Switch
key="switch"
checked={item.isSubscribe}
loading={item.loading}
onChange={(checked) => handleSubscribeChange(checked, groupIndex, itemIndex)}
/>,
]}
>
<List.Item.Meta title={item.displayName} description={item.description} />
</List.Item>
)}
/>
</Collapse.Panel>
))}
</Collapse>
</Card> </Card>
); );
}; };
export default BindSettings; export default NotificationSettings;

2
apps/react-admin/src/components/abp/account/security-settings.tsx

@ -65,7 +65,7 @@ const SecuritySettings: React.FC<Props> = ({ userInfo, onChangePassword, onChang
if (sendMailInterval > 0) { if (sendMailInterval > 0) {
return `${sendMailInterval} s`; return `${sendMailInterval} s`;
} }
return $t("AbpAccount.ClickToValidation"); return $t("AbpAccountSecurity.ClickToValidation");
}; };
return ( return (

2
apps/react-admin/src/components/abp/account/session-settings.tsx

@ -24,7 +24,7 @@ const SessionSettings: React.FC = () => {
const { mutateAsync: revokeSession } = useMutation({ const { mutateAsync: revokeSession } = useMutation({
mutationFn: revokeSessionApi, mutationFn: revokeSessionApi,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["mySessions"] }); queryClient.refetchQueries({ queryKey: ["mySessions"] });
toast.success($t("AbpIdentity.SuccessfullyRevoked")); toast.success($t("AbpIdentity.SuccessfullyRevoked"));
}, },
}); });

87
apps/react-admin/src/components/abp/adapter/api-select.tsx

@ -0,0 +1,87 @@
import type React from "react";
import { useEffect, useState, useMemo } from "react";
import { Select, type SelectProps, Spin } from "antd";
function getNestedValue(obj: any, path: string) {
if (!path) return undefined;
return path.split(".").reduce((acc, part) => acc?.[part], obj);
}
export interface ApiSelectProps extends Omit<SelectProps, "options"> {
/** Function to fetch data, returning a Promise */
api?: (params?: any) => Promise<any>;
/** Parameters to pass to the api function */
params?: any;
/** Key in the response object containing the array (e.g., 'items') */
resultField?: string;
/** Property name to use for the label */
labelField?: string;
/** Property name to use for the value */
valueField?: string;
/** Trigger fetch immediately on mount */
immediate?: boolean;
}
const ApiSelect: React.FC<ApiSelectProps> = ({
api,
params,
resultField = "items",
labelField = "label",
valueField = "value",
immediate = true,
...props
}) => {
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (immediate && api) {
fetchData();
}
}, [JSON.stringify(params)]);
const fetchData = async () => {
if (!api) return;
setLoading(true);
try {
const res = await api(params);
// Use helper instead of lodash.get
const list = resultField ? getNestedValue(res, resultField) : res;
if (Array.isArray(list)) {
setData(list);
} else if (Array.isArray(res)) {
// Fallback: if extracting resultField failed but response itself is an array
setData(res);
}
} catch (error) {
console.error("ApiSelect fetch error:", error);
} finally {
setLoading(false);
}
};
const options = useMemo(() => {
return data.map((item) => ({
...item,
label: item[labelField],
value: item[valueField],
}));
}, [data, labelField, valueField]);
return (
<Select
loading={loading}
options={options}
notFoundContent={loading ? <Spin size="small" /> : null}
onDropdownVisibleChange={(open) => {
if (open && data.length === 0 && !loading) {
fetchData();
}
}}
{...props}
/>
);
};
export default ApiSelect;

73
apps/react-admin/src/components/abp/adapter/api-tree-select.tsx

@ -0,0 +1,73 @@
import type React from "react";
import { useEffect, useState } from "react";
import { TreeSelect, type TreeSelectProps, Spin } from "antd";
function getNestedValue(obj: any, path: string) {
if (!path) return undefined;
return path.split(".").reduce((acc, part) => acc?.[part], obj);
}
export interface ApiTreeSelectProps extends Omit<TreeSelectProps, "treeData"> {
api?: (params?: any) => Promise<any>;
params?: any;
resultField?: string;
immediate?: boolean;
fieldNames?: { label: string; value: string; children: string };
}
const ApiTreeSelect: React.FC<ApiTreeSelectProps> = ({
api,
params,
resultField,
immediate = true,
fieldNames = { label: "label", value: "value", children: "children" },
...props
}) => {
const [treeData, setTreeData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (immediate && api) {
fetchData();
}
}, [JSON.stringify(params)]);
const fetchData = async () => {
if (!api) return;
setLoading(true);
try {
const res = await api(params);
let list = res;
if (resultField) {
list = getNestedValue(res, resultField);
}
if (Array.isArray(list)) {
setTreeData(list);
}
} catch (error) {
console.error("ApiTreeSelect fetch error:", error);
} finally {
setLoading(false);
}
};
return (
<TreeSelect
loading={loading}
treeData={treeData}
fieldNames={fieldNames}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
notFoundContent={loading ? <Spin size="small" /> : null}
onDropdownVisibleChange={(open) => {
if (open && treeData.length === 0 && !loading) {
fetchData();
}
}}
{...props}
/>
);
};
export default ApiTreeSelect;

39
apps/react-admin/src/components/abp/adapter/icon-picker.tsx

@ -0,0 +1,39 @@
import type React from "react";
import { useMemo } from "react";
import { Input, type InputProps } from "antd";
import { Iconify } from "@/components/icon"; // Assumes you have this component
export interface IconPickerProps extends Omit<InputProps, "onChange"> {
value?: string;
onChange?: (value: string) => void;
}
const IconPicker: React.FC<IconPickerProps> = ({ value, onChange, ...props }) => {
// A helper to render the icon preview
const iconPreview = useMemo(() => {
if (!value) return null;
return (
<div className="flex items-center justify-center w-6 h-6 text-xl">
<Iconify icon={value} />
</div>
);
}, [value]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.value);
};
return (
<Input
value={value}
onChange={handleChange}
placeholder="Type icon code (e.g. mdi:home)"
allowClear
// Display the icon at the start of the input
addonBefore={iconPreview}
{...props}
/>
);
};
export default IconPicker;

36
apps/react-admin/src/components/abp/claims/claim-table.tsx

@ -1,6 +1,6 @@
import type React from "react"; import type React from "react";
import { useState, useRef } from "react"; import { useState, useRef } from "react";
import { Button, Popconfirm, Space } from "antd"; import { Button, Card, Popconfirm, Space } from "antd";
import { EditOutlined, DeleteOutlined } from "@ant-design/icons"; import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProTable, type ActionType, type ProColumns } from "@ant-design/pro-table"; import { ProTable, type ActionType, type ProColumns } from "@ant-design/pro-table";
@ -99,22 +99,24 @@ const ClaimTable: React.FC<ClaimModalProps> = ({
return ( return (
<div> <div>
<ProTable<IdentityClaimDto> <Card>
actionRef={actionRef} <ProTable<IdentityClaimDto>
columns={columns} actionRef={actionRef}
rowKey="id" columns={columns}
search={false} rowKey="id"
dataSource={claimsData?.items} search={false}
pagination={false} dataSource={claimsData?.items}
toolBarRender={() => [ pagination={false}
withAccessChecker( toolBarRender={() => [
<Button type="primary" onClick={() => openModal()}> withAccessChecker(
{$t("AbpIdentity.AddClaim")} <Button type="primary" onClick={() => openModal()}>
</Button>, {$t("AbpIdentity.AddClaim")}
[createPolicy], </Button>,
), [createPolicy],
]} ),
/> ]}
/>
</Card>
<ClaimModal <ClaimModal
visible={modalVisible} visible={modalVisible}
claim={selectedClaim || undefined} claim={selectedClaim || undefined}

27
apps/react-admin/src/components/abp/display-names/display-name-table.tsx

@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { Button, Popconfirm } from "antd"; import { Button, Card, Popconfirm } from "antd";
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons"; import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProTable, type ProColumns } from "@ant-design/pro-table"; import { ProTable, type ProColumns } from "@ant-design/pro-table";
@ -48,18 +48,19 @@ const DisplayNameTable: React.FC<DisplayNameProps> = ({ data, onChange, onDelete
return ( return (
<> <>
<ProTable<DisplayNameInfo> <Card>
columns={columns} <ProTable<DisplayNameInfo>
dataSource={dataSource} columns={columns}
search={false} dataSource={dataSource}
pagination={false} search={false}
toolBarRender={() => [ pagination={false}
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => setModalVisible(true)}> toolBarRender={() => [
{$t("AbpOpenIddict.DisplayName:AddNew")} <Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => setModalVisible(true)}>
</Button>, {$t("AbpOpenIddict.DisplayName:AddNew")}
]} </Button>,
/> ]}
/>
</Card>
<DisplayNameModal <DisplayNameModal
visible={modalVisible} visible={modalVisible}
onClose={() => setModalVisible(false)} onClose={() => setModalVisible(false)}

333
apps/react-admin/src/components/abp/features/feature-modal.tsx

@ -0,0 +1,333 @@
import type { Validator } from "#/abp-core";
import type React from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { getApi, updateApi } from "@/api/management/features/features";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import type { FeatureGroupDto, UpdateFeaturesDto } from "#/management/features/features";
import { Modal, Form, Tabs, Card, Input, InputNumber, Checkbox, Select, Spin } from "antd";
import { useValidation } from "@/hooks/abp/use-validation";
import { useLocalizer } from "@/hooks/abp/use-localization";
interface FeatureManagementModalProps {
visible: boolean;
onClose: () => void;
providerName: string;
providerKey?: string;
displayName?: string; // For the modal title
}
// Helper Interface for Form Data Structure
export interface FeatureFormData {
groups: FeatureGroupDto[];
}
const FeatureManagementModal: React.FC<FeatureManagementModalProps> = ({
visible,
onClose,
providerName,
providerKey,
displayName,
}) => {
const { t: $t } = useTranslation();
const { Lr } = useLocalizer();
const [form] = Form.useForm();
// Validation Hook
const { fieldMustBeetWeen, fieldMustBeStringWithMinimumLengthAndMaximumLength, fieldRequired } = useValidation();
// State
const [activeTabKey, setActiveTabKey] = useState<string>("");
const [groups, setGroups] = useState<FeatureGroupDto[]>([]);
// --- Helpers ---
/**
* Generates AntD Form Rules based on ABP Validator metadata (custom hook)
*/
const createRules = (fieldLabel: string, validator: Validator) => {
const rules: any[] = [];
if (validator?.properties) {
switch (validator.name) {
case "NUMERIC": {
rules.push(
...fieldMustBeetWeen({
name: fieldLabel,
start: Number(validator.properties.MinValue),
end: Number(validator.properties.MaxValue),
trigger: "change",
}),
);
break;
}
case "STRING": {
if (validator.properties.AllowNull && String(validator.properties.AllowNull).toLowerCase() === "true") {
rules.push(
...fieldRequired({
name: fieldLabel,
trigger: "blur",
}),
);
}
rules.push(
...fieldMustBeStringWithMinimumLengthAndMaximumLength({
name: fieldLabel,
minimum: Number(validator.properties.MinLength),
maximum: Number(validator.properties.MaxLength),
trigger: "blur",
}),
);
break;
}
default:
break;
}
}
return rules;
};
/**
* Process raw API data for the UI
* 1. Localize Selection items
* 2. Convert string values to boolean/number for inputs
*/
const mapFeaturesForUi = (rawGroups: FeatureGroupDto[]) => {
// Deep clone to avoid mutating read-only props if using strict mode
const processedGroups = JSON.parse(JSON.stringify(rawGroups)) as FeatureGroupDto[];
processedGroups.forEach((group) => {
group.features.forEach((feature) => {
// Handle Selection Localization
if (feature.valueType?.name === "SelectionStringValueType") {
const valueType: any = feature.valueType;
if (valueType.itemSource?.items) {
valueType.itemSource.items.forEach((item: any) => {
if (item.displayText?.resourceName === "Fixed") {
item.displayName = item.displayText.name;
} else {
item.displayName = Lr(item.displayText.resourceName, item.displayText.name);
}
});
}
}
// Handle Value Conversion
else if (feature.valueType?.validator) {
switch (feature.valueType.validator.name) {
case "BOOLEAN":
feature.value = String(feature.value).toLowerCase() === "true";
break;
case "NUMERIC":
feature.value = Number(feature.value);
break;
default:
// Keep as string
break;
}
}
});
});
return processedGroups;
};
/**
* Convert Form Data back to DTO for API submission
*/
const getFeatureInput = (formValues: any): UpdateFeaturesDto => {
const input: UpdateFeaturesDto = { features: [] };
const formGroups = formValues.groups as FeatureGroupDto[];
if (!formGroups) return input;
formGroups.forEach((g) => {
if (!g?.features) return;
g.features.forEach((f) => {
// Only include non-null values
if (f.value !== null && f.value !== undefined) {
input.features.push({
name: f.name,
value: String(f.value), // Convert back to string
});
}
});
});
return input;
};
// --- API Hooks ---
const { mutateAsync: fetchData, isPending: isLoading } = useMutation({
mutationFn: async () => {
if (!providerName) return;
const res = await getApi({ providerName, providerKey });
return res.groups;
},
onSuccess: (rawGroups) => {
if (rawGroups) {
const processed = mapFeaturesForUi(rawGroups);
setGroups(processed);
form.setFieldsValue({ groups: processed });
// Set active tab to first group
if (processed.length > 0 && !activeTabKey) {
setActiveTabKey(processed[0].name);
}
}
},
});
const { mutateAsync: updateData, isPending: isSaving } = useMutation({
mutationFn: async (input: UpdateFeaturesDto) => {
return updateApi({ providerName, providerKey }, input);
},
onSuccess: () => {
toast.success($t("AbpUi.SavedSuccessfully"));
onClose();
},
});
// --- Effects ---
useEffect(() => {
if (visible) {
form.resetFields();
setGroups([]);
setActiveTabKey("");
fetchData();
}
}, [visible, providerName, providerKey]);
// --- Handlers ---
const handleOk = async () => {
try {
const values = await form.validateFields();
const input = getFeatureInput(values);
await updateData(input);
} catch (e) {
console.error("Validation Failed", e);
}
};
// --- Renderers ---
const renderFeatureInput = (feature: any, groupIndex: number, featureIndex: number) => {
if (!feature.valueType) return null;
const fieldName = ["groups", groupIndex, "features", featureIndex, "value"];
const valueTypeName = feature.valueType.name;
const validatorName = feature.valueType.validator?.name;
// 1. Checkbox (Boolean)
if (valueTypeName === "ToggleStringValueType" && validatorName === "BOOLEAN") {
return (
<Form.Item
name={fieldName}
valuePropName="checked"
label={feature.displayName}
extra={feature.description}
rules={createRules(feature.displayName, feature.valueType.validator)}
>
<Checkbox>{feature.displayName}</Checkbox>
</Form.Item>
);
}
// 2. Select (Selection)
if (valueTypeName === "SelectionStringValueType") {
return (
<Form.Item
name={fieldName}
label={feature.displayName}
extra={feature.description}
rules={createRules(feature.displayName, feature.valueType.validator)}
>
<Select
options={feature.valueType.itemSource?.items || []}
fieldNames={{ label: "displayName", value: "value" }}
/>
</Form.Item>
);
}
// 3. Free Text (String or Numeric)
if (valueTypeName === "FreeTextStringValueType") {
return (
<Form.Item
name={fieldName}
label={feature.displayName}
extra={feature.description}
rules={createRules(feature.displayName, feature.valueType.validator)}
>
{validatorName === "NUMERIC" ? <InputNumber style={{ width: "100%" }} /> : <Input autoComplete="off" />}
</Form.Item>
);
}
// Fallback
return (
<Form.Item name={fieldName} label={feature.displayName} extra={feature.description}>
<Input />
</Form.Item>
);
};
const modalTitle = displayName
? `${$t("AbpFeatureManagement.Features")} - ${displayName}`
: $t("AbpFeatureManagement.Features");
return (
<Modal
title={modalTitle}
open={visible}
onCancel={onClose}
onOk={handleOk}
confirmLoading={isSaving}
centered
width="50%"
maskClosable={false}
destroyOnClose
>
<Spin spinning={isLoading}>
<Form form={form} layout="vertical">
{/* We render hidden inputs for names to ensure they exist in the form values structure for getFeatureInput logic
*/}
{groups.map((g, gIdx) => (
<div key={g.name} style={{ display: "none" }}>
{g.features.map((f, fIdx) => (
<Form.Item key={f.name} name={["groups", gIdx, "features", fIdx, "name"]} initialValue={f.name}>
<Input />
</Form.Item>
))}
</div>
))}
<Tabs
tabPosition="left"
activeKey={activeTabKey}
onChange={setActiveTabKey}
items={groups.map((group, groupIndex) => ({
key: group.name,
label: group.displayName,
children: (
<div className="h-[34rem] overflow-y-auto pr-2">
<Card title={group.displayName} bordered={false}>
{group.features.map((feature, featureIndex) => (
<div key={feature.name}>{renderFeatureInput(feature, groupIndex, featureIndex)}</div>
))}
</Card>
</div>
),
}))}
/>
</Form>
</Spin>
</Modal>
);
};
export default FeatureManagementModal;

149
apps/react-admin/src/components/abp/features/state-check/feature-state-check.tsx

@ -0,0 +1,149 @@
import type React from "react";
import { useMemo } from "react";
import { Checkbox, TreeSelect, Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { getListApi as getFeaturesApi } from "@/api/management/features/feature-definitions";
import { getListApi as getGroupsApi } from "@/api/management/features/feature-group-definitions";
import { localizationSerializer } from "@/utils/abp/localization-serializer";
import { useLocalizer } from "@/hooks/abp/use-localization";
import { listToTree } from "@/utils/tree";
import { valueTypeSerializer } from "../../string-value-type";
interface ValueType {
featureNames: string[];
requiresAll: boolean;
}
interface FeatureStateCheckProps {
value?: ValueType;
onChange?: (value: ValueType) => void;
}
const FeatureStateCheck: React.FC<FeatureStateCheckProps> = ({
value = { featureNames: [], requiresAll: false },
onChange,
}) => {
const { t: $t } = useTranslation();
const { deserialize } = localizationSerializer();
const { Lr } = useLocalizer();
// 1. Fetch Data
const { data: featureData, isLoading } = useQuery({
queryKey: ["featureStateCheckData"],
queryFn: async () => {
const [groupsRes, featuresRes] = await Promise.all([getGroupsApi(), getFeaturesApi()]);
// Filter: Only BOOLEAN features are relevant for state checking
const validFeatures = featuresRes.items.filter((item) => {
if (item.valueType) {
try {
const vt = valueTypeSerializer.deserialize(item.valueType);
return vt.validator.name === "BOOLEAN";
} catch {
return false;
}
}
return true;
});
// Prepare localized features list for lookup and tree building
const features = validFeatures.map((f) => {
const d = deserialize(f.displayName);
return {
...f,
title: Lr(d.resourceName, d.name), // Localized Title
key: f.name,
value: f.name,
};
});
// Prepare localized groups
const groups = groupsRes.items.map((g) => {
const d = deserialize(g.displayName);
return {
...g,
title: Lr(d.resourceName, d.name),
};
});
return { groups, features };
},
staleTime: Number.POSITIVE_INFINITY, // Config data rarely changes
});
// 2. Construct Tree Data
const treeData = useMemo(() => {
if (!featureData) return [];
const { groups, features } = featureData;
return groups.map((group) => {
// Find features belonging to this group
const groupFeatures = features.filter((f) => f.groupName === group.name);
// Build hierarchy for features (parent/child)
const children = listToTree(groupFeatures, { id: "name", pid: "parentName" });
// Return Group Node
return {
title: group.title,
value: group.name,
key: group.name,
selectable: false, // Cannot select the group itself
checkable: false, // Cannot check the group itself
disableCheckbox: true, // Visual disabled checkbox
children: children,
};
});
}, [featureData]);
// 3. Map selected string[] to { label, value }[] for TreeSelect (Required for treeCheckStrictly)
const treeValue = useMemo(() => {
if (!featureData || !value.featureNames) return [];
return value.featureNames.map((name) => {
const feature = featureData.features.find((f) => f.name === name);
return {
label: feature?.title || name,
value: name,
};
});
}, [featureData, value.featureNames]);
// 4. Handle Change
const triggerChange = (changedValue: Partial<ValueType>) => {
onChange?.({ ...value, ...changedValue });
};
const onTreeChange = (labeledValues: { label: React.ReactNode; value: string }[]) => {
// Extract just the feature names (strings) to send back
const names = labeledValues.map((item) => item.value);
triggerChange({ featureNames: names });
};
if (isLoading) return <Spin className="my-2" />;
return (
<div className="flex flex-col gap-2 w-full">
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}>
{$t("component.simple_state_checking.requireFeatures.requiresAll")}
</Checkbox>
<TreeSelect
treeData={treeData}
value={treeValue} // Pass objects
onChange={onTreeChange} // Receive objects
allowClear
treeCheckable
treeCheckStrictly // Decouples parent/child selection logic
showCheckedStrategy={TreeSelect.SHOW_ALL} // Show every selected node explicitly
placeholder={$t("ui.placeholder.select")}
style={{ width: "100%" }}
treeDefaultExpandAll
/>
</div>
);
};
export default FeatureStateCheck;

53
apps/react-admin/src/components/abp/features/state-check/global-feature-state-check.tsx

@ -0,0 +1,53 @@
import type React from "react";
import { useMemo } from "react";
import { Checkbox, Select } from "antd";
import { useTranslation } from "react-i18next";
import useAbpStore from "@/store/abpCoreStore";
interface ValueType {
globalFeatureNames: string[];
requiresAll: boolean;
}
interface GlobalFeatureStateCheckProps {
value?: ValueType;
onChange?: (value: ValueType) => void;
}
const GlobalFeatureStateCheck: React.FC<GlobalFeatureStateCheckProps> = ({
value = { globalFeatureNames: [], requiresAll: false },
onChange,
}) => {
const { t: $t } = useTranslation();
const application = useAbpStore((state) => state.application);
const options = useMemo(() => {
if (!application?.globalFeatures?.enabledFeatures) return [];
return application.globalFeatures.enabledFeatures.map((f) => ({
label: f,
value: f,
}));
}, [application]);
const triggerChange = (changedValue: Partial<ValueType>) => {
onChange?.({ ...value, ...changedValue });
};
return (
<div className="flex flex-col gap-4 w-full">
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}>
{$t("component.simple_state_checking.requireFeatures.requiresAll")}
</Checkbox>
<Select
mode="tags"
style={{ width: "100%" }}
options={options}
value={value.globalFeatureNames}
onChange={(val) => triggerChange({ globalFeatureNames: val })}
allowClear
/>
</div>
);
};
export default GlobalFeatureStateCheck;

139
apps/react-admin/src/components/abp/permissions/state-check/permission-state-check.tsx

@ -0,0 +1,139 @@
import type React from "react";
import { useMemo } from "react";
import { Checkbox, TreeSelect, Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { getListApi as getPermissionsApi } from "@/api/management/permissions/definitions";
import { getListApi as getGroupsApi } from "@/api/management/permissions/groups";
import { listToTree } from "@/utils/tree";
import { localizationSerializer } from "@/utils/abp/localization-serializer";
import { useLocalizer } from "@/hooks/abp/use-localization";
interface ValueType {
permissions: string[];
requiresAll: boolean;
}
interface PermissionStateCheckProps {
value?: ValueType;
onChange?: (value: ValueType) => void;
}
const PermissionStateCheck: React.FC<PermissionStateCheckProps> = ({
value = { permissions: [], requiresAll: false },
onChange,
}) => {
const { t: $t } = useTranslation();
const { deserialize } = localizationSerializer();
const { Lr } = useLocalizer();
// 1. Fetch Data
const { data: permissionData, isLoading } = useQuery({
queryKey: ["permissionStateCheckData"],
queryFn: async () => {
const [groupsRes, permissionsRes] = await Promise.all([getGroupsApi(), getPermissionsApi()]);
// Prepare localized permissions list
const permissions = permissionsRes.items.map((p) => {
const d = deserialize(p.displayName);
return {
...p,
displayName: Lr(d.resourceName, d.name), // Localized Name
name: p.name,
parentName: p.parentName,
groupName: p.groupName,
};
});
// Prepare localized groups
const groups = groupsRes.items.map((g) => {
const d = deserialize(g.displayName);
return {
...g,
displayName: Lr(d.resourceName, d.name),
name: g.name,
};
});
return { groups, permissions };
},
staleTime: Number.POSITIVE_INFINITY,
});
// 2. Construct Tree Data
const treeData = useMemo(() => {
if (!permissionData) return [];
const { groups, permissions } = permissionData;
return groups.map((group) => {
// Find permissions belonging to this group
const groupPermissions = permissions.filter((p) => p.groupName === group.name);
// Build hierarchy for permissions (parent/child)
// Standard listToTree usage: (list, config)
const children = listToTree(groupPermissions, { id: "name", pid: "parentName" });
// Return Group Node
// Note: We use 'displayName' and 'name' which match the DTOs,
// and map them using the 'fieldNames' prop on TreeSelect below.
return {
displayName: group.displayName,
name: group.name,
// UI props to make group unselectable/uncheckable
checkable: false,
selectable: false,
disableCheckbox: true,
children: children,
};
});
}, [permissionData]);
// 3. Map selected string[] to { label, value } objects for TreeSelect (Strict Mode)
const treeValue = useMemo(() => {
if (!permissionData || !value.permissions) return [];
return value.permissions.map((name) => {
const permission = permissionData.permissions.find((p) => p.name === name);
return {
label: permission?.displayName || name,
value: name,
};
});
}, [permissionData, value.permissions]);
// 4. Handle Change
const triggerChange = (changedValue: Partial<ValueType>) => {
onChange?.({ ...value, ...changedValue });
};
const onTreeChange = (labeledValues: { label: React.ReactNode; value: string }[]) => {
const names = labeledValues.map((item) => item.value);
triggerChange({ permissions: names });
};
if (isLoading) return <Spin className="my-2" />;
return (
<div className="flex flex-col gap-2 w-full">
<Checkbox checked={value.requiresAll} onChange={(e) => triggerChange({ requiresAll: e.target.checked })}>
{$t("component.simple_state_checking.requirePermissions.requiresAll")}
</Checkbox>
<TreeSelect
treeData={treeData}
value={treeValue}
onChange={onTreeChange}
// Map DTO fields to AntD TreeSelect expected fields
fieldNames={{ label: "displayName", value: "name", children: "children" }}
allowClear
treeCheckable
treeCheckStrictly
showCheckedStrategy={TreeSelect.SHOW_ALL}
placeholder={$t("ui.placeholder.select")}
style={{ width: "100%" }}
treeDefaultExpandAll
/>
</div>
);
};
export default PermissionStateCheck;

5
apps/react-admin/src/components/abp/simple-state-checking/interface.ts

@ -0,0 +1,5 @@
import type { IHasSimpleStateCheckers, ISimpleStateChecker } from "#/abp-core";
export interface SimplaCheckStateBase extends IHasSimpleStateCheckers<SimplaCheckStateBase> {
stateCheckers: ISimpleStateChecker<SimplaCheckStateBase>[];
}

208
apps/react-admin/src/components/abp/simple-state-checking/simple-state-checking-modal.tsx

@ -0,0 +1,208 @@
import type React from "react";
import { useEffect, useState } from "react";
import { Modal, Form, Select } from "antd";
import { useTranslation } from "react-i18next";
import GlobalFeatureStateCheck from "@/components/abp/features/state-check/global-feature-state-check";
import PermissionStateCheck from "@/components/abp/permissions/state-check/permission-state-check";
import FeatureStateCheck from "@/components/abp/features/state-check/feature-state-check";
interface SimpleStateCheckingModalProps {
visible: boolean;
onClose: () => void;
onConfirm: (data: any) => void;
record?: any; // The existing checker to edit
options: { label: string; value: string; disabled: boolean }[];
}
const SimpleStateCheckingModal: React.FC<SimpleStateCheckingModalProps> = ({
visible,
onClose,
onConfirm,
record,
options,
}) => {
const { t: $t } = useTranslation();
const [form] = Form.useForm();
const [selectedType, setSelectedType] = useState<string | undefined>();
useEffect(() => {
if (visible) {
if (record) {
// Map record back to form values
const name = record.name;
setSelectedType(name);
let value = {};
switch (name) {
case "F":
value = { featureNames: record.featureNames, requiresAll: record.requiresAll };
break;
case "G":
value = { globalFeatureNames: record.globalFeatureNames, requiresAll: record.requiresAll };
break;
case "P":
value = { permissions: record.model?.permissions || record.permissions, requiresAll: record.requiresAll };
break;
}
form.setFieldsValue({
name: name,
value: value,
});
} else {
form.resetFields();
setSelectedType(undefined);
}
}
}, [visible, record, form]);
const handleTypeChange = (val: string) => {
setSelectedType(val);
// Reset the value field when type changes
let initialValue = {};
if (val === "F") initialValue = { featureNames: [], requiresAll: false };
if (val === "G") initialValue = { globalFeatureNames: [], requiresAll: false };
if (val === "P") initialValue = { permissions: [], requiresAll: false };
form.setFieldValue("value", initialValue);
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
// Transform form values back to ABP simple state checker structure
const result: any = {
T: values.name, // T is usually the discriminator in some serializations, checking logic below
};
// Mapping based on the Vue onSubmit logic:
// A = RequiresAll (boolean)
// T = Discriminator/Name
// N = Names array
const val = values.value || {};
const checkerObj: any = {
name: values.name,
// 'A' property seems to map to requiresAll for the checker itself
requiresAll: val.requiresAll,
};
// Specifically for serialization logic later
// The Vue code returns: { A: boolean, T: string, N: string[] }
const output: any = {
A: val.requiresAll,
T: values.name,
};
switch (values.name) {
case "F":
output.N = val.featureNames;
break;
case "G":
output.N = val.globalFeatureNames;
break;
case "P":
output.N = val.permissions;
break;
}
onConfirm(output);
onClose();
} catch (e) {
console.error(e);
}
};
return (
<Modal
title={$t("component.simple_state_checking.title")}
open={visible}
onCancel={onClose}
onOk={handleSubmit}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item name="name" label={$t("component.simple_state_checking.form.name")} rules={[{ required: true }]}>
<Select
options={options}
onChange={handleTypeChange}
disabled={!!record} // Cannot change type when editing
/>
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.name !== curr.name}>
{({ getFieldValue }) => {
const type = getFieldValue("name");
if (type === "A") {
// Authenticated check requires no extra config
return null;
}
if (type === "F") {
return (
<Form.Item
name="value"
label={$t("component.simple_state_checking.requireFeatures.featureNames")}
rules={[
{
validator: (_, val) =>
val?.featureNames?.length > 0
? Promise.resolve()
: Promise.reject($t("component.simple_state_checking.requireFeatures.featureNames")),
},
]}
>
<FeatureStateCheck />
</Form.Item>
);
}
if (type === "G") {
return (
<Form.Item
name="value"
label={$t("component.simple_state_checking.requireFeatures.featureNames")}
rules={[
{
validator: (_, val) =>
val?.globalFeatureNames?.length > 0
? Promise.resolve()
: Promise.reject($t("component.simple_state_checking.requireFeatures.featureNames")),
},
]}
>
<GlobalFeatureStateCheck />
</Form.Item>
);
}
if (type === "P") {
return (
<Form.Item
name="value"
label={$t("component.simple_state_checking.requirePermissions.permissions")}
rules={[
{
validator: (_, val) =>
val?.permissions?.length > 0
? Promise.resolve()
: Promise.reject($t("component.simple_state_checking.requirePermissions.permissions")),
},
]}
>
<PermissionStateCheck />
</Form.Item>
);
}
return null;
}}
</Form.Item>
</Form>
</Modal>
);
};
export default SimpleStateCheckingModal;

214
apps/react-admin/src/components/abp/simple-state-checking/simple-state-checking.tsx

@ -0,0 +1,214 @@
import type React from "react";
import { useState, useMemo, useEffect } from "react";
import { Button, Card, Table, Tag, Space, Row, Col, Popconfirm } from "antd";
import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import { useSimpleStateCheck } from "@/hooks/abp/fake-hooks/use-simple-state-check"; // TODO re hooks in this chain
import type { SimplaCheckStateBase } from "./interface";
import SimpleStateCheckingModal from "./simple-state-checking-modal";
import type { ColumnsType } from "antd/es/table";
interface SimpleStateCheckingProps {
state: SimplaCheckStateBase;
value?: string; // Serialized string
onChange?: (value: string | undefined) => void;
disabled?: boolean;
allowEdit?: boolean;
allowDelete?: boolean;
}
const SimpleStateChecking: React.FC<SimpleStateCheckingProps> = ({
state,
value,
onChange,
disabled = false,
allowEdit = false,
allowDelete = false,
}) => {
const { t: $t } = useTranslation();
const { deserializeArray, serializeArray, deserialize } = useSimpleStateCheck();
// Local state to manage the list of checkers
const [checkers, setCheckers] = useState<any[]>([]);
const [modalVisible, setModalVisible] = useState(false);
const [editingRecord, setEditingRecord] = useState<any>(null);
// Sync prop value with local state
useEffect(() => {
if (!value || value.length === 0) {
setCheckers([]);
} else {
const deserialized = deserializeArray(value, state);
setCheckers(deserialized);
}
}, [value, state]);
// Map for friendly names
const simpleCheckerMap: Record<string, string> = {
A: $t("component.simple_state_checking.requireAuthenticated.title"),
F: $t("component.simple_state_checking.requireFeatures.title"),
G: $t("component.simple_state_checking.requireGlobalFeatures.title"),
P: $t("component.simple_state_checking.requirePermissions.title"),
};
const handleAddNew = () => {
setEditingRecord(null);
setModalVisible(true);
};
const handleEdit = (record: any) => {
setEditingRecord(record);
setModalVisible(true);
};
const handleUpdateCheckers = (newCheckers: any[]) => {
setCheckers(newCheckers);
const serialized = serializeArray(newCheckers);
onChange?.(serialized); //TODO
};
const handleModalConfirm = (data: any) => {
// 'data' comes in format { T: 'type', A: bool, N: [] } from the modal
// Deserialize the simple object from modal back into the complex class instance used by the list
const deserializedItem = deserialize(data, state);
if (!deserializedItem) return;
const updatedList = [...checkers];
const existingIndex = updatedList.findIndex((x) => x.name === data.T);
if (existingIndex > -1) {
updatedList[existingIndex] = deserializedItem;
} else {
updatedList.push(deserializedItem);
}
handleUpdateCheckers(updatedList);
};
const handleDelete = (record: any) => {
const updatedList = checkers.filter((x) => x.name !== record.name);
handleUpdateCheckers(updatedList);
};
const handleClean = () => {
handleUpdateCheckers([]);
};
const options = useMemo(() => {
return Object.keys(simpleCheckerMap).map((key) => ({
label: simpleCheckerMap[key],
value: key,
// Disable if this type already exists in the list (assuming 1 per type based on Vue logic findIndex)
disabled: checkers.some((x) => x.name === key),
}));
}, [checkers, $t]);
const columns: ColumnsType<any> = [
{
title: $t("component.simple_state_checking.table.name"),
dataIndex: "name",
key: "name",
width: 150,
render: (name) => simpleCheckerMap[name] || name,
},
{
title: $t("component.simple_state_checking.table.properties"),
key: "properties",
render: (_, record) => {
if (record.name === "F") {
return (
<Space wrap>
{record.featureNames?.map((f: string) => (
<Tag key={f}>{f}</Tag>
))}
</Space>
);
}
if (record.name === "G") {
return (
<Space wrap>
{record.globalFeatureNames?.map((f: string) => (
<Tag key={f}>{f}</Tag>
))}
</Space>
);
}
if (record.name === "P") {
const permissions = record.model?.permissions || record.permissions || [];
return (
<Space wrap>
{permissions.map((p: string) => (
<Tag key={p}>{p}</Tag>
))}
</Space>
);
}
if (record.name === "A") {
return $t("component.simple_state_checking.requireAuthenticated.title");
}
return null;
},
},
];
if (!disabled) {
columns.push({
title: $t("component.simple_state_checking.table.actions"),
key: "action",
width: 180,
render: (_, record) => (
<Space>
{allowEdit && (
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
{$t("component.simple_state_checking.actions.update")}
</Button>
)}
{allowDelete && (
<Popconfirm title="Are you sure?" onConfirm={() => handleDelete(record)}>
<Button type="link" danger icon={<DeleteOutlined />}>
{$t("component.simple_state_checking.actions.delete")}
</Button>
</Popconfirm>
)}
</Space>
),
});
}
return (
<div className="w-full">
<Card
title={
<Row justify="space-between" align="middle">
<Col>{$t("component.simple_state_checking.title")}</Col>
<Col>
{!disabled && (
<Space>
<Button type="primary" onClick={handleAddNew} icon={<PlusOutlined />}>
{$t("component.simple_state_checking.actions.create")}
</Button>
<Button danger onClick={handleClean}>
{$t("component.simple_state_checking.actions.clean")}
</Button>
</Space>
)}
</Col>
</Row>
}
>
<Table rowKey="name" columns={columns} dataSource={checkers} pagination={false} size="small" />
</Card>
<SimpleStateCheckingModal
visible={modalVisible}
onClose={() => setModalVisible(false)}
onConfirm={handleModalConfirm}
record={editingRecord}
options={options}
/>
</div>
);
};
export default SimpleStateChecking;

4
apps/react-admin/src/components/abp/string-value-type/index.ts

@ -0,0 +1,4 @@
export * from "./interface";
export { default as StringValueTypeInput } from "./string-value-type-input";
export * from "./validator";
export * from "./value-type.ts";

3
apps/react-admin/src/components/abp/string-value-type/interface.ts

@ -0,0 +1,3 @@
export interface StringValueTypeInstance {
validate(value: any): Promise<any>;
}

540
apps/react-admin/src/components/abp/string-value-type/string-value-type-input.tsx

@ -0,0 +1,540 @@
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { Button, Card, Checkbox, Col, Form, Input, InputNumber, Modal, Row, Select, Table, Space } from "antd";
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import { useLocalizer } from "@/hooks/abp/use-localization";
import {
valueTypeSerializer,
FreeTextStringValueType,
SelectionStringValueType,
ToggleStringValueType,
type StringValueType,
type SelectionStringValueItem,
} from "./value-type";
import {
AlwaysValidValueValidator,
BooleanValueValidator,
NumericValueValidator,
StringValueValidator,
} from "./validator";
import LocalizableInput from "@/components/abp/localizable-input/localizable-input";
import { localizationSerializer } from "@/utils/abp/localization-serializer";
import type { LocalizableStringInfo } from "#/abp-core";
export interface ValueTypeInputProps {
allowDelete?: boolean;
allowEdit?: boolean;
disabled?: boolean;
value?: string;
onChange?: (value: string | undefined) => void;
onValueTypeChange?: (type: string) => void;
onValidatorChange?: (validator: string) => void;
onSelectionChange?: (items: SelectionStringValueItem[]) => void;
}
export interface ValueTypeInputHandle {
validate: (value: any) => Promise<any>;
}
const ValueTypeInput = forwardRef<ValueTypeInputHandle, ValueTypeInputProps>(
(
{
allowDelete = false,
allowEdit = false,
disabled = false,
value = "{}",
onChange,
onValueTypeChange,
onValidatorChange,
onSelectionChange,
},
ref,
) => {
const { t: $t } = useTranslation();
const { Lr } = useLocalizer();
const {
deserialize: deserializeLocalizer,
serialize: serializeLocalizer,
validate: validateLocalizer,
} = localizationSerializer();
// Internal State
const [valueType, setValueType] = useState<StringValueType>(new FreeTextStringValueType());
// Modal State for Selection Type
const [modalVisible, setModalVisible] = useState(false);
const [modalForm] = Form.useForm();
const [editingItem, setEditingItem] = useState<{
isEdit: boolean;
displayText?: string;
}>({ isEdit: false });
// Initialize/Sync State from Props
useEffect(() => {
if (!value || value.trim() === "" || value === "{}") {
setValueType(new FreeTextStringValueType());
} else {
try {
const deserialized = valueTypeSerializer.deserialize(value);
setValueType(deserialized);
} catch (e) {
console.warn("Failed to deserialize valueType", e);
}
}
}, [value]);
// Notify Parent of Changes
const triggerChange = (newValueType: StringValueType) => {
// We need to create a new object reference or clone to ensure React detects state changes
// Serialization/Deserialization is a safe way to deep clone and ensure logic consistency
const serialized = valueTypeSerializer.serialize(newValueType);
// Update internal state only if not controlled (optimization),
// but here we rely on parent prop update usually.
// However, to make UI responsive immediately for nested properties:
setValueType(valueTypeSerializer.deserialize(serialized));
if (onChange) onChange(serialized);
if (onValueTypeChange) onValueTypeChange(newValueType.name);
if (onValidatorChange) onValidatorChange(newValueType.validator.name);
if (newValueType instanceof SelectionStringValueType && onSelectionChange) {
onSelectionChange(newValueType.itemSource.items);
}
};
// Expose Validate Method
useImperativeHandle(ref, () => ({
validate: async (val: any) => {
if (valueType instanceof SelectionStringValueType) {
const items = valueType.itemSource.items;
if (items.length === 0) {
return Promise.reject($t("component.value_type_nput.type.SELECTION.itemsNotBeEmpty"));
}
if (val && !items.some((item) => item.value === val)) {
return Promise.reject($t("component.value_type_nput.type.SELECTION.itemsNotFound"));
}
}
if (!valueType.validator.isValid(val)) {
const validatorNameKey = `component.value_type_nput.validator.${valueType.validator.name}.name`;
return Promise.reject(
$t("component.value_type_nput.validator.isInvalidValue", {
0: $t(validatorNameKey),
}),
);
}
return Promise.resolve(val);
},
}));
// Handlers
const handleValueTypeChange = (type: string) => {
let newValueType: StringValueType;
switch (type) {
case "SELECTION":
case "SelectionStringValueType":
newValueType = new SelectionStringValueType();
break;
case "TOGGLE":
case "ToggleStringValueType":
newValueType = new ToggleStringValueType();
break;
default:
newValueType = new FreeTextStringValueType();
break;
}
triggerChange(newValueType);
};
const handleValidatorChange = (validatorName: string) => {
const newValueType = valueTypeSerializer.deserialize(valueTypeSerializer.serialize(valueType));
switch (validatorName) {
case "BOOLEAN":
newValueType.validator = new BooleanValueValidator();
break;
case "NULL":
newValueType.validator = new AlwaysValidValueValidator();
break;
case "NUMERIC":
newValueType.validator = new NumericValueValidator();
break;
default:
newValueType.validator = new StringValueValidator();
break;
}
triggerChange(newValueType);
};
// Generic Property Update Handler (Deep Update)
const updateValidatorProperty = (updater: (validator: any) => void) => {
const serialized = valueTypeSerializer.serialize(valueType);
const cloned = valueTypeSerializer.deserialize(serialized);
updater(cloned.validator);
triggerChange(cloned);
};
// Selection Logic
const handleAddSelection = () => {
setEditingItem({ isEdit: false });
modalForm.resetFields();
setModalVisible(true);
};
const handleClearSelection = () => {
if (valueType instanceof SelectionStringValueType) {
const cloned = valueTypeSerializer.deserialize(
valueTypeSerializer.serialize(valueType),
) as SelectionStringValueType;
cloned.itemSource.items = [];
triggerChange(cloned);
}
};
const handleEditSelection = (record: SelectionStringValueItem) => {
setEditingItem({ isEdit: true, displayText: serializeLocalizer(record.displayText) });
modalForm.setFieldsValue({
displayText: serializeLocalizer(record.displayText),
value: record.value,
});
setModalVisible(true);
};
const handleDeleteSelection = (record: SelectionStringValueItem) => {
if (valueType instanceof SelectionStringValueType) {
const displayText = serializeLocalizer(record.displayText);
const cloned = valueType as SelectionStringValueType;
cloned.itemSource.items = cloned.itemSource.items.filter(
(x) => serializeLocalizer(x.displayText) !== displayText,
);
triggerChange(cloned);
}
};
const handleModalOk = async () => {
try {
const values = await modalForm.validateFields();
if (valueType instanceof SelectionStringValueType) {
const cloned = valueTypeSerializer.deserialize(
valueTypeSerializer.serialize(valueType),
) as SelectionStringValueType;
if (editingItem.isEdit) {
const index = cloned.itemSource.items.findIndex(
(x) => serializeLocalizer(x.displayText) === editingItem.displayText,
);
if (index > -1) {
cloned.itemSource.items[index] = {
displayText: values.displayText,
value: values.value,
};
}
} else {
cloned.itemSource.items.push({
displayText: deserializeLocalizer(values.displayText),
value: values.value,
});
}
triggerChange(cloned);
setModalVisible(false);
modalForm.resetFields();
}
} catch (e) {
// Validation failed
console.warn("Validation failed in handleModalOk", e);
}
};
// Columns for Selection Table
const selectionColumns = useMemo(() => {
const cols: any[] = [
{
title: $t("component.value_type_nput.type.SELECTION.displayText"),
dataIndex: "displayText",
key: "displayText",
width: 180,
render: (text: LocalizableStringInfo) => Lr(text.resourceName, text.name),
},
{
title: $t("component.value_type_nput.type.SELECTION.value"),
dataIndex: "value",
key: "value",
width: 200,
},
];
if (!disabled) {
cols.push({
title: $t("component.value_type_nput.type.SELECTION.actions.title"),
key: "action",
width: 220,
render: (_: any, record: SelectionStringValueItem) => (
<Space>
{allowEdit && (
<Button type="link" icon={<EditOutlined />} onClick={() => handleEditSelection(record)}>
{$t("component.value_type_nput.type.SELECTION.actions.update")}
</Button>
)}
{allowDelete && (
<Button type="link" danger icon={<DeleteOutlined />} onClick={() => handleDeleteSelection(record)}>
{$t("component.value_type_nput.type.SELECTION.actions.delete")}
</Button>
)}
</Space>
),
});
}
return cols;
}, [disabled, allowEdit, allowDelete, $t, Lr]);
return (
<div className="w-full">
<Card
title={
<div className="w-full">
<Row gutter={16}>
<Col span={11}>{$t("component.value_type_nput.type.name")}</Col>
<Col span={11} offset={2}>
{$t("component.value_type_nput.validator.name")}
</Col>
</Row>
<Row gutter={16} className="mt-2">
<Col span={11}>
<Select
className="w-full"
disabled={disabled}
value={valueType.name}
onChange={handleValueTypeChange}
options={[
{
label: $t("component.value_type_nput.type.FREE_TEXT.name"),
value: "FreeTextStringValueType",
},
{
label: $t("component.value_type_nput.type.TOGGLE.name"),
value: "ToggleStringValueType",
},
{
label: $t("component.value_type_nput.type.SELECTION.name"),
value: "SelectionStringValueType",
},
]}
/>
</Col>
<Col span={11} offset={2}>
<Select
className="w-full"
disabled={disabled}
value={valueType.validator.name}
onChange={handleValidatorChange}
>
<Select.Option value="NULL">{$t("component.value_type_nput.validator.NULL.name")}</Select.Option>
<Select.Option value="BOOLEAN" disabled={valueType.name !== "ToggleStringValueType"}>
{$t("component.value_type_nput.validator.BOOLEAN.name")}
</Select.Option>
<Select.Option value="NUMERIC" disabled={valueType.name !== "FreeTextStringValueType"}>
{$t("component.value_type_nput.validator.NUMERIC.name")}
</Select.Option>
<Select.Option value="STRING" disabled={valueType.name !== "FreeTextStringValueType"}>
{$t("component.value_type_nput.validator.STRING.name")}
</Select.Option>
</Select>
</Col>
</Row>
</div>
}
>
{/* FreeText - NUMERIC */}
{valueType.name === "FreeTextStringValueType" && valueType.validator.name === "NUMERIC" && (
<div>
<Row gutter={16}>
<Col span={11}>{$t("component.value_type_nput.validator.NUMERIC.minValue")}</Col>
<Col span={11} offset={2}>
{$t("component.value_type_nput.validator.NUMERIC.maxValue")}
</Col>
</Row>
<Row gutter={16} className="mt-1">
<Col span={11}>
<InputNumber
className="w-full"
disabled={disabled}
value={(valueType.validator as NumericValueValidator).minValue}
onChange={(val) => updateValidatorProperty((v) => (v.minValue = val ? Number(val) : undefined))}
/>
</Col>
<Col span={11} offset={2}>
<InputNumber
className="w-full"
disabled={disabled}
value={(valueType.validator as NumericValueValidator).maxValue}
onChange={(val) => updateValidatorProperty((v) => (v.maxValue = val ? Number(val) : undefined))}
/>
</Col>
</Row>
</div>
)}
{/* FreeText - STRING */}
{valueType.name === "FreeTextStringValueType" && valueType.validator.name === "STRING" && (
<div className="flex flex-col gap-4 mt-2">
<Checkbox
disabled={disabled}
checked={(valueType.validator as StringValueValidator).allowNull}
onChange={(e) => updateValidatorProperty((v) => (v.allowNull = e.target.checked))}
>
{$t("component.value_type_nput.validator.STRING.allowNull")}
</Checkbox>
<div>
<div className="mb-1">{$t("component.value_type_nput.validator.STRING.regularExpression")}</div>
<Input
className="w-full"
disabled={disabled}
value={(valueType.validator as StringValueValidator).regularExpression}
onChange={(e) => updateValidatorProperty((v) => (v.regularExpression = e.target.value))}
/>
</div>
<div>
<Row gutter={16}>
<Col span={11}>{$t("component.value_type_nput.validator.STRING.minLength")}</Col>
<Col span={11} offset={2}>
{$t("component.value_type_nput.validator.STRING.maxLength")}
</Col>
</Row>
<Row gutter={16} className="mt-1">
<Col span={11}>
<InputNumber
className="w-full"
disabled={disabled}
value={(valueType.validator as StringValueValidator).minLength}
onChange={(val) => updateValidatorProperty((v) => (v.minLength = val ? Number(val) : undefined))}
/>
</Col>
<Col span={11} offset={2}>
<InputNumber
className="w-full"
disabled={disabled}
value={(valueType.validator as StringValueValidator).maxLength}
onChange={(val) => updateValidatorProperty((v) => (v.maxLength = val ? Number(val) : undefined))}
/>
</Col>
</Row>
</div>
</div>
)}
{/* SELECTION Type */}
{valueType instanceof SelectionStringValueType && (
<Card
type="inner"
title={
<Row justify="space-between" align="middle">
<Col>
{valueType.itemSource.items.length <= 0 && (
<span className="text-red-500">
{$t("component.value_type_nput.type.SELECTION.itemsNotBeEmpty")}
</span>
)}
</Col>
<Col>
{!disabled && (
<Space>
<Button type="primary" onClick={handleAddSelection} icon={<PlusOutlined />}>
{$t("component.value_type_nput.type.SELECTION.actions.create")}
</Button>
<Button danger onClick={handleClearSelection}>
{$t("component.value_type_nput.type.SELECTION.actions.clean")}
</Button>
</Space>
)}
</Col>
</Row>
}
>
<Table
rowKey="value"
columns={selectionColumns}
dataSource={valueType.itemSource.items}
pagination={false}
size="small"
scroll={{ x: true }}
/>
</Card>
)}
</Card>
{/* Modal for Selection Item */}
<Modal
title={$t("component.value_type_nput.type.SELECTION.modal.title")}
open={modalVisible}
onOk={handleModalOk}
onCancel={() => {
setModalVisible(false);
modalForm.resetFields();
}}
maskClosable={false}
width={600}
>
<Form form={modalForm} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Form.Item
name="displayText"
label={$t("component.value_type_nput.type.SELECTION.displayText")}
rules={[
{
validator: async (_, value) => {
// Logic to validate duplicate Display Text
if (!validateLocalizer(value)) {
return Promise.reject($t("component.value_type_nput.type.SELECTION.displayTextNotBeEmpty"));
}
if (valueType instanceof SelectionStringValueType) {
// const serializedValue = serializeLocalizer(value);
const exists = valueType.itemSource.items.some((x) => {
return serializeLocalizer(x.displayText) === value;
});
if (exists) {
return Promise.reject($t("component.value_type_nput.type.SELECTION.duplicateKeyOrValue"));
}
}
return Promise.resolve();
},
},
]}
>
<LocalizableInput disabled={disabled || editingItem.isEdit} />
</Form.Item>
<Form.Item
name="value"
label={$t("component.value_type_nput.type.SELECTION.value")}
rules={[
{ required: true },
{
validator: async (_, val) => {
if (valueType instanceof SelectionStringValueType) {
const exists = valueType.itemSource.items.some((x) => {
return x.value === val;
});
if (exists) {
return Promise.reject($t("component.value_type_nput.type.SELECTION.duplicateKeyOrValue"));
}
}
return Promise.resolve();
},
},
]}
>
<Input disabled={disabled} />
</Form.Item>
</Form>
</Modal>
</div>
);
},
);
ValueTypeInput.displayName = "ValueTypeInput";
export default ValueTypeInput;

130
apps/react-admin/src/components/abp/string-value-type/validator.ts

@ -0,0 +1,130 @@
import type { Dictionary } from "#/abp-core";
import { isNullOrUnDef } from "@/utils/abp/is";
import { isBoolean, isNumber } from "@/utils/inference";
import { isNullOrWhiteSpace } from "@/utils/string";
export interface ValueValidator {
isValid(value?: any): boolean;
name: string;
properties: Dictionary<string, any>;
}
export class AlwaysValidValueValidator implements ValueValidator {
name = "NULL";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
isValid(_value?: any): boolean {
return true;
}
}
export class BooleanValueValidator implements ValueValidator {
name = "BOOLEAN";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
isValid(value?: any): boolean {
if (isNullOrUnDef(value)) return true;
if (isBoolean(value)) return true;
const bolString = String(value).toLowerCase();
if (bolString === "true" || bolString === "false") return true;
return false;
}
}
export class NumericValueValidator implements ValueValidator {
name = "NUMERIC";
properties: Dictionary<string, any>;
get maxValue(): number | undefined {
return Number(this.properties.MaxValue);
}
set maxValue(value: number) {
this.properties.MaxValue = value;
}
get minValue(): number | undefined {
return Number(this.properties.MinValue);
}
set minValue(value: number) {
this.properties.MinValue = value;
}
constructor() {
this.properties = {};
}
_isValidInternal(value: number): boolean {
if (this.minValue && value < this.minValue) return false;
if (this.maxValue && value > this.maxValue) return false;
return true;
}
isValid(value?: any): boolean {
if (isNullOrUnDef(value)) return true;
if (isNumber(value)) return this._isValidInternal(value);
const numString = String(value);
if (!isNullOrUnDef(numString)) {
const num = Number(numString);
if (num) return this._isValidInternal(num);
}
return false;
}
}
export class StringValueValidator implements ValueValidator {
name = "STRING";
properties: Dictionary<string, any>;
get allowNull(): boolean {
return String(this.properties.AllowNull ?? "true")?.toLowerCase() === "true";
}
set allowNull(value: boolean) {
this.properties.AllowNull = value;
}
get maxLength(): number | undefined {
return Number(this.properties.MaxLength);
}
set maxLength(value: number) {
this.properties.MaxLength = value;
}
get minLength(): number | undefined {
return Number(this.properties.MinLength);
}
set minLength(value: number) {
this.properties.MinLength = value;
}
get regularExpression(): string {
return String(this.properties.RegularExpression ?? "");
}
set regularExpression(value: string) {
this.properties.RegularExpression = value;
}
constructor() {
this.properties = {};
}
isValid(value?: any): boolean {
if (!this.allowNull && isNullOrUnDef(value)) return false;
const valueString = String(value);
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false;
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)) {
return new RegExp(this.regularExpression).test(valueString);
}
return true;
}
}

119
apps/react-admin/src/components/abp/string-value-type/value-type.ts

@ -0,0 +1,119 @@
import type { Dictionary, LocalizableStringInfo } from "#/abp-core";
import type { ValueValidator } from "./validator";
import {
AlwaysValidValueValidator,
BooleanValueValidator,
NumericValueValidator,
StringValueValidator,
} from "./validator";
export interface StringValueType {
name: string;
properties: Dictionary<string, any>;
validator: ValueValidator;
}
export interface SelectionStringValueItem {
displayText: LocalizableStringInfo;
value: string;
}
export interface SelectionStringValueItemSource {
items: SelectionStringValueItem[];
}
export class FreeTextStringValueType implements StringValueType {
name = "FreeTextStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
constructor(validator?: ValueValidator) {
this.properties = {};
this.validator = validator ?? new AlwaysValidValueValidator();
}
}
export class ToggleStringValueType implements StringValueType {
name = "ToggleStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
constructor(validator?: ValueValidator) {
this.properties = {};
this.validator = validator ?? new BooleanValueValidator();
}
}
export class SelectionStringValueType implements StringValueType {
itemSource: SelectionStringValueItemSource;
name = "SelectionStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
constructor(validator?: ValueValidator) {
this.properties = {};
this.itemSource = {
items: [],
};
this.validator = validator ?? new AlwaysValidValueValidator();
}
}
class StringValueTypeSerializer {
_deserializeValidator(validator: any): ValueValidator {
let convertValidator: ValueValidator = new AlwaysValidValueValidator();
if (validator.name) {
switch (validator.name) {
case "BOOLEAN": {
convertValidator = new BooleanValueValidator();
break;
}
case "NULL": {
convertValidator = new AlwaysValidValueValidator();
break;
}
case "NUMERIC": {
convertValidator = new NumericValueValidator();
break;
}
case "STRING": {
convertValidator = new StringValueValidator();
break;
}
}
}
convertValidator.properties = validator.properties;
return convertValidator;
}
deserialize(value: string): StringValueType {
let valueType: StringValueType;
const valueTypeObj = JSON.parse(value);
switch (valueTypeObj.name) {
case "SELECTION":
case "SelectionStringValueType": {
valueType = new SelectionStringValueType();
(valueType as SelectionStringValueType).itemSource = valueTypeObj.itemSource;
break;
}
case "TOGGLE":
case "ToggleStringValueType": {
valueType = new ToggleStringValueType();
break;
}
default: {
valueType = new FreeTextStringValueType();
break;
}
}
valueType.properties = valueTypeObj.properties;
valueType.validator = this._deserializeValidator(valueTypeObj.validator);
return valueType;
}
serialize(value: StringValueType): string {
const valueTypeString = JSON.stringify(value);
return valueTypeString;
}
}
export const valueTypeSerializer = new StringValueTypeSerializer();

4
apps/react-admin/src/constants/management/auditing/permissions.ts

@ -4,3 +4,7 @@ export const AuditLogPermissions = {
/** 删除 */ /** 删除 */
Delete: "AbpAuditing.AuditLog.Delete", Delete: "AbpAuditing.AuditLog.Delete",
}; };
/** 系统日志权限 */
export const SystemLogPermissions = {
Default: "AbpAuditing.SystemLog",
};

1
apps/react-admin/src/constants/management/features/index.ts

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

20
apps/react-admin/src/constants/management/features/permissions.ts

@ -0,0 +1,20 @@
/** 分组权限 */
export const GroupDefinitionsPermissions = {
/** 新增 */
Create: "FeatureManagement.GroupDefinitions.Create",
Default: "FeatureManagement.GroupDefinitions",
/** 删除 */
Delete: "FeatureManagement.GroupDefinitions.Delete",
/** 更新 */
Update: "FeatureManagement.GroupDefinitions.Update",
};
/** 功能定义权限 */
export const FeatureDefinitionsPermissions = {
/** 新增 */
Create: "FeatureManagement.Definitions.Create",
Default: "FeatureManagement.Definitions",
/** 删除 */
Delete: "FeatureManagement.Definitions.Delete",
/** 更新 */
Update: "FeatureManagement.Definitions.Update",
};

1
apps/react-admin/src/constants/management/localization/index.ts

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

30
apps/react-admin/src/constants/management/localization/permissions.ts

@ -0,0 +1,30 @@
/** 资源管理权限 */
export const ResourcesPermissions = {
/** 新增 */
Create: "LocalizationManagement.Resource.Create",
Default: "LocalizationManagement.Resource",
/** 删除 */
Delete: "LocalizationManagement.Resource.Delete",
/** 更新 */
Update: "LocalizationManagement.Resource.Update",
};
/** 语言管理权限 */
export const LanguagesPermissions = {
/** 新增 */
Create: "LocalizationManagement.Language.Create",
Default: "LocalizationManagement.Language",
/** 删除 */
Delete: "LocalizationManagement.Language.Delete",
/** 更新 */
Update: "LocalizationManagement.Language.Update",
};
/** 文本管理权限 */
export const TextsPermissions = {
/** 新增 */
Create: "LocalizationManagement.Text.Create",
Default: "LocalizationManagement.Text",
/** 删除 */
Delete: "LocalizationManagement.Text.Delete",
/** 更新 */
Update: "LocalizationManagement.Text.Update",
};

28
apps/react-admin/src/constants/notifications/permissions.ts

@ -0,0 +1,28 @@
/** 分组权限 */
export const GroupDefinitionsPermissions = {
/** 新增 */
Create: "Notifications.GroupDefinitions.Create",
Default: "Notifications.GroupDefinitions",
/** 删除 */
Delete: "Notifications.GroupDefinitions.Delete",
/** 更新 */
Update: "Notifications.GroupDefinitions.Update",
};
/** 通知定义权限 */
export const NotificationDefinitionsPermissions = {
/** 新增 */
Create: "Notifications.Definitions.Create",
Default: "Notifications.Definitions",
/** 删除 */
Delete: "Notifications.Definitions.Delete",
/** 更新 */
Update: "Notifications.Definitions.Update",
};
/** 通知权限 */
export const NotificationPermissions = {
/** 发送通知 */
Create: "Notifications.Notification.Send",
Default: "Notifications.Notification",
/** 删除 */
Delete: "Notifications.Notification.Delete",
};

18
apps/react-admin/src/constants/oss/permissions.ts

@ -0,0 +1,18 @@
/** 容器权限 */
export const ContainerPermissions = {
/** 新增 */
Create: "AbpOssManagement.Container.Create",
Default: "AbpOssManagement.Container",
/** 删除 */
Delete: "AbpOssManagement.Container.Delete",
};
/** 容器权限 */
export const OssObjectPermissions = {
/** 新增 */
Create: "AbpOssManagement.OssObject.Create",
Default: "AbpOssManagement.OssObject",
/** 删除 */
Delete: "AbpOssManagement.OssObject.Delete",
/** 下载 */
Download: "AbpOssManagement.OssObject.Download",
};

59
apps/react-admin/src/constants/platform/permissions.ts

@ -0,0 +1,59 @@
/** 邮件消息权限 */
export const EmailMessagesPermissions = {
Default: "Platform.EmailMessage",
/** 删除 */
Delete: "Platform.EmailMessage.Delete",
/** 发送消息 */
SendMessage: "Platform.EmailMessage.SendMessage",
};
/** 短信消息权限 */
export const SmsMessagesPermissions = {
Default: "Platform.SmsMessage",
/** 删除 */
Delete: "Platform.SmsMessage.Delete",
/** 发送消息 */
SendMessage: "Platform.SmsMessage.SendMessage",
};
/** 数据字典权限 */
export const DataDictionaryPermissions = {
/** 新增 */
Create: "Platform.DataDictionary.Create",
/** 默认 */
Default: "Platform.DataDictionary",
/** 删除 */
Delete: "Platform.DataDictionary.Delete",
/** 管理项目 */
ManageItems: "Platform.DataDictionary.ManageItems",
/** 移动 */
Move: "Platform.DataDictionary.Move",
/** 更新 */
Update: "Platform.DataDictionary.Update",
};
/** 布局权限 */
export const LayoutPermissions = {
/** 新增 */
Create: "Platform.Layout.Create",
/** 默认 */
Default: "Platform.Layout",
/** 删除 */
Delete: "Platform.Layout.Delete",
/** 更新 */
Update: "Platform.Layout.Update",
};
/** 菜单权限 */
export const MenuPermissions = {
/** 新增 */
Create: "Platform.Menu.Create",
/** 默认 */
Default: "Platform.Menu",
/** 删除 */
Delete: "Platform.Menu.Delete",
/** 管理角色菜单 */
ManageRoles: "Platform.Menu.ManageRoles",
/** 管理用户收藏菜单 */
ManageUserFavorites: "Platform.Menu.ManageUserFavorites",
/** 管理用户菜单 */
ManageUsers: "Platform.Menu.ManageUsers",
/** 更新 */
Update: "Platform.Menu.Update",
};

44
apps/react-admin/src/constants/request/http-status.ts

@ -0,0 +1,44 @@
export enum HttpStatusCode {
Accepted = 202,
Ambiguous = 300,
BadGateway = 502,
BadRequest = 400,
Conflict = 409,
Continue = 100,
Created = 201,
ExpectationFailed = 417,
Forbidden = 403,
GatewayTimeout = 504,
Gone = 410,
HttpVersionNotSupported = 505,
InternalServerError = 500,
LengthRequired = 411,
MethodNotAllowed = 405,
Moved = 301,
NoContent = 204,
NonAuthoritativeInformation = 203,
NotAcceptable = 406,
NotFound = 404,
NotImplemented = 501,
NotModified = 304,
OK = 200,
PartialContent = 206,
PaymentRequired = 402,
PreconditionFailed = 412,
ProxyAuthenticationRequired = 407,
Redirect = 302,
RedirectKeepVerb = 307,
RedirectMethod = 303,
RequestedRangeNotSatisfiable = 416,
RequestEntityTooLarge = 413,
RequestTimeout = 408,
RequestUriTooLong = 414,
ResetContent = 205,
ServiceUnavailable = 503,
SwitchingProtocols = 101,
Unauthorized = 401,
UnsupportedMediaType = 415,
Unused = 306,
UpgradeRequired = 426,
UseProxy = 305,
}

28
apps/react-admin/src/constants/saas/permissions.ts

@ -0,0 +1,28 @@
/** 版本权限 */
export const EditionsPermissions = {
/** 新增 */
Create: "AbpSaas.Editions.Create",
/** 默认 */
Default: "AbpSaas.Editions",
/** 删除 */
Delete: "AbpSaas.Editions.Delete",
/** 管理功能 */
ManageFeatures: "AbpSaas.Editions.ManageFeatures",
/** 更新 */
Update: "AbpSaas.Editions.Update",
};
/** 租户权限 */
export const TenantsPermissions = {
/** 新增 */
Create: "AbpSaas.Tenants.Create",
/** 默认 */
Default: "AbpSaas.Tenants",
/** 删除 */
Delete: "AbpSaas.Tenants.Delete",
/** 管理连接字符串 */
ManageConnectionStrings: "AbpSaas.Tenants.ManageConnectionStrings",
/** 管理功能 */
ManageFeatures: "AbpSaas.Tenants.ManageFeatures",
/** 更新 */
Update: "AbpSaas.Tenants.Update",
};

24
apps/react-admin/src/constants/tasks/permissions.ts

@ -0,0 +1,24 @@
/** 作业管理权限 */
export const BackgroundJobsPermissions = {
/** 新增 */
Create: "TaskManagement.BackgroundJobs.Create",
Default: "TaskManagement.BackgroundJobs",
/** 删除 */
Delete: "TaskManagement.BackgroundJobs.Delete",
/** 管理触发器 */
ManageActions: "TaskManagement.BackgroundJobs.ManageActions",
/** 管理系统作业 */
ManageSystemJobs: "TaskManagement.BackgroundJobs.ManageSystemJobs",
/** 暂停 */
Pause: "TaskManagement.BackgroundJobs.Pause",
/** 恢复 */
Resume: "TaskManagement.BackgroundJobs.Resume",
/** 启动 */
Start: "TaskManagement.BackgroundJobs.Start",
/** 停止 */
Stop: "TaskManagement.BackgroundJobs.Stop",
/** 触发 */
Trigger: "TaskManagement.BackgroundJobs.Trigger",
/** 修改 */
Update: "TaskManagement.BackgroundJobs.Update",
};

10
apps/react-admin/src/constants/text-templating/permissions.ts

@ -0,0 +1,10 @@
/** 模板定义权限 */
export const TextTemplatePermissions = {
/** 新增 */
Create: "AbpTextTemplating.TextTemplateDefinitions.Create",
Default: "AbpTextTemplating.TextTemplateDefinitions",
/** 删除 */
Delete: "AbpTextTemplating.TextTemplateDefinitions.Delete",
/** 更新 */
Update: "AbpTextTemplating.TextTemplateDefinitions.Update",
};

41
apps/react-admin/src/constants/webhooks/permissions.ts

@ -0,0 +1,41 @@
/** 分组权限 */
export const GroupDefinitionsPermissions = {
/** 新增 */
Create: "AbpWebhooks.GroupDefinitions.Create",
Default: "AbpWebhooks.GroupDefinitions",
/** 删除 */
Delete: "AbpWebhooks.GroupDefinitions.Delete",
/** 更新 */
Update: "AbpWebhooks.GroupDefinitions.Update",
};
/** Webhook定义权限 */
export const WebhookDefinitionsPermissions = {
/** 新增 */
Create: "AbpWebhooks.Definitions.Create",
Default: "AbpWebhooks.Definitions",
/** 删除 */
Delete: "AbpWebhooks.Definitions.Delete",
/** 更新 */
Update: "AbpWebhooks.Definitions.Update",
};
/** Webhook订阅权限 */
export const WebhookSubscriptionPermissions = {
/** 新增 */
Create: "AbpWebhooks.Subscriptions.Create",
Default: "AbpWebhooks.Subscriptions",
/** 删除 */
Delete: "AbpWebhooks.Subscriptions.Delete",
/** 更新 */
Update: "AbpWebhooks.Subscriptions.Update",
};
/** Webhook发送记录权限 */
export const WebhooksSendAttemptsPermissions = {
Default: "AbpWebhooks.SendAttempts",
/** 删除 */
Delete: "AbpWebhooks.SendAttempts.Delete",
/** 更新 */
Resend: "AbpWebhooks.SendAttempts.Resend",
};

2
apps/react-admin/src/hooks/abp/fake-hooks/readme.md

@ -0,0 +1,2 @@
1. change them to fake as little as possible
2. /abp-react-admin/slash-admin/src/utils/abp

37
apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-authenticated-simple-state-checker.ts

@ -0,0 +1,37 @@
import type {
CurrentUser,
IHasSimpleStateCheckers,
ISimpleStateChecker,
SimpleStateCheckerContext,
} from "#/abp-core/global";
import useAbpStore from "@/store/abpCoreStore";
export interface RequireAuthenticatedStateChecker {
name: string;
}
export class RequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>
implements ISimpleStateChecker<TState>, RequireAuthenticatedStateChecker
{
_currentUser?: CurrentUser;
name = "A";
constructor(currentUser?: CurrentUser) {
this._currentUser = currentUser;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._currentUser?.isAuthenticated ?? false;
}
serialize(): string {
return JSON.stringify({
T: this.name,
});
}
}
export function requireAuthenticatedSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>(): ISimpleStateChecker<TState> {
const { application } = useAbpStore.getState();
return new RequireAuthenticatedSimpleStateChecker<TState>(application?.currentUser);
}

42
apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-features-simple-state-checker.ts

@ -0,0 +1,42 @@
import type { IHasSimpleStateCheckers, ISimpleStateChecker, SimpleStateCheckerContext } from "#/abp-core/global";
import type { IFeatureChecker } from "#/features";
import { useFeatures } from "../use-abp-feature";
export interface RequireFeaturesStateChecker {
featureNames: string[];
name: string;
requiresAll: boolean;
}
export class RequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>
implements ISimpleStateChecker<TState>, RequireFeaturesStateChecker
{
_featureChecker: IFeatureChecker;
featureNames: string[];
name = "F";
requiresAll: boolean;
constructor(featureChecker: IFeatureChecker, featureNames: string[], requiresAll = 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({
A: this.requiresAll,
N: this.featureNames,
T: this.name,
});
}
}
export function useRequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
featureNames: string[],
requiresAll = false,
): ISimpleStateChecker<TState> {
const featureChecker = useFeatures();
return new RequireFeaturesSimpleStateChecker(featureChecker, featureNames, requiresAll);
}

43
apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-global-features-simple-state-checker.ts

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

93
apps/react-admin/src/hooks/abp/fake-hooks/simple-state-checking/use-require-permissions-simple-state-checker.ts

@ -0,0 +1,93 @@
import type {
IHasSimpleStateCheckers,
ISimpleBatchStateChecker,
ISimpleStateChecker,
SimpleBatchStateCheckerContext,
SimpleStateCheckerContext,
} from "#/abp-core/global";
import type { IPermissionChecker } from "#/abp-core/permissions";
import { useAuthorization } from "../use-abp-authorization";
export class RequirePermissionsSimpleBatchStateCheckerModel<TState extends IHasSimpleStateCheckers<TState>> {
permissions: string[];
requiresAll: boolean;
state: TState;
constructor(state: TState, permissions: string[], requiresAll = true) {
this.state = state;
this.permissions = permissions;
this.requiresAll = requiresAll;
}
}
export interface RequirePermissionsStateChecker<TState extends IHasSimpleStateCheckers<TState>> {
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
name: string;
}
export class RequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>
implements ISimpleStateChecker<TState>, RequirePermissionsStateChecker<TState>
{
_permissionChecker: IPermissionChecker;
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
name = "P";
constructor(permissionChecker: IPermissionChecker, 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({
A: this.model.requiresAll,
N: this.model.permissions,
T: this.name,
});
}
}
export class RequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>>
implements ISimpleBatchStateChecker<TState>
{
_permissionChecker: IPermissionChecker;
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[];
name = "P";
constructor(permissionChecker: IPermissionChecker, models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]) {
this.models = models;
this._permissionChecker = permissionChecker;
}
isEnabled(context: SimpleBatchStateCheckerContext<TState>) {
// 1. Initialize a Map instead of a plain object
const result = new Map<TState, boolean>();
context.states.forEach((state) => {
const model = this.models.find((x) => x.state === state);
if (model) {
// 2. Use .set() to map the object key to the boolean value
result.set(model.state, 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);
}

33
apps/react-admin/src/hooks/abp/fake-hooks/use-abp-authorization.ts

@ -0,0 +1,33 @@
import type { IPermissionChecker } from "#/abp-core";
import useAbpStore from "@/store/abpCoreStore";
// import { useMemo } from "react";
export function useAuthorization(): IPermissionChecker {
// const application = useAbpStore((state) => state.application);
const { application } = useAbpStore.getState();
// const grantedPolicies = useMemo(() => {
// return application?.auth.grantedPolicies ?? {};
// }, [application]);
const grantedPolicies = application?.auth.grantedPolicies ?? {};
function isGranted(name: string | string[], requiresAll?: boolean): boolean {
if (Array.isArray(name)) {
if (requiresAll === undefined || requiresAll === true) {
return name.every((n) => grantedPolicies[n]);
}
return name.some((n) => grantedPolicies[n]);
}
return grantedPolicies[name] ?? false;
}
function authorize(name: string | string[]): void {
if (!isGranted(name)) {
throw new Error(`Authorization failed! Given policy has not granted: ${name}`);
}
}
return {
authorize,
isGranted,
};
}

55
apps/react-admin/src/hooks/abp/fake-hooks/use-abp-feature.ts

@ -0,0 +1,55 @@
import type { FeatureValue, IFeatureChecker } from "#/features";
import useAbpStore from "@/store/abpCoreStore";
// import { useMemo } from "react";
export function useFeatures(): IFeatureChecker {
// const application = useAbpStore((state) => state.application);
const { application } = useAbpStore.getState();
// const features = useMemo<FeatureValue[]>(() => {
// if (!application?.features?.values) {
// return [];
// }
// return Object.keys(application.features.values).map((name) => ({
// name,
// value: application.features.values[name] ?? "",
// }));
// }, [application]);
let features: FeatureValue[];
if (!application?.features?.values) {
features = [];
} else {
features = Object.keys(application.features.values).map((name) => ({
name,
value: application.features.values[name] ?? "",
}));
}
function get(name: string): FeatureValue | undefined {
return features.find((feature) => feature.name === name);
}
function _isEnabled(name: string): boolean {
const setting = get(name);
return setting?.value.toLowerCase() === "true";
}
const featureChecker: IFeatureChecker = {
getOrEmpty(name: string) {
return get(name)?.value ?? "";
},
isEnabled(featureNames: string | string[], requiresAll?: boolean) {
if (Array.isArray(featureNames)) {
if (featureNames.length === 0) return true;
if (requiresAll === undefined || requiresAll === true) {
return featureNames.every(_isEnabled);
}
return featureNames.some(_isEnabled);
}
return _isEnabled(featureNames);
},
};
return featureChecker;
}

43
apps/react-admin/src/hooks/abp/fake-hooks/use-abp-global-feature.ts

@ -0,0 +1,43 @@
import useAbpStore from "@/store/abpCoreStore";
import { isNullOrWhiteSpace } from "@/utils/string";
// import { useMemo } from "react";
export function useGlobalFeatures() {
// const application = useAbpStore((state) => state.application);
const { application } = useAbpStore.getState();
// const enabledFeatures = useMemo<string[]>(() => {
// if (!application?.globalFeatures?.enabledFeatures) {
// return [];
// }
// return application.globalFeatures.enabledFeatures;
// }, [application]);
let enabledFeatures: string[];
if (!application?.globalFeatures?.enabledFeatures) {
enabledFeatures = [];
} else {
enabledFeatures = application.globalFeatures.enabledFeatures;
}
function _isEnabled(name: string): boolean {
// Find if the feature exists in the enabled list
const feature = enabledFeatures.find((f) => f === 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) {
return featureNames.every(_isEnabled);
}
return featureNames.some(_isEnabled);
}
return _isEnabled(featureNames);
}
return {
isEnabled,
};
}

71
apps/react-admin/src/hooks/abp/fake-hooks/use-http-status-code-map.ts

@ -0,0 +1,71 @@
import { HttpStatusCode } from "@/constants/request/http-status";
export function useHttpStatusCodeMap() {
const httpStatusCodeMap: { [key: number]: string } = {
[HttpStatusCode.Accepted]: "202 - Accepted",
[HttpStatusCode.Ambiguous]: "300 - Ambiguous/Multiple Choices",
[HttpStatusCode.BadGateway]: "502 - Bad Gateway",
[HttpStatusCode.BadRequest]: "400 - Bad Request",
[HttpStatusCode.Conflict]: "409 - Conflict",
[HttpStatusCode.Continue]: "100 - Continue",
[HttpStatusCode.Created]: "201 - Created",
[HttpStatusCode.ExpectationFailed]: "417 - Expectation Failed",
[HttpStatusCode.Forbidden]: "403 - Forbidden",
[HttpStatusCode.GatewayTimeout]: "504 - Gateway Timeout",
[HttpStatusCode.Gone]: "410 - Gone",
[HttpStatusCode.HttpVersionNotSupported]: "505 - Http Version Not Supported",
[HttpStatusCode.InternalServerError]: "500 - Internal Server Error",
[HttpStatusCode.LengthRequired]: "411 - Length Required",
[HttpStatusCode.MethodNotAllowed]: "405 - Method Not Allowed",
[HttpStatusCode.Moved]: "301 - Moved/Moved Permanently",
[HttpStatusCode.NoContent]: "204 - No Content",
[HttpStatusCode.NonAuthoritativeInformation]: "203 - Non Authoritative Information",
[HttpStatusCode.NotAcceptable]: "406 - Not Acceptable",
[HttpStatusCode.NotFound]: "404 - Not Found",
[HttpStatusCode.NotImplemented]: "501 - Not Implemented",
[HttpStatusCode.NotModified]: "304 - Not Modified",
[HttpStatusCode.OK]: "200 - OK",
[HttpStatusCode.PartialContent]: "206 - Partial Content",
[HttpStatusCode.PaymentRequired]: "402 - Payment Required",
[HttpStatusCode.PreconditionFailed]: "412 - Precondition Failed",
[HttpStatusCode.ProxyAuthenticationRequired]: "407 - Proxy Authentication Required",
[HttpStatusCode.Redirect]: "302 - Found/Redirect",
[HttpStatusCode.RedirectKeepVerb]: "307 - Redirect Keep Verb/Temporary Redirect",
[HttpStatusCode.RedirectMethod]: "303 - Redirect Method/See Other",
[HttpStatusCode.RequestedRangeNotSatisfiable]: "416 - Requested Range Not Satisfiable",
[HttpStatusCode.RequestEntityTooLarge]: "413 - Request Entity Too Large",
[HttpStatusCode.RequestTimeout]: "408 - Request Timeout",
[HttpStatusCode.RequestUriTooLong]: "414 - Request Uri Too Long",
[HttpStatusCode.ResetContent]: "205 - Reset Content",
[HttpStatusCode.ServiceUnavailable]: "503 - Service Unavailable",
[HttpStatusCode.SwitchingProtocols]: "101 - Switching Protocols",
[HttpStatusCode.Unauthorized]: "401 - Unauthorized",
[HttpStatusCode.UnsupportedMediaType]: "415 - Unsupported Media Type",
[HttpStatusCode.Unused]: "306 - Unused",
[HttpStatusCode.UpgradeRequired]: "426 - Upgrade Required",
[HttpStatusCode.UseProxy]: "305 - Use Proxy",
};
function getHttpStatusColor(statusCode: HttpStatusCode) {
if (statusCode < 200) {
return "default";
}
if (statusCode >= 200 && statusCode < 300) {
return "success";
}
if (statusCode >= 300 && statusCode < 400) {
return "processing";
}
if (statusCode >= 400 && statusCode < 500) {
return "warning";
}
if (statusCode >= 500) {
return "error";
}
}
return {
getHttpStatusColor,
httpStatusCodeMap,
};
}

107
apps/react-admin/src/hooks/abp/fake-hooks/use-simple-state-check.ts

@ -0,0 +1,107 @@
import { isNullOrUnDef } from "@/utils/abp/is";
import type { IHasSimpleStateCheckers, ISimpleStateChecker, ISimpleStateCheckerSerializer } from "#/abp-core/global";
import { isNullOrWhiteSpace } from "@/utils/string";
import { requireAuthenticatedSimpleStateChecker } from "./simple-state-checking/use-require-authenticated-simple-state-checker";
import { useRequireFeaturesSimpleStateChecker } from "./simple-state-checking/use-require-features-simple-state-checker";
import { useRequirePermissionsSimpleStateChecker } from "./simple-state-checking/use-require-permissions-simple-state-checker";
import { useRequireGlobalFeaturesSimpleStateChecker } from "./simple-state-checking/use-require-global-features-simple-state-checker";
export function useSimpleStateCheck<TState extends IHasSimpleStateCheckers<TState>>(): ISimpleStateCheckerSerializer {
function 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 requireAuthenticatedSimpleStateChecker();
}
case "F": {
const features = jsonObject.N as string[];
if (features === undefined) {
throw new 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 new Error(`'N' is not an array in the serialized state checker! JsonObject: ${jsonObject}`);
}
return useRequireGlobalFeaturesSimpleStateChecker(globalFeatures, jsonObject.A === true);
}
case "P": {
const permissions = jsonObject.N as string[];
if (permissions === undefined) {
throw new Error(`'N' is not an array in the serialized state checker! JsonObject: ${jsonObject}`);
}
return useRequirePermissionsSimpleStateChecker({
permissions,
requiresAll: jsonObject.A === true,
state,
});
}
default: {
return undefined;
}
}
}
function 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) => deserialize(json, state))
.filter((checker) => !isNullOrUnDef(checker))
.map((checker) => checker);
}
const stateChecker = deserialize(jsonObject, state);
if (!stateChecker) return [];
return [stateChecker];
}
function serialize<TState extends IHasSimpleStateCheckers<TState>>(
checker: ISimpleStateChecker<TState>,
): string | undefined {
return checker.serialize();
}
function serializeArray<TState extends IHasSimpleStateCheckers<TState>>(
stateCheckers: ISimpleStateChecker<TState>[],
): string | undefined {
if (stateCheckers.length === 0) return undefined;
if (stateCheckers.length === 1) {
const stateChecker = stateCheckers[0];
const single = stateChecker?.serialize();
if (isNullOrUnDef(single)) return undefined;
return `[${single}]`;
}
let serializedCheckers = "";
stateCheckers.forEach((checker) => {
const serializedChecker = checker.serialize();
if (!isNullOrUnDef(serializedChecker)) {
serializedCheckers += `${serializedChecker},`;
}
});
if (serializedCheckers.endsWith(",")) {
serializedCheckers = serializedCheckers.slice(0, Math.max(0, serializedCheckers.length - 1));
}
return serializedCheckers.length > 0 ? `[${serializedCheckers}]` : undefined;
}
return {
deserialize,
deserializeArray,
serialize,
serializeArray,
};
}

69
apps/react-admin/src/hooks/abp/identity/usePasswordValidator.ts

@ -0,0 +1,69 @@
import { useAbpSettings } from "@/store/abpSettingStore";
import { ValidationEnum } from "@/constants/abp-core";
import { getUnique, isNullOrWhiteSpace } from "@/utils/string";
import { isDigit, isLetterOrDigit, isLower, isUpper } from "@/utils/abp/regex";
import { useLocalizer } from "../use-localization";
import { useMemo } from "react";
export function usePasswordValidator() {
const { getNumber, isTrue } = useAbpSettings();
const { L } = useLocalizer(["AbpIdentity", "AbpValidation", "AbpUi"]);
const passwordSetting = useMemo(() => {
return {
requiredDigit: isTrue("Abp.Identity.Password.RequireDigit"),
requiredLength: getNumber("Abp.Identity.Password.RequiredLength"),
requiredLowercase: isTrue("Abp.Identity.Password.RequireLowercase"),
requiredUniqueChars: getNumber("Abp.Identity.Password.RequiredUniqueChars"),
requireNonAlphanumeric: isTrue("Abp.Identity.Password.RequireNonAlphanumeric"),
requireUppercase: isTrue("Abp.Identity.Password.RequireUppercase"),
};
}, [getNumber, isTrue]);
function validate(password: string): Promise<void> {
return new Promise((resolve, reject) => {
// 1. Check Empty
if (isNullOrWhiteSpace(password)) {
return reject(L(ValidationEnum.FieldRequired, [L("DisplayName:Password")]));
}
const setting = passwordSetting;
// 2. Check Length
if (setting.requiredLength > 0 && password.length < setting.requiredLength) {
return reject(L("Volo.Abp.Identity:PasswordTooShort", [String(setting.requiredLength)]));
}
// 3. Check Non-Alphanumeric
if (setting.requireNonAlphanumeric && isLetterOrDigit(password)) {
return reject(L("Volo.Abp.Identity:PasswordRequiresNonAlphanumeric"));
}
// 4. Check Digit
if (setting.requiredDigit && !isDigit(password)) {
return reject(L("Volo.Abp.Identity:PasswordRequiresDigit"));
}
// 5. Check Lowercase
if (setting.requiredLowercase && !isLower(password)) {
return reject(L("Volo.Abp.Identity:PasswordRequiresLower"));
}
// 6. Check Uppercase
if (setting.requireUppercase && !isUpper(password)) {
return reject(L("Volo.Abp.Identity:PasswordRequiresUpper"));
}
// 7. Check Unique Chars
if (setting.requiredUniqueChars >= 1 && getUnique(password).length < setting.requiredUniqueChars) {
return reject(L("Volo.Abp.Identity:PasswordRequiredUniqueChars", [String(setting.requiredUniqueChars)]));
}
return resolve();
});
}
return {
validate,
};
}

65
apps/react-admin/src/hooks/abp/identity/useRandomPassword.ts

@ -0,0 +1,65 @@
import { useAbpSettings } from "@/store/abpSettingStore";
/**
* https://www.html5tricks.com/demo/js-passwd-generator/index.html
*
*/
export function useRandomPassword() {
const randomFunc: { [key: string]: () => string } = {
defaultNumber: getRandomNumber,
lower: getRandomLower,
number: getRandomNumber,
symbol: getRandomSymbol,
upper: getRandomUpper,
};
function getRandomLower() {
return String.fromCodePoint(Math.floor(Math.random() * 26) + 97);
}
function getRandomUpper() {
return String.fromCodePoint(Math.floor(Math.random() * 26) + 65);
}
function getRandomNumber() {
return String.fromCodePoint(Math.floor(Math.random() * 10) + 48);
}
function getRandomSymbol() {
const symbols = '~!@#$%^&*()_+{}":?><;.,';
return symbols[Math.floor(Math.random() * symbols.length)] ?? "";
}
function generatePassword() {
const { getNumber, isTrue } = useAbpSettings();
// 根据配置项生成随机密码
// 密码长度
const length = getNumber("Abp.Identity.Password.RequiredLength");
// 需要小写字母
const lower = isTrue("Abp.Identity.Password.RequireLowercase");
// 需要大写字母
const upper = isTrue("Abp.Identity.Password.RequireUppercase");
// 需要数字
const number = isTrue("Abp.Identity.Password.RequireDigit");
// 需要符号
const symbol = isTrue("Abp.Identity.Password.RequireNonAlphanumeric");
// 默认生成数字
const defaultNumber = !lower && !upper && !number && !symbol;
let generatedPassword = "";
const typesArr = [{ lower }, { upper }, { number }, { symbol }, { defaultNumber }].filter(
(item) => Object.values(item)[0],
);
for (let i = 0; i < length; i++) {
typesArr.forEach((type) => {
const funcName = Object.keys(type)[0];
if (funcName && randomFunc[funcName]) {
generatedPassword += randomFunc[funcName]();
}
});
}
return generatedPassword.slice(0, length);
}
return { generatePassword };
}

63
apps/react-admin/src/hooks/abp/use-Job-enums-map.ts

@ -0,0 +1,63 @@
import { useTranslation } from "react-i18next";
import { JobPriority, JobSource, JobStatus, JobType } from "#/tasks/job-infos";
export function useJobEnumsMap() {
const { t: $t } = useTranslation();
const jobStatusMap: Record<number, string> = {
[JobStatus.Completed]: $t("TaskManagement.DisplayName:Completed"),
[JobStatus.FailedRetry]: $t("TaskManagement.DisplayName:FailedRetry"),
[JobStatus.None]: $t("TaskManagement.DisplayName:None"),
[JobStatus.Paused]: $t("TaskManagement.DisplayName:Paused"),
[JobStatus.Queuing]: $t("TaskManagement.DisplayName:Queuing"),
[JobStatus.Running]: $t("TaskManagement.DisplayName:Running"),
[JobStatus.Stopped]: $t("TaskManagement.DisplayName:Stopped"),
};
const jobStatusColor: Record<number, string> = {
[JobStatus.Completed]: "#339933",
[JobStatus.FailedRetry]: "#FF6600",
[JobStatus.None]: "",
[JobStatus.Paused]: "#CC6633",
[JobStatus.Queuing]: "#008B8B",
[JobStatus.Running]: "#3399CC",
[JobStatus.Stopped]: "#F00000",
};
const jobTypeMap: Record<number, string> = {
[JobType.Once]: $t("TaskManagement.DisplayName:Once"),
[JobType.Period]: $t("TaskManagement.DisplayName:Period"),
[JobType.Persistent]: $t("TaskManagement.DisplayName:Persistent"),
};
const jobPriorityMap: Record<number, string> = {
[JobPriority.AboveNormal]: $t("TaskManagement.DisplayName:AboveNormal"),
[JobPriority.BelowNormal]: $t("TaskManagement.DisplayName:BelowNormal"),
[JobPriority.High]: $t("TaskManagement.DisplayName:High"),
[JobPriority.Low]: $t("TaskManagement.DisplayName:Low"),
[JobPriority.Normal]: $t("TaskManagement.DisplayName:Normal"),
};
const jobPriorityColor: Record<number, string> = {
[JobPriority.AboveNormal]: "orange",
[JobPriority.BelowNormal]: "cyan",
[JobPriority.High]: "red",
[JobPriority.Low]: "purple",
[JobPriority.Normal]: "blue",
};
const jobSourceMap: Record<number, string> = {
[JobSource.None]: $t("TaskManagement.DisplayName:None"),
[JobSource.System]: $t("TaskManagement.DisplayName:System"),
[JobSource.User]: $t("TaskManagement.DisplayName:User"),
};
return {
jobPriorityColor,
jobPriorityMap,
jobSourceMap,
jobStatusColor,
jobStatusMap,
jobTypeMap,
};
}

1
apps/react-admin/src/hooks/abp/use-localization.ts

@ -1,5 +1,4 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import type { Dictionary } from "#/abp-core/global";
import { format } from "@/utils/string"; import { format } from "@/utils/string";
import useLocaleStore, { useLocale } from "@/store/localeI18nStore"; import useLocaleStore, { useLocale } from "@/store/localeI18nStore";
import { getResources } from "@/utils/abp/localzations/get-resources"; import { getResources } from "@/utils/abp/localzations/get-resources";

346
apps/react-admin/src/hooks/abp/use-validation.ts

@ -0,0 +1,346 @@
import type { RuleCreator } from "#/abp-core";
import type {
Field,
FieldBeetWeen,
FieldContains,
FieldDefineValidator,
FieldLength,
FieldMatch,
FieldRange,
FieldRegular,
FieldValidator,
Rule,
RuleType,
} from "#/abp-core";
import { ValidationEnum } from "@/constants/abp-core";
import { useLocalizer } from "./use-localization";
import { isEmail, isPhone } from "@/utils/abp/regex";
export function useValidation(): RuleCreator {
const { L } = useLocalizer(["AbpValidation"]);
function _getFieldName(field: Field) {
return __getFieldName(field.name ?? "", field.resourceName, field.prefix, field.connector);
}
function __getFieldName(fieldName: string, resourceName?: string, prefix?: string, connector?: string) {
if (fieldName && resourceName) {
const finalFieldName = prefix ? `${prefix}${connector ?? ":"}${fieldName}` : fieldName;
const { L: l } = useLocalizer(resourceName);
return l(finalFieldName);
}
return fieldName;
}
function _createRule(options: {
len?: number;
max?: number;
message?: string;
min?: number;
required?: boolean;
trigger?: "blur" | "change" | ["change", "blur"];
type?: "array" | RuleType;
validator?: (rule: any, value: any, callback: any, source?: any, options?: any) => Promise<void> | void;
}): Rule[] {
return [
{
len: options.len,
max: options.max,
message: options.message,
min: options.min,
required: options.required,
trigger: options.trigger,
type: options.type,
validator: options.validator,
},
];
}
function _createValidator(field: Field, useNameEnum: string, notNameEnum: string, required?: boolean): Rule {
const message = field.name ? L(useNameEnum, [_getFieldName(field)]) : L(notNameEnum);
return {
message,
required,
trigger: field.trigger,
type: field.type,
};
}
function _createLengthValidator(
field: FieldLength,
checkMaximum: boolean,
useNameEnum: string,
notNameEnum: string,
required?: boolean,
): Rule {
const message = field.name ? L(useNameEnum, [_getFieldName(field), field.length]) : L(notNameEnum, [field.length]);
function checkLength(value: any[] | string) {
return checkMaximum ? field.length > value.length : value.length > field.length;
}
return {
message,
required,
trigger: field.trigger,
type: field.type,
validator: (_: any, value: string) => {
if (!checkLength(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
};
}
function _createLengthRangValidator(
field: FieldRange,
useNameEnum: string,
notNameEnum: string,
required?: boolean,
): Rule {
const message = field.name
? L(useNameEnum, [_getFieldName(field), field.maximum, field.minimum])
: L(notNameEnum, [field.minimum, field.maximum]);
return {
message,
required,
trigger: field.trigger,
type: field.type,
validator: (_: any, value: string) => {
if (value.length < field.minimum || value.length > field.maximum) {
return Promise.reject(message);
}
return Promise.resolve();
},
};
}
function _createBeetWeenValidator(field: FieldBeetWeen): Rule {
const message = field.name
? L(ValidationEnum.FieldMustBeetWeen, [_getFieldName(field), field.start, field.end])
: L(ValidationEnum.ThisFieldMustBeBetween, [field.start, field.end]);
return {
message,
trigger: field.trigger,
validator: (_: any, value: number) => {
// beetween不在进行必输检查, 改为数字有效性检查
if (Number.isNaN(value)) {
return Promise.reject(message);
}
return value < field.start || value > field.end ? Promise.reject(message) : Promise.resolve();
},
};
}
function _createRegularExpressionValidator(field: FieldRegular, required?: boolean): Rule {
const message = field.name
? L(ValidationEnum.FieldMustMatchRegularExpression, [_getFieldName(field), field.expression])
: L(ValidationEnum.ThisFieldMustMatchTheRegularExpression, [field.expression]);
return {
message,
pattern: new RegExp(field.expression),
required,
trigger: field.trigger,
type: field.type,
};
}
function _createEmailValidator(field: Field, required?: boolean): Rule {
const message = field.name
? L(ValidationEnum.FieldDoNotValidEmailAddress, [_getFieldName(field)])
: L(ValidationEnum.ThisFieldIsNotAValidEmailAddress);
return {
message,
required,
trigger: field.trigger,
type: field.type,
validator: (_: any, value: string) => {
if (!isEmail(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
};
}
function _createPhoneValidator(field: Field, required?: boolean): Rule {
const message = field.name
? L(ValidationEnum.FieldDoNotValidPhoneNumber, [_getFieldName(field)])
: L(ValidationEnum.ThisFieldIsNotAValidPhoneNumber);
return {
message,
required,
trigger: field.trigger,
type: field.type,
validator: (_: any, value: string) => {
if (!isPhone(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
};
}
const ruleCreator: RuleCreator = {
defineValidator(field: FieldDefineValidator) {
return _createRule(field);
},
doNotMatch(field: FieldMatch) {
const message = L(ValidationEnum.DoNotMatch, [
__getFieldName(field.name, field.resourceName, field.prefix),
__getFieldName(field.matchField, field.resourceName, field.prefix),
]);
return _createRule({
message,
required: field.required,
trigger: field.trigger,
type: field.type,
validator: (_, value: string) => {
if (value !== field.matchValue) {
return Promise.reject(message);
}
return Promise.resolve();
},
});
},
fieldDoNotValidCreditCardNumber(field: Field) {
if (field.name) {
return _createRule({
message: L(ValidationEnum.FieldDoNotValidCreditCardNumber, [_getFieldName(field)]),
trigger: field.trigger,
type: field.type,
});
}
return _createRule({
message: L(ValidationEnum.ThisFieldIsNotAValidCreditCardNumber),
trigger: field.trigger,
type: field.type,
});
},
fieldDoNotValidEmailAddress(field: Field) {
return [_createEmailValidator(field)];
},
fieldDoNotValidFullyQualifiedUrl(field: Field) {
if (field.name) {
return _createRule({
message: L(ValidationEnum.FieldDoNotValidFullyQualifiedUrl, [_getFieldName(field)]),
trigger: field.trigger,
type: field.type,
});
}
return _createRule({
message: L(ValidationEnum.ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl),
trigger: field.trigger,
type: field.type,
});
},
fieldDoNotValidPhoneNumber(field: Field) {
return [_createPhoneValidator(field)];
},
fieldInvalid(field: FieldValidator) {
const message = field.name
? L(ValidationEnum.FieldInvalid, [_getFieldName(field)])
: L(ValidationEnum.ThisFieldIsInvalid);
return _createRule({
message,
required: field.required,
trigger: field.trigger,
type: field.type,
validator: (_, value: any) => {
if (!field.validator(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
});
},
fieldIsNotValid(field: FieldValidator) {
const message = field.name
? L(ValidationEnum.FieldIsNotValid, [_getFieldName(field)])
: L(ValidationEnum.ThisFieldIsNotValid);
return _createRule({
message,
required: field.required,
trigger: field.trigger,
type: field.type,
validator: (_, value: any) => {
if (field.validator(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
});
},
fieldMustBeetWeen(field: FieldBeetWeen) {
return [_createBeetWeenValidator(field)];
},
fieldMustBeStringOrArrayWithMaximumLength(field: FieldLength) {
return [
_createLengthValidator(
field,
true,
ValidationEnum.FieldMustBeStringOrArrayWithMaximumLength,
ValidationEnum.ThisFieldMustBeAStringOrArrayTypeWithAMaximumLength,
),
];
},
fieldMustBeStringOrArrayWithMinimumLength(field: FieldLength) {
return [
_createLengthValidator(
field,
false,
ValidationEnum.FieldMustBeStringOrArrayWithMinimumLength,
ValidationEnum.ThisFieldMustBeAStringOrArrayTypeWithAMinimumLength,
),
];
},
fieldMustBeStringWithMaximumLength(field: FieldLength) {
return [
_createLengthValidator(
field,
true,
ValidationEnum.FieldMustBeStringWithMaximumLength,
ValidationEnum.ThisFieldMustBeAStringWithAMaximumLength,
),
];
},
fieldMustBeStringWithMinimumLengthAndMaximumLength(field: FieldRange) {
return [
_createLengthRangValidator(
field,
ValidationEnum.FieldMustBeStringWithMinimumLengthAndMaximumLength,
ValidationEnum.ThisFieldMustBeAStringWithAMinimumLengthAndAMaximumLength,
),
];
},
fieldMustMatchRegularExpression(field: FieldRegular) {
return [_createRegularExpressionValidator(field)];
},
fieldOnlyAcceptsFilesExtensions(field: FieldContains) {
const message = field.name
? L(ValidationEnum.FieldOnlyAcceptsFilesExtensions, [_getFieldName(field), field.value])
: L(ValidationEnum.ThisFieldMustMatchTheRegularExpression, [field.value]);
return _createRule({
message,
trigger: field.trigger,
type: field.type,
validator: (_, value: string) => {
if (!field.value.includes(value)) {
return Promise.reject(message);
}
return Promise.resolve();
},
});
},
fieldRequired(field: Field) {
return [_createValidator(field, ValidationEnum.FieldRequired, ValidationEnum.ThisFieldIsRequired, true)];
},
mapEnumValidMessage(enumName: string, args?: any[] | Record<string, string> | undefined) {
return L(enumName, args);
},
};
return ruleCreator;
}

79
apps/react-admin/src/locales/lang/en_US/abp.json

@ -9,6 +9,18 @@
"phoneNumber": "Phone Number", "phoneNumber": "Phone Number",
"getCode": "Get Code", "getCode": "Get Code",
"code": "Code" "code": "Code"
},
"qrcodeLogin": {
"scaned": "Please confirm login on your phone."
},
"errors": {
"accountLockedByInvalidLoginAttempts": "The user account has been locked out due to invalid login attempts. Please wait a while and try again.",
"accountInactive": "You are not allowed to login! Your account is inactive.",
"invalidUserNameOrPassword": "Invalid username or password!",
"tokenHasExpired": "The token is no longer valid!",
"requiresTwoFactor": "Identity verification is required. Please select a verification method!",
"shouldChangePassword": "Your password has expired. Please change it and login!",
"accessDenied": "You have refused the necessary authorization for the application. Please log in again!"
} }
}, },
"manage": { "manage": {
@ -20,7 +32,6 @@
"claimTypes": "Claim Types", "claimTypes": "Claim Types",
"securityLogs": "Security Logs", "securityLogs": "Security Logs",
"organizationUnits": "Organization Units", "organizationUnits": "Organization Units",
"auditLogs": "Audit Logs",
"sessions": "Sessions" "sessions": "Sessions"
}, },
"permissions": { "permissions": {
@ -28,6 +39,11 @@
"groups": "Groups", "groups": "Groups",
"definitions": "Definitions" "definitions": "Definitions"
}, },
"features": {
"title": "Features",
"groups": "Groups",
"definitions": "Definitions"
},
"settings": { "settings": {
"title": "Settings", "title": "Settings",
"definitions": "Definitions", "definitions": "Definitions",
@ -35,8 +51,24 @@
}, },
"notifications": { "notifications": {
"title": "Notifications", "title": "Notifications",
"myNotifilers": "My Notifilers" "myNotifilers": "My Notifilers",
} "groups": "Groups",
"definitions": "Definitions"
},
"localization": {
"title": "Localization",
"resources": "Resources",
"languages": "Languages",
"texts": "Texts"
},
"dataProtection": {
"title": "Data Protection",
"entityTypeInfos": "Entity Type Infos"
},
"auditLogs": "Audit Logs",
"loggings": "System Logs",
"openApi": "Api Document",
"cache": "Cache Management"
}, },
"openiddict": { "openiddict": {
"title": "OpenIddict", "title": "OpenIddict",
@ -66,7 +98,8 @@
"noticeSettings": "Notice Settings", "noticeSettings": "Notice Settings",
"authenticatorSettings": "Authenticator Settings", "authenticatorSettings": "Authenticator Settings",
"changeAvatar": "Change Avatar", "changeAvatar": "Change Avatar",
"sessionSettings": "Session Settings" "sessionSettings": "Session Settings",
"personalDataSettings": "Personal Data Settings"
}, },
"profile": "My Profile" "profile": "My Profile"
}, },
@ -76,7 +109,45 @@
"title": "Message Manage", "title": "Message Manage",
"email": "Email Messages", "email": "Email Messages",
"sms": "Sms Messages" "sms": "Sms Messages"
},
"dataDictionaries": "Data Dictionaries",
"layouts": "Layouts",
"menus": "Menus"
},
"saas": {
"title": "Saas",
"editions": "Editions",
"tenants": "Tenants"
},
"demo": {
"title": "Demo",
"books": "Books"
},
"tasks": {
"title": "Task Management",
"jobInfo": {
"title": "Job Manage"
} }
},
"webhooks": {
"title": "Webhooks",
"groups": "Groups",
"definitions": "Definitions",
"subscriptions": "Subscriptions",
"sendAttempts": "Send Attempts"
},
"textTemplating": {
"title": "Text Templating",
"definitions": "Definitions"
},
"oss": {
"title": "Object storage",
"containers": "Containers",
"objects": "Files"
},
"wechat": {
"title": "WeChat",
"settings": "Settings"
} }
} }
} }

58
apps/react-admin/src/locales/lang/en_US/authentication.json

@ -0,0 +1,58 @@
{
"authentication": {
"welcomeBack": "Welcome Back",
"pageTitle": "Plug-and-play Admin system",
"pageDesc": "Efficient, versatile frontend template",
"loginSuccess": "Login Successful",
"loginSuccessDesc": "Welcome Back",
"loginSubtitle": "Enter your account details to manage your projects",
"selectAccount": "Quick Select Account",
"username": "Username",
"password": "Password",
"usernameTip": "Please enter username",
"passwordErrorTip": "Password is incorrect",
"passwordTip": "Please enter password",
"verifyRequiredTip": "Please complete the verification first",
"rememberMe": "Remember Me",
"createAnAccount": "Create an Account",
"createAccount": "Create Account",
"alreadyHaveAccount": "Already have an account?",
"accountTip": "Don't have an account?",
"signUp": "Sign Up",
"signUpSubtitle": "Make managing your applications simple and fun",
"confirmPassword": "Confirm Password",
"confirmPasswordTip": "The passwords do not match",
"agree": "I agree to",
"privacyPolicy": "Privacy-policy",
"terms": "Terms",
"agreeTip": "Please agree to the Privacy Policy and Terms",
"goToLogin": "Login instead",
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
"forgetPassword": "Forget Password?",
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
"emailTip": "Please enter email",
"emailValidErrorTip": "The email format you entered is incorrect",
"sendResetLink": "Send Reset Link",
"email": "Email",
"qrcodeSubtitle": "Scan the QR code with your phone to login",
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",
"qrcodeLogin": "QR Code Login",
"codeSubtitle": "Enter your phone number to start managing your project",
"code": "Security code",
"codeTip": "Security code required {0} characters",
"mobile": "Mobile",
"mobileLogin": "Mobile Login",
"mobileTip": "Please enter mobile number",
"mobileErrortip": "The phone number format is incorrect",
"sendCode": "Get Security code",
"sendText": "Resend in {0}s",
"thirdPartyLogin": "Or continue with",
"loginAgainTitle": "Please Log In Again",
"loginAgainSubTitle": "Your login session has expired. Please log in again to continue.",
"layout": {
"center": "Align Center",
"alignLeft": "Align Left",
"alignRight": "Align Right"
}
}
}

3
apps/react-admin/src/locales/lang/en_US/component.json

@ -117,6 +117,9 @@
"requiresAllDesc": "If checked, you need to have all the selected permissions.", "requiresAllDesc": "If checked, you need to have all the selected permissions.",
"permissions": "Required Permissions" "permissions": "Required Permissions"
} }
},
"table": {
"selectedItemWellBeDeleted": "Multiple items selected will be deleted!"
} }
} }
} }

4
apps/react-admin/src/locales/lang/en_US/index.ts

@ -3,6 +3,8 @@ import sys from "./sys.json";
import ui from "./ui.json"; import ui from "./ui.json";
import abp from "./abp.json"; import abp from "./abp.json";
import component from "./component.json"; import component from "./component.json";
import workbench from "./workbench.json";
import authentication from "./authentication.json";
export default { export default {
...common, ...common,
@ -10,4 +12,6 @@ export default {
...ui, ...ui,
...abp, ...abp,
...component, ...component,
...workbench,
...authentication,
}; };

39
apps/react-admin/src/locales/lang/en_US/workbench.json

@ -0,0 +1,39 @@
{
"workbench": {
"header": {
"welcome": {
"atoon": "Good afternoon, {0}, pay attention to rest oh~",
"afternoon": "Good afternoon, {0}, relax in time, can improve work efficiency~",
"evening": "Good evening, {0}. Still at work? The off work~",
"morning": "Good morning, {0}. Begin your day~"
},
"notifier": {
"title": "Notifier",
"count": "({0})"
}
},
"content": {
"favoriteMenu": {
"title": "Favorite Menus",
"home": "Home",
"dashboard": "Dashboard",
"profile": "Personal Profile",
"settings": "Personal Settings",
"notifiers": "Notifiers",
"manage": "Manage menu",
"create": "New menu",
"delete": "Delete Menu",
"select": "Select Menu",
"color": "Select Color",
"alias": "Alias Name",
"icon": "Icon"
},
"trends": {
"title": "Latest News"
},
"todo": {
"title": "Todo List"
}
}
}
}

79
apps/react-admin/src/locales/lang/zh_CN/abp.json

@ -9,6 +9,18 @@
"phoneNumber": "手机号码", "phoneNumber": "手机号码",
"getCode": "获取验证码", "getCode": "获取验证码",
"code": "验证码" "code": "验证码"
},
"qrcodeLogin": {
"scaned": "请在手机上确认登录."
},
"errors": {
"accountLockedByInvalidLoginAttempts": "由于尝试登录无效,用户帐户被锁定.请稍候再试!",
"accountInactive": "您不能登录,您的帐户是无效的!",
"invalidUserNameOrPassword": "用户名或密码错误!",
"tokenHasExpired": "您的请求会话已过期,请重新登录!",
"requiresTwoFactor": "需要验证身份,请选择一种验证方式!",
"shouldChangePassword": "您的密码已过期,请修改密码后登录!",
"accessDenied": "您拒绝了应用程序必须的授权, 请重新登录!"
} }
}, },
"manage": { "manage": {
@ -20,7 +32,6 @@
"claimTypes": "身份标识", "claimTypes": "身份标识",
"securityLogs": "安全日志", "securityLogs": "安全日志",
"organizationUnits": "组织机构", "organizationUnits": "组织机构",
"auditLogs": "审计日志",
"sessions": "会话管理" "sessions": "会话管理"
}, },
"permissions": { "permissions": {
@ -28,6 +39,11 @@
"groups": "权限分组", "groups": "权限分组",
"definitions": "权限定义" "definitions": "权限定义"
}, },
"features": {
"title": "功能管理",
"groups": "功能分组",
"definitions": "功能定义"
},
"settings": { "settings": {
"title": "设置管理", "title": "设置管理",
"definitions": "设置定义", "definitions": "设置定义",
@ -35,8 +51,24 @@
}, },
"notifications": { "notifications": {
"title": "通知管理", "title": "通知管理",
"myNotifilers": "我的通知" "myNotifilers": "我的通知",
} "groups": "通知分组",
"definitions": "通知定义"
},
"localization": {
"title": "本地化管理",
"resources": "资源管理",
"languages": "语言管理",
"texts": "文档管理"
},
"dataProtection": {
"title": "数据权限",
"entityTypeInfos": "实体列表"
},
"auditLogs": "审计日志",
"loggings": "系统日志",
"openApi": "接口文档",
"cache": "缓存管理"
}, },
"openiddict": { "openiddict": {
"title": "OpenIddict", "title": "OpenIddict",
@ -66,7 +98,8 @@
"noticeSettings": "新消息通知", "noticeSettings": "新消息通知",
"authenticatorSettings": "身份验证程序", "authenticatorSettings": "身份验证程序",
"changeAvatar": "更改头像", "changeAvatar": "更改头像",
"sessionSettings": "会话管理" "sessionSettings": "会话管理",
"personalDataSettings": "个人信息管理"
}, },
"profile": "个人中心" "profile": "个人中心"
}, },
@ -76,7 +109,45 @@
"title": "消息管理", "title": "消息管理",
"email": "邮件消息", "email": "邮件消息",
"sms": "短信消息" "sms": "短信消息"
},
"dataDictionaries": "数据字典",
"layouts": "布局管理",
"menus": "菜单管理"
},
"saas": {
"title": "Saas",
"editions": "版本管理",
"tenants": "租户管理"
},
"demo": {
"title": "演示",
"books": "书籍列表"
},
"tasks": {
"title": "后台作业",
"jobInfo": {
"title": "作业管理"
} }
},
"webhooks": {
"title": "Webhook管理",
"groups": "Webhook分组",
"definitions": "Webhook定义",
"subscriptions": "管理订阅",
"sendAttempts": "发送记录"
},
"textTemplating": {
"title": "文本模板",
"definitions": "模板定义"
},
"oss": {
"title": "对象存储",
"containers": "容器管理",
"objects": "文件管理"
},
"wechat": {
"title": "微信集成",
"settings": "微信设置"
} }
} }
} }

58
apps/react-admin/src/locales/lang/zh_CN/authentication.json

@ -0,0 +1,58 @@
{
"authentication": {
"welcomeBack": "欢迎回来",
"pageTitle": "开箱即用的大型中后台管理系统",
"pageDesc": "工程化、高性能、跨组件库的前端模版",
"loginSuccess": "登录成功",
"loginSuccessDesc": "欢迎回来",
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目",
"selectAccount": "快速选择账号",
"username": "账号",
"password": "密码",
"usernameTip": "请输入用户名",
"passwordTip": "请输入密码",
"verifyRequiredTip": "请先完成验证",
"passwordErrorTip": "密码错误",
"rememberMe": "记住账号",
"createAnAccount": "创建一个账号",
"createAccount": "创建账号",
"alreadyHaveAccount": "已经有账号了?",
"accountTip": "还没有账号?",
"signUp": "注册",
"signUpSubtitle": "让您的应用程序管理变得简单而有趣",
"confirmPassword": "确认密码",
"confirmPasswordTip": "两次输入的密码不一致",
"agree": "我同意",
"privacyPolicy": "隐私政策",
"terms": "条款",
"agreeTip": "请同意隐私政策和条款",
"goToLogin": "去登录",
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
"forgetPassword": "忘记密码?",
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接",
"emailTip": "请输入邮箱",
"emailValidErrorTip": "你输入的邮箱格式不正确",
"sendResetLink": "发送重置链接",
"email": "邮箱",
"qrcodeSubtitle": "请用手机扫描二维码登录",
"qrcodePrompt": "扫码后点击 '确认',即可完成登录",
"qrcodeLogin": "扫码登录",
"codeSubtitle": "请输入您的手机号码以开始管理您的项目",
"code": "验证码",
"codeTip": "请输入{0}位验证码",
"mobile": "手机号码",
"mobileTip": "请输入手机号",
"mobileErrortip": "手机号码格式错误",
"mobileLogin": "手机号登录",
"sendCode": "获取验证码",
"sendText": "{0}秒后重新获取",
"thirdPartyLogin": "其他登录方式",
"loginAgainTitle": "重新登录",
"loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。",
"layout": {
"center": "居中",
"alignLeft": "居左",
"alignRight": "居右"
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save