import { useEffect, useState } from 'react' import { Activity, Cpu, Database, Film, HardDrive, Radio, Users } from 'lucide-react' import { statsAPI } from '../api/stats' import type { Hardware, StatsSnapshot } from '../types' // fmtBytes is a tiny helper shared by the dashboard cards. function fmtBytes(n: number): string { if (!n || n <= 0) return '—' const u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] let v = n let i = 0 while (v >= 1024 && i < u.length - 1) { v /= 1024 i++ } return `${v.toFixed(2)} ${u[i]}` } function fmtHours(seconds: number): string { if (!seconds || seconds <= 0) return '—' const h = Math.floor(seconds / 3600) return `${h.toLocaleString()} h` } // StatsPage renders the operator dashboard. Aggregate stats refresh less often, // while hardware metrics are polled every 2 s for a real-time monitoring feel. export function StatsPage() { const [snap, setSnap] = useState(null) const [hardware, setHardware] = useState(null) const [lastMonitorAt, setLastMonitorAt] = useState('') const [monitorError, setMonitorError] = useState('') const [loading, setLoading] = useState(true) useEffect(() => { let cancelled = false const tick = () => statsAPI.snapshot().then((s) => { if (!cancelled) { setSnap(s) setHardware((current) => current ?? s.hardware) setLastMonitorAt((current) => current || s.generated_at) } }) tick().finally(() => setLoading(false)) const id = window.setInterval(tick, 30_000) return () => { cancelled = true window.clearInterval(id) } }, []) useEffect(() => { let cancelled = false const tick = () => statsAPI .monitor() .then((m) => { if (!cancelled) { setHardware(m) setLastMonitorAt(new Date().toISOString()) setMonitorError('') } }) .catch((err: unknown) => { if (!cancelled) { setMonitorError((err as { message?: string })?.message ?? '实时监控暂不可用') } }) tick() const id = window.setInterval(tick, 2_000) return () => { cancelled = true window.clearInterval(id) } }, []) if (loading) return

加载中…

if (!snap) return

无法获取统计数据

const live = hardware ?? snap.hardware const memPct = live.memory_total > 0 ? (live.memory_used / live.memory_total) * 100 : 0 const diskPct = live.disk_total > 0 ? (live.disk_used / live.disk_total) * 100 : 0 return (

运行状态

聚合快照:{new Date(snap.generated_at).toLocaleString()} · 实时监控每 2 秒刷新

{monitorError ? '监控重试中' : '实时监控中'}

最近刷新:{lastMonitorAt ? new Date(lastMonitorAt).toLocaleTimeString() : '—'}

} label="媒体库" value={snap.libraries.toLocaleString()} /> } label="媒体总数" value={snap.media_count.toLocaleString()} /> } label="用户" value={snap.users_count.toLocaleString()} /> } label="入库容量" value={fmtBytes(snap.total_size_bytes)} /> } label="累计时长" value={fmtHours(snap.total_seconds)} /> } label="CPU 占用" value={`${live.cpu_percent.toFixed(1)}%`} meter={live.cpu_percent} /> } label="内存占用" value={`${memPct.toFixed(1)}%`} /> } label="数据盘占用" value={`${diskPct.toFixed(1)}%`} />

系统实时监控

Live
{monitorError &&

实时监控错误:{monitorError}

}

聚合统计

) } function Tile({ icon, label, value, meter, }: { icon: React.ReactNode label: string value: string meter?: number }) { return (
{icon}

{label}

{value}

{typeof meter === 'number' && }
) } function Meter({ label, value }: { label: string; value: number }) { return (
{label} {value.toFixed(1)}%
) } function Bar({ value }: { value: number }) { const pct = Math.max(0, Math.min(100, value || 0)) const color = pct > 85 ? 'bg-red-500' : pct > 65 ? 'bg-amber-500' : 'bg-emerald-500' return (
) } function Row({ label, value }: { label: string; value: string }) { return (
{label} {value}
) }