import { Activity, AlertTriangle, CakeSlice, Candy, Info, MoonStar, Sparkles } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { AiProviderResponse, AuditEventResponse, JobResponse, PlatformResourceUsageResponse, RunEndpointResponse, ServerInstanceResponse, ServerMetricsResponse } from "../api/types"; import { UsageMeter } from "../components/OperationControls"; import { EmptyState, ErrorState, LoadingState } from "../components/StateViews"; import type { PageComponentProps } from "../contracts/page"; import type { GameTypeDistributionEntry, PlatformOverviewSignal } from "../contracts/workspace"; import { serverIsOnline } from "../contracts/workspace"; import { cx } from "../utils/classes"; type ModuleState = { status: "loading" } | { status: "error"; reason: string } | { status: "ready"; data: T }; interface OverviewData { instances: ServerInstanceResponse[]; endpoints: RunEndpointResponse[]; jobs: JobResponse[]; } export function HomePage({ onNavigate }: PageComponentProps) { const [core, setCore] = useState>({ status: "loading" }); const [metrics, setMetrics] = useState>({ status: "loading" }); const [usage, setUsage] = useState>({ status: "loading" }); const [providers, setProviders] = useState>({ status: "loading" }); const [signals, setSignals] = useState>({ status: "loading" }); const refreshCore = useCallback(async () => { setCore({ status: "loading" }); try { const [instances, endpoints, jobs] = await Promise.all([ platformApiClient.listServerInstances(), platformApiClient.listRunEndpoints(), platformApiClient.listJobs() ]); setCore({ status: "ready", data: { instances: instances.items, endpoints: endpoints.items, jobs: jobs.items } }); } catch (error) { setCore({ status: "error", reason: error instanceof Error ? error.message : "加载失败" }); } }, []); const refreshMetrics = useCallback(async () => { setMetrics({ status: "loading" }); try { const response = await platformApiClient.listServerMetrics(); setMetrics({ status: "ready", data: response.items }); } catch { setMetrics({ status: "ready", data: [] }); } }, []); const refreshUsage = useCallback(async () => { setUsage({ status: "loading" }); try { const response = await platformApiClient.getPlatformResourceUsage(); setUsage({ status: "ready", data: response }); } catch { setUsage({ status: "error", reason: "平台资源指标接口尚未提供" }); } }, []); const refreshProviders = useCallback(async () => { setProviders({ status: "loading" }); try { const response = await platformApiClient.listAiProviders(); setProviders({ status: "ready", data: response.items }); } catch (error) { setProviders({ status: "error", reason: error instanceof Error ? error.message : "加载失败" }); } }, []); const refreshSignals = useCallback(async () => { setSignals({ status: "loading" }); try { const response = await platformApiClient.listAuditEvents(); setSignals({ status: "ready", data: response.items }); } catch { setSignals({ status: "ready", data: [] }); } }, []); useEffect(() => { void refreshCore(); void refreshMetrics(); void refreshUsage(); void refreshProviders(); void refreshSignals(); }, [refreshCore, refreshMetrics, refreshUsage, refreshProviders, refreshSignals]); const distribution = useMemo(() => { if (core.status !== "ready") { return []; } const counts = new Map(); for (const instance of core.data.instances) { counts.set(instance.pluginId, (counts.get(instance.pluginId) ?? 0) + 1); } return [...counts.entries()].map(([serverType, count]) => ({ serverType, label: serverType, count })); }, [core]); const overviewSignals = useMemo(() => { const collected: PlatformOverviewSignal[] = []; if (core.status === "ready") { for (const instance of core.data.instances.filter((item) => item.state === "failed")) { collected.push({ id: `fault-${instance.id}`, kind: "fault", summary: `服务器 ${instance.name} 处于异常状态`, detail: `实例 ${instance.id} 状态为 failed,需要排查`, targetPage: "servers", targetId: instance.id, tone: "error", at: instance.updatedAt }); } for (const job of core.data.jobs.filter((item) => item.state === "failed").slice(0, 5)) { collected.push({ id: `job-${job.id}`, kind: "job", summary: `任务 ${job.capability} 失败`, detail: `任务 ${job.id}${job.serverInstanceId ? `(服务器 ${job.serverInstanceId})` : ""} 执行失败`, targetPage: "servers", targetId: job.serverInstanceId, tone: "error", at: job.updatedAt }); } } if (providers.status === "ready") { for (const provider of providers.data.filter((item) => item.status === "error")) { collected.push({ id: `ai-${provider.id}`, kind: "aiProvider", summary: `AI 提供商 ${provider.name} 连接异常`, detail: `提供商 ${provider.id} 状态为 error,LLM 辅助可能不可用`, targetPage: "aiProviders", targetId: provider.id, tone: "warning", at: "" }); } } if (signals.status === "ready") { for (const event of signals.data.slice(0, 5)) { collected.push({ id: `audit-${event.id}`, kind: "log", summary: event.summary || `${event.action} ${event.resourceKind}`, detail: `${event.actorId} 对 ${event.resourceKind}/${event.resourceId} 执行 ${event.action}(${event.result})`, targetPage: event.resourceKind === "server-instance" ? "servers" : "maintenance", targetId: event.resourceId, tone: event.result === "failure" ? "warning" : "info", at: event.createdAt }); } } return collected.slice(0, 8); }, [core, providers, signals]); const onlineCount = core.status === "ready" ? core.data.instances.filter((item) => serverIsOnline(item.state)).length : 0; const offlineCount = core.status === "ready" ? core.data.instances.length - onlineCount : 0; const activeProviders = providers.status === "ready" ? providers.data.filter((item) => item.status === "active").length : 0; const errorProviders = providers.status === "ready" ? providers.data.filter((item) => item.status === "error").length : 0; const metricAverages = useMemo(() => { if (usage.status === "ready") { return { cpu: usage.data.cpuPercent, memory: usage.data.memoryPercent, disk: usage.data.diskPercent, source: "平台指标" }; } if (metrics.status === "ready" && metrics.data.length > 0) { const average = (values: Array) => { const known = values.filter((value): value is number => typeof value === "number"); return known.length > 0 ? known.reduce((sum, value) => sum + value, 0) / known.length : undefined; }; return { cpu: average(metrics.data.map((item) => item.cpuPercent)), memory: average(metrics.data.map((item) => item.memoryPercent)), disk: average(metrics.data.map((item) => item.diskPercent)), source: "按服务器均值" }; } return { cpu: undefined, memory: undefined, disk: undefined, source: "暂无数据" }; }, [usage, metrics]); return (

平台管理员

平台概览

{core.status === "ready" ? "数据已加载" : core.status === "loading" ? "加载中" : "部分模块加载失败"}
{core.status === "loading" && } {core.status === "error" && void refreshCore()} />} {core.status === "ready" && core.data.instances.length === 0 && ( } title="还没有服务器实例" description="平台尚未创建任何服务器。前往服务器管理创建第一个实例,或检查运行节点是否在线。" actionLabel="前往服务器管理" onAction={() => onNavigate("servers")} /> )} {core.status === "ready" && core.data.instances.length > 0 && (
在线服务器 {onlineCount}

共 {core.data.instances.length} 个实例

0 ? "metric-tone-warning" : "metric-tone-neutral")}> 离线 / 未运行 {offlineCount}

{core.data.instances.filter((item) => item.state === "failed").length} 个异常

运行节点 {core.data.endpoints.filter((item) => item.status === "online").length}

共 {core.data.endpoints.length} 个节点

0 ? "metric-tone-warning" : "metric-tone-success")}> LLM 连接 {providers.status === "ready" ? `${activeProviders} 可用` : providers.status === "loading" ? "…" : "未知"}

{errorProviders > 0 ? `${errorProviders} 个提供商异常` : "提供商状态正常"}

)}

资源负载

{metricAverages.source}

游戏类型分布

{core.status === "loading" ? ( ) : distribution.length === 0 ? ( ) : (
{distribution.map((entry) => ( {entry.label}:{entry.count} 个实例 ))}
)}

最近运营信号

{signals.status === "loading" && core.status === "loading" ? ( ) : overviewSignals.length === 0 ? ( ) : (
{overviewSignals.map((signal) => ( ))}
)}
); }