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 (
)
}