Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a90e02a04c | ||
|
|
9a223f9565 |
@@ -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;
|
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { IconFont } from '@gpustack/core-ui';
|
||||||
|
import { Link, useLocation, useNavigate } from '@umijs/max';
|
||||||
|
import { Menu } from 'antd';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
icon?: string;
|
||||||
|
selectedIcon?: string;
|
||||||
|
defaultIcon?: string;
|
||||||
|
children?: MenuItem[];
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeaderMenuProps {
|
||||||
|
menuData: MenuItem[];
|
||||||
|
initialState?: Global.InitialStateType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css }) => {
|
||||||
|
return {
|
||||||
|
headerMenu: css`
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: none;
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
&.ant-menu-horizontal {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
&.ant-menu-horizontal > .ant-menu-item::after,
|
||||||
|
&.ant-menu-horizontal > .ant-menu-submenu::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ant-menu-title-content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.anticon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const isItemSelected = (item: MenuItem, pathname: string) => {
|
||||||
|
return (
|
||||||
|
pathname === item.path ||
|
||||||
|
(Array.isArray(item.subMenu) && item.subMenu.includes(pathname))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderMenu: React.FC<HeaderMenuProps> = (props) => {
|
||||||
|
const { menuData } = props;
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const buildLeaf = (item: MenuItem) => {
|
||||||
|
const selected = isItemSelected(item, location.pathname);
|
||||||
|
return {
|
||||||
|
key: item.path as string,
|
||||||
|
label: (
|
||||||
|
<Link
|
||||||
|
prefetch="intent"
|
||||||
|
to={(item.path as string).replace('/*', '')}
|
||||||
|
target={item.target}
|
||||||
|
>
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<IconFont
|
||||||
|
type={selected ? item.selectedIcon || '' : item.defaultIcon || ''}
|
||||||
|
/>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
return menuData.map((item) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
label: item.name,
|
||||||
|
children: item.children.map((child) => buildLeaf(child))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return buildLeaf(item);
|
||||||
|
});
|
||||||
|
}, [menuData, location.pathname]);
|
||||||
|
|
||||||
|
const selectedKeys = useMemo(() => {
|
||||||
|
const keys: string[] = [];
|
||||||
|
for (const item of menuData) {
|
||||||
|
const leaves =
|
||||||
|
item.children && item.children.length > 0 ? item.children : [item];
|
||||||
|
for (const leaf of leaves) {
|
||||||
|
if (isItemSelected(leaf, location.pathname)) {
|
||||||
|
keys.push(leaf.path as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}, [menuData, location.pathname]);
|
||||||
|
|
||||||
|
const handleClick = ({ key }: { key: string }) => {
|
||||||
|
if (key.startsWith('/')) {
|
||||||
|
navigate(key.replace('/*', ''));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
className={styles.headerMenu}
|
||||||
|
mode="horizontal"
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
items={items}
|
||||||
|
onClick={handleClick}
|
||||||
|
triggerSubMenuAction="hover"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderMenu;
|
||||||
+21
-57
@@ -22,7 +22,7 @@ import {
|
|||||||
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
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 { CoreUIProvider, IconFont } from '@gpustack/core-ui';
|
import { CoreUIProvider } from '@gpustack/core-ui';
|
||||||
import {
|
import {
|
||||||
Access,
|
Access,
|
||||||
Outlet,
|
Outlet,
|
||||||
@@ -39,18 +39,18 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
type IRoute
|
type IRoute
|
||||||
} from '@umijs/max';
|
} from '@umijs/max';
|
||||||
import { Button, ConfigProvider, Modal, theme } from 'antd';
|
import { ConfigProvider, Modal, theme } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef } 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 } from './Logo';
|
||||||
import ErrorBoundary from './error-boundary';
|
import ErrorBoundary from './error-boundary';
|
||||||
import { ExtraContent } from './extraRender';
|
import { ExtraContent } from './extraRender';
|
||||||
|
import HeaderMenu from './header-menu';
|
||||||
import { patchRoutes } from './runtime';
|
import { patchRoutes } from './runtime';
|
||||||
import SiderMenu from './sider-menu';
|
|
||||||
|
|
||||||
// Pages that use the page container in the page
|
// Pages that use the page container in the page
|
||||||
const NO_CONTAINER_PAGES = [
|
const NO_CONTAINER_PAGES = [
|
||||||
@@ -133,7 +133,7 @@ const mapRoutes = (routes: IRoute[], role: string) => {
|
|||||||
|
|
||||||
export default (props: any) => {
|
export default (props: any) => {
|
||||||
const [, contextHolder] = Modal.useModal();
|
const [, contextHolder] = Modal.useModal();
|
||||||
const { themeData, setUserSettings, userSettings } = useUserSettings();
|
const { themeData, userSettings } = useUserSettings();
|
||||||
const [userInfo] = useAtom(userAtom);
|
const [userInfo] = useAtom(userAtom);
|
||||||
const [routeCache] = useAtom(routeCacheAtom);
|
const [routeCache] = useAtom(routeCacheAtom);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -238,13 +238,6 @@ export default (props: any) => {
|
|||||||
|
|
||||||
const coreUISlots = useMemo(() => ({ ExtraContent }), []);
|
const coreUISlots = useMemo(() => ({ ExtraContent }), []);
|
||||||
|
|
||||||
const handleToggleCollapse = (e: any) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setUserSettings({
|
|
||||||
...userSettings,
|
|
||||||
collapsed: !userSettings.collapsed
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const newRoutes = filterRoutes(
|
const newRoutes = filterRoutes(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
clientRoutes.filter((route) => route.id === 'max-tabs'),
|
clientRoutes.filter((route) => route.id === 'max-tabs'),
|
||||||
@@ -274,16 +267,19 @@ export default (props: any) => {
|
|||||||
return NO_CONTAINER_PAGES.includes(matchedRoute?.name as string);
|
return NO_CONTAINER_PAGES.includes(matchedRoute?.name as string);
|
||||||
}, [matchedRoute]);
|
}, [matchedRoute]);
|
||||||
|
|
||||||
const collapsed = useMemo(() => {
|
|
||||||
return userSettings.collapsed || false;
|
|
||||||
}, [userSettings.collapsed]);
|
|
||||||
|
|
||||||
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
|
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
|
||||||
return <>{logo}</>;
|
return <>{logo}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
|
const headerContentRender = (
|
||||||
return <SiderMenu {...menuProps}></SiderMenu>;
|
headerProps: any,
|
||||||
|
defaultDom: React.ReactNode
|
||||||
|
) => {
|
||||||
|
return <HeaderMenu {...headerProps}></HeaderMenu>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionsRender = () => {
|
||||||
|
return <ExtraContent isDarkTheme={userSettings.isDarkTheme} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPageChange = async (route: any) => {
|
const onPageChange = async (route: any) => {
|
||||||
@@ -337,17 +333,6 @@ export default (props: any) => {
|
|||||||
navigate(pagepath);
|
navigate(pagepath);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCollapse = (value: boolean) => {
|
|
||||||
// only trigger by window resize
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setUserSettings({
|
|
||||||
...userSettings,
|
|
||||||
collapsed: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
componentSize="large"
|
componentSize="large"
|
||||||
@@ -408,48 +393,27 @@ export default (props: any) => {
|
|||||||
<DarkMask></DarkMask>
|
<DarkMask></DarkMask>
|
||||||
<ProLayout
|
<ProLayout
|
||||||
fixSiderbar
|
fixSiderbar
|
||||||
fixedHeader={false}
|
fixedHeader
|
||||||
headerRender={false}
|
|
||||||
breadcrumbRender={false}
|
breadcrumbRender={false}
|
||||||
route={route}
|
route={route}
|
||||||
location={location}
|
location={location}
|
||||||
title={userConfig.title}
|
title={userConfig.title}
|
||||||
navTheme={userSettings.theme}
|
navTheme={userSettings.theme}
|
||||||
layout="side"
|
layout="top"
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
paddingBlock: 0,
|
paddingBlock: 0,
|
||||||
paddingInline: 0
|
paddingInline: 0
|
||||||
}}
|
}}
|
||||||
openKeys={false}
|
|
||||||
disableMobile={true}
|
disableMobile={true}
|
||||||
siderWidth={220}
|
|
||||||
menuFooterRender={() => (
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
type={'text'}
|
|
||||||
onClick={handleToggleCollapse}
|
|
||||||
>
|
|
||||||
<IconFont
|
|
||||||
type={collapsed ? 'icon-expand-left' : 'icon-expand-right'}
|
|
||||||
className="font-size-18"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
onCollapse={onCollapse}
|
|
||||||
onMenuHeaderClick={onMenuHeaderClick}
|
onMenuHeaderClick={onMenuHeaderClick}
|
||||||
collapsed={userSettings.collapsed}
|
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
menu={{
|
menu={{
|
||||||
locale: true,
|
locale: true
|
||||||
type: 'group'
|
|
||||||
}}
|
}}
|
||||||
splitMenus={true}
|
logo={<LogoIcon />}
|
||||||
logo={userSettings.collapsed ? <SLogoIcon /> : <LogoIcon />}
|
headerContentRender={headerContentRender}
|
||||||
menuContentRender={menuContentRender}
|
actionsRender={actionsRender}
|
||||||
{...runtimeConfig}
|
{...runtimeConfig}
|
||||||
ErrorBoundary={ErrorBoundary}
|
ErrorBoundary={ErrorBoundary}
|
||||||
>
|
>
|
||||||
@@ -457,7 +421,7 @@ export default (props: any) => {
|
|||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
height: '100vh',
|
height: '100%',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -214,7 +214,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',
|
||||||
|
|||||||
@@ -213,7 +213,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': 'ドキュメント',
|
||||||
|
|||||||
@@ -211,7 +211,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': 'Документация',
|
||||||
|
|||||||
@@ -216,7 +216,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',
|
||||||
|
|||||||
@@ -204,7 +204,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': '文档',
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { ExtraContent } from '@/layouts/extraRender';
|
|
||||||
import {
|
import {
|
||||||
PageContainer,
|
PageContainer,
|
||||||
RouteContext,
|
RouteContext,
|
||||||
type PageContainerProps
|
type PageContainerProps
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { useOverlayScroller } from '@gpustack/core-ui';
|
import { useOverlayScroller } from '@gpustack/core-ui';
|
||||||
import { Divider } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useContext, useEffect, useRef } from 'react';
|
import { useContext, useEffect, useRef } from 'react';
|
||||||
import pageBoxCss from './styles/page-box.less';
|
import pageBoxCss from './styles/page-box.less';
|
||||||
@@ -53,13 +51,7 @@ export const PageContainerInner: React.FC<
|
|||||||
{leftContent || pageContext.title}
|
{leftContent || pageContext.title}
|
||||||
</div>
|
</div>
|
||||||
<div className={pageBoxCss.right}>
|
<div className={pageBoxCss.right}>
|
||||||
{rightContent && (
|
{rightContent && <div>{rightContent}</div>}
|
||||||
<div>
|
|
||||||
{rightContent}
|
|
||||||
<Divider orientation="vertical" style={{ margin: '0 16px' }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ExtraContent />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user