feat(branding): rebrand footer vendor to MesaStack, trim footer/topbar
- Footer vendor label unified to "MesaStack" across all locales. - Remove footer Help button; keep version as plain text (no click). - Remove top-right GitHub star widget (delete unused github-star.tsx); keep version as plain text (no click), retain upgrade "New" badge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
39a6455e7b
commit
99c95b87e5
@@ -1,9 +1,7 @@
|
|||||||
import { GPUStackVersionAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom } from '@/atoms/user';
|
||||||
import { getAtomStorage } from '@/atoms/utils';
|
import { getAtomStorage } from '@/atoms/utils';
|
||||||
import VersionInfo, { modalConfig } from '@/components/version-info';
|
|
||||||
import externalLinks from '@/constants/external-links';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Divider, Modal, Typography } from 'antd';
|
import { Divider, Typography } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -33,20 +31,10 @@ const useStyles = createStyles(({ token, css }) => ({
|
|||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
|
|
||||||
const showVersion = () => {
|
|
||||||
modal.info({
|
|
||||||
...modalConfig,
|
|
||||||
width: 460,
|
|
||||||
content: <VersionInfo intl={intl} />
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextHolder}
|
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<div className="footer-content">
|
<div className="footer-content">
|
||||||
<div className="footer-content-left">
|
<div className="footer-content-left">
|
||||||
@@ -63,18 +51,7 @@ const Footer: React.FC = () => {
|
|||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
</CompanyWrapper>
|
</CompanyWrapper>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<span>{getAtomStorage(GPUStackVersionAtom)?.version}</span>
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
href={externalLinks.documentation}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'common.button.help' })}
|
|
||||||
</Button>
|
|
||||||
<Divider orientation="vertical" />
|
|
||||||
<Button type="link" size="small" onClick={showVersion}>
|
|
||||||
{getAtomStorage(GPUStackVersionAtom)?.version}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
|
||||||
import PluginExtraField from '@/components/plugin-extra-fields';
|
import PluginExtraField from '@/components/plugin-extra-fields';
|
||||||
import VersionInfo, { modalConfig } from '@/components/version-info';
|
|
||||||
import externalLinks from '@/constants/external-links';
|
import externalLinks from '@/constants/external-links';
|
||||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
|
||||||
import { logout } from '@/pages/login/apis';
|
import { logout } from '@/pages/login/apis';
|
||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
import {
|
import {
|
||||||
@@ -13,12 +11,11 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { DropdownActions, IconFont } from '@gpustack/core-ui';
|
import { DropdownActions, IconFont } from '@gpustack/core-ui';
|
||||||
import { history, useIntl, useNavigate } from '@umijs/max';
|
import { history, useIntl, useNavigate } from '@umijs/max';
|
||||||
import { Avatar, Button, Divider, Modal } from 'antd';
|
import { Avatar, Divider } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { DEFAULT_ENTER_PAGE } from '../config/settings';
|
import { DEFAULT_ENTER_PAGE } from '../config/settings';
|
||||||
import GithubStar from './github-star';
|
|
||||||
|
|
||||||
const NewLabel = styled.span`
|
const NewLabel = styled.span`
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -98,11 +95,9 @@ const CustomItem = styled.div`
|
|||||||
|
|
||||||
export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
||||||
const { isDarkTheme } = props;
|
const { isDarkTheme } = props;
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const intl = useIntl();
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
|
||||||
const [version] = useAtom(GPUStackVersionAtom);
|
const [version] = useAtom(GPUStackVersionAtom);
|
||||||
const [updateCheck] = useAtom(UpdateCheckAtom);
|
const [updateCheck] = useAtom(UpdateCheckAtom);
|
||||||
const intl = useIntl();
|
|
||||||
const initialInfo = useModel('@@initialState') || {
|
const initialInfo = useModel('@@initialState') || {
|
||||||
initialState: undefined,
|
initialState: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -140,16 +135,6 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
return {};
|
return {};
|
||||||
}, [isDarkTheme]);
|
}, [isDarkTheme]);
|
||||||
|
|
||||||
const showVersion = () => {
|
|
||||||
saveScrollHeight();
|
|
||||||
modal.info({
|
|
||||||
...modalConfig,
|
|
||||||
width: 460,
|
|
||||||
content: <VersionInfo intl={intl} />,
|
|
||||||
onCancel: restoreScrollHeight
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout();
|
await logout();
|
||||||
navigate(loginPath);
|
navigate(loginPath);
|
||||||
@@ -259,25 +244,20 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{contextHolder}
|
|
||||||
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
|
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
|
||||||
{process.env.ENABLE_ENTERPRISE !== 'true' && <GithubStar />}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<span
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
onClick={showVersion}
|
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--ant-color-text-tertiary)'
|
color: 'var(--ant-color-text-tertiary)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{version.version}
|
{version.version}
|
||||||
</Button>
|
</span>
|
||||||
{showUpgrade && (
|
{showUpgrade && (
|
||||||
<NewLabel>
|
<NewLabel>
|
||||||
<span className="text">
|
<span className="text">
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
import externalLinks from '@/constants/external-links';
|
|
||||||
import { GithubFilled } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Tooltip } from 'antd';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const REPO = 'gpustack/gpustack';
|
|
||||||
const CACHE_KEY = 'gpustack:github-stars';
|
|
||||||
const CACHE_TTL = 24 * 60 * 60 * 1000;
|
|
||||||
const FETCH_TIMEOUT = 4000;
|
|
||||||
|
|
||||||
const StarLink = styled.a`
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: stretch;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: var(--ant-border-radius);
|
|
||||||
border: 1px solid var(--ant-color-border-secondary);
|
|
||||||
background-color: var(--ant-color-bg-container);
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
transition:
|
|
||||||
border-color 0.2s,
|
|
||||||
color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--ant-color-border);
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.seg {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seg + .seg {
|
|
||||||
border-left: 1px solid var(--ant-color-border-secondary);
|
|
||||||
background-color: var(--ant-color-fill-quaternary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.anticon {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.count {
|
|
||||||
font-weight: 500;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
min-width: 1.5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const formatCount = (n: number): string => {
|
|
||||||
if (n >= 1000) {
|
|
||||||
const k = n / 1000;
|
|
||||||
return k >= 10 ? `${Math.round(k)}k` : `${k.toFixed(1)}k`;
|
|
||||||
}
|
|
||||||
return String(n);
|
|
||||||
};
|
|
||||||
|
|
||||||
type CacheEntry = { value: number; time: number };
|
|
||||||
|
|
||||||
const readCache = (): CacheEntry | null => {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(CACHE_KEY);
|
|
||||||
if (!raw) return null;
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
if (typeof parsed?.value !== 'number' || typeof parsed?.time !== 'number') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeCache = (value: number) => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(
|
|
||||||
CACHE_KEY,
|
|
||||||
JSON.stringify({ value, time: Date.now() })
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// ignore quota errors
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const GithubStar = () => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const [count, setCount] = useState<number | null>(
|
|
||||||
() => readCache()?.value ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const cached = readCache();
|
|
||||||
const fresh = cached && Date.now() - cached.time < CACHE_TTL;
|
|
||||||
if (fresh) return;
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
|
||||||
|
|
||||||
fetch(`https://api.github.com/repos/${REPO}`, { signal: controller.signal })
|
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
|
||||||
.then((data) => {
|
|
||||||
if (!data || typeof data.stargazers_count !== 'number') return;
|
|
||||||
setCount(data.stargazers_count);
|
|
||||||
writeCache(data.stargazers_count);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// offline, blocked, rate-limited — stay hidden if no cache
|
|
||||||
})
|
|
||||||
.finally(() => clearTimeout(timer));
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
controller.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={intl.formatMessage({ id: 'common.github.star.tooltip' })}>
|
|
||||||
<StarLink href={externalLinks.github} target="_blank" rel="noreferrer">
|
|
||||||
<span className="seg">
|
|
||||||
<GithubFilled />
|
|
||||||
</span>
|
|
||||||
<span className="seg">
|
|
||||||
<span className="count">
|
|
||||||
{count != null ? formatCount(count) : 'Star'}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</StarLink>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GithubStar;
|
|
||||||
@@ -212,7 +212,7 @@ export default {
|
|||||||
'common.form.password': 'Password',
|
'common.form.password': 'Password',
|
||||||
'common.form.username': 'Username',
|
'common.form.username': 'Username',
|
||||||
'common.login.rember': 'Remember me',
|
'common.login.rember': 'Remember me',
|
||||||
'settings.company': 'MesaStack.ai',
|
'settings.company': 'MesaStack',
|
||||||
'common.button.help': 'Help',
|
'common.button.help': 'Help',
|
||||||
'common.button.feedback': 'Feedback',
|
'common.button.feedback': 'Feedback',
|
||||||
'common.button.docs': 'Documentation',
|
'common.button.docs': 'Documentation',
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ export default {
|
|||||||
'common.form.password': 'パスワード',
|
'common.form.password': 'パスワード',
|
||||||
'common.form.username': 'ユーザー名',
|
'common.form.username': 'ユーザー名',
|
||||||
'common.login.rember': 'ログイン状態を保持',
|
'common.login.rember': 'ログイン状態を保持',
|
||||||
'settings.company': 'MesaStack.ai',
|
'settings.company': 'MesaStack',
|
||||||
'common.button.help': 'ヘルプ',
|
'common.button.help': 'ヘルプ',
|
||||||
'common.button.feedback': 'フィードバック',
|
'common.button.feedback': 'フィードバック',
|
||||||
'common.button.docs': 'ドキュメント',
|
'common.button.docs': 'ドキュメント',
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export default {
|
|||||||
'common.form.password': 'Пароль',
|
'common.form.password': 'Пароль',
|
||||||
'common.form.username': 'Имя пользователя',
|
'common.form.username': 'Имя пользователя',
|
||||||
'common.login.rember': 'Запомнить меня',
|
'common.login.rember': 'Запомнить меня',
|
||||||
'settings.company': 'MesaStack.ai',
|
'settings.company': 'MesaStack',
|
||||||
'common.button.help': 'Помощь',
|
'common.button.help': 'Помощь',
|
||||||
'common.button.feedback': 'Обратная связь',
|
'common.button.feedback': 'Обратная связь',
|
||||||
'common.button.docs': 'Документация',
|
'common.button.docs': 'Документация',
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export default {
|
|||||||
'common.form.password': 'Şifre',
|
'common.form.password': 'Şifre',
|
||||||
'common.form.username': 'Kullanıcı adı',
|
'common.form.username': 'Kullanıcı adı',
|
||||||
'common.login.rember': 'Beni hatırla',
|
'common.login.rember': 'Beni hatırla',
|
||||||
'settings.company': 'MesaStack.ai',
|
'settings.company': 'MesaStack',
|
||||||
'common.button.help': 'Yardım',
|
'common.button.help': 'Yardım',
|
||||||
'common.button.feedback': 'Geri Bildirim',
|
'common.button.feedback': 'Geri Bildirim',
|
||||||
'common.button.docs': 'Dokümantasyon',
|
'common.button.docs': 'Dokümantasyon',
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ export default {
|
|||||||
'common.form.password': '密码',
|
'common.form.password': '密码',
|
||||||
'common.form.username': '用户名',
|
'common.form.username': '用户名',
|
||||||
'common.login.rember': '记住我',
|
'common.login.rember': '记住我',
|
||||||
'settings.company': '数澈软件',
|
'settings.company': 'MesaStack',
|
||||||
'common.button.help': '帮助',
|
'common.button.help': '帮助',
|
||||||
'common.button.feedback': '反馈',
|
'common.button.feedback': '反馈',
|
||||||
'common.button.docs': '文档',
|
'common.button.docs': '文档',
|
||||||
|
|||||||
Reference in New Issue
Block a user