build: sync config

This commit is contained in:
jialin
2026-04-24 14:28:30 +08:00
committed by jialin
parent 48b357b1c6
commit 6c161c6ae7
55 changed files with 731 additions and 90 deletions
+26 -2
View File
@@ -1,6 +1,9 @@
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { getGPUStackPlugin } from '@/plugins';
import { GPUStackPluginManager } from '@/plugins/manager';
import { mergeEnterpriseRoutes } from '@/plugins/route-merger';
import { requestConfig } from '@/request-config';
import {
queryCurrentUserState,
@@ -14,6 +17,7 @@ import {
readState,
writeState
} from '@/utils/localstore/index';
import '@gpustack/core-ui/style.css';
import { RequestConfig, history } from '@umijs/max';
import { message } from 'antd';
@@ -33,8 +37,18 @@ const checkDefaultPage = async (userInfo: any) => {
export async function getInitialState(): Promise<{
fetchUserInfo: () => Promise<Global.UserInfo>;
currentUser?: Global.UserInfo;
pluginData?: Record<string, any>;
}> {
const { location } = history;
const enterprisePlugin = getGPUStackPlugin();
// initialize plugins and merge enterprise locales
let pluginData = {};
try {
pluginData = await GPUStackPluginManager.initialize();
} catch (error) {
console.error('Failed to initialize plugins:', error);
}
const getUpdateCheck = async () => {
try {
@@ -103,11 +117,13 @@ export async function getInitialState(): Promise<{
checkDefaultPage(userInfo);
return {
fetchUserInfo,
currentUser: userInfo
currentUser: userInfo,
pluginData
};
}
return {
fetchUserInfo
fetchUserInfo,
pluginData
};
}
@@ -115,3 +131,11 @@ export const request: RequestConfig = {
baseURL: `/${GPUSTACK_API_BASE_URL}`,
...requestConfig
};
/**
* 动态修改路由
* @param routes 路由配置
*/
export function patchClientRoutes({ routes }: any) {
mergeEnterpriseRoutes(routes);
}
+2 -2
View File
@@ -1,9 +1,9 @@
import {
InfiniteScroller,
ResizeContainer,
TemplateCardSkeleton
TemplateCardSkeleton,
useScrollerContext
} from '@gpustack/core-ui';
import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context';
import { Spin } from 'antd';
import React from 'react';
import styled from 'styled-components';
+1
View File
@@ -0,0 +1 @@
export const COLOR_PRIMARY = '#007BFF';
+1 -1
View File
@@ -1,4 +1,4 @@
import { COLOR_PRIMARY } from './index';
import { COLOR_PRIMARY } from './constants';
export default {
'root-entry-name': 'variable',
+3 -2
View File
@@ -1,10 +1,11 @@
import { COLOR_PRIMARY } from './constants';
import dark from './dark';
import light from './light';
export const COLOR_PRIMARY = '#007BFF';
export default {
light,
dark,
colorPrimary: COLOR_PRIMARY
};
export { COLOR_PRIMARY };
+1 -1
View File
@@ -1,4 +1,4 @@
import { COLOR_PRIMARY } from './index';
import { COLOR_PRIMARY } from './constants';
export default {
'root-entry-name': 'variable',
+17
View File
@@ -0,0 +1,17 @@
import { userSettingsHelperAtom } from '@/atoms/settings';
import { getAtomStorage, setAtomStorage } from '@/atoms/utils';
export default function useUserSettingsStorage() {
const setStorageUserSettings = (value: Record<string, any>) => {
setAtomStorage(userSettingsHelperAtom, value || {});
};
const getStorageUserSettings = () => {
return getAtomStorage(userSettingsHelperAtom);
};
return {
setStorageUserSettings,
getStorageUserSettings
};
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { userSettingsHelperAtom } from '@/atoms/settings';
import themeConfig from '@/config/theme';
import { useAtom } from 'jotai';
import { useEffect, useMemo } from 'react';
import { userSettingsHelperAtom } from '../atoms/settings';
import themeConfig from '../config/theme';
type Theme = 'light' | 'realDark' | 'auto';
+15
View File
@@ -3,6 +3,7 @@ import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links';
import useBodyScroll from '@/hooks/use-body-scroll';
import { logout } from '@/pages/login/apis';
import { getGPUStackPlugin } from '@/plugins';
import { useModel } from '@@/plugin-model';
import {
DiscordOutlined,
@@ -105,6 +106,7 @@ const CustomItem = styled.div`
export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
const { isDarkTheme } = props;
const pluginManager = getGPUStackPlugin();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [modal, contextHolder] = Modal.useModal();
const [version] = useAtom(GPUStackVersionAtom);
@@ -117,6 +119,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
};
const { initialState } = initialInfo;
console.log('plugin+++++++++', pluginManager);
const navigate = useNavigate();
@@ -291,6 +294,18 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
</NewLabel>
)}
</div>
<Button
type="text"
size="small"
onClick={() => {
history.push('/settings');
}}
style={{
color: 'var(--ant-color-text-tertiary)'
}}
>
<IconFont type="icon-settings-02" style={{ fontSize: 18 }} />
</Button>
<DropdownActions menu={{ ...helpMenu }} popupRender={helpPopupRender}>
<IconWrapper>
<IconFont
+17 -3
View File
@@ -3,8 +3,10 @@ import { userAtom } from '@/atoms/user';
import DarkMask from '@/components/dark-mask';
import routeCachekey from '@/config/route-cachekey';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { COLOR_PRIMARY } from '@/config/theme';
import useOverlayScroller from '@/hooks/use-overlay-scroller';
import useUserSettings from '@/hooks/use-user-settings';
import useUserSettingsStorage from '@/hooks/use-user-settings-storage';
import useAddResource from '@/pages/dashboard/hooks/use-add-resource';
import { logout } from '@/pages/login/apis';
import {
@@ -128,6 +130,7 @@ export default (props: any) => {
});
const [, contextHolder] = Modal.useModal();
const { themeData, setUserSettings, userSettings } = useUserSettings();
const userSettingsStorage = useUserSettingsStorage();
const [userInfo] = useAtom(userAtom);
const [routeCache] = useAtom(routeCacheAtom);
const location = useLocation();
@@ -205,7 +208,7 @@ export default (props: any) => {
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
console.log('route++++++++', route, clientRoutes);
patchRoutes({
routes: route.children,
initialState: initialInfo.initialState
@@ -363,7 +366,13 @@ export default (props: any) => {
apiBaseUrl: GPUSTACK_API_BASE_URL,
theme: userSettings.theme,
iconUrl: '//at.alicdn.com/t/c/font_4613488_8fi68fmt1th.js',
isDarkTheme: userSettings.isDarkTheme
isDarkTheme: userSettings.isDarkTheme,
defaultColorPrimary: COLOR_PRIMARY
}}
hooks={{
useUserSettings: useUserSettings,
useUserSettingsStorage: () => userSettingsStorage,
useIntl: useIntl
}}
i18n={intl}
locale={{
@@ -371,7 +380,12 @@ export default (props: any) => {
setLocale: setLocale
}}
services={{
request: request
request: request,
router: {
push: (path: string) => navigate(path),
replace: (path: string) => navigate(path, { replace: true }),
goBack: () => navigate(-1)
}
}}
localStore={{
readColumnSettings,
@@ -1,7 +1,6 @@
// columns.ts
import { tableSorter } from '@/config/settings';
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { AutoTooltip, DropdownButtons, icons } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Tag } from 'antd';
import { ColumnsType } from 'antd/lib/table';
@@ -1,9 +1,9 @@
import {
InfiniteScroller,
ResizeContainer,
TemplateCardSkeleton
TemplateCardSkeleton,
useScrollerContext
} from '@gpustack/core-ui';
import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context';
import { Spin } from 'antd';
import React from 'react';
import backendListCss from '../styles/backend-list.less';
+1 -1
View File
@@ -7,7 +7,7 @@ import {
GPUDriverMap,
ManufacturerMap
} from '@/pages/resources/config/gpu-driver';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
import jsYaml from 'js-yaml';
import { trim } from 'lodash';
@@ -1,6 +1,10 @@
import { DownloadOutlined } from '@ant-design/icons';
import { DropdownButtons, IconFont, useDownloadLogs } from '@gpustack/core-ui';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import {
DropdownButtons,
IconFont,
icons,
useDownloadLogs
} from '@gpustack/core-ui';
import { BENCHMARKS_API } from '../apis';
import { BenchmarkStatusValueMap } from '../config';
import { BenchmarkListItem as ListItem } from '../config/types';
+6 -2
View File
@@ -1,7 +1,11 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui';
import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context';
import {
CollapsePanel,
IconFont,
ScrollSpyTabs,
useWrapperContext
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import {
+1 -1
View File
@@ -10,9 +10,9 @@ import {
FilterBar,
IconFont,
Table as SealTable,
TableOrder,
TableProvider
} from '@gpustack/core-ui';
import { TableOrder } from '@gpustack/core-ui/lib/components/table/types';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { message } from 'antd';
@@ -1,6 +1,4 @@
import { DropdownActions } from '@gpustack/core-ui';
import ListMap from '@gpustack/core-ui/lib/components/dynamic-form/components/list-map';
import { FieldSchema } from '@gpustack/core-ui/lib/components/dynamic-form/config/types';
import { DropdownActions, type FieldSchema, ListMap } from '@gpustack/core-ui';
import { PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
@@ -14,9 +14,9 @@ import {
IconFont,
LabelSelector,
Select as SealSelect,
useAppUtils
useAppUtils,
type CollapseContainerProps
} from '@gpustack/core-ui';
import { type CollapseContainerProps } from '@gpustack/core-ui/lib/components/collapse-container';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Button, Form } from 'antd';
@@ -1,6 +1,4 @@
import ListMap from '@gpustack/core-ui/lib/components/dynamic-form/components/list-map';
import { statusType } from '@gpustack/core-ui/lib/components/dynamic-form/config/types';
import useValidateFields from '@gpustack/core-ui/lib/components/dynamic-form/hooks/use-validate-fields';
import { ListMap, type statusType, useValidateFields } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React, { forwardRef, useState } from 'react';
@@ -1,4 +1,4 @@
import { FieldSchema } from '@gpustack/core-ui/lib/components/dynamic-form/config/types';
import { type FieldSchema } from '@gpustack/core-ui';
export const fields = {
volumes: {
+1 -1
View File
@@ -2,7 +2,7 @@ import { StatusMaps } from '@/config';
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
import { StatusType } from '@/config/types';
import { GPUsConfigs } from '@/pages/resources/config/gpu-driver';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
export const ClusterStatusValueMap = {
Provisioning: 'provisioning',
@@ -1,4 +1,4 @@
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
import React from 'react';
import { ProviderValueMap } from '.';
@@ -6,10 +6,10 @@ import {
AutoTooltip,
DropdownButtons,
GrafanaIcon,
StatusTag
StatusTag,
icons,
type TableColumnProps as SealColumnProps
} from '@gpustack/core-ui';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { ColumnProps as SealColumnProps } from '@gpustack/core-ui/lib/components/table/types';
import { useIntl } from '@umijs/max';
import { Tooltip } from 'antd';
import dayjs from 'dayjs';
@@ -1,6 +1,9 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
import { ColumnProps } from '@gpustack/core-ui/lib/components/table/types';
import {
AutoTooltip,
DropdownButtons,
type TableColumnProps
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import dayjs from 'dayjs';
import _ from 'lodash';
@@ -27,7 +30,7 @@ const actionItems = [
const usePoolsColumns = (
handleSelect: (val: string, record: ListItem) => void,
sortOrder?: string[]
): ColumnProps[] => {
): TableColumnProps[] => {
const intl = useIntl();
return useMemo(() => {
@@ -1,9 +1,9 @@
import {
InfiniteScroller,
ResizeContainer,
TemplateCardSkeleton
TemplateCardSkeleton,
useScrollerContext
} from '@gpustack/core-ui';
import { useScrollerContext } from '@gpustack/core-ui/lib/components/infinite-scroller/use-scroller-context';
import { Spin } from 'antd';
import React from 'react';
import styled from 'styled-components';
@@ -2,10 +2,10 @@ import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-bench
import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons';
import {
DropdownButtons,
type HandlerOptions,
IconFont,
useDownloadStream
} from '@gpustack/core-ui';
import { HandlerOptions } from '@gpustack/core-ui/lib/hooks/use-chunk-fetch';
import { useIntl } from '@umijs/max';
import { Progress, notification } from 'antd';
import dayjs from 'dayjs';
@@ -1,6 +1,5 @@
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { SimpleTable, StatusTag } from '@gpustack/core-ui';
import { type ColumnProps } from '@gpustack/core-ui/lib/components/simple-table';
import { SimpleTable, StatusTag, type ColumnProps } from '@gpustack/core-ui';
import { Progress, Tooltip } from 'antd';
import _ from 'lodash';
import { InstanceStatusMap, status } from '../../config';
+2 -2
View File
@@ -18,9 +18,9 @@ import {
DropdownActions,
DropdownButtons,
PageTools,
Table as SealTable
Table as SealTable,
TableOrder
} from '@gpustack/core-ui';
import { TableOrder } from '@gpustack/core-ui/lib/components/table/types';
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import { useMemoizedFn, useToggle } from 'ahooks';
import { Button, Space, message } from 'antd';
+1 -1
View File
@@ -1,5 +1,5 @@
import HotKeys from '@/config/hotkeys';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
import React from 'react';
import { modelCategoriesMap, modelSourceMap } from './index';
+1 -2
View File
@@ -3,8 +3,7 @@ import useSetChunkRequest from '@/hooks/use-chunk-request';
import { usePaginationStatus } from '@/hooks/use-pagination-status';
import { useTableMultiSort } from '@/hooks/use-table-sort';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { TableProvider } from '@gpustack/core-ui';
import { TableOrder } from '@gpustack/core-ui/lib/components/table/types';
import { TableOrder, TableProvider } from '@gpustack/core-ui';
import { useMemoizedFn } from 'ahooks';
import _ from 'lodash';
import qs from 'query-string';
+6 -2
View File
@@ -1,7 +1,11 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui';
import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context';
import {
CollapsePanel,
IconFont,
ScrollSpyTabs,
useWrapperContext
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
@@ -3,9 +3,13 @@ import { systemConfigAtom } from '@/atoms/system';
import { OPENAI_COMPATIBLE, tableSorter } from '@/config/settings';
import { TargetStatusValueMap } from '@/pages/model-routes/config';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { AutoTooltip, DropdownButtons, GrafanaIcon } from '@gpustack/core-ui';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { ColumnProps } from '@gpustack/core-ui/lib/components/table/types';
import {
AutoTooltip,
DropdownButtons,
GrafanaIcon,
icons,
type TableColumnProps
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Tooltip } from 'antd';
@@ -98,7 +102,7 @@ const useModelsColumns = ({
clusterList,
sortOrder,
targetList
}: ModelsColumnsHookProps & { targetList: any[] }): ColumnProps[] => {
}: ModelsColumnsHookProps & { targetList: any[] }): TableColumnProps[] => {
const intl = useIntl();
const systemConfig = useAtomValue(systemConfigAtom);
@@ -1,5 +1,5 @@
import useBodyScroll from '@/hooks/use-body-scroll';
import { PageSize } from '@gpustack/core-ui/lib/components/logs-viewer/config';
import { PageSize } from '@gpustack/core-ui';
import { useState } from 'react';
import { MODEL_INSTANCE_API } from '../apis';
import { InstanceRealtimeLogStatus } from '../config';
+1 -1
View File
@@ -1,6 +1,6 @@
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
import { ProviderEnum } from './providers';
export { maasProviderLabelMap, maasProviderOptions } from './providers';
+8 -4
View File
@@ -1,10 +1,14 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { json2Yaml, yaml2Json } from '@/pages/backends/config';
import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui';
import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context';
import useFinishFailed from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-finish-failed';
import useScrollActiveChange from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-scroll-active-change';
import {
CollapsePanel,
IconFont,
ScrollSpyTabs,
useFinishFailed,
useScrollActiveChange,
useWrapperContext
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
+1 -1
View File
@@ -1,6 +1,6 @@
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import { icons } from '@gpustack/core-ui';
export const TargetStatusValueMap: Record<string, string> = {
Active: 'active',
+8 -4
View File
@@ -1,10 +1,14 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { modelCategoriesMap } from '@/pages/llmodels/config';
import { CollapsePanel, IconFont, ScrollSpyTabs } from '@gpustack/core-ui';
import { useWrapperContext } from '@gpustack/core-ui/lib/components/column-wrapper/use-wrapper-context';
import useFinishFailed from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-finish-failed';
import useScrollActiveChange from '@gpustack/core-ui/lib/components/scroll-spy-tabs/use-scroll-active-change';
import {
CollapsePanel,
IconFont,
ScrollSpyTabs,
useFinishFailed,
useScrollActiveChange,
useWrapperContext
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
@@ -1,8 +1,11 @@
// columns.ts
import { tableSorter } from '@/config/settings';
import ModelTag from '@/pages/_components/model-tag';
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
import { type ColumnProps } from '@gpustack/core-ui/lib/components/table/types';
import {
AutoTooltip,
DropdownButtons,
type TableColumnProps
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import dayjs from 'dayjs';
import { useMemo } from 'react';
@@ -11,7 +14,7 @@ import { RouteItem } from '../config/types';
const useAccessColumns = (
handleSelect: (val: string, record: RouteItem) => void,
onCellClick?: (record: RouteItem, dataIndex: string) => void
): ColumnProps[] => {
): TableColumnProps[] => {
const intl = useIntl();
const filterActions = (record: RouteItem) => {
+1 -1
View File
@@ -11,9 +11,9 @@ import {
FilterBar,
IconFont,
Table as SealTable,
TableOrder,
TableProvider
} from '@gpustack/core-ui';
import { TableOrder } from '@gpustack/core-ui/lib/components/table/types';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { message } from 'antd';
+1 -1
View File
@@ -6,9 +6,9 @@ import {
AlertInfo,
ImageEditor as CanvasImageEditor,
IconFont,
processImage,
SingleImage
} from '@gpustack/core-ui';
import { processImage } from '@gpustack/core-ui/lib/components/image-editor/extract-image-colors';
import { useIntl } from '@umijs/max';
import { Divider } from 'antd';
import _ from 'lodash';
+22
View File
@@ -0,0 +1,22 @@
import { getGPUStackPlugin } from '@/plugins';
import { Result } from 'antd';
import React from 'react';
const SettingsPage: React.FC = () => {
const enterprisePlugin = getGPUStackPlugin();
const BrandingUI = enterprisePlugin?.components?.BrandingUI;
if (!BrandingUI) {
return (
<Result
status="404"
title="404"
subTitle="Enterprise settings page is unavailable."
/>
);
}
return <BrandingUI />;
};
export default SettingsPage;
+6 -2
View File
@@ -1,7 +1,11 @@
// columns.ts
import { tableSorter } from '@/config/settings';
import { AutoTooltip, DropdownButtons, IconFont } from '@gpustack/core-ui';
import icons from '@gpustack/core-ui/lib/components/icon-font/icons';
import {
AutoTooltip,
DropdownButtons,
IconFont,
icons
} from '@gpustack/core-ui';
import { useIntl, useModel } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Tag } from 'antd';
+23
View File
@@ -0,0 +1,23 @@
import { GPUStackPluginManager } from './manager';
import { AppPlugin } from './types';
/**
* Hook to access the enterprise plugin instance
*/
export const getGPUStackPlugin = (): AppPlugin | undefined => {
return GPUStackPluginManager.get('enterprise');
};
/**
* Hook to access a plugin by its name
*/
export const getPlugin = (name: string): AppPlugin | undefined => {
return GPUStackPluginManager.get(name);
};
/**
* Hook to check if a plugin is registered
*/
export const hasPlugin = (name: string): boolean => {
return GPUStackPluginManager.has(name);
};
+34
View File
@@ -0,0 +1,34 @@
import { addLocale } from '@umijs/max';
const mergedLocales = new Set<string>();
/**
* Merge enterprise plugin locales into the main application
*/
export function mergeEnterpriseLocales(locales: Record<string, any> = {}) {
if (!locales) {
return;
}
console.log('Merging enterprise plugin locales:', Object.keys(locales));
// Iterate over all locale configurations of the enterprise plugin
Object.entries(locales).forEach(([locale, messages]) => {
try {
if (mergedLocales.has(locale)) {
return;
}
// Merge the enterprise locale into the main application
addLocale(locale, messages, {
momentLocale: '',
// @ts-ignore
antd: locale
});
mergedLocales.add(locale);
console.log(`✓ Merged enterprise locale: ${locale}`);
} catch (error) {
console.error(`Failed to merge enterprise locale ${locale}:`, error);
}
});
}
+81
View File
@@ -0,0 +1,81 @@
import { AppPlugin, IPluginManager } from './types';
class PluginManager implements IPluginManager {
private plugins = new Map<string, AppPlugin>();
private initialized = false;
/**
* register a plugin
*/
register(name: string, plugin: AppPlugin) {
if (this.plugins.has(name)) {
console.warn(`Plugin "${name}" is already registered. Overwriting...`);
}
this.plugins.set(name, plugin);
}
/**
* get a plugin
*/
get(name: string): AppPlugin | undefined {
return this.plugins.get(name);
}
/**
* check if a plugin exists
*/
has(name: string): boolean {
return this.plugins.has(name);
}
/**
* get all plugins
*/
getAll(): Map<string, AppPlugin> {
return this.plugins;
}
/**
* initialize all plugins
*/
async initialize(): Promise<Record<string, any>> {
if (this.initialized) {
return {};
}
const initData: Record<string, any> = {};
for (const [name, plugin] of this.plugins.entries()) {
if (plugin.onAppInit) {
try {
const data = await plugin.onAppInit();
if (data) {
initData[name] = data;
}
} catch (error) {
console.error(`Failed to initialize plugin "${name}":`, error);
}
}
}
this.initialized = true;
return initData;
}
/**
* 通知所有插件应用已就绪
*/
async notifyReady(): Promise<void> {
for (const [name, plugin] of this.plugins.entries()) {
if (plugin.onAppReady) {
try {
await plugin.onAppReady();
} catch (error) {
console.error(`Plugin "${name}" onAppReady failed:`, error);
}
}
}
}
}
export const GPUStackPluginManager = new PluginManager();
+2
View File
@@ -0,0 +1,2 @@
// If the enterprise plugin is not installed, imports will be redirected to this empty module to prevent errors.
export default undefined;
+57
View File
@@ -0,0 +1,57 @@
import { getGPUStackPlugin } from '@/plugins';
import type { RouteConfig } from '@/plugins/types';
type ClientRoute = RouteConfig & {
id?: string;
children?: ClientRoute[];
routes?: ClientRoute[];
};
function getLayoutChildren(routes: ClientRoute[]): ClientRoute[] | null {
const rootRoute = routes.find((route) => route.path === '/');
if (!rootRoute) return null;
const rootChildren = rootRoute.routes || rootRoute.children;
if (!Array.isArray(rootChildren)) return null;
const globalLayout = rootChildren.find((route) =>
route.id?.includes('global-layout')
);
if (!globalLayout) return null;
const layoutChildren = globalLayout.children || globalLayout.routes;
return Array.isArray(layoutChildren) ? layoutChildren : null;
}
function mergeRoute(targetRoutes: ClientRoute[], pluginRoute: ClientRoute) {
const existsIndex = targetRoutes.findIndex(
(route) =>
(pluginRoute.path && route.path === pluginRoute.path) ||
(pluginRoute.key && route.key === pluginRoute.key)
);
if (existsIndex === -1) {
targetRoutes.push(pluginRoute);
return;
}
const currentRoute = targetRoutes[existsIndex];
targetRoutes[existsIndex] = {
...currentRoute,
...pluginRoute,
routes: pluginRoute.routes || currentRoute.routes,
children: pluginRoute.children || currentRoute.children
};
}
export function mergeEnterpriseRoutes(routes: ClientRoute[]) {
const enterprisePlugin = getGPUStackPlugin();
if (!enterprisePlugin?.routes?.length) return;
const layoutChildren = getLayoutChildren(routes);
if (!layoutChildren) return;
enterprisePlugin.routes.forEach((route) =>
mergeRoute(layoutChildren, route as ClientRoute)
);
}
+95
View File
@@ -0,0 +1,95 @@
import { ComponentType } from 'react';
/**
* routes
*/
export interface RouteConfig {
path: string;
component?: ComponentType;
routes?: RouteConfig[];
[key: string]: any;
}
/**
* login module configuration
*/
export interface LoginPlugin {
shouldUseCustomLogin?: () => boolean;
CustomLoginComponent?: ComponentType;
}
/**
* branding module configuration
*/
export interface BrandingPlugin {
ConfigPage?: ComponentType;
}
/**
* localization configuration
*/
export interface LocalesConfig {
[locale: string]: Record<string, any>;
}
/**
* application plugin interface
*/
export interface AppPlugin {
/**
* application initialization hook
* called when the application starts, can return initialization data
*/
onAppInit?: () => Promise<Record<string, any>> | Record<string, any>;
/**
* application ready hook
* called when the application is fully loaded
*/
onAppReady?: () => void | Promise<void>;
/**
* login module
*/
login?: LoginPlugin;
/**
* branding module
*/
branding?: BrandingPlugin;
/**
* localization configuration
*/
locales?: LocalesConfig;
/**
* routes extension
*/
routes?: RouteConfig[];
/**
* components extension
*/
components?: Record<string, ComponentType>;
/**
* Hooks extension
*/
hooks?: Record<string, (...args: unknown[]) => unknown>;
/**
* other custom fields for future extensions
*/
[key: string]: any;
}
/**
* plugin manager interface
*/
export interface IPluginManager {
register(name: string, plugin: AppPlugin): void;
get(name: string): AppPlugin | undefined;
has(name: string): boolean;
getAll(): Map<string, AppPlugin>;
}