Skip to content

Commit 034b3f1

Browse files
author
CodeJudge
committed
feat: 系统状态监控页面
1 parent 4a9214f commit 034b3f1

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const AdminProblemForm = lazy(() => import('./pages/AdminProblemForm'));
1616
const Leaderboard = lazy(() => import('./pages/Leaderboard'));
1717
const Profile = lazy(() => import('./pages/Profile'));
1818
const NotFound = lazy(() => import('./pages/NotFound'));
19+
const SystemHealth = lazy(() => import('./pages/SystemHealth'));
1920

2021
export default function App() {
2122
return (
@@ -40,6 +41,7 @@ export default function App() {
4041
<Route path="/admin/problems/new" element={<AdminProblemForm />} />
4142
<Route path="/admin/problems/:id/edit" element={<AdminProblemForm />} />
4243
<Route path="/leaderboard" element={<Leaderboard />} />
44+
<Route path="/system" element={<SystemHealth />} />
4345
<Route path="*" element={<NotFound />} />
4446
</Routes>
4547
</Suspense>

frontend/src/components/Navbar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from 'react';
22
import { Link, useLocation, useNavigate } from 'react-router-dom';
3-
import { Code2, User, LogOut, ShieldCheck, Dices, Menu, X } from 'lucide-react';
3+
import { Code2, User, LogOut, ShieldCheck, Dices, Menu, X, Activity } from 'lucide-react';
44
import { useAuth } from '../context/AuthContext';
55

66
export default function Navbar() {
@@ -124,6 +124,9 @@ export default function Navbar() {
124124
<ShieldCheck className="w-3.5 h-3.5" />
125125
管理
126126
</Link>
127+
<Link to="/system" className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-blue-600/15 text-blue-400 border border-blue-600/30 hover:bg-blue-600/25 transition-colors">
128+
<Activity className="w-3.5 h-3.5" /> 系统
129+
</Link>
127130
)}
128131
<button
129132
onClick={logout}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useState, useEffect } from 'react';
2+
import { Activity, Database, Users, FileText, Server, Clock, CheckCircle2, AlertTriangle } from 'lucide-react';
3+
4+
export default function SystemHealth() {
5+
const [health, setHealth] = useState<any>(null);
6+
const [adminStats, setAdminStats] = useState<any>(null);
7+
const [loading, setLoading] = useState(true);
8+
9+
useEffect(() => {
10+
Promise.all([
11+
fetch('/api/health').then(r => r.json()),
12+
fetch('/api/problems/admin/stats', { headers: { Authorization: `Bearer ${localStorage.getItem('oj_token')}` } }).then(r => r.json()),
13+
])
14+
.then(([h, stats]) => {
15+
setHealth(h);
16+
setAdminStats(stats);
17+
})
18+
.catch(() => {})
19+
.finally(() => setLoading(false));
20+
}, []);
21+
22+
if (loading) return <div className="flex justify-center py-20"><div className="w-8 h-8 border-2 border-primary-500 border-t-transparent rounded-full animate-spin" /></div>;
23+
24+
return (
25+
<div>
26+
<h1 className="text-2xl font-bold text-white mb-6 flex items-center gap-3">
27+
<Activity className="w-7 h-7 text-primary-400" /> 系统状态
28+
</h1>
29+
30+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
31+
{/* Server Status */}
32+
<div className="card p-6">
33+
<div className="flex items-center gap-3 mb-4">
34+
<Server className="w-5 h-5 text-blue-400" />
35+
<h2 className="text-lg font-semibold text-white">服务器状态</h2>
36+
</div>
37+
<div className="flex items-center gap-2 mb-4">
38+
<CheckCircle2 className="w-5 h-5 text-emerald-400" />
39+
<span className="text-emerald-400 font-medium">运行中</span>
40+
</div>
41+
<div className="flex items-center gap-2 text-dark-400 text-sm">
42+
<Clock className="w-4 h-4" />
43+
<span>{health?.timestamp ? new Date(health.timestamp).toLocaleString('zh-CN') : '-'}</span>
44+
</div>
45+
</div>
46+
47+
{/* Database Stats */}
48+
<div className="card p-6">
49+
<div className="flex items-center gap-3 mb-4">
50+
<Database className="w-5 h-5 text-purple-400" />
51+
<h2 className="text-lg font-semibold text-white">数据统计</h2>
52+
</div>
53+
<div className="space-y-3">
54+
{[
55+
{ icon: FileText, label: '题目总数', value: adminStats?.totalProblems ?? '-', color: 'text-blue-400' },
56+
{ icon: Users, label: '用户总数', value: adminStats?.totalUsers ?? '-', color: 'text-emerald-400' },
57+
{ icon: Activity, label: '提交总数', value: adminStats?.totalSubmissions ?? '-', color: 'text-amber-400' },
58+
].map(item => (
59+
<div key={item.label} className="flex items-center justify-between">
60+
<div className="flex items-center gap-2">
61+
<item.icon className={`w-4 h-4 ${item.color}`} />
62+
<span className="text-dark-300 text-sm">{item.label}</span>
63+
</div>
64+
<span className="text-white font-semibold">{item.value}</span>
65+
</div>
66+
))}
67+
<div className="flex items-center justify-between pt-2 border-t border-dark-700">
68+
<span className="text-dark-400 text-xs">接受率</span>
69+
<span className="text-emerald-400 text-sm font-medium">{adminStats?.acceptanceRate ?? 0}%</span>
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
75+
);
76+
}

frontend/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/app.tsx","./src/main.tsx","./src/components/choicequestion.tsx","./src/components/codeeditor.tsx","./src/components/errorboundary.tsx","./src/components/fillblankquestion.tsx","./src/components/footer.tsx","./src/components/gamificationsection.tsx","./src/components/keyboardshortcuts.tsx","./src/components/markdownrenderer.tsx","./src/components/navbar.tsx","./src/components/problemcard.tsx","./src/components/submissionstatus.tsx","./src/context/authcontext.tsx","./src/context/bookmarkcontext.tsx","./src/hooks/usedocumenttitle.ts","./src/pages/admin.tsx","./src/pages/adminproblemform.tsx","./src/pages/home.tsx","./src/pages/leaderboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/problemdetail.tsx","./src/pages/problems.tsx","./src/pages/profile.tsx","./src/pages/register.tsx","./src/pages/submissions.tsx","./src/services/api.ts","./src/types/index.ts"],"version":"5.9.3"}
1+
{"root":["./src/app.tsx","./src/main.tsx","./src/components/choicequestion.tsx","./src/components/codeeditor.tsx","./src/components/errorboundary.tsx","./src/components/fillblankquestion.tsx","./src/components/footer.tsx","./src/components/gamificationsection.tsx","./src/components/keyboardshortcuts.tsx","./src/components/markdownrenderer.tsx","./src/components/navbar.tsx","./src/components/problemcard.tsx","./src/components/submissionstatus.tsx","./src/context/authcontext.tsx","./src/context/bookmarkcontext.tsx","./src/hooks/usedocumenttitle.ts","./src/pages/admin.tsx","./src/pages/adminproblemform.tsx","./src/pages/home.tsx","./src/pages/leaderboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/problemdetail.tsx","./src/pages/problems.tsx","./src/pages/profile.tsx","./src/pages/register.tsx","./src/pages/submissions.tsx","./src/pages/systemhealth.tsx","./src/services/api.ts","./src/types/index.ts"],"errors":true,"version":"5.9.3"}

0 commit comments

Comments
 (0)