"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 `${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")}`; } 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 }: { percent: number }) { const color = percent > 90 ? "bg-red-500" : percent > 70 ? "bg-amber-500" : "bg-blue-500"; return (
); } function numberFmt(n: number) { return n.toLocaleString("en-US"); } export default function ServerPage() { const { isLoggedIn } = useAuth(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const fetchData = useCallback(async () => { try { const res = await authFetch("/api/server/status"); if (!res.ok) return; const json = await res.json(); setData(json); } catch (e) { // ignore } finally { setLoading(false); } }, []); useEffect(() => { if (!isLoggedIn) return; fetchData(); const iv = setInterval(fetchData, 10000); return () => clearInterval(iv); }, [isLoggedIn, fetchData]); if (!isLoggedIn) { return (

请先登录查看服务器状态

登录 注册
); } return (
{/* 标题 */}

服务器监控

GCP asia-northeast1-b · 每10秒自动刷新

{data?.backfill_running && ( 回补运行中 )}
{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

{/* 运行时间 & 网络 */}
系统
{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 内存 重启 运行时间
{p.name} {p.status} {p.cpu}% {p.memory_mb} MB {p.restarts} {uptimeStr(p.uptime_ms)}
{/* PostgreSQL */}

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)} 小时`}

))}
)}
{/* 说明 */}
说明:数据每10秒自动刷新。PM2进程状态实时反映服务运行情况,回补运行中时CPU和网络负载会偏高属正常现象。
)}
); }