import { useEffect, useState, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { NavBar, Card, Empty, DotLoading } from 'antd-mobile' import { api } from '../api/client.js' import SwipeTabs from '../components/SwipeTabs.jsx' // 监控页: 两个横向分类 // - 主机监控: 实时数据来自 komari (服务端实时代理, 不落库), 点击节点看 24h 时序 // - 服务监控: 暂留空占位 function fmtTime(s) { if (!s) return '' const d = new Date(s) if (isNaN(d.getTime())) return s const pad = (n) => String(n).padStart(2, '0') return pad(d.getHours()) + ':' + pad(d.getMinutes()) } function fmtBytes(b) { if (!b) return '0 B' const units = ['B', 'KB', 'MB', 'GB', 'TB'] let i = 0 let v = Number(b) while (v >= 1024 && i < units.length - 1) { v /= 1024; i++ } return v.toFixed(v >= 100 ? 0 : 1) + ' ' + units[i] } function fmtUptime(sec) { const s = Number(sec) || 0 const d = Math.floor(s / 86400) const h = Math.floor((s % 86400) / 3600) return d > 0 ? d + '天' + h + '小时' : h + '小时' } function pct(used, total) { return total > 0 ? Math.min(100, (used / total) * 100) : 0 } export default function MonitorPage() { const [active, setActive] = useState('servers') const tabs = [ { key: 'servers', label: '主机监控', render: () => }, { key: 'services', label: '服务监控', render: () => }, ] return (
监控
) } // 主机监控列表模块级缓存: 切走再进入时先展示上次数据, 拉取成功后再静默更新 let cachedServers = null // 主机监控: komari 实时节点列表 (30s 轮询, 静默刷新) function ServerView() { const [items, setItems] = useState(cachedServers) // 有缓存直接先展示, 无缓存为 null 进入加载态 const [err, setErr] = useState('') const navigate = useNavigate() const load = useCallback((silent) => { if (!silent && cachedServers === null) setItems(null) // 仅无缓存时显示加载中 if (!silent) setErr('') api('/servers') .then((data) => { cachedServers = data.items || []; setErr(''); setItems(data.items || []) }) .catch((e) => { if (!silent) { setErr(e.message); if (cachedServers === null) setItems([]) } }) }, []) useEffect(() => { load(false) const timer = setInterval(() => load(true), 30000) return () => clearInterval(timer) }, [load]) return (
{items === null &&
加载中...
} {err && (
{err} load(false)}>重试
)} {items !== null && !err && items.length === 0 && } {items !== null && items.length > 0 && ( <>
实时数据 · 每 30 秒自动刷新
{items.map((n) => ( navigate('/monitor/server/' + n.uuid)}>
{n.name} 更新于 {fmtTime(n.recordedAt)}
CPU
{n.cpuUsage.toFixed ? n.cpuUsage.toFixed(1) : n.cpuUsage}%
内存
{fmtBytes(n.ramUsed)} / {fmtBytes(n.memTotal)}
磁盘
{fmtBytes(n.diskUsed)} / {fmtBytes(n.diskTotal)}
负载 {n.load1} / {n.load5} / {n.load15} {n.cpuCores} 核 进程 {n.process} 连接 {n.connections} 运行 {fmtUptime(n.uptime)}
))}
)}
) } // 进度条 (超 80% 变红) function Meter({ percent }) { const p = Math.max(0, Math.min(100, Number(percent) || 0)) return (
= 80 ? ' warn' : '')} style={{ width: p + '%' }} />
) } // 服务监控: 暂留空 function ServiceView() { return (
) }