import type { DependencyCatalogResponse, DependencyProbeViewResponse, GamePluginResponse, JobResponse, RunEndpointResponse, ServerDeploymentMode, ServerInstanceResponse, ServerInstanceState } from "../api/types"; export type ServerWorkflowViewState = "api" | "local" | "saving"; export type ServerLifecycleActionLabel = "create" | "start" | "stop" | "refresh"; export type ServerRemovalAction = "delete"; export interface ServerCreateFormState { id: string; name: string; pluginId: string; createInputs: Record; deploymentMode: ServerDeploymentMode; serverRoot: string; workingDirectory: string; installCommand: string; startCommand: string; stopCommand: string; statusCommand: string; shell: "" | "posix-sh" | "powershell" | "cmd"; } export interface ServerWorkflowActionState { label: ServerLifecycleActionLabel; serverInstanceId?: string; success: boolean; message: string; } export interface ServerMetadataFormState { name: string; } export interface ServerRemovalConfirmationState { action: ServerRemovalAction; serverInstanceId: string; name: string; state: ServerInstanceState; } export interface ServerManagementSummary { total: number; running: number; pendingJobs: number; failed: number; } export type RuntimeObservationFreshness = "fresh" | "unverified"; export function runtimeObservationFreshness(instance: ServerInstanceResponse, endpoint: RunEndpointResponse | undefined, now = Date.now()): RuntimeObservationFreshness { if (!endpoint || endpoint.status !== "online") return "unverified"; const heartbeat = Date.parse(endpoint.lastHeartbeatAt); return Number.isFinite(heartbeat) && now-heartbeat <= 30_000 ? "fresh" : "unverified"; } export const emptyServerCreateForm: ServerCreateFormState = { id: "", name: "", pluginId: "", createInputs: {}, deploymentMode: "guided-install", serverRoot: "", workingDirectory: "", installCommand: "", startCommand: "", stopCommand: "", statusCommand: "", shell: "" }; export function summarizeServerManagement(instances: ServerInstanceResponse[], jobs: JobResponse[]): ServerManagementSummary { return { total: instances.length, running: instances.filter((instance) => instance.state === "running").length, pendingJobs: jobs.filter((job) => isPendingJobState(job.state)).length, failed: instances.filter((instance) => instance.state === "failed").length }; } export function pendingJobsForServer(jobs: JobResponse[], serverInstanceId: string): JobResponse[] { return jobs.filter((job) => job.serverInstanceId === serverInstanceId && isPendingJobState(job.state)); } export function pluginLabel(plugin: GamePluginResponse | undefined, pluginId: string): string { if (!plugin) { return pluginId; } return plugin.serverDisplayName || plugin.name || plugin.id; } export function serverCreatePluginOptions(plugins: GamePluginResponse[]): GamePluginResponse[] { const selected = new Map(); for (const plugin of plugins.filter(pluginCanCreateServer)) { const key = serverCreatePluginIdentity(plugin); const current = selected.get(key); if (!current || compareServerCreatePlugins(plugin, current) < 0) selected.set(key, plugin); } return Array.from(selected.values()).sort(compareServerCreatePlugins); } export function pluginCanCreateServer(plugin: GamePluginResponse): boolean { return plugin.status === "installed" && plugin.declaredPermissions.includes("server.create"); } export function pluginCreateInputValues(plugin: GamePluginResponse | undefined, values: Record | undefined): Record { return { ...pluginCreateInputDefaults(plugin), ...(values ?? {}) }; } export function canStartServer(state: ServerInstanceState): boolean { return state === "ready" || state === "stopped" || state === "failed"; } export function canStopServer(state: ServerInstanceState): boolean { return state === "running"; } export function canRestartServer(state: ServerInstanceState): boolean { return state === "running" || state === "stopped"; } export function canUpdateServerGame(state: ServerInstanceState): boolean { return state === "running" || state === "stopped" || state === "ready" || state === "failed"; } export type GameUpdateAvailability = "unknown" | "available" | "up-to-date"; export interface GameUpdateStatus { availability: GameUpdateAvailability; installed?: string; latest?: string; } export function steamUpdateProbe(catalog?: DependencyCatalogResponse): DependencyProbeViewResponse | undefined { return catalog?.probes.find((probe) => probe.kind === "steam.update"); } export function gameUpdateStatusFromProbe(probe?: DependencyProbeViewResponse): GameUpdateStatus | undefined { if (!probe || probe.kind !== "steam.update") { return undefined; } const update = dependencyEvidenceValue(probe.evidence, "update"); return { availability: update === "yes" ? "available" : update === "no" ? "up-to-date" : "unknown", installed: dependencyEvidenceValue(probe.evidence, "installed"), latest: dependencyEvidenceValue(probe.evidence, "latest") }; } export function gameUpdateStatusLabel(status?: GameUpdateStatus): string { switch (status?.availability) { case "available": return "发现新版本"; case "up-to-date": return "已是最新版本"; case "unknown": return "版本未知"; default: return "尚未检查"; } } function dependencyEvidenceValue(evidence: string | undefined, key: string): string | undefined { if (!evidence) { return undefined; } for (const token of evidence.split(/\s+/)) { const separator = token.indexOf("="); if (separator <= 0 || token.slice(0, separator) !== key) { continue; } return token.slice(separator + 1) || undefined; } return undefined; } export function isPendingJobState(state: JobResponse["state"]): boolean { return state === "queued" || state === "accepted" || state === "running" || state === "retrying"; } export function defaultServerCreateForm(plugins: GamePluginResponse[]): ServerCreateFormState { const plugin = plugins[0]; return { ...emptyServerCreateForm, pluginId: plugin?.id ?? "", createInputs: pluginCreateInputDefaults(plugin) }; } export function pluginCreateInputDefaults(plugin: GamePluginResponse | undefined): Record { return Object.fromEntries((plugin?.createFields ?? []).map((field) => [field.key, field.defaultValue ?? ""])); } function serverCreatePluginIdentity(plugin: GamePluginResponse): string { return `${plugin.serverType}:${plugin.serverDisplayName || plugin.name || plugin.id}`.toLowerCase(); } function compareServerCreatePlugins(left: GamePluginResponse, right: GamePluginResponse): number { const leftScore = serverCreatePluginScore(left); const rightScore = serverCreatePluginScore(right); for (let index = 0; index < leftScore.length; index += 1) { const delta = leftScore[index]-rightScore[index]; if (delta !== 0) return delta; } const labelDelta = pluginLabel(left, left.id).localeCompare(pluginLabel(right, right.id)); return labelDelta || left.id.localeCompare(right.id); } function serverCreatePluginScore(plugin: GamePluginResponse): number[] { return [ (plugin.createFields?.length ?? 0) > 0 ? 0 : 1, plugin.tags.includes("development") ? 1 : 0, plugin.id === `game.${plugin.serverType}` ? 0 : 1, plugin.id.includes(".codex.") ? 1 : 0, -versionWeight(plugin.version) ]; } function versionWeight(version: string): number { return version.split(/[^0-9]+/).filter(Boolean).slice(0, 3).reduce((weight, part, index) => weight+Number(part || 0)*Math.pow(1000, 2-index), 0); } export function serverMetadataFormFromInstance(instance: ServerInstanceResponse): ServerMetadataFormState { return { name: instance.name }; } export function canDeleteServer(state: ServerInstanceState): boolean { return state !== "deleted"; }