import { Download, RefreshCw } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { platformApiClient } from "../api/client"; import type { DependencyProbeViewResponse, JobResponse, ServerInstanceResponse } from "../api/types"; import type { PageComponentProps } from "../contracts/page"; import { canUpdateServerGame, gameUpdateStatusFromProbe, gameUpdateStatusLabel, steamUpdateProbe, type GameUpdateStatus } from "../contracts/serverManagement"; import { dependencyJobRequest, serverLifecycleCommandRequest } from "../schemas/serverManagement"; import { ConfirmDialog } from "./OperationControls"; import { ResultBadge } from "./StateViews"; const dependencyCheckPollAttempts = 45; const dependencyCheckPollMs = 2000; export function gameUpdateCheckNote(status?: GameUpdateStatus): string { if (!status) { return "尚未检查过 SCUM 公开分支版本。"; } if (status.availability === "available") { const installed = status.installed ? `本地 ${status.installed}` : "本地版本未知"; const latest = status.latest ? `公开分支 ${status.latest}` : "公开分支版本未知"; return `${installed},${latest},可以执行更新。`; } if (status.availability === "up-to-date") { return status.installed ? `本地版本 ${status.installed} 已与公开分支一致。` : "本地版本已与公开分支一致。"; } return "SteamCMD 未返回可比较的版本号,请稍后重新检查或在服务器上确认 SteamCMD 可用。"; } interface ServerGameUpdatePanelProps { instance: ServerInstanceResponse; session: PageComponentProps["session"]; operations: PageComponentProps["operations"]; canManage: boolean; onChanged: () => void; } export function ServerGameUpdatePanel({ instance, session, operations, canManage, onChanged }: ServerGameUpdatePanelProps) { const [probe, setProbe] = useState(null); const [supported, setSupported] = useState(false); const [busy, setBusy] = useState<"check" | "update" | null>(null); const [note, setNote] = useState(null); const [result, setResult] = useState<{ status: "succeeded" | "failed" | "pending"; label: string } | null>(null); const [confirmUpdate, setConfirmUpdate] = useState(false); const status = useMemo(() => gameUpdateStatusFromProbe(probe ?? undefined), [probe]); const updateAvailable = status?.availability === "available"; const updateAllowed = updateAvailable && canUpdateServerGame(instance.state); const loadCatalog = useCallback(async () => { try { const catalog = await platformApiClient.getDependencyCatalog(instance.id); const candidate = steamUpdateProbe(catalog) ?? null; setProbe(candidate); setSupported(candidate !== null); } catch { setProbe(null); setSupported(false); } }, [instance.id]); useEffect(() => { void loadCatalog(); }, [loadCatalog]); async function checkForUpdate() { if (!probe) { return; } const operationId = operations.begin({ intent: "检查游戏更新", targetKind: "server", targetId: instance.id, requester: session.displayName }); setBusy("check"); setNote("正在向 SteamCMD 查询 SCUM 公开分支版本…"); setResult({ status: "pending", label: "正在检查版本" }); try { const job = await platformApiClient.checkDependencies(instance.id, dependencyJobRequest(instance.id, probe.key)); const finalJob = await waitForDependencyCheckJob(job.id); if (!finalJob) { throw new Error("版本检查任务还没有返回结果,可稍后在运维控制台查看该任务。"); } if (finalJob.state !== "succeeded") { throw new Error(finalJob.progress.message || finalJob.executionResult?.summary || "版本检查失败"); } const catalog = await platformApiClient.getDependencyCatalog(instance.id); const candidate = steamUpdateProbe(catalog) ?? null; setProbe(candidate); const label = gameUpdateStatusLabel(gameUpdateStatusFromProbe(candidate ?? undefined)); const summary = gameUpdateCheckNote(gameUpdateStatusFromProbe(candidate ?? undefined)); setNote(summary); setResult({ status: "succeeded", label }); operations.succeed(operationId, `游戏版本检查完成:${label}。${summary}`, finalJob); } catch (error) { const reason = error instanceof Error ? error.message : "版本检查失败"; setNote(reason); setResult({ status: "failed", label: reason }); operations.fail(operationId, reason, operationId); } finally { setBusy(null); } } async function submitUpdate() { const operationId = operations.begin({ intent: "更新游戏版本", targetKind: "server", targetId: instance.id, requester: session.displayName }); setBusy("update"); try { const response = await platformApiClient.updateServerGame(instance.id, serverLifecycleCommandRequest(instance, "update")); const message = `更新任务 ${response.job.id} 已派发:先按插件声明优雅关闭 SCUM 服务,再执行 SteamCMD 更新,成功后自动重新启动。`; setNote(message); setResult({ status: "pending", label: "更新任务已派发" }); operations.succeed(operationId, message, response.job); onChanged(); } catch (error) { const reason = error instanceof Error ? error.message : "更新派发失败"; setNote(reason); setResult({ status: "failed", label: reason }); operations.fail(operationId, reason, operationId); } finally { setBusy(null); } } if (!supported || !probe) { return null; } return (

游戏版本更新

{result && }

检查 Steam 公开分支的 SCUM 版本号,发现新版本后更新按钮会点亮。更新会先通过插件声明的优雅关机流程停止 SCUM 服务,关闭失败时中止更新,不会直接结束进程。

本地版本
{status?.installed ?? "未记录"}
公开分支版本
{status?.latest ?? "未记录"}
更新状态
{gameUpdateStatusLabel(status)}

{note ?? gameUpdateCheckNote(status)}

setConfirmUpdate(false)} onConfirm={() => { setConfirmUpdate(false); void submitUpdate(); }} />
); } function updateTitle(canManage: boolean, updateAvailable: boolean, updateAllowed: boolean, state: ServerInstanceResponse["state"]): string { if (!canManage) { return "当前账号没有管理权限"; } if (!updateAvailable) { return "先检查更新;检测到新版本后该按钮会点亮"; } if (!updateAllowed) { return `当前状态 ${state} 不能执行游戏更新`; } return "优雅关闭 SCUM 服务后执行 SteamCMD 更新,成功后自动启动"; } async function waitForDependencyCheckJob(jobId: string): Promise { for (let attempt = 0; attempt < dependencyCheckPollAttempts; attempt += 1) { const current = await platformApiClient.getJob(jobId); if (current.state === "succeeded" || current.state === "failed" || current.state === "cancelled") { return current; } await new Promise((resolve) => window.setTimeout(resolve, dependencyCheckPollMs)); } return null; }