import { ListChecks, RotateCcw, ServerCog, Sparkles, WandSparkles } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { JobResponse, RunEndpointResponse, ServerInstanceResponse } from "../api/types"; import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews"; import { ProductionOperationsPanel } from "../components/ProductionOperationsPanel"; import { jobCapabilityLabel } from "../contracts/jobPresentation"; import type { PageComponentProps } from "../contracts/page"; import { cx } from "../utils/classes"; type ModuleState = { status: "loading" } | { status: "error"; reason: string } | { status: "ready"; data: T }; export function MaintenancePage({ session, operations, onNavigate }: PageComponentProps) { const [endpoints, setEndpoints] = useState>({ status: "loading" }); const [jobs, setJobs] = useState>({ status: "loading" }); const [servers, setServers] = useState>({ status: "loading" }); const [triageResult, setTriageResult] = useState<{ status: "pending" | "succeeded" | "failed"; label: string } | null>(null); const refreshEndpoints = useCallback(async () => { setEndpoints({ status: "loading" }); try { const response = await platformApiClient.listRunEndpoints(); setEndpoints({ status: "ready", data: response.items }); } catch (error) { setEndpoints({ status: "error", reason: error instanceof Error ? error.message : "加载失败" }); } }, []); const refreshJobs = useCallback(async () => { setJobs({ status: "loading" }); try { const response = await platformApiClient.listJobs(); setJobs({ status: "ready", data: response.items }); } catch (error) { setJobs({ status: "error", reason: error instanceof Error ? error.message : "加载失败" }); } }, []); const refreshServers = useCallback(async () => { setServers({ status: "loading" }); try { const response = await platformApiClient.listServerInstances(); setServers({ status: "ready", data: response.items }); } catch (error) { setServers({ status: "error", reason: error instanceof Error ? error.message : "加载失败" }); } }, []); const refreshAll = useCallback(() => { void refreshEndpoints(); void refreshJobs(); void refreshServers(); }, [refreshEndpoints, refreshJobs, refreshServers]); useEffect(() => { refreshAll(); }, [refreshAll]); const endpointItems = endpoints.status === "ready" ? endpoints.data : []; const jobItems = jobs.status === "ready" ? jobs.data : []; const serverItems = servers.status === "ready" ? servers.data : []; const failedJobs = useMemo(() => jobItems.filter((job) => job.state === "failed").slice(0, 8), [jobItems]); const serverById = useMemo(() => new Map(serverItems.map((server) => [server.id, server])), [serverItems]); const endpointById = useMemo(() => new Map(endpointItems.map((endpoint) => [endpoint.id, endpoint])), [endpointItems]); const unhealthyEndpointCount = endpointItems.filter((endpoint) => endpoint.status !== "online" || heartbeatAgeMinutes(endpoint.lastHeartbeatAt) > 5).length; async function retryJob(job: JobResponse) { const retryStamp = Date.now(); const operationId = operations.begin({ intent: "重试任务", targetKind: "platform", targetId: job.id, requester: session.displayName }); setTriageResult({ status: "pending", label: `正在重试 ${job.id}` }); try { const retried = await platformApiClient.createJob({ id: `${job.id}-retry-${retryStamp}`, serverInstanceId: job.serverInstanceId, runEndpointId: job.runEndpointId, capability: job.capability, targetKey: job.targetKey, inputRef: job.inputRef, idempotencyKey: `web-retry-${job.id}-${retryStamp}`, progress: { percent: 0, message: `retry of ${job.id}` } }); operations.succeed(operationId, `已创建重试任务:${retried.id}`, retried); setTriageResult({ status: "succeeded", label: `已创建重试任务 ${retried.id}` }); void refreshJobs(); } catch (error) { const message = error instanceof Error ? error.message : "任务重试失败"; operations.fail(operationId, message); setTriageResult({ status: "failed", label: message }); } } return (

平台管理员

系统维护

维护排障入口 从节点详情和最近失败任务进入重试、查看相关服务器、查看日志链路,不需要直接接触 run 端。
{triageResult && }

运行节点

{endpoints.status === "loading" && } {endpoints.status === "error" && ( void refreshEndpoints()} compact /> )} {servers.status === "error" && void refreshServers()} compact />} {endpoints.status === "ready" && endpoints.data.length === 0 && ( void refreshEndpoints()} /> )} {endpoints.status === "ready" && endpoints.data.length > 0 && (
{endpoints.data.map((endpoint) => { const relatedServers = serverItems.filter((server) => server.runEndpointId === endpoint.id); const firstRelatedServer = relatedServers[0]; return (
{endpoint.displayName} {endpoint.id}
{endpointStatusLabel(endpoint)} 任务 {endpoint.capacity.runningJobs}/{endpoint.capacity.maxJobs}(排队 {endpoint.capacity.queuedJobs}) {heartbeatReason(endpoint)}
节点详情 版本 {endpoint.version} 能力 {endpoint.capabilities.slice(0, 4).join(" / ") || "未上报"} 相关服务器 {relatedServers.length}
); })}
)}

最近失败任务

{jobs.status === "loading" && } {jobs.status === "error" && void refreshJobs()} compact />} {jobs.status === "ready" && failedJobs.length === 0 && ( void refreshJobs()} /> )} {jobs.status === "ready" && failedJobs.length > 0 && (
{failedJobs.map((job) => { const server = job.serverInstanceId ? serverById.get(job.serverInstanceId) : undefined; const endpoint = endpointById.get(job.runEndpointId); return (
{jobCapabilityLabel(job.capability)} {jobStateLabel(job.state)}
任务 {job.id} 服务器 {server ? server.name : job.serverInstanceId ?? "平台任务"} 节点 {endpoint ? endpoint.displayName : job.runEndpointId} {progressMessage(job)} 尝试 {job.attempt}/{job.retryPolicy.maxAttempts} {formatTimestamp(job.updatedAt)}
); })}
)}
); } function endpointStatusClass(endpoint: RunEndpointResponse): string { if (endpoint.status === "online" && heartbeatAgeMinutes(endpoint.lastHeartbeatAt) <= 5) { return "status-active"; } if (endpoint.status === "offline") { return "status-error"; } return "status-disabled"; } function endpointStatusLabel(endpoint: RunEndpointResponse): string { if (endpoint.status === "online" && heartbeatAgeMinutes(endpoint.lastHeartbeatAt) > 5) { return "心跳延迟"; } return endpoint.status === "online" ? "在线" : endpoint.status === "offline" ? "离线" : endpoint.status; } function heartbeatReason(endpoint: RunEndpointResponse): string { const age = heartbeatAgeMinutes(endpoint.lastHeartbeatAt); if (!Number.isFinite(age)) { return `心跳时间异常:${endpoint.lastHeartbeatAt}`; } if (endpoint.status === "offline") { return `心跳异常:节点离线,最后 ${formatTimestamp(endpoint.lastHeartbeatAt)}`; } if (age > 5) { return `心跳异常:${Math.round(age)} 分钟未更新`; } if (endpoint.capacity.runningJobs >= endpoint.capacity.maxJobs) { return "容量已满:等待任务会继续排队"; } return `心跳正常:${formatTimestamp(endpoint.lastHeartbeatAt)}`; } function heartbeatAgeMinutes(value: string): number { const timestamp = new Date(value).getTime(); if (!Number.isFinite(timestamp)) { return Number.POSITIVE_INFINITY; } return (Date.now() - timestamp) / 60000; } function jobStateLabel(state: JobResponse["state"]): string { switch (state) { case "queued": return "排队"; case "accepted": return "已接收"; case "running": return "运行中"; case "retrying": return "等待重试"; case "succeeded": return "成功"; case "cancelled": return "已取消"; default: return "失败"; } } function progressMessage(job: JobResponse): string { return job.progress.message ? `原因 ${job.progress.message}` : `进度 ${job.progress.percent}%`; } function formatTimestamp(value: string): string { const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); }