116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { Layout, Menu, theme } from 'antd';
|
|
import { useState } from 'react';
|
|
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
|
import Login from './pages/Login';
|
|
import SSOLogin from './pages/SSOLogin';
|
|
import Services from './pages/Services';
|
|
import Stats from './pages/Stats';
|
|
import Tokens from './pages/Tokens';
|
|
import { clearSession, getUserInfo } from './services/iamAuth';
|
|
const { Header, Content, Sider } = Layout;
|
|
|
|
/** 登录守卫:sessionStorage 无 userToken 则跳登录页(携带来源路径,登录后回跳) */
|
|
function RequireAuth({ children }: { children: React.ReactNode }) {
|
|
const location = useLocation();
|
|
const token = sessionStorage.getItem('userToken');
|
|
if (!token) {
|
|
return <Navigate to="/login" replace state={{ from: location.pathname + location.search }} />;
|
|
}
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function AppLayout() {
|
|
const nav = useNavigate();
|
|
const loc = useLocation();
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const {
|
|
token: { colorBgContainer },
|
|
} = theme.useToken();
|
|
|
|
const userInfo = getUserInfo();
|
|
const displayName = userInfo?.userName || userInfo?.userId || '';
|
|
|
|
return (
|
|
<Layout style={{ minHeight: '100vh' }}>
|
|
<Sider collapsible collapsed={collapsed} onCollapse={setCollapsed}>
|
|
<div
|
|
style={{
|
|
height: 48,
|
|
margin: 12,
|
|
color: '#fff',
|
|
textAlign: 'center',
|
|
lineHeight: '48px',
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
MCP Auth
|
|
</div>
|
|
<Menu
|
|
theme="dark"
|
|
mode="inline"
|
|
selectedKeys={[loc.pathname]}
|
|
items={[
|
|
{ key: '/tokens', label: 'Token 管理' },
|
|
{ key: '/services', label: '服务管理' },
|
|
{ key: '/stats', label: '统计概览' },
|
|
]}
|
|
onClick={({ key }) => nav(key)}
|
|
/>
|
|
</Sider>
|
|
<Layout>
|
|
<Header
|
|
style={{
|
|
padding: '0 24px',
|
|
background: colorBgContainer,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<span>MCP Token 鉴权管理后台</span>
|
|
<span style={{ fontSize: 13, color: '#999' }}>
|
|
{displayName} ·{' '}
|
|
<a
|
|
onClick={() => {
|
|
clearSession();
|
|
nav('/login', { replace: true });
|
|
}}
|
|
>
|
|
退出
|
|
</a>
|
|
</span>
|
|
</Header>
|
|
<Content style={{ margin: 24 }}>
|
|
<div
|
|
style={{
|
|
padding: 24,
|
|
minHeight: 480,
|
|
background: colorBgContainer,
|
|
borderRadius: 8,
|
|
}}
|
|
>
|
|
<Routes>
|
|
<Route path="/tokens" element={<Tokens />} />
|
|
<Route path="/services" element={<Services />} />
|
|
<Route path="/stats" element={<Stats />} />
|
|
<Route path="*" element={<Navigate to="/tokens" replace />} />
|
|
</Routes>
|
|
</div>
|
|
</Content>
|
|
</Layout>
|
|
</Layout>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
// 路由变化时重渲染,重读 sessionStorage 登录态
|
|
useLocation();
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<Login />} />
|
|
<Route path="/sso-login" element={<SSOLogin />} />
|
|
<Route path="/*" element={<RequireAuth><AppLayout /></RequireAuth>} />
|
|
</Routes>
|
|
);
|
|
}
|