import { CakeSlice, Candy, Search, Sparkles } from "lucide-react"; import { type ChangeEvent, type FormEvent, useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { GamePluginResponse, JobResponse, RunEndpointResponse, ServerInstanceResponse, ServerMetricsResponse } from "../api/types"; import { UsageMeter } from "../components/OperationControls"; import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews"; import type { PageComponentProps } from "../contracts/page"; import { defaultServerCreateForm, endpointLabel, pendingJobsForServer, pluginLabel, type ServerCreateFormState } from "../contracts/serverManagement"; import { filterServerCards, serverIsOnline, type ServerCardView, type ServerStatusFilter } from "../contracts/workspace"; import { serverCreateRequestFromForm } from "../schemas/serverManagement"; import { isPlatformAdmin } from "../contracts/workspace"; import { cx } from "../utils/classes"; type ListState = "loading" | "ready" | "error"; const statusFilters: Array<{ id: ServerStatusFilter; label: string }> = [ { id: "all", label: "全部" }, { id: "online", label: "在线" }, { id: "offline", label: "离线" }, { id: "attention", label: "需关注" } ]; export function ServersPage({ session, operations, onNavigate }: PageComponentProps) { const [listState, setListState] = useState("loading"); const [listError, setListError] = useState(""); const [plugins, setPlugins] = useState([]); const [endpoints, setEndpoints] = useState([]); const [instances, setInstances] = useState([]); const [jobs, setJobs] = useState([]); const [metrics, setMetrics] = useState>(new Map()); const [metricsPending, setMetricsPending] = useState(true); const [keyword, setKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [form, setForm] = useState(() => defaultServerCreateForm([], [])); const [showCreate, setShowCreate] = useState(false); const refresh = useCallback(async () => { setListState("loading"); try { const [pluginResponse, endpointResponse, instanceResponse, jobResponse] = await Promise.all([ platformApiClient.listGamePlugins(), platformApiClient.listRunEndpoints(), platformApiClient.listServerInstances(), platformApiClient.listJobs() ]); setPlugins(pluginResponse.items); setEndpoints(endpointResponse.items); setInstances(instanceResponse.items); setJobs(jobResponse.items); setForm((current) => ({ ...current, pluginId: pluginResponse.items.some((plugin) => plugin.id === current.pluginId) ? current.pluginId : pluginResponse.items[0]?.id || "", runEndpointId: endpointResponse.items.some((endpoint) => endpoint.id === current.runEndpointId) ? current.runEndpointId : endpointResponse.items[0]?.id || "" })); setListState("ready"); setListError(""); } catch (error) { setListState("error"); setListError(error instanceof Error ? error.message : "加载失败"); } setMetricsPending(true); try { const metricsResponse = await platformApiClient.listServerMetrics(); setMetrics(new Map(metricsResponse.items.map((item) => [item.serverInstanceId, item]))); } catch { setMetrics(new Map()); } finally { setMetricsPending(false); } }, []); useEffect(() => { void refresh(); }, [refresh]); const cards = useMemo( () => instances.map((instance) => ({ instance, metrics: metrics.get(instance.id), pendingJobs: pendingJobsForServer(jobs, instance.id).length })), [instances, jobs, metrics] ); const visibleCards = useMemo(() => filterServerCards(cards, keyword, statusFilter), [cards, keyword, statusFilter]); const createPending = operations.isPending("platform", "创建服务器"); function updateForm(event: ChangeEvent) { const { name, value } = event.target; setForm((current) => ({ ...current, [name]: value })); } async function handleCreate(event: FormEvent) { event.preventDefault(); const operationId = operations.begin({ intent: "创建服务器", targetKind: "server", targetId: "platform", requester: session.displayName }); try { const result = await platformApiClient.createServerWorkflow(serverCreateRequestFromForm(form)); operations.succeed(operationId, `已创建实例 ${result.instance.id},安装任务 ${result.job.id} 已派发`, result.job); setForm(defaultServerCreateForm(plugins, endpoints)); setShowCreate(false); await refresh(); } catch (error) { operations.fail(operationId, error instanceof Error ? error.message : "创建失败", operationId); } } const latestCreate = operations.operations.find((operation) => operation.intent === "创建服务器"); return (

{isPlatformAdmin(session) ? "平台管理员" : "我的服务器"}

服务器管理

{latestCreate && (
)} {showCreate && (
void handleCreate(event)} aria-label="创建服务器">

创建服务器

)}
{listState === "loading" && } {listState === "error" && void refresh()} />} {listState === "ready" && cards.length === 0 && ( } title="暂无可管理的服务器" description={ isPlatformAdmin(session) ? "平台还没有服务器实例。点击上方“创建服务器”开始,或检查运行节点状态。" : "当前账号名下没有可管理的服务器。如果这不符合预期,请联系平台管理员为你分配服务器,或点击刷新重试。" } actionLabel="刷新" onAction={() => void refresh()} /> )} {listState === "ready" && cards.length > 0 && visibleCards.length === 0 && ( { setKeyword(""); setStatusFilter("all"); }} /> )} {visibleCards.length > 0 && (
{visibleCards.map((card) => ( onNavigate("serverDetail", { serverId: card.instance.id })} /> ))}
)}
); } interface ServerCardProps { card: ServerCardView; metricsPending: boolean; onOpen: () => void; } function ServerCard({ card, metricsPending, onOpen }: ServerCardProps) { const { instance, metrics, pendingJobs } = card; const online = serverIsOnline(instance.state); return ( ); } function formatStat(value: number | undefined, pending: boolean, format: (value: number) => string): string { if (typeof value === "number" && Number.isFinite(value)) { return format(value); } return pending ? "…" : "--"; } export function stateLabel(state: ServerInstanceResponse["state"]): string { switch (state) { case "installing": return "安装中"; case "ready": return "就绪"; case "running": return "运行中"; case "stopped": return "已停止"; case "failed": return "异常"; case "draft": return "草稿"; case "deleted": return "已删除"; } } export function statusClass(state: ServerInstanceResponse["state"]): string { if (state === "running" || state === "ready") { return "status-active"; } if (state === "failed" || state === "deleted") { return "status-error"; } return "status-disabled"; }