diff --git a/config/config.ts b/config/config.ts index 9dbc3ccd..36546891 100644 --- a/config/config.ts +++ b/config/config.ts @@ -14,7 +14,7 @@ const isProduction = env === 'production'; const t = Date.now(); export default defineConfig({ proxy: { - ...proxy('http://192.168.50.166:8080') + ...proxy() }, history: { type: 'hash' diff --git a/config/proxy.ts b/config/proxy.ts index 152f3259..1013d6a6 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -1,4 +1,12 @@ -const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai', 'version', 'proxy']; +const proxyTableList = [ + 'cli', + 'v1', + 'auth', + 'v1-openai', + 'version', + 'proxy', + 'update' +]; // @ts-ingore export default function createProxyTable(target?: string) { diff --git a/package.json b/package.json index 15f0e0d2..85e174c1 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "format": "prettier --cache --write .", "postinstall": "max setup", "prepare": "husky", + "preview": "max preview", "setup": "max setup", "start": "npm run dev" }, diff --git a/src/app.tsx b/src/app.tsx index d2df5432..c07bfa11 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,9 +1,10 @@ -import { GPUStackVersionAtom } from '@/atoms/user'; +import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user'; import { setAtomStorage } from '@/atoms/utils'; import { requestConfig } from '@/request-config'; import { queryCurrentUserState, - queryVersionInfo + queryVersionInfo, + updateCheck } from '@/services/profile/apis'; import { RequestConfig, history } from '@umijs/max'; @@ -16,11 +17,24 @@ export async function getInitialState(): Promise<{ }> { const { location } = history; + const getUpdateCheck = async () => { + try { + const data = await updateCheck(); + setAtomStorage(UpdateCheckAtom, data); + return data; + } catch (error) { + console.error('updateCheck error', error); + } + }; + const fetchUserInfo = async (): Promise => { try { const data = await queryCurrentUserState({ skipErrorHandler: true }); + if (data.is_admin) { + getUpdateCheck(); + } return data; } catch (error) { history.push(loginPath); diff --git a/src/assets/styles/common.less b/src/assets/styles/common.less index 3045b92d..3816fe1d 100644 --- a/src/assets/styles/common.less +++ b/src/assets/styles/common.less @@ -152,6 +152,10 @@ font-size: var(--font-size-large); } +.font-size-20 { + font-size: 20px; +} + .font-400 { font-weight: var(--font-weight-normal); } diff --git a/src/atoms/user.ts b/src/atoms/user.ts index 7e9ff7be..4e88c34e 100644 --- a/src/atoms/user.ts +++ b/src/atoms/user.ts @@ -11,6 +11,12 @@ export const GPUStackVersionAtom = atom<{ git_commit: '' }); +export const UpdateCheckAtom = atom<{ + latest_version: string; +}>({ + latest_version: '' +}); + export const initialPasswordAtom = atomWithStorage( 'initialPassword', '' diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx index 8eb50ac1..4f32b162 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -12,6 +12,7 @@ const Footer: React.FC = () => { const showVersion = () => { Modal.info({ ...modalConfig, + width: 460, content: }); }; diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index 9c91958e..eabca2bf 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -1,7 +1,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_4jkdkc8jcf7.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_l9554igzbh.js' }); export default IconFont; diff --git a/src/components/version-info/index.less b/src/components/version-info/index.less index b3c6c4cd..7716095b 100644 --- a/src/components/version-info/index.less +++ b/src/components/version-info/index.less @@ -1,6 +1,5 @@ .version-box { display: flex; - margin-bottom: 40px; flex-direction: column; align-items: center; @@ -25,18 +24,25 @@ .ver { line-height: 32px; display: flex; + justify-content: center; font-size: var(--font-size-middle); + color: var(--ant-color-text-secondary); + gap: 10px; .label { display: flex; justify-content: flex-start; - width: 60px; font-size: var(--font-size-middle); - font-weight: var(--font-weight-medium); - } - - .val { - color: var(--ant-color-text-secondary); } } + + .upgrade { + display: flex; + margin-top: 20px; + justify-content: space-between; + padding-block: 20px 4px; + align-items: center; + width: 100%; + border-top: 1px solid var(--ant-color-split); + } } diff --git a/src/components/version-info/index.tsx b/src/components/version-info/index.tsx index c8901cd8..a2db7260 100644 --- a/src/components/version-info/index.tsx +++ b/src/components/version-info/index.tsx @@ -1,34 +1,49 @@ import Logo from '@/assets/images/gpustack-logo.png'; -import { GPUStackVersionAtom } from '@/atoms/user'; +import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user'; import { getAtomStorage } from '@/atoms/utils'; +import externalLinks from '@/constants/external-links'; +import { Button } from 'antd'; import React from 'react'; import './index.less'; const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => { - // get the data attr from html - const version = document.documentElement.getAttribute('data-version'); + const latestVersion = getAtomStorage(UpdateCheckAtom).latest_version; + const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version; return (
logo
-
-
- - {' '} - {intl.formatMessage({ id: 'common.footer.version.server' })} - - - {getAtomStorage(GPUStackVersionAtom)?.version || - getAtomStorage(GPUStackVersionAtom)?.git_commit} - -
-
- UI - {version} -
+ +
+ + {' '} + {intl.formatMessage({ id: 'common.footer.version' })} + + + {getAtomStorage(GPUStackVersionAtom)?.version || + getAtomStorage(GPUStackVersionAtom)?.git_commit} +
+ {getAtomStorage(userAtom)?.is_admin && ( +
+ + {latestVersion && latestVersion !== currentVersion + ? intl.formatMessage( + { id: 'users.version.update' }, + { version: latestVersion } + ) + : intl.formatMessage( + { id: 'users.version.islatest' }, + { version: currentVersion } + )} + + +
+ )}
); }; diff --git a/src/constants/external-links.ts b/src/constants/external-links.ts index 67d5f873..a8d9494f 100644 --- a/src/constants/external-links.ts +++ b/src/constants/external-links.ts @@ -2,5 +2,6 @@ export default { documentation: 'https://docs.gpustack.ai/', github: 'https://github.com/gpustack/gpustack', discord: 'https://discord.gg/VXYJzuaqwD', - site: 'https://gpustack.ai/' + site: 'https://gpustack.ai/', + release: 'https://github.com/gpustack/gpustack/releases' }; diff --git a/src/global.less b/src/global.less index 5bb98665..6dc0b6cc 100644 --- a/src/global.less +++ b/src/global.less @@ -434,6 +434,26 @@ body { font-size: var(--font-size-middle); } } + + .new-icon { + position: relative; + bottom: 3px; + display: flex; + color: #fff; + width: 36px; + height: 15px; + margin-left: 5px; + justify-content: center; + align-items: center; + background-color: var(--ant-orange-5); + border-radius: 8px 8px 8px 0; + transform: scale(0.9); + + .text { + transform: scale(0.8); + line-height: 1em; + } + } } } diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index b999540b..e6aee535 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -1,6 +1,6 @@ // @ts-nocheck -import { userAtom } from '@/atoms/user'; +import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user'; import ShortCuts, { modalConfig as ShortCutsConfig } from '@/components/short-cuts'; @@ -88,6 +88,8 @@ const mapRoutes = (routes: IRoute[], role: string) => { export default (props: any) => { const [userInfo] = useAtom(userAtom); + const [version] = useAtom(GPUStackVersionAtom); + const [updateCheck] = useAtom(UpdateCheckAtom); const location = useLocation(); const navigate = useNavigate(); const intl = useIntl(); @@ -115,6 +117,7 @@ export default (props: any) => { const showVersion = () => { Modal.info({ ...modalConfig, + width: 460, content: }); }; @@ -169,6 +172,14 @@ export default (props: any) => { [location.pathname] ); + const showUpgrade = useMemo(() => { + return ( + initialState?.currentUser?.is_admin && + updateCheck.latest_version && + updateCheck.latest_version !== version?.version + ); + }, [updateCheck, version, initialState]); + const renderMenuHeader = useCallback( (logo, title) => { return ( @@ -206,12 +217,13 @@ export default (props: any) => { setInitialState, intl, siderWidth: layoutProps.siderWidth, - collapsed: layoutProps.collapsed + collapsed: layoutProps.collapsed, + showUpgrade }); return dom; }, - [intl] + [intl, version, updateCheck] ); return ( diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index ef48f6a6..977fa96c 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -26,7 +26,7 @@ export const getRightRenderContent = (opts: { siderWidth: number; intl: any; }) => { - const { intl, collapsed, siderWidth } = opts; + const { intl, collapsed, siderWidth, showUpgrade } = opts; const allLocals = getAllLocales(); if (opts.runtimeConfig.rightRender) { @@ -109,14 +109,28 @@ export const getRightRenderContent = (opts: { mode: 'vertical', expandIcon: false, // inlineCollapsed: collapsed, - triggerSubMenuAction: 'hover', + triggerSubMenuAction: 'click', items: [ { key: 'help', icon: , label: ( - - {intl?.formatMessage?.({ id: 'common.button.help' })} + + + {intl?.formatMessage?.({ id: 'common.button.help' })} + {showUpgrade && ( + + )} + ), children: helpList.map((item) => ({ @@ -125,7 +139,16 @@ export const getRightRenderContent = (opts: { {item.icon} {item.key === 'version' ? ( - {item.label} + <> + {item.label} + {showUpgrade && ( + + + {intl.formatMessage({ id: 'common.text.new' })} + + + )} + ) : ( { - console.log('requestInterceptors+++++++++++++++', url, options); if (NoBaseURLAPIs.some((api) => url.startsWith(api))) { options.baseURL = ''; return { url, options }; diff --git a/src/services/profile/apis.ts b/src/services/profile/apis.ts index 494a0a76..98b826e9 100644 --- a/src/services/profile/apis.ts +++ b/src/services/profile/apis.ts @@ -12,3 +12,9 @@ export async function queryVersionInfo() { method: 'GET' }); } + +export async function updateCheck() { + return request(`/update/`, { + method: 'GET' + }); +}