"use client"; import { useEffect, useState } from "react"; import { AlertTriangle, RefreshCcw, ShieldCheck, Database, Cpu, HardDrive, RadioTower } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; import type { SourceHealthPayload, SystemStatusPayload, HealthPayload } from "@/types/ops"; function sourceStatusTone(status?: string) { if (status === "fresh") return "text-emerald-500"; if (status === "expected_wait") return "text-blue-500"; if (status === "delayed") return "text-amber-500"; if (status === "stale" || status === "missing") return "text-red-500"; return "text-slate-500"; } function sourceStatusLabel(status?: string) { if (status === "fresh") return "正常"; if (status === "expected_wait") return "等待更新"; if (status === "delayed") return "延迟"; if (status === "stale") return "断线"; if (status === "missing") return "缺失"; return "未知"; } function formatAge(ageMin?: number | null) { if (ageMin == null) return "—"; if (ageMin < 60) return `${Math.round(ageMin)}m`; return `${(ageMin / 60).toFixed(1)}h`; } export function SystemPageClient() { const [loading, setLoading] = useState(true); const [health, setHealth] = useState(null); const [status, setStatus] = useState(null); const [sourceHealth, setSourceHealth] = useState(null); const [error, setError] = useState(""); const load = async () => { setLoading(true); setError(""); try { const [h, s, sh] = await Promise.all([ opsApi.health(), opsApi.systemStatus() as Promise, opsApi.sourceHealth(80) as Promise, ]); setHealth(h); setStatus(s); setSourceHealth(sh); } catch (e) { setError(String(e).slice(0, 200)); } finally { setLoading(false); } }; useEffect(() => { void load(); }, []); if (loading) { return
加载中...
; } if (error) { return
加载失败: {error}
; } const dbOk = status?.db?.ok ?? health?.db?.ok; const cacheAnalysis = status?.cache?.analysis; const sourceIssues = (sourceHealth?.cities || []) .flatMap((city) => (city.sources || []) .filter((source) => ["delayed", "stale", "missing", "unknown"].includes(String(source.status || ""))) .map((source) => ({ city: city.city, source })), ) .slice(0, 10); return (

系统状态

{/* Health badges */}
Health
{health?.status ?? "—"}
Database
{dbOk ? "OK" : "FAIL"}
存储模式
{status?.state_storage_mode ?? "—"}
概率引擎
{status?.probability?.engine_mode ?? "—"}
{/* Features & Integrations */}
功能开关
{status?.features ? Object.entries(status.features).map(([k, v]) => (
{k} {String(v)}
)) : 无数据}
集成状态
{status?.integrations ? Object.entries(status.integrations).map(([k, v]) => (
{k} {String(v)}
)) : 无数据}
{/* Cache Analysis */} {cacheAnalysis ? ( 缓存分析
总请求
{cacheAnalysis.total_requests ?? 0}
命中
{cacheAnalysis.cache_hits ?? 0}
未命中
{cacheAnalysis.cache_misses ?? 0}
命中率
{cacheAnalysis.hit_rate != null ? `${(cacheAnalysis.hit_rate * 100).toFixed(0)}%` : "—"}
) : null} 城市数据源健康
{["fresh", "expected_wait", "delayed", "stale", "missing"].map((key) => (
{sourceStatusLabel(key)}
{sourceHealth?.status_counts?.[key] ?? 0}
))}
{sourceIssues.length ? (
{sourceIssues.map(({ city, source }, index) => ( ))}
城市 来源 状态 延迟 最近观测 原因
{city}
{source.source_label || source.source_code}
{source.role}
{sourceStatusLabel(source.status)} {formatAge(source.age_min)} {source.observed_at || "—"} {source.reason || "—"}
) : (
当前缓存内未发现 MGM、KNMI、IMS 或机场站断线/延迟异常。
)} {(sourceHealth?.cities || []).some((city) => !city.cache_exists) ? (
有城市缺少 full/panel 缓存,可能是后台冷启动或该城市尚未预热。
) : null}
{/* DB Path */} {status?.db?.db_path ? ( 数据库路径 {status.db.db_path} ) : null}
); }