Complete platform management workflows
This commit is contained in:
@@ -14,7 +14,14 @@ import {
|
||||
type ServerCreateFormState
|
||||
} from "../contracts/serverManagement";
|
||||
import { filterServerCards, serverIsOnline, type ServerCardView, type ServerStatusFilter } from "../contracts/workspace";
|
||||
import { serverCreateRequestFromForm } from "../schemas/serverManagement";
|
||||
import {
|
||||
clientManagerBuildRequest,
|
||||
dependencyJobRequest,
|
||||
logBackfillRequest,
|
||||
runDistributionGenerateRequest,
|
||||
runUpdateRequest,
|
||||
serverCreateRequestFromForm
|
||||
} from "../schemas/serverManagement";
|
||||
import { isPlatformAdmin } from "../contracts/workspace";
|
||||
import { cx } from "../utils/classes";
|
||||
|
||||
@@ -114,6 +121,56 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleQuickRuntimeAction(instance: ServerInstanceResponse, action: ServerQuickRuntimeAction) {
|
||||
const defaults = quickRuntimeDefaultsForPlugin(instance.pluginId);
|
||||
const intent = quickRuntimeActionLabel(action);
|
||||
const operationId = operations.begin({ intent, targetKind: "server", targetId: `${instance.id}:${action}`, requester: session.displayName });
|
||||
try {
|
||||
let message = "运行操作已提交";
|
||||
if (action === "generate-run") {
|
||||
const distribution = await platformApiClient.generateRunDistribution(instance.id, runDistributionGenerateRequest(instance.id, defaults.runOs, "amd64"));
|
||||
message = `run 包已生成,artifact ${distribution.artifactId}`;
|
||||
} else if (action === "download-run") {
|
||||
const reference = await platformApiClient.downloadLatestRunDistribution(instance.id);
|
||||
message = `run 下载引用已创建,artifact ${reference.artifactId}`;
|
||||
} else if (action === "push-run-update") {
|
||||
const reference = await platformApiClient.downloadLatestRunDistribution(instance.id);
|
||||
const update = await platformApiClient.pushRunUpdate(instance.id, runUpdateRequest(instance.id, reference.artifactId, reference.checksum));
|
||||
message = `run 更新任务已排队,job ${update.jobId ?? update.id}`;
|
||||
} else if (action === "generate-client-manager") {
|
||||
const distribution = await platformApiClient.generateClientManager(
|
||||
instance.id,
|
||||
clientManagerBuildRequest({
|
||||
serverInstanceId: instance.id,
|
||||
profileKey: defaults.clientProfileKey,
|
||||
targetOs: defaults.clientOs,
|
||||
targetArch: "amd64",
|
||||
repositoryUrl: defaults.repositoryUrl,
|
||||
sourceRevision: "main"
|
||||
})
|
||||
);
|
||||
message = `客户端管理器已生成,artifact ${distribution.artifactId}`;
|
||||
} else if (action === "dependencies-check") {
|
||||
const job = await platformApiClient.checkDependencies(instance.id, dependencyJobRequest(instance.id, defaults.probeKey));
|
||||
message = `依赖检查任务已排队,job ${job.id}`;
|
||||
} else if (action === "dependencies-install") {
|
||||
const job = await platformApiClient.installDependencies(instance.id, dependencyJobRequest(instance.id, defaults.probeKey, defaults.installPlanKey));
|
||||
message = `依赖安装任务已排队,job ${job.id}`;
|
||||
} else if (action === "live-logs") {
|
||||
onNavigate("serverDetail", { serverId: instance.id });
|
||||
message = "已打开服务器详情,可切换到日志页查看实时日志";
|
||||
} else if (action === "historical-logs") {
|
||||
const job = await platformApiClient.requestLogBackfill(instance.id, logBackfillRequest(instance.id, defaults.logSourceKey));
|
||||
message = `历史日志回填任务已排队,job ${job.id}`;
|
||||
}
|
||||
operations.succeed(operationId, message);
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
operations.fail(operationId, error instanceof Error ? error.message : "运行操作失败", operationId);
|
||||
}
|
||||
}
|
||||
|
||||
const latestCreate = operations.operations.find((operation) => operation.intent === "创建服务器");
|
||||
|
||||
return (
|
||||
@@ -240,7 +297,13 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
{visibleCards.length > 0 && (
|
||||
<div className="server-card-grid" aria-label="server list">
|
||||
{visibleCards.map((card) => (
|
||||
<ServerCard key={card.instance.id} card={card} metricsPending={metricsPending} onOpen={() => onNavigate("serverDetail", { serverId: card.instance.id })} />
|
||||
<ServerCard
|
||||
key={card.instance.id}
|
||||
card={card}
|
||||
metricsPending={metricsPending}
|
||||
onOpen={() => onNavigate("serverDetail", { serverId: card.instance.id })}
|
||||
onQuickAction={(action) => void handleQuickRuntimeAction(card.instance, action)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -248,17 +311,28 @@ export function ServersPage({ session, operations, onNavigate }: PageComponentPr
|
||||
);
|
||||
}
|
||||
|
||||
type ServerQuickRuntimeAction =
|
||||
| "generate-run"
|
||||
| "download-run"
|
||||
| "push-run-update"
|
||||
| "generate-client-manager"
|
||||
| "dependencies-check"
|
||||
| "dependencies-install"
|
||||
| "live-logs"
|
||||
| "historical-logs";
|
||||
|
||||
interface ServerCardProps {
|
||||
card: ServerCardView;
|
||||
metricsPending: boolean;
|
||||
onOpen: () => void;
|
||||
onQuickAction: (action: ServerQuickRuntimeAction) => void;
|
||||
}
|
||||
|
||||
function ServerCard({ card, metricsPending, onOpen }: ServerCardProps) {
|
||||
function ServerCard({ card, metricsPending, onOpen, onQuickAction }: ServerCardProps) {
|
||||
const { instance, metrics, pendingJobs } = card;
|
||||
const online = serverIsOnline(instance.state);
|
||||
return (
|
||||
<button type="button" className="server-card" onClick={onOpen} aria-label={`打开 ${instance.name} 详情`}>
|
||||
<article className="server-card" aria-label={`${instance.name} 服务器卡片`}>
|
||||
<div className="server-card-head">
|
||||
<span>
|
||||
<strong>{instance.name}</strong>
|
||||
@@ -289,10 +363,72 @@ function ServerCard({ card, metricsPending, onOpen }: ServerCardProps) {
|
||||
<UsageMeter label="内存" percent={metrics?.memoryPercent} />
|
||||
<UsageMeter label="磁盘" percent={metrics?.diskPercent} />
|
||||
</div>
|
||||
</button>
|
||||
<div className="action-strip" style={{ justifyContent: "space-between" }}>
|
||||
<button type="button" className="icon-command" onClick={onOpen}>
|
||||
<Sparkles size={14} />
|
||||
<span>详情</span>
|
||||
</button>
|
||||
<details className="runtime-action-menu">
|
||||
<summary className="icon-command">运行操作</summary>
|
||||
<div className="action-list">
|
||||
{serverQuickActions.map((action) => (
|
||||
<button key={action} type="button" className="theme-upload" onClick={() => onQuickAction(action)}>
|
||||
<Candy size={13} />
|
||||
<span>{quickRuntimeActionLabel(action)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
const serverQuickActions: ServerQuickRuntimeAction[] = [
|
||||
"generate-run",
|
||||
"download-run",
|
||||
"push-run-update",
|
||||
"generate-client-manager",
|
||||
"dependencies-check",
|
||||
"dependencies-install",
|
||||
"live-logs",
|
||||
"historical-logs"
|
||||
];
|
||||
|
||||
function quickRuntimeActionLabel(action: ServerQuickRuntimeAction): string {
|
||||
switch (action) {
|
||||
case "generate-run":
|
||||
return "生成 run";
|
||||
case "download-run":
|
||||
return "下载 run";
|
||||
case "push-run-update":
|
||||
return "推送更新";
|
||||
case "generate-client-manager":
|
||||
return "生成客户端";
|
||||
case "dependencies-check":
|
||||
return "依赖检查";
|
||||
case "dependencies-install":
|
||||
return "依赖安装";
|
||||
case "live-logs":
|
||||
return "实时日志";
|
||||
case "historical-logs":
|
||||
return "历史日志";
|
||||
}
|
||||
}
|
||||
|
||||
function quickRuntimeDefaultsForPlugin(pluginId: string) {
|
||||
const isScum = pluginId.toLowerCase().includes("scum");
|
||||
return {
|
||||
runOs: isScum ? "windows" : "linux",
|
||||
clientOs: "windows",
|
||||
clientProfileKey: isScum ? "scum-client-manager" : "client-manager",
|
||||
repositoryUrl: isScum ? "https://github.com/F88888/scum_client.git" : "https://github.com/example/client-manager.git",
|
||||
probeKey: isScum ? "steamcmd" : "java-21",
|
||||
installPlanKey: isScum ? "install-steamcmd-linux" : "install-java-linux",
|
||||
logSourceKey: isScum ? "server-log" : "latest-log"
|
||||
};
|
||||
}
|
||||
|
||||
function formatStat(value: number | undefined, pending: boolean, format: (value: number) => string): string {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return format(value);
|
||||
|
||||
Reference in New Issue
Block a user