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 (
{desc}
主机与 BOT 综合状态(每 2 秒刷新)
{error}
加载中…
) : !data.disk.length ? (未获取到磁盘信息
) : ({fmtBytes(d.used)} / {fmtBytes(d.total)}
负载: {cpu?.load_avg ? cpu.load_avg.join(' / ') : '—'}
{cpu?.per_core?.length ? (已用 {mem ? fmtBytes(mem.used) : '—'} · 可用 {mem ? fmtBytes(mem.available) : '—'}
共 {mem ? fmtBytes(mem.total) : '—'}
加载中…
) : !data.bots.length ? (暂无在线 QQ 账号
) : (