import { useCallback, useEffect, useRef, useState } from 'react' import { Avatar, Card } from '@heroui/react' import { hubDashboard, hubDashboardStream } from '../../api/client' interface BotInfo { self_id: string adapter: string nick: string avatar: string ws_connected: boolean online: boolean | null msg_recv: number | null msg_sent: number | null } interface SystemInfo { hostname: string os: string python: string nonebot: string boot_time: number system_uptime_seconds: number bot_uptime_seconds: number } interface CpuInfo { percent: number per_core: number[] logical: number | null physical: number | null brand: string load_avg: number[] | null } interface MemInfo { percent: number used: number total: number available: number process_rss: number process_percent: number } interface ProcessInfo { pid: number name: string rss: number cpu_percent: number memory_percent: number create_time: number } interface DiskInfo { mount: string device: string percent: number used: number total: number } interface NicInfo { name: string up: boolean sent: number recv: number ip: string } interface NetworkInfo { sent: number recv: number sent_rate: number recv_rate: number packets_sent: number packets_recv: number interfaces: NicInfo[] } interface BackendInfo { nonebot_version: string plugin_count: number python: string driver: string driver_type: string } interface DashboardData { ok: boolean bots: BotInfo[] system: SystemInfo cpu: CpuInfo memory: MemInfo process: ProcessInfo disk: DiskInfo[] network: NetworkInfo backend: BackendInfo } function fmtBytes(n: number): string { if (!Number.isFinite(n)) return '—' const units = ['B', 'KB', 'MB', 'GB', 'TB'] let v = n let i = 0 while (v >= 1024 && i < units.length - 1) { v /= 1024; i++ } return v.toFixed(1) + ' ' + units[i] } function fmtRate(n: number): string { if (!Number.isFinite(n) || n < 0) return '0 B/s' return fmtBytes(n) + '/s' } function fmtUptime(sec: number): string { if (!Number.isFinite(sec)) return '—' const d = Math.floor(sec / 86400) const h = Math.floor((sec % 86400) / 3600) const m = Math.floor((sec % 3600) / 60) const parts: string[] = [] if (d) parts.push(d + ' 天') if (h) parts.push(h + ' 小时') parts.push(m + ' 分') return parts.join(' ') } function Bar({ value, color }: { value: number; color?: string }) { const v = Math.max(0, Math.min(100, value)) const c = color || (v >= 80 ? 'bg-rose-500' : v >= 60 ? 'bg-amber-500' : 'bg-indigo-500') return (
) } function coreColor(v: number): string { if (v >= 80) return 'bg-rose-500' if (v >= 60) return 'bg-amber-500' if (v >= 30) return 'bg-indigo-500' return 'bg-emerald-500' } function InfoRow({ label, value }: { label: string; value: string }) { return (
{label} {value || '—'}
) } function SectionTitle({ title, desc }: { title: string; desc: string }) { return (

{title}

{desc}

) } export default function HomePage() { const [data, setData] = useState(null) const [error, setError] = useState('') const [loading, setLoading] = useState(false) const refreshingRef = useRef(false) const refresh = useCallback(async (silent = false) => { if (refreshingRef.current) return refreshingRef.current = true if (!silent) setLoading(true) try { const d = await hubDashboard() if (!d || d.ok === false) throw new Error((d && (d as any).msg) || '采集失败') setData(d) setError('') } catch (e: any) { setError(e.message || '加载失败') } finally { refreshingRef.current = false if (!silent) setLoading(false) } }, []) useEffect(() => { refresh(true) // 初始静默加载,快速出首屏 const dispose = hubDashboardStream({ onData: (d) => { if (d && d.ok !== false) { setData(d); setError('') } }, }) return () => { dispose() } }, [refresh]) const cpu = data?.cpu const mem = data?.memory const proc = data?.process const sys = data?.system const net = data?.network const backend = data?.backend return (

总览

主机与 BOT 综合状态(每 2 秒刷新)

{error && !data &&

{error}

} {/* 1. 主机规格信息:系统 / 处理器 / 内存 / 硬盘 */}
系统
处理器
内存
硬盘
{!data ? (

加载中…

) : !data.disk.length ? (

未获取到磁盘信息

) : (
{data.disk.map((d) => (
{d.mount} {d.percent}%

{fmtBytes(d.used)} / {fmtBytes(d.total)}

))}
)}
{/* 2. 资源占用信息:CPU / 内存 / 网络 */}
{/* CPU */}
CPU 使用率{cpu?.logical ? ' ' + cpu.logical + ' 核' : ''} {cpu ? cpu.percent + '%' : '—'}

负载: {cpu?.load_avg ? cpu.load_avg.join(' / ') : '—'}

{cpu?.per_core?.length ? (
{cpu.per_core.map((v, i) => (
{i}
{v.toFixed(0)}%
))}
) : null}
{/* 内存 */}
内存使用 {mem ? mem.percent + '%' : '—'}

已用 {mem ? fmtBytes(mem.used) : '—'} · 可用 {mem ? fmtBytes(mem.available) : '—'}

共 {mem ? fmtBytes(mem.total) : '—'}

{/* 网络 */}
网络流量 {net?.interfaces?.length ? {net.interfaces.length} 网卡 : null}
↓ 下行速率
{net ? fmtRate(net.recv_rate) : '—'}
↑ 上行速率
{net ? fmtRate(net.sent_rate) : '—'}
累计下行
{net ? fmtBytes(net.recv) : '—'}
累计上行
{net ? fmtBytes(net.sent) : '—'}
{/* 本进程 */}
运行进程(bot)
PID
{proc?.pid ?? '—'}
进程名
{proc?.name || '—'}
CPU
{proc?.cpu_percent != null ? proc.cpu_percent + '%' : '—'}
内存占用
{proc?.memory_percent != null ? proc.memory_percent + '%' : '—'}
RSS
{proc ? fmtBytes(proc.rss) : '—'}
{/* 3. BOT 后端信息:NoneBot 版本 / 驱动方式 / 装载插件数 */}
NoneBot 版本
{backend?.nonebot_version || '—'}
驱动方式
{backend?.driver || '—'}
{backend?.driver_type || ''}
装载插件数
{backend?.plugin_count ?? '—'} 个
Python
{backend?.python || '—'}
bot 运行 {sys ? fmtUptime(sys.bot_uptime_seconds) : '—'}
{/* 4. BOT 协议端信息:登录账号昵称 / 账号 / 适配器类型 */} {!data ? (

加载中…

) : !data.bots.length ? (

暂无在线 QQ 账号

) : (
{data.bots.map(bot => (
{(bot.nick || bot.self_id).charAt(0).toUpperCase()}
{bot.nick}
{bot.self_id} · {bot.adapter}
{bot.msg_recv != null && 收 {bot.msg_recv}} {bot.msg_sent != null && 发 {bot.msg_sent}} {bot.online != null && {bot.online ? '在线' : '离线'}} {bot.ws_connected ? '已连接' : '未连接'}
))}
)}
) }