@nocobase/client-v2 2.2.0-beta.9 → 3.0.0-alpha.1
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/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +1 -0
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +190 -141
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +195 -146
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +185 -6
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/index.ts +2 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +111 -16
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +78 -32
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +110 -70
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -9,21 +9,23 @@
|
|
|
9
9
|
|
|
10
10
|
import { createCollectionContextMeta, useFlowEngine } from '@nocobase/flow-engine';
|
|
11
11
|
import React, { createContext, type FC, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
-
import {
|
|
12
|
+
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
|
13
13
|
import { useACLRoleContext } from '../acl';
|
|
14
14
|
import type { Application } from '../Application';
|
|
15
|
-
import { getCurrentV2RedirectPath,
|
|
15
|
+
import { getCurrentV2RedirectPath, redirectToV2Signin } from '../authRedirect';
|
|
16
16
|
import { AppNotFound } from '../components';
|
|
17
17
|
import { PluginFlowEngine } from '../flow';
|
|
18
18
|
import {
|
|
19
|
-
ADMIN_LAYOUT_MODEL_UID,
|
|
20
19
|
AdminLayoutMenuItemModel,
|
|
21
20
|
AdminLayoutModel,
|
|
22
21
|
AppSwitcherActionPanelModel,
|
|
23
22
|
} from '../flow/admin-shell/admin-layout';
|
|
24
23
|
import { useApp } from '../hooks/useApp';
|
|
25
24
|
import { Plugin } from '../Plugin';
|
|
25
|
+
import type { PluginClass } from '../PluginManager';
|
|
26
26
|
import { AdminSettingsLayoutModel } from '../settings-center';
|
|
27
|
+
import { SettingsDocumentRedirect } from '../settings-app/SettingsDocumentRedirect';
|
|
28
|
+
import { type CurrentUserAuthStatus, setCurrentUserAuthStatus } from './currentUserAuthStatus';
|
|
27
29
|
import { LocalePlugin } from './plugins/LocalePlugin';
|
|
28
30
|
|
|
29
31
|
export type CurrentUserState = {
|
|
@@ -33,8 +35,6 @@ export type CurrentUserState = {
|
|
|
33
35
|
loading: boolean;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
type CurrentUserAuthStatus = 'unknown' | 'authenticated' | 'unauthenticated' | 'redirecting';
|
|
37
|
-
|
|
38
38
|
type CurrentUserInternalState = CurrentUserState & {
|
|
39
39
|
authStatus: CurrentUserAuthStatus;
|
|
40
40
|
error?: Error | null;
|
|
@@ -181,6 +181,19 @@ const DataSourceBootstrapProvider: FC = ({ children }) => {
|
|
|
181
181
|
return <>{children}</>;
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
function redirectUnauthenticatedRoute(
|
|
185
|
+
app: Application,
|
|
186
|
+
location: { pathname: string; search?: string; hash?: string },
|
|
187
|
+
navigate: ReturnType<typeof useNavigate>,
|
|
188
|
+
) {
|
|
189
|
+
const redirectPath = getCurrentV2RedirectPath(app, location);
|
|
190
|
+
if (app.pluginSettingsManager.getRouteName('') === 'settings.') {
|
|
191
|
+
redirectToV2Signin(app, redirectPath);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
navigate(`/signin?redirect=${encodeURIComponent(redirectPath)}`, { replace: true });
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
const CurrentUserProvider: FC = ({ children }) => {
|
|
185
198
|
const app = useApp<Application>();
|
|
186
199
|
const location = useLocation();
|
|
@@ -234,9 +247,17 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
234
247
|
if (user?.id == null) {
|
|
235
248
|
// 用 react-router navigate (虚拟跳转)而不是 location.replace, 这样如果有其他响应拦截器已经发起了 window.location.href 整页跳转(例如 2FA 插件接收到服务端 302 重定向), 真实跳转可以胜出 navigate, 不会被这里的 signin 重定向覆盖。
|
|
236
249
|
setState({ loading: true, authStatus: 'unauthenticated', error: null });
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
redirectUnauthenticatedRoute(app, locationRef.current, navigate);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
await app.apiClient.auth.syncCookies();
|
|
256
|
+
} catch {
|
|
257
|
+
// Cookie bootstrap is best-effort; auth:check remains the source of truth for the current page load.
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!mounted) {
|
|
240
261
|
return;
|
|
241
262
|
}
|
|
242
263
|
|
|
@@ -267,9 +288,7 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
267
288
|
const isAuthError = errorLike?.response?.status === 401 || errorLike?.status === 401;
|
|
268
289
|
if (isAuthError) {
|
|
269
290
|
setState({ loading: true, authStatus: 'unauthenticated', error: null });
|
|
270
|
-
|
|
271
|
-
replace: true,
|
|
272
|
-
});
|
|
291
|
+
redirectUnauthenticatedRoute(app, locationRef.current, navigate);
|
|
273
292
|
return;
|
|
274
293
|
}
|
|
275
294
|
setState({
|
|
@@ -287,6 +306,10 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
287
306
|
};
|
|
288
307
|
}, [app, authCheckRouteState, navigate]);
|
|
289
308
|
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
setCurrentUserAuthStatus(app, state.authStatus);
|
|
311
|
+
}, [app, state.authStatus]);
|
|
312
|
+
|
|
290
313
|
if (state.error) {
|
|
291
314
|
throw state.error;
|
|
292
315
|
}
|
|
@@ -298,18 +321,7 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
298
321
|
return <CurrentUserContext.Provider value={contextValue}>{children}</CurrentUserContext.Provider>;
|
|
299
322
|
};
|
|
300
323
|
|
|
301
|
-
|
|
302
|
-
const app = useApp<Application>();
|
|
303
|
-
const hasToken = !!app?.apiClient?.auth?.token;
|
|
304
|
-
const targetPath = getDefaultV2AdminRedirectPath(app);
|
|
305
|
-
|
|
306
|
-
if (!hasToken) {
|
|
307
|
-
// 用 react-router <Navigate /> 而非 location.replace, 避免覆盖同时段其它响应拦截器触发的 window.location.href (例如 2FA 接收到服务端 302 时设置的整页跳转)。
|
|
308
|
-
return <Navigate replace to={`/signin?redirect=${encodeURIComponent(targetPath)}`} />;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return <Navigate replace to="/admin" />;
|
|
312
|
-
};
|
|
324
|
+
CurrentUserProvider.displayName = 'CurrentUserProvider';
|
|
313
325
|
|
|
314
326
|
/**
|
|
315
327
|
* client-v2 使用的内建插件集合。
|
|
@@ -334,56 +346,11 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
334
346
|
AppSwitcherActionPanelModel,
|
|
335
347
|
AdminSettingsLayoutModel,
|
|
336
348
|
});
|
|
337
|
-
this.app.layoutManager.registerLayout({
|
|
338
|
-
routeName: 'admin',
|
|
339
|
-
routePath: '/admin',
|
|
340
|
-
uid: ADMIN_LAYOUT_MODEL_UID,
|
|
341
|
-
layoutModelClass: 'AdminLayoutModel',
|
|
342
|
-
});
|
|
343
349
|
|
|
344
|
-
this.app
|
|
345
|
-
key: 'plugin-manager',
|
|
346
|
-
title: this.app.i18n.t('Plugin manager'),
|
|
347
|
-
icon: 'ApiOutlined',
|
|
348
|
-
aclSnippet: 'pm',
|
|
349
|
-
sort: -200,
|
|
350
|
-
});
|
|
351
|
-
this.app.pluginSettingsManager.addPageTabItem({
|
|
352
|
-
menuKey: 'plugin-manager',
|
|
353
|
-
key: 'index',
|
|
354
|
-
title: this.app.i18n.t('Plugin manager'),
|
|
355
|
-
componentLoader: () => import('../settings-center/plugin-manager'),
|
|
356
|
-
aclSnippet: 'pm',
|
|
357
|
-
sort: -200,
|
|
358
|
-
});
|
|
359
|
-
this.app.pluginSettingsManager.addMenuItem({
|
|
360
|
-
key: 'system-settings',
|
|
361
|
-
title: this.app.i18n.t('System settings'),
|
|
362
|
-
icon: 'SettingOutlined',
|
|
363
|
-
aclSnippet: 'pm.system-settings.system-settings',
|
|
364
|
-
});
|
|
365
|
-
this.app.pluginSettingsManager.addPageTabItem({
|
|
366
|
-
menuKey: 'system-settings',
|
|
367
|
-
key: 'index',
|
|
368
|
-
title: this.app.i18n.t('System settings'),
|
|
369
|
-
componentLoader: () => import('../settings-center/SystemSettingsPage'),
|
|
370
|
-
aclSnippet: 'pm.system-settings.system-settings',
|
|
371
|
-
});
|
|
372
|
-
// 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.
|
|
373
|
-
this.app.pluginSettingsManager.addMenuItem({
|
|
374
|
-
key: 'security',
|
|
375
|
-
title: this.app.i18n.t('Security'),
|
|
376
|
-
icon: 'SafetyOutlined',
|
|
377
|
-
aclSnippet: 'pm.security',
|
|
378
|
-
});
|
|
350
|
+
registerDefaultSettings(this.app);
|
|
379
351
|
}
|
|
380
352
|
|
|
381
353
|
addRoutes() {
|
|
382
|
-
this.router.add('root', {
|
|
383
|
-
path: '/',
|
|
384
|
-
element: <RootRedirect />,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
354
|
this.router.add('not-found', {
|
|
388
355
|
path: '*',
|
|
389
356
|
Component: AppNotFound,
|
|
@@ -391,7 +358,7 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
391
358
|
|
|
392
359
|
this.router.add('admin.settings', {
|
|
393
360
|
path: '/admin/settings',
|
|
394
|
-
|
|
361
|
+
Component: SettingsDocumentRedirect,
|
|
395
362
|
});
|
|
396
363
|
this.router.add('admin.settings.route-empty', {
|
|
397
364
|
path: '*',
|
|
@@ -407,4 +374,77 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
|
|
|
407
374
|
}
|
|
408
375
|
}
|
|
409
376
|
|
|
377
|
+
function registerDefaultSettings(app: Application) {
|
|
378
|
+
app.pluginSettingsManager.addMenuItem({
|
|
379
|
+
key: 'plugin-manager',
|
|
380
|
+
title: app.i18n.t('Plugin manager'),
|
|
381
|
+
icon: 'ApiOutlined',
|
|
382
|
+
aclSnippet: 'pm',
|
|
383
|
+
sort: -200,
|
|
384
|
+
});
|
|
385
|
+
app.pluginSettingsManager.addPageTabItem({
|
|
386
|
+
menuKey: 'plugin-manager',
|
|
387
|
+
key: 'index',
|
|
388
|
+
title: app.i18n.t('Plugin manager'),
|
|
389
|
+
componentLoader: () => import('../settings-center/plugin-manager'),
|
|
390
|
+
aclSnippet: 'pm',
|
|
391
|
+
sort: -200,
|
|
392
|
+
});
|
|
393
|
+
app.pluginSettingsManager.addMenuItem({
|
|
394
|
+
key: 'system-settings',
|
|
395
|
+
title: app.i18n.t('System settings'),
|
|
396
|
+
icon: 'SettingOutlined',
|
|
397
|
+
aclSnippet: 'pm.system-settings.system-settings',
|
|
398
|
+
});
|
|
399
|
+
app.pluginSettingsManager.addPageTabItem({
|
|
400
|
+
menuKey: 'system-settings',
|
|
401
|
+
key: 'index',
|
|
402
|
+
title: app.i18n.t('System settings'),
|
|
403
|
+
componentLoader: () => import('../settings-center/SystemSettingsPage'),
|
|
404
|
+
aclSnippet: 'pm.system-settings.system-settings',
|
|
405
|
+
});
|
|
406
|
+
// 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.
|
|
407
|
+
app.pluginSettingsManager.addMenuItem({
|
|
408
|
+
key: 'security',
|
|
409
|
+
title: app.i18n.t('Security'),
|
|
410
|
+
icon: 'SafetyOutlined',
|
|
411
|
+
aclSnippet: 'pm.security',
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Internal built-in runtime for the standalone Client V2 settings entry.
|
|
417
|
+
* It intentionally shares the existing plugin lane and runtime providers but
|
|
418
|
+
* does not register the Admin Layout.
|
|
419
|
+
*/
|
|
420
|
+
export class SettingsBuildInPlugin extends Plugin<any, Application> {
|
|
421
|
+
async afterAdd() {
|
|
422
|
+
await this.app.pm.add(PluginFlowEngine);
|
|
423
|
+
await this.app.pm.add(LocalePlugin as unknown as PluginClass, { name: 'builtin-locale' });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async load() {
|
|
427
|
+
this.app.use(CurrentUserProvider);
|
|
428
|
+
this.app.use(DataSourceBootstrapProvider);
|
|
429
|
+
this.app.flowEngine.registerModels({ AdminSettingsLayoutModel });
|
|
430
|
+
|
|
431
|
+
this.router.add('settings', {
|
|
432
|
+
path: '/settings',
|
|
433
|
+
authCheck: true,
|
|
434
|
+
componentLoader: () => import('../settings-center/AdminSettingsLayout'),
|
|
435
|
+
});
|
|
436
|
+
this.router.add('settingsDetails', {
|
|
437
|
+
path: '/settings',
|
|
438
|
+
authCheck: true,
|
|
439
|
+
Component: Outlet,
|
|
440
|
+
});
|
|
441
|
+
this.router.add('settings.route-empty', {
|
|
442
|
+
path: '*',
|
|
443
|
+
Component: Outlet,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
registerDefaultSettings(this.app);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
410
450
|
export { NocoBaseBuildInPlugin as NocoBaseBuildInPluginV2 };
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { Application, type ApplicationOptions } from '../index';
|
|
11
|
+
import { SettingsPluginSettingsManager } from './SettingsPluginSettingsManager';
|
|
12
|
+
import { SettingsRouterManager } from './SettingsRouterManager';
|
|
13
|
+
import { SettingsShell } from './SettingsShell';
|
|
14
|
+
|
|
15
|
+
export class SettingsApplication extends Application {
|
|
16
|
+
/** 设置中心的标记,插件通过 `isSettingsApp(app)` 读它,避免反向依赖这个类。 */
|
|
17
|
+
readonly isSettingsApp = true;
|
|
18
|
+
|
|
19
|
+
protected addCustomProviders() {
|
|
20
|
+
super.addCustomProviders();
|
|
21
|
+
this.use(SettingsShell);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected createRouterManager(options: ApplicationOptions): SettingsRouterManager<this> {
|
|
25
|
+
return new SettingsRouterManager<this>(options.router, this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected createPluginSettingsManager(_options: ApplicationOptions): SettingsPluginSettingsManager<this> {
|
|
29
|
+
return new SettingsPluginSettingsManager<this>(this);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { css } from '@emotion/css';
|
|
11
|
+
import { observer } from '@nocobase/flow-engine';
|
|
12
|
+
import { theme as antdTheme } from 'antd';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
15
|
+
import { useSystemSettings } from '../flow/system-settings';
|
|
16
|
+
import { getSettingsHeaderColors } from './settingsTheme';
|
|
17
|
+
|
|
18
|
+
const brandClassName = css`
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
height: 100%;
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const logoImageClassName = css`
|
|
28
|
+
height: 24px;
|
|
29
|
+
width: auto;
|
|
30
|
+
max-width: 140px;
|
|
31
|
+
object-fit: contain;
|
|
32
|
+
display: block;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const titleClassName = css`
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
line-height: 1;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const suffixClassName = css`
|
|
44
|
+
line-height: 1;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
font-weight: 400;
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 设置中心顶栏品牌区。
|
|
51
|
+
*
|
|
52
|
+
* 沿用系统设置里的站点 logo / 标题,并在其后补一个弱化的「设置中心」副标题,
|
|
53
|
+
* 用于区分设置中心和业务端。
|
|
54
|
+
*/
|
|
55
|
+
export const SettingsBrand = observer(() => {
|
|
56
|
+
const { token } = antdTheme.useToken();
|
|
57
|
+
const headerColors = getSettingsHeaderColors(token);
|
|
58
|
+
const result = useSystemSettings();
|
|
59
|
+
const { t } = useTranslation();
|
|
60
|
+
const { t: tCollections } = useTranslation('lm-collections');
|
|
61
|
+
const title = tCollections(result?.data?.data?.title || 'NocoBase');
|
|
62
|
+
const logoUrl = result?.data?.data?.logo?.url;
|
|
63
|
+
|
|
64
|
+
if (result?.loading) {
|
|
65
|
+
return <div className={brandClassName} />;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={brandClassName}>
|
|
70
|
+
{logoUrl ? (
|
|
71
|
+
// 站点 logo 通常是为深色顶栏画的浅色图;设置中心顶栏是灰白的,
|
|
72
|
+
// 统一压成单色,避免浅色 logo 直接消失在白底上。
|
|
73
|
+
<img
|
|
74
|
+
alt={title}
|
|
75
|
+
className={logoImageClassName}
|
|
76
|
+
src={logoUrl}
|
|
77
|
+
style={{ filter: headerColors.dark ? 'brightness(0) invert(1)' : 'brightness(0)' }}
|
|
78
|
+
/>
|
|
79
|
+
) : (
|
|
80
|
+
<span className={titleClassName} style={{ color: headerColors.text, fontSize: token.fontSizeHeading4 }}>
|
|
81
|
+
{title}
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
84
|
+
<span
|
|
85
|
+
aria-hidden
|
|
86
|
+
style={{
|
|
87
|
+
alignSelf: 'center',
|
|
88
|
+
background: headerColors.text,
|
|
89
|
+
height: 16,
|
|
90
|
+
opacity: 0.3,
|
|
91
|
+
width: 1,
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
<span
|
|
95
|
+
className={suffixClassName}
|
|
96
|
+
style={{
|
|
97
|
+
color: headerColors.text,
|
|
98
|
+
fontSize: token.fontSize,
|
|
99
|
+
opacity: 0.75,
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{t('Settings center')}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
SettingsBrand.displayName = 'SettingsBrand';
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export { SettingsBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
@@ -0,0 +1,26 @@
|
|
|
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 React, { useEffect } from 'react';
|
|
11
|
+
import { useLocation } from 'react-router-dom';
|
|
12
|
+
import { useApp } from '../hooks/useApp';
|
|
13
|
+
import { resolveStandaloneSettingsPath } from './runtimePaths';
|
|
14
|
+
|
|
15
|
+
export function SettingsDocumentRedirect() {
|
|
16
|
+
const app = useApp();
|
|
17
|
+
const location = useLocation();
|
|
18
|
+
const sourcePath = `${location.pathname}${location.search}${location.hash}`;
|
|
19
|
+
const targetPath = resolveStandaloneSettingsPath(app, sourcePath, location.pathname);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
window.location.replace(targetPath);
|
|
23
|
+
}, [targetPath]);
|
|
24
|
+
|
|
25
|
+
return app.renderComponent('AppSpin');
|
|
26
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { observer } from '@nocobase/flow-engine';
|
|
11
|
+
import { ConfigProvider, Menu, theme as antdTheme } from 'antd';
|
|
12
|
+
import React, { useCallback, useMemo } from 'react';
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
15
|
+
import { useApp } from '../hooks/useApp';
|
|
16
|
+
import { useSettingsGroups } from '../settings-center/useSettingsGroups';
|
|
17
|
+
import { buildSettingsHeaderMenuTheme } from './settingsTheme';
|
|
18
|
+
|
|
19
|
+
const navStyle: React.CSSProperties = {
|
|
20
|
+
background: 'transparent',
|
|
21
|
+
borderBottom: 'none',
|
|
22
|
+
flex: 'auto',
|
|
23
|
+
minWidth: 0,
|
|
24
|
+
lineHeight: '46px',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 设置中心顶栏的一级导航。
|
|
29
|
+
*
|
|
30
|
+
* 应用、插件管理各自只有一个页面,直接当一级入口;其余全部收在「系统设置」下,
|
|
31
|
+
* 鼠标移上去展开下拉,也可以点进去从左栏走。
|
|
32
|
+
*/
|
|
33
|
+
export const SettingsGroupNav: React.FC = observer(() => {
|
|
34
|
+
const { t } = useTranslation();
|
|
35
|
+
const app = useApp();
|
|
36
|
+
const navigate = useNavigate();
|
|
37
|
+
const location = useLocation();
|
|
38
|
+
const { token } = antdTheme.useToken();
|
|
39
|
+
const { groups, activeGroupKey, currentTopLevelSetting, getGroupEntryPath } = useSettingsGroups();
|
|
40
|
+
const headerMenuTheme = useMemo(() => buildSettingsHeaderMenuTheme(token), [token]);
|
|
41
|
+
// 登录 / 找回密码等免鉴权页面共用同一个 shell,这些页面上不该出现设置导航。
|
|
42
|
+
const isAuthRoute = app.router.isSkippedAuthCheckRoute(location.pathname);
|
|
43
|
+
|
|
44
|
+
const handleClick = useCallback(
|
|
45
|
+
({ key }: { key: string }) => {
|
|
46
|
+
const [groupKey, settingName] = key.split('::');
|
|
47
|
+
const targetPath = settingName
|
|
48
|
+
? groups.find((group) => group.key === groupKey)?.settings.find((item) => item.name === settingName)?.path
|
|
49
|
+
: getGroupEntryPath(groupKey);
|
|
50
|
+
|
|
51
|
+
if (targetPath && targetPath !== location.pathname) {
|
|
52
|
+
navigate(targetPath);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
[getGroupEntryPath, groups, location.pathname, navigate],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// 单入口分组直接当一级项,标题取配置项自己的名字(顺带继承插件的翻译);
|
|
59
|
+
// 系统设置这种大分组挂上子项,鼠标移上去展开。
|
|
60
|
+
const items = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
groups.map((group) => {
|
|
63
|
+
if (group.settings.length <= 1) {
|
|
64
|
+
return {
|
|
65
|
+
key: group.key,
|
|
66
|
+
label: group.settings[0]?.label ?? t(group.title),
|
|
67
|
+
icon: group.settings[0]?.icon,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 已经站在这个分组里时不再弹下拉:左栏已经把同一份内容铺开了,再浮一层纯属打扰。
|
|
72
|
+
if (group.key === activeGroupKey) {
|
|
73
|
+
return { key: group.key, label: t(group.title) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
key: group.key,
|
|
78
|
+
label: t(group.title),
|
|
79
|
+
children: group.settings
|
|
80
|
+
.filter((setting) => !setting.hidden)
|
|
81
|
+
.map((setting) => ({
|
|
82
|
+
key: `${group.key}::${setting.name}`,
|
|
83
|
+
label: setting.label ?? setting.title,
|
|
84
|
+
icon: setting.icon,
|
|
85
|
+
})),
|
|
86
|
+
// 点标题本身也要能进去,而不是只能从下拉里挑。
|
|
87
|
+
onTitleClick: handleClick,
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
[activeGroupKey, groups, handleClick, t],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 子菜单标题只有在它的某个子项被选中时才会高亮,所以两个 key 都要给。
|
|
94
|
+
const selectedKeys = useMemo(() => {
|
|
95
|
+
if (!activeGroupKey) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return currentTopLevelSetting?.name
|
|
99
|
+
? [activeGroupKey, `${activeGroupKey}::${currentTopLevelSetting.name}`]
|
|
100
|
+
: [activeGroupKey];
|
|
101
|
+
}, [activeGroupKey, currentTopLevelSetting?.name]);
|
|
102
|
+
|
|
103
|
+
if (isAuthRoute || items.length <= 1) {
|
|
104
|
+
return <div style={{ flex: 'auto' }} />;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<ConfigProvider theme={headerMenuTheme}>
|
|
109
|
+
<Menu
|
|
110
|
+
mode="horizontal"
|
|
111
|
+
disabledOverflow={false}
|
|
112
|
+
selectedKeys={selectedKeys}
|
|
113
|
+
items={items}
|
|
114
|
+
style={navStyle}
|
|
115
|
+
triggerSubMenuAction="hover"
|
|
116
|
+
onClick={handleClick}
|
|
117
|
+
/>
|
|
118
|
+
</ConfigProvider>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
SettingsGroupNav.displayName = 'SettingsGroupNav';
|
|
123
|
+
|
|
124
|
+
export default SettingsGroupNav;
|
|
@@ -0,0 +1,38 @@
|
|
|
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 type { BaseApplication } from '../BaseApplication';
|
|
11
|
+
import { PluginSettingsManager } from '../PluginSettingsManager';
|
|
12
|
+
import { resolveSettingsAppScopeWithinPublicPath } from './settingsDocumentPath';
|
|
13
|
+
|
|
14
|
+
const SETTINGS_ROUTE_PREFIX = 'settings.';
|
|
15
|
+
const MAIN_SETTINGS_PATH_PREFIX = '/settings/';
|
|
16
|
+
|
|
17
|
+
export class SettingsPluginSettingsManager<
|
|
18
|
+
TApp extends BaseApplication<any> = BaseApplication<any>,
|
|
19
|
+
> extends PluginSettingsManager<TApp> {
|
|
20
|
+
getRouteName(name: string) {
|
|
21
|
+
return `${SETTINGS_ROUTE_PREFIX}${name}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getRoutePath(name: string) {
|
|
25
|
+
const appScope = resolveSettingsAppScopeWithinPublicPath(this.app.getPublicPath(), this.app.router.getBasename?.());
|
|
26
|
+
const pathPrefix = appScope ? '/' : MAIN_SETTINGS_PATH_PREFIX;
|
|
27
|
+
const separatorIndex = name.indexOf('.');
|
|
28
|
+
const menuName = separatorIndex < 0 ? name : name.slice(0, separatorIndex);
|
|
29
|
+
const pageName = separatorIndex < 0 ? undefined : name.slice(separatorIndex + 1);
|
|
30
|
+
const menuPath = `${pathPrefix}${menuName}`;
|
|
31
|
+
|
|
32
|
+
if (!pageName || pageName === 'index') {
|
|
33
|
+
return menuPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `${menuPath}/${pageName}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 type { BaseApplication } from '../BaseApplication';
|
|
11
|
+
import { RouterManager, type RouteType } from '../RouterManager';
|
|
12
|
+
import { resolveSettingsAppScopeWithinPublicPath } from './settingsDocumentPath';
|
|
13
|
+
|
|
14
|
+
function isSettingsOwnedRoute(name: string) {
|
|
15
|
+
return (
|
|
16
|
+
name === 'not-found' ||
|
|
17
|
+
name === 'settings' ||
|
|
18
|
+
name.startsWith('settings.') ||
|
|
19
|
+
name === 'settingsDetails' ||
|
|
20
|
+
name.startsWith('settingsDetails.') ||
|
|
21
|
+
name === 'auth' ||
|
|
22
|
+
name.startsWith('auth.') ||
|
|
23
|
+
name === '2fa' ||
|
|
24
|
+
name.startsWith('2fa.')
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isSettingsAuthenticationRoute(name: string) {
|
|
29
|
+
return name === 'auth' || name.startsWith('auth.') || name === '2fa' || name.startsWith('2fa.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class SettingsRouterManager<
|
|
33
|
+
TApp extends BaseApplication<any> = BaseApplication<any>,
|
|
34
|
+
> extends RouterManager<TApp> {
|
|
35
|
+
private rebaseScopedSettingsRoute(route: RouteType) {
|
|
36
|
+
const appScope = resolveSettingsAppScopeWithinPublicPath(this.app.getPublicPath(), this.getBasename());
|
|
37
|
+
if (!route.path?.startsWith('/') || !appScope) {
|
|
38
|
+
return route;
|
|
39
|
+
}
|
|
40
|
+
if (route.path === '/settings') {
|
|
41
|
+
return {
|
|
42
|
+
...route,
|
|
43
|
+
path: '/',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!route.path.startsWith('/settings/')) {
|
|
47
|
+
return route;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...route,
|
|
51
|
+
path: route.path.slice('/settings'.length),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private rebaseAuthenticationRoute(route: RouteType) {
|
|
56
|
+
if (!route.path?.startsWith('/')) {
|
|
57
|
+
return route;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const settingsRoot = this.app.pluginSettingsManager.getRoutePath('').replace(/\/+$/, '');
|
|
61
|
+
if (!settingsRoot) {
|
|
62
|
+
return route;
|
|
63
|
+
}
|
|
64
|
+
if (route.path === settingsRoot || route.path.startsWith(`${settingsRoot}/`)) {
|
|
65
|
+
return route;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...route,
|
|
70
|
+
path: `${settingsRoot}/${route.path.replace(/^\/+/, '')}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
add(name: string, route: RouteType) {
|
|
75
|
+
if (!isSettingsOwnedRoute(name)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const scopedRoute = this.rebaseScopedSettingsRoute(route);
|
|
80
|
+
const ownedRoute = isSettingsAuthenticationRoute(name) ? this.rebaseAuthenticationRoute(scopedRoute) : scopedRoute;
|
|
81
|
+
|
|
82
|
+
super.add(
|
|
83
|
+
name,
|
|
84
|
+
name === 'settingsDetails' || name.startsWith('settingsDetails.')
|
|
85
|
+
? {
|
|
86
|
+
...ownedRoute,
|
|
87
|
+
authCheck: true,
|
|
88
|
+
}
|
|
89
|
+
: ownedRoute,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|