@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/Application.d.ts +7 -0
- package/es/BaseApplication.d.ts +13 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
- package/es/collection-manager/field-configure.d.ts +80 -0
- package/es/collection-manager/field-validation.d.ts +43 -0
- package/es/collection-manager/filter-operators/index.d.ts +46 -0
- package/es/collection-manager/filter-operators/operators.d.ts +30 -0
- package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
- package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
- package/es/collection-manager/interfaces/collection.d.ts +12 -51
- package/es/collection-manager/interfaces/color.d.ts +1 -16
- package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
- package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
- package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
- package/es/collection-manager/interfaces/datetime.d.ts +1 -44
- package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
- package/es/collection-manager/interfaces/email.d.ts +1 -29
- package/es/collection-manager/interfaces/id.d.ts +1 -16
- package/es/collection-manager/interfaces/index.d.ts +2 -3
- package/es/collection-manager/interfaces/input.d.ts +1 -102
- package/es/collection-manager/interfaces/integer.d.ts +1 -95
- package/es/collection-manager/interfaces/json.d.ts +16 -7
- package/es/collection-manager/interfaces/m2m.d.ts +11 -19
- package/es/collection-manager/interfaces/m2o.d.ts +11 -19
- package/es/collection-manager/interfaces/markdown.d.ts +1 -63
- package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
- package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
- package/es/collection-manager/interfaces/number.d.ts +1 -87
- package/es/collection-manager/interfaces/o2m.d.ts +12 -24
- package/es/collection-manager/interfaces/obo.d.ts +207 -0
- package/es/collection-manager/interfaces/oho.d.ts +207 -0
- package/es/collection-manager/interfaces/password.d.ts +1 -56
- package/es/collection-manager/interfaces/percent.d.ts +1 -84
- package/es/collection-manager/interfaces/phone.d.ts +1 -25
- package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
- package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
- package/es/collection-manager/interfaces/richText.d.ts +1 -63
- package/es/collection-manager/interfaces/select.d.ts +12 -44
- package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
- package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
- package/es/collection-manager/interfaces/textarea.d.ts +1 -51
- package/es/collection-manager/interfaces/time.d.ts +1 -16
- package/es/collection-manager/interfaces/types.d.ts +3 -12
- package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
- package/es/collection-manager/interfaces/url.d.ts +1 -20
- package/es/collection-manager/interfaces/uuid.d.ts +1 -34
- package/es/collection-manager/template-fields.d.ts +53 -0
- package/es/components/KeepAlive.d.ts +22 -0
- package/es/components/RouterBridge.d.ts +9 -0
- package/es/components/form/DialogFormLayout.d.ts +5 -29
- package/es/components/form/VariableInput.d.ts +53 -2
- package/es/components/form/filter/CollectionFilter.d.ts +49 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
- package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
- package/es/components/form/filter/FilterValueInput.d.ts +29 -0
- package/es/components/form/filter/index.d.ts +11 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
- package/es/data-source/index.d.ts +9 -0
- package/es/flow/FlowPage.d.ts +2 -1
- package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
- package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
- package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
- package/es/flow/components/FlowRoute.d.ts +10 -1
- package/es/flow/components/filter/index.d.ts +2 -0
- package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
- package/es/flow/index.d.ts +4 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
- package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
- package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +6 -0
- package/es/index.mjs +552 -459
- package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
- package/es/layout-manager/LayoutManager.d.ts +22 -0
- package/es/layout-manager/LayoutRoute.d.ts +14 -0
- package/es/layout-manager/index.d.ts +13 -0
- package/es/layout-manager/types.d.ts +20 -0
- package/es/layout-manager/utils.d.ts +14 -0
- package/es/nocobase-buildin-plugin/index.d.ts +3 -10
- package/es/settings-center/index.d.ts +1 -1
- package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
- package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
- package/es/settings-center/plugin-manager/types.d.ts +34 -0
- package/lib/index.js +552 -459
- package/package.json +8 -7
- package/src/Application.tsx +51 -12
- package/src/BaseApplication.tsx +32 -0
- package/src/PluginSettingsManager.ts +1 -1
- package/src/RouterManager.tsx +17 -1
- package/src/__tests__/PluginSettingsManager.test.ts +41 -2
- package/src/__tests__/app.test.tsx +17 -1
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
- package/src/__tests__/plugin-manager.test.tsx +177 -0
- package/src/__tests__/settings-center.test.tsx +24 -2
- package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
- package/src/collection-manager/field-configure.ts +548 -0
- package/src/collection-manager/field-validation.ts +195 -0
- package/src/collection-manager/filter-operators/index.ts +176 -0
- package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
- package/src/collection-manager/interfaces/checkbox.ts +2 -9
- package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
- package/src/collection-manager/interfaces/collection.ts +2 -15
- package/src/collection-manager/interfaces/color.ts +2 -2
- package/src/collection-manager/interfaces/createdAt.ts +2 -2
- package/src/collection-manager/interfaces/createdBy.ts +1 -12
- package/src/collection-manager/interfaces/dateOnly.ts +8 -2
- package/src/collection-manager/interfaces/datetime.ts +2 -2
- package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
- package/src/collection-manager/interfaces/email.ts +2 -9
- package/src/collection-manager/interfaces/id.ts +1 -2
- package/src/collection-manager/interfaces/index.ts +2 -3
- package/src/collection-manager/interfaces/input.ts +2 -133
- package/src/collection-manager/interfaces/integer.ts +2 -71
- package/src/collection-manager/interfaces/json.tsx +17 -11
- package/src/collection-manager/interfaces/m2m.tsx +0 -21
- package/src/collection-manager/interfaces/m2o.tsx +0 -22
- package/src/collection-manager/interfaces/markdown.ts +2 -51
- package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
- package/src/collection-manager/interfaces/nanoid.ts +2 -2
- package/src/collection-manager/interfaces/number.ts +2 -85
- package/src/collection-manager/interfaces/o2m.tsx +1 -22
- package/src/collection-manager/interfaces/obo.tsx +145 -0
- package/src/collection-manager/interfaces/oho.tsx +145 -0
- package/src/collection-manager/interfaces/password.ts +2 -44
- package/src/collection-manager/interfaces/percent.ts +2 -74
- package/src/collection-manager/interfaces/phone.ts +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +0 -133
- package/src/collection-manager/interfaces/radioGroup.ts +2 -2
- package/src/collection-manager/interfaces/richText.ts +2 -51
- package/src/collection-manager/interfaces/select.ts +2 -14
- package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
- package/src/collection-manager/interfaces/tableoid.ts +1 -2
- package/src/collection-manager/interfaces/textarea.ts +2 -51
- package/src/collection-manager/interfaces/time.ts +2 -2
- package/src/collection-manager/interfaces/types.ts +4 -12
- package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
- package/src/collection-manager/interfaces/updatedAt.ts +2 -2
- package/src/collection-manager/interfaces/updatedBy.ts +1 -12
- package/src/collection-manager/interfaces/url.ts +2 -4
- package/src/collection-manager/interfaces/uuid.ts +2 -2
- package/src/collection-manager/template-fields.ts +109 -0
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/README.md +90 -6
- package/src/components/README.zh-CN.md +90 -7
- package/src/components/RouterBridge.tsx +28 -4
- package/src/components/__tests__/KeepAlive.test.tsx +63 -0
- package/src/components/__tests__/RouterBridge.test.tsx +27 -0
- package/src/components/form/DialogFormLayout.tsx +5 -29
- package/src/components/form/VariableInput.tsx +101 -28
- package/src/components/form/__tests__/VariableInput.test.ts +85 -0
- package/src/components/form/filter/CollectionFilter.tsx +111 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
- package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
- package/src/components/form/filter/FilterValueInput.tsx +198 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
- package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
- package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
- package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
- package/src/components/form/filter/index.ts +13 -0
- package/src/components/form/filter/useFilterActionProps.ts +203 -0
- package/src/components/form/index.tsx +1 -0
- package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
- package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
- package/src/data-source/index.ts +10 -0
- package/src/flow/FlowPage.tsx +35 -7
- package/src/flow/__tests__/FlowPage.test.tsx +79 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
- package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
- package/src/flow/actions/aclCheck.tsx +4 -0
- package/src/flow/actions/aclCheckRefresh.tsx +4 -0
- package/src/flow/actions/dateTimeFormat.tsx +12 -8
- package/src/flow/actions/linkageRules.tsx +122 -0
- package/src/flow/actions/openView.tsx +28 -4
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
- package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
- package/src/flow/admin-shell/admin-layout/index.ts +3 -0
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
- package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
- package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
- package/src/flow/components/AdminLayout.tsx +4 -154
- package/src/flow/components/FlowRoute.tsx +105 -15
- package/src/flow/components/filter/index.ts +3 -0
- package/src/flow/components/filter/useFilterOptions.ts +102 -0
- package/src/flow/index.ts +4 -0
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
- package/src/flow/models/base/ActionModel.tsx +8 -1
- package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
- package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
- package/src/flow/models/base/RouteModel.tsx +1 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
- package/src/flow/models/blocks/assign-form/index.ts +1 -0
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
- package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
- package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
- package/src/flow/models/blocks/form/submitHandler.ts +8 -1
- package/src/flow/models/blocks/form/submitValues.ts +4 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
- package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
- package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
- package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
- package/src/index.ts +6 -0
- package/src/layout-manager/LayoutContentRoute.tsx +90 -0
- package/src/layout-manager/LayoutManager.tsx +185 -0
- package/src/layout-manager/LayoutRoute.tsx +138 -0
- package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
- package/src/layout-manager/index.ts +14 -0
- package/src/layout-manager/types.ts +22 -0
- package/src/layout-manager/utils.ts +37 -0
- package/src/nocobase-buildin-plugin/index.tsx +69 -67
- package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
- package/src/settings-center/index.ts +1 -1
- package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
- package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
- package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
- package/src/settings-center/plugin-manager/index.tsx +254 -0
- package/src/settings-center/plugin-manager/types.ts +35 -0
- package/src/settings-center/utils.tsx +8 -1
- package/src/theme/__tests__/globalStyles.test.ts +24 -0
- package/src/theme/globalStyles.ts +10 -0
- package/src/utils/globalDeps.ts +2 -0
- package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
- package/es/collection-manager/interfaces/o2o.d.ts +0 -621
- package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
- package/es/collection-manager/interfaces/subTable.d.ts +0 -172
- package/src/collection-manager/interfaces/linkTo.ts +0 -120
- package/src/collection-manager/interfaces/o2o.tsx +0 -561
- package/src/collection-manager/interfaces/subTable.ts +0 -218
- package/src/settings-center/PluginManagerPage.tsx +0 -162
|
@@ -15,7 +15,7 @@ import type { Application } from '../Application';
|
|
|
15
15
|
import { getCurrentV2RedirectPath, getDefaultV2AdminRedirectPath } from '../authRedirect';
|
|
16
16
|
import { AppNotFound } from '../components';
|
|
17
17
|
import { PluginFlowEngine } from '../flow';
|
|
18
|
-
import { AdminLayoutMenuItemModel, AdminLayoutModel } from '../flow/admin-shell/admin-layout';
|
|
18
|
+
import { ADMIN_LAYOUT_MODEL_UID, AdminLayoutMenuItemModel, AdminLayoutModel } from '../flow/admin-shell/admin-layout';
|
|
19
19
|
import { useApp } from '../hooks/useApp';
|
|
20
20
|
import { Plugin } from '../Plugin';
|
|
21
21
|
import { AdminSettingsLayoutModel } from '../settings-center';
|
|
@@ -56,6 +56,30 @@ function isAdminRuntimeRoute(pathname: string, basename?: string) {
|
|
|
56
56
|
return normalizedPathname === '/admin' || normalizedPathname.startsWith('/admin/');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function hasAuthCheckRoute(app: Application, pathname: string) {
|
|
60
|
+
const matchedRoutes = app.router.matchRoutes(pathname) || [];
|
|
61
|
+
return matchedRoutes.some((match) => match?.route?.authCheck === true);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function shouldCheckRuntimeRoute(app: Application, pathname: string) {
|
|
65
|
+
return isAdminRuntimeRoute(pathname, app.router.getBasename()) || hasAuthCheckRoute(app, pathname);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type CurrentUserAuthCheckRouteState = 'skipped' | 'unchecked' | 'required';
|
|
69
|
+
|
|
70
|
+
function getCurrentUserAuthCheckRouteState(app: Application, pathname: string): CurrentUserAuthCheckRouteState {
|
|
71
|
+
const basename = app.router.getBasename();
|
|
72
|
+
if (isBuiltinAuthRoute(pathname, basename) || app.router.isSkippedAuthCheckRoute(pathname)) {
|
|
73
|
+
return 'skipped';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!shouldCheckRuntimeRoute(app, pathname)) {
|
|
77
|
+
return 'unchecked';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return 'required';
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
export const CurrentUserContext = createContext<CurrentUserState | null>(null);
|
|
60
84
|
CurrentUserContext.displayName = 'CurrentUserContext';
|
|
61
85
|
|
|
@@ -64,15 +88,9 @@ export function useCurrentUserContext() {
|
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
/**
|
|
67
|
-
* 返回当前用户在 v2 应用上下文中可选的角色列表,等价于 v1 `useCurrentRoles
|
|
68
|
-
* 从 FlowEngine 全局上下文 `engine.context.user.roles` 派生(CurrentUserProvider 在
|
|
69
|
-
* `/auth:check` 成功后通过 `defineProperty('user', { value })` 写入),按需追加匿名角色,
|
|
70
|
-
* 并去掉合并角色 `__union__`。v2 中角色 title 可能含有 `{{t('...')}}` 模板,因此用
|
|
71
|
-
* flowEngine.context.t 解析。
|
|
91
|
+
* 返回当前用户在 v2 应用上下文中可选的角色列表,等价于 v1 `useCurrentRoles`:从 FlowEngine 全局上下文 `engine.context.user.roles` 派生(CurrentUserProvider 在 `/auth:check` 成功后通过 `defineProperty('user', { value })` 写入),按需追加匿名角色,并去掉合并角色 `__union__`。v2 中角色 title 可能含有 `{{t('...')}}` 模板,因此用 flowEngine.context.t 解析。
|
|
72
92
|
*
|
|
73
|
-
* 不读 React `CurrentUserContext`:FlowEngine 的 dialog/drawer/popover 内容通过 `ctx.viewer`
|
|
74
|
-
* 渲染到独立的 ElementsHolder,部分场景会脱离原 Provider 树;FlowEngine 全局上下文是同一份
|
|
75
|
-
* 数据但不受 React 树位置影响。
|
|
93
|
+
* 不读 React `CurrentUserContext`:FlowEngine 的 dialog/drawer/popover 内容通过 `ctx.viewer` 渲染到独立的 ElementsHolder,部分场景会脱离原 Provider 树;FlowEngine 全局上下文是同一份数据但不受 React 树位置影响。
|
|
76
94
|
*/
|
|
77
95
|
export function useCurrentRoles(): CurrentRoleOption[] {
|
|
78
96
|
const { allowAnonymous } = useACLRoleContext();
|
|
@@ -93,7 +111,7 @@ export function useCurrentRoles(): CurrentRoleOption[] {
|
|
|
93
111
|
}
|
|
94
112
|
|
|
95
113
|
const DataSourceBootstrapProvider: FC = ({ children }) => {
|
|
96
|
-
const app = useApp();
|
|
114
|
+
const app = useApp<Application>();
|
|
97
115
|
const location = useLocation();
|
|
98
116
|
const [loading, setLoading] = useState(true);
|
|
99
117
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -104,7 +122,7 @@ const DataSourceBootstrapProvider: FC = ({ children }) => {
|
|
|
104
122
|
const basename = app.router.getBasename();
|
|
105
123
|
const isSkippedAuthCheckRoute =
|
|
106
124
|
isBuiltinAuthRoute(location.pathname, basename) || app.router.isSkippedAuthCheckRoute(location.pathname);
|
|
107
|
-
const shouldBootstrap =
|
|
125
|
+
const shouldBootstrap = shouldCheckRuntimeRoute(app, location.pathname);
|
|
108
126
|
|
|
109
127
|
if (isSkippedAuthCheckRoute || !shouldBootstrap) {
|
|
110
128
|
setLoading(false);
|
|
@@ -152,28 +170,25 @@ const DataSourceBootstrapProvider: FC = ({ children }) => {
|
|
|
152
170
|
};
|
|
153
171
|
|
|
154
172
|
const CurrentUserProvider: FC = ({ children }) => {
|
|
155
|
-
const app = useApp();
|
|
173
|
+
const app = useApp<Application>();
|
|
156
174
|
const location = useLocation();
|
|
157
175
|
const navigate = useNavigate();
|
|
158
176
|
const [state, setState] = useState<CurrentUserState>({ loading: true });
|
|
159
|
-
const pathnameRef = useRef(location.pathname);
|
|
160
|
-
pathnameRef.current = location.pathname;
|
|
161
177
|
const locationRef = useRef(location);
|
|
162
178
|
locationRef.current = location;
|
|
179
|
+
const authCheckRouteState = getCurrentUserAuthCheckRouteState(app, location.pathname);
|
|
163
180
|
|
|
164
181
|
useEffect(() => {
|
|
165
182
|
let mounted = true;
|
|
166
|
-
const isSkippedAuthCheckRoute =
|
|
167
|
-
isBuiltinAuthRoute(pathnameRef.current, app.router.getBasename()) ||
|
|
168
|
-
app.router.isSkippedAuthCheckRoute(pathnameRef.current);
|
|
169
|
-
const shouldCheckCurrentUser = isAdminRuntimeRoute(pathnameRef.current, app.router.getBasename());
|
|
170
183
|
|
|
171
|
-
if (
|
|
184
|
+
if (authCheckRouteState !== 'required') {
|
|
172
185
|
// 认证页等免鉴权路由不应再执行 `/auth:check`,否则未登录时会重复鉴权并触发重定向抖动。
|
|
173
186
|
setState({ loading: false });
|
|
174
187
|
return;
|
|
175
188
|
}
|
|
176
189
|
|
|
190
|
+
setState((previous) => (previous.loading ? previous : { ...previous, loading: true }));
|
|
191
|
+
|
|
177
192
|
const run = async () => {
|
|
178
193
|
try {
|
|
179
194
|
const res = await app.apiClient.request({
|
|
@@ -182,21 +197,18 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
182
197
|
skipAuth: true,
|
|
183
198
|
});
|
|
184
199
|
|
|
200
|
+
if (!mounted) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
185
204
|
const user = res?.data?.data;
|
|
186
|
-
// 服务端通过 `{ code: 302, redirect }` 通知客户端先去某个中间页(例如 2FA 验证页)
|
|
187
|
-
// 这类响应没有 user.id,但也不能视为未登录——否则会和处理 302 的全局响应拦截器
|
|
188
|
-
// (例如 plugin-two-factor-authentication 注册的那一个)竞态,而 `window.location.replace`
|
|
189
|
-
// 会覆盖更早发出的 `window.location.href`,把用户错误地弹回登录页。让响应拦截器接管跳转。
|
|
205
|
+
// 服务端通过 `{ code: 302, redirect }` 通知客户端先去某个中间页(例如 2FA 验证页)。这类响应没有 user.id,但也不能视为未登录——否则会和处理 302 的全局响应拦截器 (例如 plugin-two-factor-authentication 注册的那一个)竞态,而 `window.location.replace` 会覆盖更早发出的 `window.location.href`,把用户错误地弹回登录页。让响应拦截器接管跳转。
|
|
190
206
|
if (user?.code === 302) {
|
|
191
|
-
|
|
192
|
-
setState({ loading: false });
|
|
193
|
-
}
|
|
207
|
+
setState({ loading: false });
|
|
194
208
|
return;
|
|
195
209
|
}
|
|
196
210
|
if (user?.id == null) {
|
|
197
|
-
// 用 react-router navigate (虚拟跳转)而不是 location.replace,
|
|
198
|
-
// 已经发起了 window.location.href 整页跳转(例如 2FA 插件接收到服务端 302 重定向),
|
|
199
|
-
// 真实跳转可以胜出 navigate, 不会被这里的 signin 重定向覆盖。
|
|
211
|
+
// 用 react-router navigate (虚拟跳转)而不是 location.replace, 这样如果有其他响应拦截器已经发起了 window.location.href 整页跳转(例如 2FA 插件接收到服务端 302 重定向), 真实跳转可以胜出 navigate, 不会被这里的 signin 重定向覆盖。
|
|
200
212
|
navigate(`/signin?redirect=${encodeURIComponent(getCurrentV2RedirectPath(app, locationRef.current))}`, {
|
|
201
213
|
replace: true,
|
|
202
214
|
});
|
|
@@ -215,13 +227,15 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
215
227
|
meta: userMeta,
|
|
216
228
|
});
|
|
217
229
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
});
|
|
223
|
-
}
|
|
230
|
+
setState({
|
|
231
|
+
data: res?.data,
|
|
232
|
+
loading: false,
|
|
233
|
+
});
|
|
224
234
|
} catch (error: any) {
|
|
235
|
+
if (!mounted) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
225
239
|
const isAuthError = error?.response?.status === 401 || error?.status === 401;
|
|
226
240
|
if (isAuthError) {
|
|
227
241
|
navigate(`/signin?redirect=${encodeURIComponent(getCurrentV2RedirectPath(app, locationRef.current))}`, {
|
|
@@ -229,19 +243,17 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
229
243
|
});
|
|
230
244
|
return;
|
|
231
245
|
}
|
|
232
|
-
|
|
233
|
-
setState({ loading: false });
|
|
234
|
-
}
|
|
246
|
+
setState({ loading: false });
|
|
235
247
|
throw error;
|
|
236
248
|
}
|
|
237
249
|
};
|
|
238
250
|
|
|
239
|
-
|
|
251
|
+
run();
|
|
240
252
|
|
|
241
253
|
return () => {
|
|
242
254
|
mounted = false;
|
|
243
255
|
};
|
|
244
|
-
}, [app, navigate]);
|
|
256
|
+
}, [app, authCheckRouteState, navigate]);
|
|
245
257
|
|
|
246
258
|
if (state.loading) {
|
|
247
259
|
return app.renderComponent('AppSpin');
|
|
@@ -251,13 +263,12 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
251
263
|
};
|
|
252
264
|
|
|
253
265
|
const RootRedirect: FC = () => {
|
|
254
|
-
const app = useApp();
|
|
266
|
+
const app = useApp<Application>();
|
|
255
267
|
const hasToken = !!app?.apiClient?.auth?.token;
|
|
256
268
|
const targetPath = getDefaultV2AdminRedirectPath(app);
|
|
257
269
|
|
|
258
270
|
if (!hasToken) {
|
|
259
|
-
// 用 react-router <Navigate /> 而非 location.replace,
|
|
260
|
-
// 触发的 window.location.href (例如 2FA 接收到服务端 302 时设置的整页跳转)。
|
|
271
|
+
// 用 react-router <Navigate /> 而非 location.replace, 避免覆盖同时段其它响应拦截器触发的 window.location.href (例如 2FA 接收到服务端 302 时设置的整页跳转)。
|
|
261
272
|
return <Navigate replace to={`/signin?redirect=${encodeURIComponent(targetPath)}`} />;
|
|
262
273
|
}
|
|
263
274
|
|
|
@@ -267,8 +278,7 @@ const RootRedirect: FC = () => {
|
|
|
267
278
|
/**
|
|
268
279
|
* client-v2 使用的内建插件集合。
|
|
269
280
|
*
|
|
270
|
-
* 只迁移当前 v2 运行时仍然需要的部分,显式跳过 schemaInitializerManager
|
|
271
|
-
* 以及用户标注暂不迁移的旧插件注册逻辑。
|
|
281
|
+
* 只迁移当前 v2 运行时仍然需要的部分,显式跳过 schemaInitializerManager 以及用户标注暂不迁移的旧插件注册逻辑。
|
|
272
282
|
*/
|
|
273
283
|
export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
274
284
|
async afterAdd() {
|
|
@@ -286,6 +296,12 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
286
296
|
AdminLayoutMenuItemModel,
|
|
287
297
|
AdminSettingsLayoutModel,
|
|
288
298
|
});
|
|
299
|
+
this.app.layoutManager.registerLayout({
|
|
300
|
+
routeName: 'admin',
|
|
301
|
+
routePath: '/admin',
|
|
302
|
+
uid: ADMIN_LAYOUT_MODEL_UID,
|
|
303
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
304
|
+
});
|
|
289
305
|
|
|
290
306
|
this.app.pluginSettingsManager.addMenuItem({
|
|
291
307
|
key: 'plugin-manager',
|
|
@@ -298,7 +314,7 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
298
314
|
menuKey: 'plugin-manager',
|
|
299
315
|
key: 'index',
|
|
300
316
|
title: this.app.i18n.t('Plugin manager'),
|
|
301
|
-
componentLoader: () => import('../settings-center/
|
|
317
|
+
componentLoader: () => import('../settings-center/plugin-manager'),
|
|
302
318
|
aclSnippet: 'pm',
|
|
303
319
|
sort: -200,
|
|
304
320
|
});
|
|
@@ -317,6 +333,13 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
317
333
|
aclSnippet: 'pm.system-settings.system-settings',
|
|
318
334
|
sort: -100,
|
|
319
335
|
});
|
|
336
|
+
// Parent menu for security-related plugin settings (password policy, locked users, etc.). Registered here in the buildin plugin so any pro plugin can attach page tabs to `menuKey: 'security'` without each one re-registering the same parent.
|
|
337
|
+
this.app.pluginSettingsManager.addMenuItem({
|
|
338
|
+
key: 'security',
|
|
339
|
+
title: this.app.i18n.t('Security'),
|
|
340
|
+
icon: 'SafetyOutlined',
|
|
341
|
+
aclSnippet: 'pm.security',
|
|
342
|
+
});
|
|
320
343
|
}
|
|
321
344
|
|
|
322
345
|
addRoutes() {
|
|
@@ -330,10 +353,6 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
330
353
|
Component: AppNotFound,
|
|
331
354
|
});
|
|
332
355
|
|
|
333
|
-
this.router.add('admin', {
|
|
334
|
-
path: '/admin',
|
|
335
|
-
componentLoader: () => import('../flow/components/AdminLayout'),
|
|
336
|
-
});
|
|
337
356
|
this.router.add('admin.settings', {
|
|
338
357
|
path: '/admin/settings',
|
|
339
358
|
componentLoader: () => import('../settings-center/AdminSettingsLayout'),
|
|
@@ -342,23 +361,6 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
342
361
|
path: '*',
|
|
343
362
|
Component: Outlet,
|
|
344
363
|
});
|
|
345
|
-
this.router.add('admin.page', {
|
|
346
|
-
path: '/admin/:name',
|
|
347
|
-
componentLoader: () => import('../flow/components/FlowRoute'),
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
this.router.add('admin.page.tab', {
|
|
351
|
-
path: '/admin/:name/tab/:tabUid',
|
|
352
|
-
componentLoader: () => import('../flow/components/FlowRoute'),
|
|
353
|
-
});
|
|
354
|
-
this.router.add('admin.page.view', {
|
|
355
|
-
path: '/admin/:name/view/*',
|
|
356
|
-
componentLoader: () => import('../flow/components/FlowRoute'),
|
|
357
|
-
});
|
|
358
|
-
this.router.add('admin.page.tab.view', {
|
|
359
|
-
path: '/admin/:name/tab/:tabUid/view/*',
|
|
360
|
-
componentLoader: () => import('../flow/components/FlowRoute'),
|
|
361
|
-
});
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
addComponents() {}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useMemoizedFn } from 'ahooks';
|
|
11
|
+
import { Button, Input, Modal, Table } from 'antd';
|
|
12
|
+
import type { TableProps } from 'antd';
|
|
13
|
+
import _ from 'lodash';
|
|
14
|
+
import React, { useMemo, useState } from 'react';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import { useApp } from '../../hooks/useApp';
|
|
17
|
+
import type { IPluginData } from './types';
|
|
18
|
+
|
|
19
|
+
interface BulkEnableButtonProps {
|
|
20
|
+
plugins: IPluginData[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const BulkEnableButton: React.FC<BulkEnableButtonProps> = ({ plugins }) => {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
const app = useApp();
|
|
26
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
27
|
+
const [searchValue, setSearchValue] = useState('');
|
|
28
|
+
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
29
|
+
|
|
30
|
+
const disabledPlugins = useMemo(() => plugins.filter((plugin) => !plugin.enabled), [plugins]);
|
|
31
|
+
|
|
32
|
+
const items = useMemo(() => {
|
|
33
|
+
const value = searchValue.toLowerCase().trim();
|
|
34
|
+
if (!value) return disabledPlugins;
|
|
35
|
+
return disabledPlugins.filter(
|
|
36
|
+
(plugin) =>
|
|
37
|
+
(plugin.displayName || '').toLowerCase().includes(value) ||
|
|
38
|
+
(plugin.description || '').toLowerCase().includes(value),
|
|
39
|
+
);
|
|
40
|
+
}, [disabledPlugins, searchValue]);
|
|
41
|
+
|
|
42
|
+
const handleOpen = useMemoizedFn(() => setIsModalOpen(true));
|
|
43
|
+
|
|
44
|
+
const handleCancel = useMemoizedFn(() => {
|
|
45
|
+
setSelectedRowKeys([]);
|
|
46
|
+
setIsModalOpen(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const handleOk = useMemoizedFn(async () => {
|
|
50
|
+
await app.apiClient.request({
|
|
51
|
+
url: 'pm:enable',
|
|
52
|
+
params: { filterByTk: selectedRowKeys },
|
|
53
|
+
});
|
|
54
|
+
setIsModalOpen(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const columns: TableProps<IPluginData>['columns'] = useMemo(
|
|
58
|
+
() => [
|
|
59
|
+
{ title: t('Plugin'), dataIndex: 'displayName', ellipsis: true },
|
|
60
|
+
{ title: t('Description'), dataIndex: 'description', ellipsis: true, width: 300 },
|
|
61
|
+
{ title: t('Package name'), dataIndex: 'packageName', width: 300, ellipsis: true },
|
|
62
|
+
],
|
|
63
|
+
[t],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Button onClick={handleOpen}>{t('Bulk enable')}</Button>
|
|
69
|
+
<Modal width={1000} title={t('Bulk enable')} open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
|
|
70
|
+
<Input
|
|
71
|
+
style={{ marginBottom: '1em' }}
|
|
72
|
+
placeholder={t('Search plugin...')}
|
|
73
|
+
allowClear
|
|
74
|
+
onChange={(e) => setSearchValue(e.target.value)}
|
|
75
|
+
/>
|
|
76
|
+
<Table<IPluginData>
|
|
77
|
+
rowSelection={{
|
|
78
|
+
type: 'checkbox',
|
|
79
|
+
selectedRowKeys,
|
|
80
|
+
onChange(selectedKeys) {
|
|
81
|
+
const names = items.map((item) => item.name);
|
|
82
|
+
setSelectedRowKeys((preSelectedRowKeys) => {
|
|
83
|
+
if (selectedKeys.length === 0) {
|
|
84
|
+
return preSelectedRowKeys.filter((key) => !names.includes(String(key)));
|
|
85
|
+
}
|
|
86
|
+
if (selectedKeys.length === names.length) {
|
|
87
|
+
return _.uniq([...preSelectedRowKeys, ...selectedKeys]);
|
|
88
|
+
}
|
|
89
|
+
return preSelectedRowKeys;
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
onSelect(record) {
|
|
93
|
+
setSelectedRowKeys((preSelectedRowKeys) => {
|
|
94
|
+
if (preSelectedRowKeys.includes(record.name)) {
|
|
95
|
+
return preSelectedRowKeys.filter((key) => key !== record.name);
|
|
96
|
+
}
|
|
97
|
+
return preSelectedRowKeys.concat(record.name);
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
}}
|
|
101
|
+
rowKey="name"
|
|
102
|
+
scroll={{ y: '60vh' }}
|
|
103
|
+
size="small"
|
|
104
|
+
pagination={false}
|
|
105
|
+
columns={columns}
|
|
106
|
+
dataSource={items}
|
|
107
|
+
/>
|
|
108
|
+
</Modal>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
CloseCircleFilled,
|
|
12
|
+
DeleteOutlined,
|
|
13
|
+
LoadingOutlined,
|
|
14
|
+
ReadOutlined,
|
|
15
|
+
SettingOutlined,
|
|
16
|
+
WarningFilled,
|
|
17
|
+
} from '@ant-design/icons';
|
|
18
|
+
import { css } from '@emotion/css';
|
|
19
|
+
import { useMemoizedFn } from 'ahooks';
|
|
20
|
+
import { App, Card, Divider, Modal, Popconfirm, Result, Space, Switch, Tooltip, Typography, theme } from 'antd';
|
|
21
|
+
import React, { FC, useMemo, useState } from 'react';
|
|
22
|
+
import { useTranslation } from 'react-i18next';
|
|
23
|
+
import { useNavigate } from 'react-router-dom';
|
|
24
|
+
import { useApp } from '../../hooks/useApp';
|
|
25
|
+
import { PluginDetail } from './PluginDetail';
|
|
26
|
+
import type { IPluginData } from './types';
|
|
27
|
+
|
|
28
|
+
interface PluginCardProps {
|
|
29
|
+
data: IPluginData;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const PluginCard: FC<PluginCardProps> = ({ data }) => {
|
|
33
|
+
const { token } = theme.useToken();
|
|
34
|
+
const { t } = useTranslation();
|
|
35
|
+
const navigate = useNavigate();
|
|
36
|
+
const app = useApp();
|
|
37
|
+
const { modal } = App.useApp();
|
|
38
|
+
const [detailOpen, setDetailOpen] = useState(false);
|
|
39
|
+
|
|
40
|
+
const { name, displayName, isCompatible, packageName, builtIn, enabled, removable, description, error, homepage } =
|
|
41
|
+
data;
|
|
42
|
+
|
|
43
|
+
const title = displayName || name || packageName;
|
|
44
|
+
|
|
45
|
+
const openDetail = useMemoizedFn(() => {
|
|
46
|
+
if (!error) {
|
|
47
|
+
setDetailOpen(true);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const handleEnable = useMemoizedFn(async () => {
|
|
52
|
+
await app.apiClient.request({
|
|
53
|
+
url: 'pm:enable',
|
|
54
|
+
params: { filterByTk: name },
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const handleDisable = useMemoizedFn(async () => {
|
|
59
|
+
await app.apiClient.request({
|
|
60
|
+
url: 'pm:disable',
|
|
61
|
+
params: { filterByTk: name },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const handleRemove = useMemoizedFn(async () => {
|
|
66
|
+
await app.apiClient.request({
|
|
67
|
+
url: 'pm:remove',
|
|
68
|
+
params: { filterByTk: name },
|
|
69
|
+
});
|
|
70
|
+
modal.info({
|
|
71
|
+
icon: null,
|
|
72
|
+
width: 520,
|
|
73
|
+
content: (
|
|
74
|
+
<Result
|
|
75
|
+
icon={<LoadingOutlined />}
|
|
76
|
+
title={t('Plugin removing')}
|
|
77
|
+
subTitle={t('Plugin is removing, please wait...')}
|
|
78
|
+
/>
|
|
79
|
+
),
|
|
80
|
+
footer: null,
|
|
81
|
+
});
|
|
82
|
+
const checkHealth = () => {
|
|
83
|
+
app.apiClient
|
|
84
|
+
.request({ url: '__health_check', method: 'get', skipNotify: true })
|
|
85
|
+
.then((response) => {
|
|
86
|
+
if (response?.data === 'ok') {
|
|
87
|
+
window.location.reload();
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.catch(() => {
|
|
91
|
+
// health check still failing, keep polling
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
setInterval(checkHealth, 1000);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const handleSwitchChange = useMemoizedFn(async (checked: boolean) => {
|
|
98
|
+
if (!isCompatible && checked) {
|
|
99
|
+
modal.confirm({
|
|
100
|
+
title: t('Plugin dependency version mismatch'),
|
|
101
|
+
content: t(
|
|
102
|
+
'The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?',
|
|
103
|
+
),
|
|
104
|
+
onOk: handleEnable,
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!checked) {
|
|
109
|
+
modal.confirm({
|
|
110
|
+
title: t('Are you sure to disable this plugin?'),
|
|
111
|
+
onOk: handleDisable,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await handleEnable();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const openSettings = useMemoizedFn(() => {
|
|
119
|
+
navigate(app.pluginSettingsManager.getRoutePath(name));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const cardClassName = useMemo(
|
|
123
|
+
() => css`
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
height: 100%;
|
|
127
|
+
|
|
128
|
+
.ant-card-body {
|
|
129
|
+
flex-grow: 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ant-card-actions {
|
|
133
|
+
li .ant-space {
|
|
134
|
+
gap: ${token.marginXXS}px !important;
|
|
135
|
+
}
|
|
136
|
+
li:first-child {
|
|
137
|
+
width: 80% !important;
|
|
138
|
+
border-inline-end: 0;
|
|
139
|
+
text-align: left;
|
|
140
|
+
padding-inline-start: ${token.padding}px;
|
|
141
|
+
}
|
|
142
|
+
li:last-child {
|
|
143
|
+
width: 20% !important;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`,
|
|
147
|
+
[token.marginXXS, token.padding],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const cardTitle = (
|
|
151
|
+
<Space>
|
|
152
|
+
{error ? (
|
|
153
|
+
<Tooltip title={t('Plugin loading failed. Please check the server logs.')}>
|
|
154
|
+
<Typography.Text type="danger">
|
|
155
|
+
<CloseCircleFilled />
|
|
156
|
+
</Typography.Text>
|
|
157
|
+
</Tooltip>
|
|
158
|
+
) : null}
|
|
159
|
+
{!isCompatible ? (
|
|
160
|
+
<Tooltip title={t('Plugin dependencies check failed')}>
|
|
161
|
+
<Typography.Text type="warning">
|
|
162
|
+
<WarningFilled />
|
|
163
|
+
</Typography.Text>
|
|
164
|
+
</Tooltip>
|
|
165
|
+
) : null}
|
|
166
|
+
<span>{title}</span>
|
|
167
|
+
</Space>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const linksAction = (
|
|
171
|
+
<Space split={<Divider type="vertical" />} key="links" size={token.marginXXS}>
|
|
172
|
+
{homepage && (
|
|
173
|
+
<a
|
|
174
|
+
href={homepage}
|
|
175
|
+
target="_blank"
|
|
176
|
+
rel="noreferrer"
|
|
177
|
+
onClick={(event) => event.stopPropagation()}
|
|
178
|
+
aria-label={t('Docs')}
|
|
179
|
+
>
|
|
180
|
+
<ReadOutlined /> {t('Docs')}
|
|
181
|
+
</a>
|
|
182
|
+
)}
|
|
183
|
+
{enabled && app.pluginSettingsManager.has(name) && (
|
|
184
|
+
<a
|
|
185
|
+
onClick={(e) => {
|
|
186
|
+
e.stopPropagation();
|
|
187
|
+
openSettings();
|
|
188
|
+
}}
|
|
189
|
+
aria-label={t('Settings')}
|
|
190
|
+
>
|
|
191
|
+
<SettingOutlined /> {t('Settings')}
|
|
192
|
+
</a>
|
|
193
|
+
)}
|
|
194
|
+
{removable && (
|
|
195
|
+
<Popconfirm
|
|
196
|
+
key="delete"
|
|
197
|
+
disabled={builtIn}
|
|
198
|
+
title={t('Are you sure to delete this plugin?')}
|
|
199
|
+
onConfirm={(e) => {
|
|
200
|
+
e?.stopPropagation();
|
|
201
|
+
handleRemove();
|
|
202
|
+
}}
|
|
203
|
+
onCancel={(e) => e?.stopPropagation()}
|
|
204
|
+
okText={t('Yes')}
|
|
205
|
+
cancelText={t('No')}
|
|
206
|
+
>
|
|
207
|
+
<a
|
|
208
|
+
onClick={(e) => e.stopPropagation()}
|
|
209
|
+
aria-label={t('Remove')}
|
|
210
|
+
style={builtIn ? { color: token.colorTextDisabled, cursor: 'not-allowed' } : undefined}
|
|
211
|
+
>
|
|
212
|
+
<DeleteOutlined /> {t('Remove')}
|
|
213
|
+
</a>
|
|
214
|
+
</Popconfirm>
|
|
215
|
+
)}
|
|
216
|
+
</Space>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const switchAction = (
|
|
220
|
+
<Switch
|
|
221
|
+
aria-label={t('Enable')}
|
|
222
|
+
key="enable"
|
|
223
|
+
size="small"
|
|
224
|
+
disabled={builtIn || error}
|
|
225
|
+
onChange={(checked, e) => {
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
handleSwitchChange(checked);
|
|
228
|
+
}}
|
|
229
|
+
checked={!!enabled}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<>
|
|
235
|
+
{detailOpen && <PluginDetail plugin={data} onCancel={() => setDetailOpen(false)} />}
|
|
236
|
+
<Card
|
|
237
|
+
role="button"
|
|
238
|
+
aria-label={title}
|
|
239
|
+
size="small"
|
|
240
|
+
variant="borderless"
|
|
241
|
+
hoverable
|
|
242
|
+
onClick={openDetail}
|
|
243
|
+
styles={{
|
|
244
|
+
body: { paddingTop: token.paddingSM },
|
|
245
|
+
header: { border: 'none', minHeight: 'inherit', paddingTop: token.padding },
|
|
246
|
+
}}
|
|
247
|
+
className={cardClassName}
|
|
248
|
+
title={cardTitle}
|
|
249
|
+
actions={[linksAction, switchAction]}
|
|
250
|
+
>
|
|
251
|
+
<Card.Meta
|
|
252
|
+
description={
|
|
253
|
+
<Typography.Paragraph
|
|
254
|
+
type="secondary"
|
|
255
|
+
className={css`
|
|
256
|
+
display: -webkit-box;
|
|
257
|
+
-webkit-box-orient: vertical;
|
|
258
|
+
-webkit-line-clamp: 2;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
margin-bottom: 0;
|
|
261
|
+
`}
|
|
262
|
+
>
|
|
263
|
+
{description}
|
|
264
|
+
</Typography.Paragraph>
|
|
265
|
+
}
|
|
266
|
+
/>
|
|
267
|
+
</Card>
|
|
268
|
+
</>
|
|
269
|
+
);
|
|
270
|
+
};
|