chore: upgrade info

This commit is contained in:
jialin
2025-12-04 16:43:33 +08:00
parent 1c699fe307
commit a274349ace
7 changed files with 56 additions and 246 deletions
+7 -2
View File
@@ -79,10 +79,15 @@ export async function getInitialState(): Promise<{
const getAppVersionInfo = async () => { const getAppVersionInfo = async () => {
try { try {
const data = await queryVersionInfo(); const data = await queryVersionInfo();
const isProduction = data.version?.indexOf('0.0.0') === -1;
const isDev = data.version?.indexOf('0.0.0') > -1;
const isRc = data.version?.indexOf('rc') > -1;
setAtomStorage(GPUStackVersionAtom, { setAtomStorage(GPUStackVersionAtom, {
...data, ...data,
isProduction isProd: !isDev && !isRc,
isDev,
isRc
}); });
} catch (error) { } catch (error) {
console.error('queryVersionInfo error', error); console.error('queryVersionInfo error', error);
+6 -2
View File
@@ -6,11 +6,15 @@ export const userAtom = atomWithStorage<any>('userInfo', null);
export const GPUStackVersionAtom = atom<{ export const GPUStackVersionAtom = atom<{
version: string; version: string;
git_commit: string; git_commit: string;
isProduction: boolean; isProd: boolean;
isDev?: boolean;
isRc?: boolean;
}>({ }>({
version: '', version: '',
git_commit: '', git_commit: '',
isProduction: false isProd: false,
isDev: false,
isRc: false
}); });
export const UpdateCheckAtom = atom<{ export const UpdateCheckAtom = atom<{
+21 -17
View File
@@ -1,18 +1,29 @@
import Logo from '@/assets/images/gpustack-logo.png'; import Logo from '@/assets/images/gpustack-logo.png';
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user'; import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import externalLinks from '@/constants/external-links'; import externalLinks from '@/constants/external-links';
import { Button } from 'antd'; import { Button } from 'antd';
import { useAtom } from 'jotai';
import React from 'react'; import React from 'react';
import './index.less'; import './index.less';
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => { const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
const latestVersion = getAtomStorage(UpdateCheckAtom).latest_version; const [gpuStackVersionAtom] = useAtom(GPUStackVersionAtom);
const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version; const [userDataAtom] = useAtom(userAtom);
const [updateCheck] = useAtom(UpdateCheckAtom);
const isProd = // current version info
currentVersion?.indexOf('rc') === -1 && const {
currentVersion?.indexOf('0.0.0') === -1; version: currentVersion,
git_commit,
isProd,
isDev
} = gpuStackVersionAtom;
// user info
const { is_admin } = userDataAtom || {};
// update check latest version info
const { latest_version: latestVersion } = updateCheck;
const uiVersion = document.documentElement.getAttribute('data-version'); const uiVersion = document.documentElement.getAttribute('data-version');
@@ -29,10 +40,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
</span> </span>
)} )}
{isProd ? ( {isProd ? (
<span className="val"> <span className="val">{currentVersion || git_commit}</span>
{getAtomStorage(GPUStackVersionAtom)?.version ||
getAtomStorage(GPUStackVersionAtom)?.git_commit}
</span>
) : ( ) : (
<span className="val dev"> <span className="val dev">
<span className="item"> <span className="item">
@@ -40,9 +48,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
{' '} {' '}
{intl.formatMessage({ id: 'common.footer.version.server' })} {intl.formatMessage({ id: 'common.footer.version.server' })}
</span> </span>
{currentVersion.indexOf('0.0.0') > -1 {isDev ? git_commit : currentVersion}
? getAtomStorage(GPUStackVersionAtom)?.git_commit
: getAtomStorage(GPUStackVersionAtom)?.version}
</span> </span>
<span className="item"> <span className="item">
<span className="tl">UI</span> <span className="tl">UI</span>
@@ -51,12 +57,10 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
</span> </span>
)} )}
</div> </div>
{getAtomStorage(userAtom)?.is_admin && isProd && ( {is_admin && isProd && (
<div className="upgrade-text"> <div className="upgrade-text">
<span className="m-l-5"> <span className="m-l-5">
{latestVersion && {latestVersion && latestVersion !== currentVersion && !isDev
latestVersion !== currentVersion &&
latestVersion.indexOf('0.0.0') === -1
? intl.formatMessage( ? intl.formatMessage(
{ id: 'users.version.update' }, { id: 'users.version.update' },
{ version: latestVersion } { version: latestVersion }
+2 -2
View File
@@ -122,12 +122,12 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
initialState?.currentUser?.is_admin && initialState?.currentUser?.is_admin &&
updateCheck.latest_version && updateCheck.latest_version &&
updateCheck.latest_version !== version?.version && updateCheck.latest_version !== version?.version &&
updateCheck.latest_version?.indexOf('0.0.0') === -1 && version?.isProd
updateCheck.latest_version?.indexOf('rc') === -1
); );
}, [ }, [
updateCheck.latest_version, updateCheck.latest_version,
version.version, version.version,
version.isProd,
initialState?.currentUser?.is_admin initialState?.currentUser?.is_admin
]); ]);
+18 -221
View File
@@ -1,16 +1,11 @@
// @ts-nocheck // @ts-nocheck
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache'; import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user'; import { userAtom } from '@/atoms/user';
import DarkMask from '@/components/dark-mask'; import DarkMask from '@/components/dark-mask';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import ShortCuts, {
modalConfig as ShortCutsConfig
} from '@/components/short-cuts';
import VersionInfo, { modalConfig } from '@/components/version-info';
import routeCachekey from '@/config/route-cachekey'; import routeCachekey from '@/config/route-cachekey';
import { DEFAULT_ENTER_PAGE } from '@/config/settings'; import { DEFAULT_ENTER_PAGE } from '@/config/settings';
import useBodyScroll from '@/hooks/use-body-scroll';
import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useOverlayScroller from '@/hooks/use-overlay-scroller';
import useUserSettings from '@/hooks/use-user-settings'; import useUserSettings from '@/hooks/use-user-settings';
import { logout } from '@/pages/login/apis'; import { logout } from '@/pages/login/apis';
@@ -18,7 +13,6 @@ import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { ProLayout } from '@ant-design/pro-components'; import { ProLayout } from '@ant-design/pro-components';
import { import {
Link,
Outlet, Outlet,
dropByCacheKey, dropByCacheKey,
history, history,
@@ -29,18 +23,17 @@ import {
useNavigate, useNavigate,
type IRoute type IRoute
} from '@umijs/max'; } from '@umijs/max';
import { Button, ConfigProvider, Modal, Tooltip, theme } from 'antd'; import { Button, ConfigProvider, Modal, theme } from 'antd';
import 'driver.js/dist/driver.css'; import 'driver.js/dist/driver.css';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import 'overlayscrollbars/overlayscrollbars.css'; import 'overlayscrollbars/overlayscrollbars.css';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo } from 'react';
import { PageContainerInner } from '../pages/_components/page-box'; import { PageContainerInner } from '../pages/_components/page-box';
import Exception from './Exception'; import Exception from './Exception';
import './Layout.css'; import './Layout.css';
import { LogoIcon, SLogoIcon } from './Logo'; import { LogoIcon, SLogoIcon } from './Logo';
import ErrorBoundary from './error-boundary'; import ErrorBoundary from './error-boundary';
import { ExtraContent } from './extraRender'; import { ExtraContent } from './extraRender';
import { getRightRenderContent } from './rightRender';
import { patchRoutes } from './runtime'; import { patchRoutes } from './runtime';
import SiderMenu from './sider-menu'; import SiderMenu from './sider-menu';
@@ -57,16 +50,11 @@ const NO_CONTAINER_PAGES = [
const loginPath = DEFAULT_ENTER_PAGE.login; const loginPath = DEFAULT_ENTER_PAGE.login;
type InitialStateType = {
fetchUserInfo: () => Promise<Global.UserInfo>;
currentUser?: Global.UserInfo;
};
// Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown // Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown
const filterRoutes = ( const filterRoutes = (
routes: IRoute[], routes: IRoute[],
filterFn: (route: IRoute) => boolean filterFn: (route: IRoute) => boolean
) => { ): any[] => {
if (routes.length === 0) { if (routes.length === 0) {
return []; return [];
} }
@@ -117,21 +105,13 @@ export default (props: any) => {
defer: false defer: false
}); });
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
const { themeData, setTheme, setUserSettings, userSettings, isDarkTheme } = const { themeData, setUserSettings, userSettings } = useUserSettings();
useUserSettings();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const { initialize: initializeMenu } = useOverlayScroller();
const [userInfo] = useAtom(userAtom); const [userInfo] = useAtom(userAtom);
const [routeCache] = useAtom(routeCacheAtom); const [routeCache] = useAtom(routeCacheAtom);
const [version] = useAtom(GPUStackVersionAtom);
const [updateCheck] = useAtom(UpdateCheckAtom);
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl(); const intl = useIntl();
const { clientRoutes, pluginManager } = useAppData(); const { clientRoutes } = useAppData();
// const [collapsed, setCollapsed] = useState(userSettings.collapsed || false);
const [collapseValue, setCollapseValue] = useState(false);
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
const initialInfo = (useModel && useModel('@@initialState')) || { const initialInfo = (useModel && useModel('@@initialState')) || {
initialState: undefined, initialState: undefined,
@@ -150,23 +130,6 @@ export default (props: any) => {
return intl.formatMessage({ id: args.id }); return intl.formatMessage({ id: args.id });
}; };
const showVersion = () => {
saveScrollHeight();
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />,
onCancel: restoreScrollHeight
});
};
const showShortcuts = () => {
Modal.info({
...ShortCutsConfig,
content: <ShortCuts intl={intl} />
});
};
const initRouteCacheValue = (pathname) => { const initRouteCacheValue = (pathname) => {
if (routeCache.get(pathname) === undefined && routeCachekey[pathname]) { if (routeCache.get(pathname) === undefined && routeCachekey[pathname]) {
setRouteCache(pathname, false); setRouteCache(pathname, false);
@@ -184,22 +147,17 @@ export default (props: any) => {
const runtimeConfig = { const runtimeConfig = {
...initialInfo, ...initialInfo,
logout: async (userInfo) => { logout: async () => {
await logout(); await logout();
navigate(loginPath); navigate(loginPath);
}, },
showVersion: () => { showVersion: () => {},
return showVersion(); showShortcuts: () => {},
},
showShortcuts: () => {
return showShortcuts();
},
notFound: <span>404 not found</span> notFound: <span>404 not found</span>
}; };
const handleToggleCollapse = (e: any) => { const handleToggleCollapse = (e: any) => {
e.stopPropagation(); e.stopPropagation();
// setCollapsed(!collapsed);
setUserSettings({ setUserSettings({
...userSettings, ...userSettings,
collapsed: !userSettings.collapsed collapsed: !userSettings.collapsed
@@ -235,38 +193,6 @@ export default (props: any) => {
console.log('matchedRoute=========', matchedRoute, route); console.log('matchedRoute=========', matchedRoute, route);
const allRouteKeys = useMemo(() => {
const keys = new Set<string>();
const childrenRoutes = route?.children || [];
const traverseRoutes = (routes) => {
routes.forEach((r) => {
if (r.path) {
keys.add(r.path);
}
if (r.children) {
traverseRoutes(r.children);
}
});
};
traverseRoutes(childrenRoutes);
return keys;
}, [route?.children]);
const showUpgrade = useMemo(() => {
return (
initialState?.currentUser?.is_admin &&
updateCheck.latest_version &&
updateCheck.latest_version !== version?.version &&
updateCheck.latest_version?.indexOf('0.0.0') === -1 &&
updateCheck.latest_version?.indexOf('rc') === -1
);
}, [
updateCheck.latest_version,
version.version,
initialState?.currentUser?.is_admin
]);
useEffect(() => { useEffect(() => {
const body = document.querySelector('body'); const body = document.querySelector('body');
if (body) { if (body) {
@@ -306,92 +232,13 @@ export default (props: any) => {
); );
}; };
const handleToggleGroup = (menuItemProps, e) => { const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
e.stopPropagation();
if (collapseKeys.has(menuItemProps.key)) {
collapseKeys.delete(menuItemProps.key);
} else {
collapseKeys.add(menuItemProps.key);
}
setCollapseKeys(new Set(collapseKeys));
};
const menuContentRender = (menuProps, defaultDom) => {
return <SiderMenu {...menuProps}></SiderMenu>; return <SiderMenu {...menuProps}></SiderMenu>;
}; };
const actionRender = (layoutProps) => { const onPageChange = (route: any) => {
const dom = getRightRenderContent({
runtimeConfig,
loading,
initialState,
setInitialState,
intl,
isDarkTheme: userSettings.isDarkTheme,
siderWidth: layoutProps?.siderWidth,
collapsed: layoutProps?.collapsed,
showUpgrade
});
return dom;
};
const menuItemRender = (menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children) {
return defaultDom;
}
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
return (
<Tooltip
title={collapsed ? menuItemProps.name : false}
placement="right"
>
<Link
to={menuItemProps.path.replace('/*', '')}
target={menuItemProps.target}
>
{defaultDom}
</Link>
</Tooltip>
);
}
return (
<Tooltip title={collapsed ? menuItemProps.name : false} placement="right">
{defaultDom}
</Tooltip>
);
};
const menuDataRender = (menuData) => {
const currentItem = menuData.find((s) => location.pathname === s.path);
const result = menuData.map((item) => {
const newItem = { ...item };
const selected =
location.pathname === newItem.path ||
location.pathname.indexOf(newItem.path) > -1;
if (newItem.icon) {
newItem.icon = selected ? (
<IconFont type={newItem.selectedIcon} />
) : (
<IconFont type={newItem.defaultIcon} />
);
}
if (newItem.children) {
newItem.children = menuDataRender(newItem.children);
}
return newItem;
});
return result;
};
const onPageChange = (route) => {
const { location } = history; const { location } = history;
const { pathname } = location; const { pathname } = location;
console.log('onPageChange', pathname, route);
initRouteCacheValue(pathname); initRouteCacheValue(pathname);
dropRouteCache(pathname); dropRouteCache(pathname);
@@ -413,7 +260,7 @@ export default (props: any) => {
} }
}; };
const onMenuHeaderClick = (e) => { const onMenuHeaderClick = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
const pagepath = initialState?.currentUser?.is_admin const pagepath = initialState?.currentUser?.is_admin
@@ -423,65 +270,13 @@ export default (props: any) => {
navigate(pagepath); navigate(pagepath);
}; };
const onCollapse = (value) => { const onCollapse = (value: boolean) => {
setUserSettings({ setUserSettings({
...userSettings, ...userSettings,
collapsed: value collapsed: value
}); });
}; };
useEffect(() => {
// clear previous marks and measures
performance.clearMarks();
performance.clearMeasures();
// record start time
performance.mark('route-start');
requestAnimationFrame(() => {
// record end time
performance.mark('route-end');
// make sure `route-start` exists
if (performance.getEntriesByName('route-start').length > 0) {
performance.measure('route-change', 'route-start', 'route-end');
const measure = performance.getEntriesByName('route-change')[0];
console.log(
`[Performance] Route change to ${location.pathname} took ${measure.duration.toFixed(2)}ms`
);
// clear marks and measures
performance.clearMarks();
performance.clearMeasures();
} else {
console.warn('Missing performance mark: route-start');
}
});
}, [location.pathname]);
const onRenderCallback = (id, phase, actualDuration) => {
console.log(
`[Profiler] Route: ${id} - Phase: ${phase} - Render time: ${actualDuration.toFixed(2)}ms`
);
};
const currentTheme = useMemo(() => {
const data = {
algorithm: userSettings.isDarkTheme
? theme.darkAlgorithm
: theme.defaultAlgorithm,
...themeData
};
return data;
}, [userSettings.isDarkTheme, themeData]);
const outlet = runtimeConfig.childrenRender ? (
runtimeConfig.childrenRender(<Outlet />, props)
) : (
<Outlet />
);
return ( return (
<ConfigProvider <ConfigProvider
componentSize="large" componentSize="large"
@@ -530,7 +325,7 @@ export default (props: any) => {
type: 'group' type: 'group'
}} }}
splitMenus={true} splitMenus={true}
logo={collapsed ? SLogoIcon : LogoIcon} logo={userSettings.collapsed ? <SLogoIcon /> : <LogoIcon />}
menuContentRender={menuContentRender} menuContentRender={menuContentRender}
disableContentMargin disableContentMargin
{...runtimeConfig} {...runtimeConfig}
@@ -544,9 +339,11 @@ export default (props: any) => {
noAccessible={runtimeConfig?.noAccessible} noAccessible={runtimeConfig?.noAccessible}
> >
{isNoContainerPage ? ( {isNoContainerPage ? (
outlet <Outlet />
) : ( ) : (
<PageContainerInner>{outlet}</PageContainerInner> <PageContainerInner>
<Outlet />
</PageContainerInner>
)} )}
</Exception> </Exception>
</ProLayout> </ProLayout>
+1 -1
View File
@@ -88,7 +88,7 @@ const useStyles = createStyles(({ token, css }) => ({
const LoginForm = () => { const LoginForm = () => {
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const { styles } = useStyles(); const { styles } = useStyles();
const [userInfo, setUserInfo] = useAtom(userAtom); const [, setUserInfo] = useAtom(userAtom);
const { initialState, setInitialState } = useModel('@@initialState') || {}; const { initialState, setInitialState } = useModel('@@initialState') || {};
const [authError, setAuthError] = useState<Error | null>(null); const [authError, setAuthError] = useState<Error | null>(null);
const intl = useIntl(); const intl = useIntl();
+1 -1
View File
@@ -34,7 +34,7 @@ const PasswordForm: React.FC = () => {
}); });
await setUserInfo({ await setUserInfo({
...userInfo, ...(userInfo || {}),
require_password_change: false require_password_change: false
}); });
setInitialPassword(''); setInitialPassword('');