"use client"; import { useEffect, useState, useCallback } from "react"; import { authFetch } from "@/lib/auth"; import { useAuth } from "@/lib/auth"; import Link from "next/link"; interface PM2Proc { name: string; status: string; cpu: number; memory_mb: number; restarts: number; uptime_ms: number; pid: number; } interface ServerStatus { timestamp: number; cpu: { percent: number; cores: number }; memory: { total_gb: number; used_gb: number; percent: number; swap_percent: number }; disk: { total_gb: number; used_gb: number; free_gb: number; percent: number }; load: { load1: number; load5: number; load15: number }; uptime_hours: number; network: { bytes_sent_gb: number; bytes_recv_gb: number }; pm2: PM2Proc[]; postgres: { db_size_mb: number; agg_trades_count: number; rate_snapshots_count: number; symbols: Record; }; backfill_running: boolean; } function bjtStr(ms: number) { const d = new Date(ms + 8 * 3600 * 1000); return `${d.getUTCFullYear()}-${String(d.getUTCMonth()+1).padStart(2,"0")}-${String(d.getUTCDate()).padStart(2,"0")} ${String(d.getUTCHours()).padStart(2,"0")}:${String(d.getUTCMinutes()).padStart(2,"0")}:${String(d.getUTCSeconds()).padStart(2,"0")}`; } function uptimeStr(ms: number) { if (!ms) return "-"; const now = Date.now(); const diff = now - ms; const h = Math.floor(diff / 3600000); const m = Math.floor((diff % 3600000) / 60000); if (h > 24) return `${Math.floor(h/24)}d ${h%24}h`; if (h > 0) return `${h}h ${m}m`; return `${m}m`; } function ProgressBar({ percent, color = "cyan" }: { percent: number; color?: string }) { const colorClass = percent > 90 ? "bg-red-500" : percent > 70 ? "bg-amber-500" : color === "cyan" ? "bg-cyan-500" : "bg-emerald-500"; return (
); } function StatusBadge({ status }: { status: string }) { const isOnline = status === "online"; return ( {status} ); } function numberFmt(n: number) { return n.toLocaleString("en-US"); } export default function ServerPage() { const { isLoggedIn, accessToken } = useAuth(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [lastUpdate, setLastUpdate] = useState(0); const fetchData = useCallback(async () => { try { const res = await authFetch("/api/server/status"); if (!res.ok) return; const json = await res.json(); setData(json); setLastUpdate(Date.now()); } catch (e) { // ignore } finally { setLoading(false); } }, []); useEffect(() => { if (!isLoggedIn) return; fetchData(); const iv = setInterval(fetchData, 10000); // 10秒刷新 return () => clearInterval(iv); }, [isLoggedIn, fetchData]); if (!isLoggedIn) { return (

请先登录

登录
); } return (
{/* 顶栏 */}
← 返回

🖥️ 服务器监控

GCP asia-northeast1-b
{data?.backfill_running && ( 回补运行中 )} 每10秒刷新 {lastUpdate > 0 && · 更新于 {bjtStr(lastUpdate)}}
{loading ? (
加载中...
) : !data ? (
获取失败
) : ( <> {/* 系统概览 4卡片 */}
{/* CPU */}
CPU {data.cpu.cores} 核
{data.cpu.percent}%
负载: {data.load.load1} / {data.load.load5} / {data.load.load15}
{/* 内存 */}
内存 {data.memory.used_gb}G / {data.memory.total_gb}G
{data.memory.percent}%
{data.memory.swap_percent > 0 && (
Swap: {data.memory.swap_percent}%
)}
{/* 硬盘 */}
硬盘 {data.disk.free_gb}G 可用
{data.disk.percent}%
{data.disk.used_gb}G / {data.disk.total_gb}G
{/* 运行时间 & 网络 */}
系统 Uptime
{data.uptime_hours > 24 ? `${Math.floor(data.uptime_hours/24)}天` : `${data.uptime_hours}h`}
↑ 发送: {data.network.bytes_sent_gb} GB
↓ 接收: {data.network.bytes_recv_gb} GB
{/* PM2 进程列表 */}

📦 PM2 进程

{data.pm2.map((p, i) => ( ))}
名称 状态 CPU 内存 重启 运行时间 PID
{p.name} {p.cpu}% {p.memory_mb} MB {p.restarts} {uptimeStr(p.uptime_ms)} {p.pid || "-"}
{/* 数据库信息 */}

🗄️ PostgreSQL

数据库大小
{data.postgres.db_size_mb > 1024 ? `${(data.postgres.db_size_mb / 1024).toFixed(1)} GB` : `${data.postgres.db_size_mb} MB`}
aggTrades 总条数
{numberFmt(data.postgres.agg_trades_count)}
费率快照
{numberFmt(data.postgres.rate_snapshots_count)}
回补状态
{data.backfill_running ? "🔄 运行中" : "⏸ 停止"}
{data.postgres.symbols && Object.keys(data.postgres.symbols).length > 0 && (
数据覆盖范围
{Object.entries(data.postgres.symbols).map(([sym, info]) => (
{sym}
{bjtStr(info.earliest_ms)} → {bjtStr(info.latest_ms)}
{info.span_hours > 24 ? `${(info.span_hours/24).toFixed(1)} 天` : `${info.span_hours.toFixed(1)} 小时`}
))}
)}
)}
); }