@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.45",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -20,17 +20,18 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@ant-design/icons": "^5.6.1",
|
|
23
|
+
"@ctrl/tinycolor": "^3.6.0",
|
|
23
24
|
"@dnd-kit/core": "^6.0.0",
|
|
24
25
|
"@dnd-kit/sortable": "^7.0.0",
|
|
25
26
|
"@emotion/css": "^11.7.1",
|
|
26
27
|
"@formily/antd-v5": "1.2.3",
|
|
27
28
|
"@formily/react": "^2.2.27",
|
|
28
29
|
"@formily/shared": "^2.2.27",
|
|
29
|
-
"@nocobase/evaluators": "2.1.0-alpha.
|
|
30
|
-
"@nocobase/flow-engine": "2.1.0-alpha.
|
|
31
|
-
"@nocobase/sdk": "2.1.0-alpha.
|
|
32
|
-
"@nocobase/shared": "2.1.0-alpha.
|
|
33
|
-
"@nocobase/utils": "2.1.0-alpha.
|
|
30
|
+
"@nocobase/evaluators": "2.1.0-alpha.45",
|
|
31
|
+
"@nocobase/flow-engine": "2.1.0-alpha.45",
|
|
32
|
+
"@nocobase/sdk": "2.1.0-alpha.45",
|
|
33
|
+
"@nocobase/shared": "2.1.0-alpha.45",
|
|
34
|
+
"@nocobase/utils": "2.1.0-alpha.45",
|
|
34
35
|
"ahooks": "^3.7.2",
|
|
35
36
|
"antd": "5.24.2",
|
|
36
37
|
"antd-style": "3.7.1",
|
|
@@ -44,5 +45,5 @@
|
|
|
44
45
|
"react-i18next": "^11.15.1",
|
|
45
46
|
"react-router-dom": "^6.30.1"
|
|
46
47
|
},
|
|
47
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "e9e24987e12d0ad10a5db8815b1e1b7b447f1938"
|
|
48
49
|
}
|
package/src/Application.tsx
CHANGED
|
@@ -42,6 +42,7 @@ export class Application extends BaseApplication<
|
|
|
42
42
|
PluginSettingsManager
|
|
43
43
|
> {
|
|
44
44
|
public declare dataSourceManager: any;
|
|
45
|
+
public hasLoadError = false;
|
|
45
46
|
|
|
46
47
|
protected createApiClient(options: ApplicationOptions) {
|
|
47
48
|
return new APIClient({
|
|
@@ -99,9 +100,29 @@ export class Application extends BaseApplication<
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
async load() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
try {
|
|
104
|
+
this.hasLoadError = false;
|
|
105
|
+
await this.loadWebSocket();
|
|
106
|
+
await this.pm.load();
|
|
107
|
+
await this.flowEngine.flowSettings.load();
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
this.hasLoadError = true;
|
|
110
|
+
|
|
111
|
+
if (error?.response?.data?.errors?.[0]?.code === 'BLOCKED_IP') {
|
|
112
|
+
this.hasLoadError = false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (this.ws.enabled) {
|
|
116
|
+
await new Promise((resolve) => {
|
|
117
|
+
setTimeout(() => resolve(null), 1000);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
this.error = {
|
|
121
|
+
code: 'LOAD_ERROR',
|
|
122
|
+
...this.apiClient.toErrMessages(error)?.[0],
|
|
123
|
+
};
|
|
124
|
+
console.error(error, this.error);
|
|
125
|
+
}
|
|
105
126
|
this.updateFavicon();
|
|
106
127
|
}
|
|
107
128
|
|
|
@@ -130,20 +151,14 @@ export class Application extends BaseApplication<
|
|
|
130
151
|
return;
|
|
131
152
|
}
|
|
132
153
|
|
|
133
|
-
if (this.error && data.payload.code === 'APP_RUNNING') {
|
|
134
|
-
this.maintained = true;
|
|
135
|
-
this.setMaintaining(false);
|
|
136
|
-
this.error = null;
|
|
137
|
-
globalThis.window.location.reload();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
154
|
const maintaining = data.type === 'maintaining' && data.payload.code !== 'APP_RUNNING';
|
|
142
|
-
console.log('ws:message', { maintaining, data });
|
|
143
155
|
if (maintaining) {
|
|
144
156
|
this.setMaintaining(true);
|
|
145
157
|
this.error = data.payload;
|
|
146
158
|
} else {
|
|
159
|
+
if (this.hasLoadError) {
|
|
160
|
+
globalThis.window.location.reload();
|
|
161
|
+
}
|
|
147
162
|
this.setMaintaining(false);
|
|
148
163
|
this.maintained = true;
|
|
149
164
|
this.error = null;
|
|
@@ -193,4 +208,28 @@ export class Application extends BaseApplication<
|
|
|
193
208
|
addFieldInterfaceOperator(name: string, operatorOption: any) {
|
|
194
209
|
return this.dataSourceManager.addFieldInterfaceOperator(name, operatorOption);
|
|
195
210
|
}
|
|
211
|
+
|
|
212
|
+
registerFieldFilterOperator(operator: any) {
|
|
213
|
+
return this.dataSourceManager.registerFieldFilterOperator(operator);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
registerFieldFilterOperatorGroup(name: string, operators: any[] = []) {
|
|
217
|
+
return this.dataSourceManager.registerFieldFilterOperatorGroup(name, operators);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
addFieldFilterOperatorsToGroup(name: string, operators: any[] = []) {
|
|
221
|
+
return this.dataSourceManager.addFieldFilterOperatorsToGroup(name, operators);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
registerFieldValidationConfigure(item: any) {
|
|
225
|
+
return this.dataSourceManager.collectionFieldInterfaceManager?.registerFieldValidationConfigure?.(item);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
registerFieldValidationConfigureGroup(name: string, items: any[] = []) {
|
|
229
|
+
return this.dataSourceManager.collectionFieldInterfaceManager?.registerFieldValidationConfigureGroup?.(name, items);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
addFieldValidationConfiguresToGroup(name: string, items: any[] = []) {
|
|
233
|
+
return this.dataSourceManager.collectionFieldInterfaceManager?.addFieldValidationConfiguresToGroup?.(name, items);
|
|
234
|
+
}
|
|
196
235
|
}
|
package/src/BaseApplication.tsx
CHANGED
|
@@ -25,11 +25,13 @@ import { I18nextProvider } from 'react-i18next';
|
|
|
25
25
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
26
26
|
import { Link, NavLink, Navigate } from 'react-router-dom';
|
|
27
27
|
import { isValidElementType } from 'react-is';
|
|
28
|
+
import type { AppListProps } from '@ant-design/pro-layout/es/components/AppsLogoComponents/types';
|
|
28
29
|
import AntdAppProvider from './theme/AntdAppProvider';
|
|
29
30
|
import { GlobalThemeProvider } from './theme';
|
|
30
31
|
import { AIManager } from './ai';
|
|
31
32
|
import { AppError, AppMaintaining, AppMaintainingDialog, AppNotFound, AppSpin, BlankComponent } from './components';
|
|
32
33
|
import { SystemSettingsSource } from './flow/system-settings';
|
|
34
|
+
import { LayoutManager } from './layout-manager/LayoutManager';
|
|
33
35
|
import type { PluginClass, PluginManager, PluginType } from './PluginManager';
|
|
34
36
|
import { RouteRepository } from './RouteRepository';
|
|
35
37
|
import type {
|
|
@@ -54,6 +56,7 @@ declare global {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
type AnyComponent = RenderableComponentType<any>;
|
|
59
|
+
type AppListLoader = (app: BaseApplication<any>) => Promise<AppListProps> | AppListProps;
|
|
57
60
|
type AuthTokenPayload = {
|
|
58
61
|
token: string;
|
|
59
62
|
authenticator: string | null;
|
|
@@ -122,6 +125,7 @@ export abstract class BaseApplication<
|
|
|
122
125
|
public components: Record<string, AnyComponent> = {};
|
|
123
126
|
public pluginManager: TPluginManager;
|
|
124
127
|
public pluginSettingsManager: TPluginSettingsManager;
|
|
128
|
+
public layoutManager: LayoutManager<this>;
|
|
125
129
|
public aiManager!: AIManager;
|
|
126
130
|
public devDynamicImport?: DevDynamicImport;
|
|
127
131
|
public requirejs!: RequireJS;
|
|
@@ -153,8 +157,10 @@ export abstract class BaseApplication<
|
|
|
153
157
|
private wsAuthorized = false;
|
|
154
158
|
apps: {
|
|
155
159
|
Component?: AnyComponent | null;
|
|
160
|
+
loadAppList?: AppListLoader | null;
|
|
156
161
|
} = {
|
|
157
162
|
Component: null,
|
|
163
|
+
loadAppList: null,
|
|
158
164
|
};
|
|
159
165
|
|
|
160
166
|
get pm(): TPluginManager {
|
|
@@ -169,6 +175,18 @@ export abstract class BaseApplication<
|
|
|
169
175
|
return this.wsAuthorized;
|
|
170
176
|
}
|
|
171
177
|
|
|
178
|
+
public setDocumentLanguage(language?: string | null) {
|
|
179
|
+
if (typeof document === 'undefined') {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (language) {
|
|
184
|
+
document.documentElement.lang = language;
|
|
185
|
+
} else {
|
|
186
|
+
document.documentElement.removeAttribute('lang');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
172
190
|
constructor(protected options: TOptions = {} as TOptions) {
|
|
173
191
|
this.initRequireJs();
|
|
174
192
|
this.defineObservableState();
|
|
@@ -180,6 +198,7 @@ export abstract class BaseApplication<
|
|
|
180
198
|
this.initializeExtendedState();
|
|
181
199
|
this.i18n = this.createI18n(options);
|
|
182
200
|
this.router = this.createRouterManager(options);
|
|
201
|
+
this.layoutManager = this.createLayoutManager(options);
|
|
183
202
|
this.pluginManager = this.createPluginManager(options);
|
|
184
203
|
this.flowEngine = new FlowEngine();
|
|
185
204
|
this.flowEngine.registerModels({ ApplicationModel });
|
|
@@ -205,6 +224,7 @@ export abstract class BaseApplication<
|
|
|
205
224
|
this.addRoutes();
|
|
206
225
|
this.i18n.on('languageChanged', (lng) => {
|
|
207
226
|
this.apiClient.auth.locale = lng;
|
|
227
|
+
this.setDocumentLanguage(lng);
|
|
208
228
|
});
|
|
209
229
|
this.initListeners();
|
|
210
230
|
this.afterManagersInitialized();
|
|
@@ -218,6 +238,7 @@ export abstract class BaseApplication<
|
|
|
218
238
|
maintained: observable.ref,
|
|
219
239
|
maintaining: observable.ref,
|
|
220
240
|
error: observable.ref,
|
|
241
|
+
apps: observable,
|
|
221
242
|
});
|
|
222
243
|
}
|
|
223
244
|
|
|
@@ -517,6 +538,14 @@ export abstract class BaseApplication<
|
|
|
517
538
|
});
|
|
518
539
|
}
|
|
519
540
|
|
|
541
|
+
setAppsComponent({ Component }: { Component: AnyComponent }) {
|
|
542
|
+
this.apps.Component = Component;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
setAppsProvider({ loadAppList }: { loadAppList: AppListLoader }) {
|
|
546
|
+
this.apps.loadAppList = loadAppList;
|
|
547
|
+
}
|
|
548
|
+
|
|
520
549
|
protected getRootFallback() {
|
|
521
550
|
return this.renderComponent('AppSpin');
|
|
522
551
|
}
|
|
@@ -554,6 +583,9 @@ export abstract class BaseApplication<
|
|
|
554
583
|
protected abstract createRouterManager(options: TOptions): TRouterManager;
|
|
555
584
|
protected abstract createPluginManager(options: TOptions): TPluginManager;
|
|
556
585
|
protected abstract createPluginSettingsManager(options: TOptions): TPluginSettingsManager;
|
|
586
|
+
protected createLayoutManager(_options: TOptions) {
|
|
587
|
+
return new LayoutManager(this);
|
|
588
|
+
}
|
|
557
589
|
protected createWebSocketClient(options: TOptions) {
|
|
558
590
|
return new WebSocketClient(options.ws ?? false);
|
|
559
591
|
}
|
|
@@ -524,7 +524,7 @@ export class PluginSettingsManager<TApp extends BaseApplication<any> = BaseAppli
|
|
|
524
524
|
|
|
525
525
|
if (page.key === 'index') {
|
|
526
526
|
this.app.router.add(this.getRouteName(page.name), {
|
|
527
|
-
|
|
527
|
+
path: '',
|
|
528
528
|
Component: fallbackComponent,
|
|
529
529
|
componentLoader: page.componentLoader,
|
|
530
530
|
});
|
package/src/RouterManager.tsx
CHANGED
|
@@ -50,10 +50,25 @@ export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
|
50
50
|
Component?: ComponentTypeAndString;
|
|
51
51
|
componentLoader?: ComponentLoader;
|
|
52
52
|
skipAuthCheck?: boolean;
|
|
53
|
+
authCheck?: boolean;
|
|
53
54
|
}
|
|
54
55
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
55
56
|
export type RouterComponentType = React.FC<{ BaseLayout?: ComponentType }>;
|
|
56
57
|
|
|
58
|
+
function removeBasename(pathname: string, basename?: string) {
|
|
59
|
+
if (!basename || basename === '/') {
|
|
60
|
+
return pathname;
|
|
61
|
+
}
|
|
62
|
+
const normalizedBasename = basename.replace(/\/$/, '');
|
|
63
|
+
if (pathname === normalizedBasename) {
|
|
64
|
+
return '/';
|
|
65
|
+
}
|
|
66
|
+
if (pathname.startsWith(`${normalizedBasename}/`)) {
|
|
67
|
+
return pathname.slice(normalizedBasename.length) || '/';
|
|
68
|
+
}
|
|
69
|
+
return pathname;
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<any>> {
|
|
58
73
|
protected routes: Record<string, RouteType> = {};
|
|
59
74
|
protected options: RouterOptions;
|
|
@@ -186,8 +201,9 @@ export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<a
|
|
|
186
201
|
|
|
187
202
|
matchRoutes(pathname: string) {
|
|
188
203
|
const routes = this.getRoutesTree();
|
|
204
|
+
const basename = this.router?.basename || this.getBasename();
|
|
189
205
|
// @ts-ignore
|
|
190
|
-
return matchRoutes<RouteType>(routes, pathname,
|
|
206
|
+
return matchRoutes<RouteType>(routes, removeBasename(pathname, basename));
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
isSkippedAuthCheckRoute(pathname: string) {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { createMockClient } from '@nocobase/client-v2';
|
|
12
|
+
import { createMemoryRouter } from 'react-router-dom';
|
|
13
|
+
import type { RouteObject } from 'react-router-dom';
|
|
12
14
|
|
|
13
15
|
describe('PluginSettingsManager v2', () => {
|
|
14
16
|
it('should return menu -> page two-level structure', () => {
|
|
@@ -72,7 +74,7 @@ describe('PluginSettingsManager v2', () => {
|
|
|
72
74
|
expect(app.pluginSettingsManager.getRoutePath('demo.advanced')).toBe('/admin/settings/demo/advanced');
|
|
73
75
|
|
|
74
76
|
expect(app.router.get('admin.settings.demo')).toMatchObject({ path: 'demo' });
|
|
75
|
-
expect(app.router.get('admin.settings.demo.index')).toMatchObject({
|
|
77
|
+
expect(app.router.get('admin.settings.demo.index')).toMatchObject({ path: '' });
|
|
76
78
|
expect(app.router.get('admin.settings.demo.advanced')).toMatchObject({ path: 'advanced' });
|
|
77
79
|
});
|
|
78
80
|
|
|
@@ -110,7 +112,44 @@ describe('PluginSettingsManager v2', () => {
|
|
|
110
112
|
});
|
|
111
113
|
|
|
112
114
|
expect(app.pluginSettingsManager.get('demo.index')).toMatchObject({ componentLoader });
|
|
113
|
-
expect(app.router.get('admin.settings.demo.index')).toMatchObject({ componentLoader,
|
|
115
|
+
expect(app.router.get('admin.settings.demo.index')).toMatchObject({ componentLoader, path: '' });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should allow nested routes under index page route', () => {
|
|
119
|
+
const app = createMockClient();
|
|
120
|
+
const findRoute = (routes: RouteObject[], routeId: string): RouteObject | null => {
|
|
121
|
+
for (const route of routes) {
|
|
122
|
+
if (route.id === routeId) {
|
|
123
|
+
return route;
|
|
124
|
+
}
|
|
125
|
+
const matched = route.children ? findRoute(route.children, routeId) : null;
|
|
126
|
+
if (matched) {
|
|
127
|
+
return matched;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
app.pluginSettingsManager.addMenuItem({ key: 'demo', title: 'Demo' });
|
|
134
|
+
app.pluginSettingsManager.addPageTabItem({ menuKey: 'demo', key: 'index', title: 'Overview' });
|
|
135
|
+
app.router.add('admin.settings.demo.index.layout', {
|
|
136
|
+
path: 'configure',
|
|
137
|
+
Component: () => React.createElement('div', null, 'configure'),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const routes = app.router.getRoutesTree();
|
|
141
|
+
const indexRoute = findRoute(routes, 'admin.settings.demo.index');
|
|
142
|
+
|
|
143
|
+
expect(indexRoute).toMatchObject({
|
|
144
|
+
id: 'admin.settings.demo.index',
|
|
145
|
+
path: '',
|
|
146
|
+
});
|
|
147
|
+
expect(indexRoute).not.toHaveProperty('index');
|
|
148
|
+
expect(findRoute(routes, 'admin.settings.demo.index.layout')).toMatchObject({
|
|
149
|
+
id: 'admin.settings.demo.index.layout',
|
|
150
|
+
path: 'configure',
|
|
151
|
+
});
|
|
152
|
+
expect(() => createMemoryRouter(routes, { initialEntries: ['/demo/configure/form-1'] })).not.toThrow();
|
|
114
153
|
});
|
|
115
154
|
|
|
116
155
|
it('should merge duplicate registration and refresh route config', () => {
|
|
@@ -33,6 +33,7 @@ describe('app', () => {
|
|
|
33
33
|
|
|
34
34
|
afterEach(() => {
|
|
35
35
|
document.querySelectorAll('link[rel="shortcut icon"]').forEach((node) => node.remove());
|
|
36
|
+
document.documentElement.removeAttribute('lang');
|
|
36
37
|
vi.restoreAllMocks();
|
|
37
38
|
});
|
|
38
39
|
|
|
@@ -89,6 +90,14 @@ describe('app', () => {
|
|
|
89
90
|
expect(favicon.getAttribute('href')).toBe('/favicon/favicon.ico');
|
|
90
91
|
});
|
|
91
92
|
|
|
93
|
+
it('should sync document language when app language changes', async () => {
|
|
94
|
+
const app = new Application({ router });
|
|
95
|
+
|
|
96
|
+
await app.i18n.changeLanguage('ja-JP');
|
|
97
|
+
|
|
98
|
+
expect(document.documentElement.lang).toBe('ja-JP');
|
|
99
|
+
});
|
|
100
|
+
|
|
92
101
|
it('should escape app version html placeholder content', () => {
|
|
93
102
|
expect(getAppVersionHTML('<script>alert(1)</script>&"')).toBe(
|
|
94
103
|
'<span class="nb-app-version">v<script>alert(1)</script>&"</span>',
|
|
@@ -234,6 +243,10 @@ describe('app', () => {
|
|
|
234
243
|
});
|
|
235
244
|
await renderApp(app);
|
|
236
245
|
expect(await screen.findByText('Hello Basename Route')).toBeInTheDocument();
|
|
246
|
+
expect(app.router.matchRoutes('/v2/demo/app-info')?.some((match) => match.route.path === '/demo/app-info')).toBe(
|
|
247
|
+
true,
|
|
248
|
+
);
|
|
249
|
+
expect(app.router.matchRoutes('/demo/app-info')?.some((match) => match.route.path === '/demo/app-info')).toBe(true);
|
|
237
250
|
});
|
|
238
251
|
|
|
239
252
|
it('should support plugin settings componentLoader lazy functionality', async () => {
|
|
@@ -370,7 +383,10 @@ describe('app', () => {
|
|
|
370
383
|
|
|
371
384
|
await waitFor(() => expect(screen.queryByText('maintaining error message')).not.toBeInTheDocument());
|
|
372
385
|
expect(screen.getByText('Hello')).toBeInTheDocument();
|
|
373
|
-
|
|
386
|
+
// Aligned with v1: a routine maintaining→APP_RUNNING cycle does not
|
|
387
|
+
// reload the page. Only `hasLoadError === true` (set when the initial
|
|
388
|
+
// `app.load()` itself fails) triggers a recovery reload.
|
|
389
|
+
expect(reloadMock).not.toHaveBeenCalled();
|
|
374
390
|
} finally {
|
|
375
391
|
Object.defineProperty(globalThis.window, 'location', {
|
|
376
392
|
configurable: true,
|
|
@@ -28,6 +28,7 @@ describe('client-v2 defineGlobalDeps', () => {
|
|
|
28
28
|
expect(define).toHaveBeenCalledWith('@nocobase/evaluators/client', expect.any(Function));
|
|
29
29
|
expect(define).toHaveBeenCalledWith('@dnd-kit/core', expect.any(Function));
|
|
30
30
|
expect(define).toHaveBeenCalledWith('@dnd-kit/sortable', expect.any(Function));
|
|
31
|
+
expect(define).toHaveBeenCalledWith('@ctrl/tinycolor', expect.any(Function));
|
|
31
32
|
expect(define).toHaveBeenCalledWith('ahooks', expect.any(Function));
|
|
32
33
|
expect(define).toHaveBeenCalledWith('dayjs', expect.any(Function));
|
|
33
34
|
expect(define).toHaveBeenCalledWith('lodash', expect.any(Function));
|
|
@@ -7,11 +7,21 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createMockClient } from '@nocobase/client-v2';
|
|
11
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import { createMockClient, Plugin } from '@nocobase/client-v2';
|
|
11
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
14
14
|
|
|
15
|
+
class SkippedPublicRoutePlugin extends Plugin {
|
|
16
|
+
async load() {
|
|
17
|
+
this.router.add('public', {
|
|
18
|
+
path: '/public',
|
|
19
|
+
skipAuthCheck: true,
|
|
20
|
+
Component: () => <div>public page</div>,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
describe('nocobase buildin plugin auth redirect', () => {
|
|
16
26
|
const originalLocation = globalThis.window.location;
|
|
17
27
|
|
|
@@ -107,6 +117,39 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
107
117
|
});
|
|
108
118
|
});
|
|
109
119
|
|
|
120
|
+
it('should check current user after navigating from skipped route to v2 admin', async () => {
|
|
121
|
+
const app = createMockClient({
|
|
122
|
+
publicPath: '/v2/',
|
|
123
|
+
plugins: [NocoBaseBuildInPlugin as any, SkippedPublicRoutePlugin as any],
|
|
124
|
+
router: { type: 'memory', initialEntries: ['/v2/public'] },
|
|
125
|
+
});
|
|
126
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
127
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
128
|
+
});
|
|
129
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: {} });
|
|
130
|
+
|
|
131
|
+
const Root = app.getRootComponent();
|
|
132
|
+
render(<Root />);
|
|
133
|
+
|
|
134
|
+
expect(await screen.findByText('public page')).toBeInTheDocument();
|
|
135
|
+
const authCheckRequestsBeforeNavigation = app.apiMock.history.get.filter(
|
|
136
|
+
(request) => request.url === '/auth:check',
|
|
137
|
+
).length;
|
|
138
|
+
expect(authCheckRequestsBeforeNavigation).toBe(0);
|
|
139
|
+
|
|
140
|
+
await act(async () => {
|
|
141
|
+
await app.router.router.navigate('/v2/admin');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(app.apiMock.history.get.filter((request) => request.url === '/auth:check').length).toBeGreaterThan(
|
|
146
|
+
authCheckRequestsBeforeNavigation,
|
|
147
|
+
);
|
|
148
|
+
expect(app.router.router.state.location.pathname).toBe('/v2/signin');
|
|
149
|
+
expect(app.router.router.state.location.search).toBe('?redirect=%2Fv2%2Fadmin');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
110
153
|
it('should render v2 admin root without redirecting away', async () => {
|
|
111
154
|
const app = createMockClient({
|
|
112
155
|
publicPath: '/v2/',
|
|
@@ -0,0 +1,177 @@
|
|
|
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 { ACLRolesCheckProvider, createMockClient, Plugin } from '@nocobase/client-v2';
|
|
11
|
+
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
14
|
+
|
|
15
|
+
class TestAclPlugin extends Plugin {
|
|
16
|
+
async load() {
|
|
17
|
+
this.app.use(ACLRolesCheckProvider);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type MockClientApplication = ReturnType<typeof createMockClient>;
|
|
22
|
+
|
|
23
|
+
const renderApp = (app: MockClientApplication) => {
|
|
24
|
+
const Root = app.getRootComponent();
|
|
25
|
+
render(<Root />);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const waitForGetRequests = async (app: MockClientApplication, urls: string[]) => {
|
|
29
|
+
await waitFor(
|
|
30
|
+
() => {
|
|
31
|
+
const history = app.apiMock.history.get.map((request) => request.url);
|
|
32
|
+
expect(history).toEqual(expect.arrayContaining(urls));
|
|
33
|
+
},
|
|
34
|
+
{ timeout: 3000 },
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const setupApp = (pmList: any[]) => {
|
|
39
|
+
const app = createMockClient({
|
|
40
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
41
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/plugin-manager'] },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.apiMock.onGet('/auth:check').reply(200, {
|
|
45
|
+
data: { id: 1, nickname: 'Super Admin' },
|
|
46
|
+
});
|
|
47
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
48
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
49
|
+
});
|
|
50
|
+
app.apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app' } });
|
|
51
|
+
app.apiMock.onGet('roles:check').reply(200, {
|
|
52
|
+
data: { role: 'root', snippets: ['pm', 'pm.system-settings.system-settings'] },
|
|
53
|
+
});
|
|
54
|
+
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, { data: [] });
|
|
55
|
+
app.apiMock.onGet('systemSettings:get').reply(200, {
|
|
56
|
+
data: {
|
|
57
|
+
id: 1,
|
|
58
|
+
title: 'NocoBase',
|
|
59
|
+
raw_title: 'NocoBase',
|
|
60
|
+
enabledLanguages: ['en-US'],
|
|
61
|
+
logo: null,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
app.apiMock.onGet('pm:list').reply(200, { data: pmList });
|
|
65
|
+
app.apiMock.onGet('pm:listEnabledV2').reply(200, { data: [] });
|
|
66
|
+
|
|
67
|
+
// pm:* mutations default to GET in axios when called via api.request without method
|
|
68
|
+
app.apiMock.onGet('pm:enable').reply(200, { data: {} });
|
|
69
|
+
app.apiMock.onGet('pm:disable').reply(200, { data: {} });
|
|
70
|
+
app.apiMock.onGet('pm:remove').reply(200, { data: {} });
|
|
71
|
+
|
|
72
|
+
return app;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe('plugin-manager page', () => {
|
|
76
|
+
it('fires pm:enable when toggling switch on a disabled plugin', async () => {
|
|
77
|
+
const app = setupApp([
|
|
78
|
+
{
|
|
79
|
+
name: 'demo-plugin',
|
|
80
|
+
packageName: '@nocobase/demo-plugin',
|
|
81
|
+
displayName: 'Demo plugin',
|
|
82
|
+
description: 'A demo',
|
|
83
|
+
enabled: false,
|
|
84
|
+
builtIn: false,
|
|
85
|
+
removable: true,
|
|
86
|
+
version: '0.1.0',
|
|
87
|
+
isCompatible: true,
|
|
88
|
+
keywords: [],
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
renderApp(app);
|
|
93
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'pm:list']);
|
|
94
|
+
|
|
95
|
+
const card = await screen.findByRole('button', { name: 'Demo plugin' });
|
|
96
|
+
const switchControl = within(card.closest('.ant-card') as HTMLElement).getByRole('switch');
|
|
97
|
+
expect(switchControl).toHaveAttribute('aria-checked', 'false');
|
|
98
|
+
|
|
99
|
+
fireEvent.click(switchControl);
|
|
100
|
+
|
|
101
|
+
await waitFor(() => {
|
|
102
|
+
const enableCall = app.apiMock.history.get.find((req) => req.url === 'pm:enable');
|
|
103
|
+
expect(enableCall).toBeDefined();
|
|
104
|
+
expect(enableCall?.params).toMatchObject({ filterByTk: 'demo-plugin' });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('fires pm:disable after confirm when toggling switch on an enabled plugin', async () => {
|
|
109
|
+
const app = setupApp([
|
|
110
|
+
{
|
|
111
|
+
name: 'demo-plugin',
|
|
112
|
+
packageName: '@nocobase/demo-plugin',
|
|
113
|
+
displayName: 'Demo plugin',
|
|
114
|
+
description: 'A demo',
|
|
115
|
+
enabled: true,
|
|
116
|
+
builtIn: false,
|
|
117
|
+
removable: true,
|
|
118
|
+
version: '0.1.0',
|
|
119
|
+
isCompatible: true,
|
|
120
|
+
keywords: [],
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
renderApp(app);
|
|
125
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'pm:list']);
|
|
126
|
+
|
|
127
|
+
const card = await screen.findByRole('button', { name: 'Demo plugin' });
|
|
128
|
+
const switchControl = within(card.closest('.ant-card') as HTMLElement).getByRole('switch');
|
|
129
|
+
expect(switchControl).toHaveAttribute('aria-checked', 'true');
|
|
130
|
+
|
|
131
|
+
fireEvent.click(switchControl);
|
|
132
|
+
|
|
133
|
+
const confirmTitle = await screen.findByText('Are you sure to disable this plugin?');
|
|
134
|
+
const confirmDialog = confirmTitle.closest('.ant-modal-confirm') as HTMLElement;
|
|
135
|
+
expect(confirmDialog).not.toBeNull();
|
|
136
|
+
const okButton = within(confirmDialog).getByText('OK');
|
|
137
|
+
fireEvent.click(okButton);
|
|
138
|
+
|
|
139
|
+
await waitFor(() => {
|
|
140
|
+
const disableCall = app.apiMock.history.get.find((req) => req.url === 'pm:disable');
|
|
141
|
+
expect(disableCall).toBeDefined();
|
|
142
|
+
expect(disableCall?.params).toMatchObject({ filterByTk: 'demo-plugin' });
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('fires pm:remove after Popconfirm on a removable plugin', async () => {
|
|
147
|
+
const app = setupApp([
|
|
148
|
+
{
|
|
149
|
+
name: 'demo-plugin',
|
|
150
|
+
packageName: '@nocobase/demo-plugin',
|
|
151
|
+
displayName: 'Demo plugin',
|
|
152
|
+
description: 'A demo',
|
|
153
|
+
enabled: false,
|
|
154
|
+
builtIn: false,
|
|
155
|
+
removable: true,
|
|
156
|
+
version: '0.1.0',
|
|
157
|
+
isCompatible: true,
|
|
158
|
+
keywords: [],
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
renderApp(app);
|
|
163
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'pm:list']);
|
|
164
|
+
|
|
165
|
+
const removeLink = await screen.findByText('Remove');
|
|
166
|
+
fireEvent.click(removeLink);
|
|
167
|
+
|
|
168
|
+
const yesButton = await screen.findByRole('button', { name: 'Yes' });
|
|
169
|
+
fireEvent.click(yesButton);
|
|
170
|
+
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
const removeCall = app.apiMock.history.get.find((req) => req.url === 'pm:remove');
|
|
173
|
+
expect(removeCall).toBeDefined();
|
|
174
|
+
expect(removeCall?.params).toMatchObject({ filterByTk: 'demo-plugin' });
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|