fix: 补齐 Webterm provider 可见性
This commit is contained in:
@@ -766,6 +766,7 @@ function platformInfraHelpSummary(): unknown {
|
||||
"bun scripts/cli.ts platform-infra webterm apply --confirm",
|
||||
"bun scripts/cli.ts platform-infra webterm status",
|
||||
"bun scripts/cli.ts platform-infra webterm validate",
|
||||
"bun scripts/cli.ts platform-infra webterm logs --target local-7682 --tail 100",
|
||||
"bun scripts/cli.ts platform-infra wechat-archive plan",
|
||||
"bun scripts/cli.ts platform-infra wechat-archive apply --confirm",
|
||||
"bun scripts/cli.ts platform-infra wechat-archive validate --full",
|
||||
|
||||
@@ -111,6 +111,11 @@ export async function runWebtermCommand(config: UniDeskConfig, args: string[]):
|
||||
const result = validate(options.targetId);
|
||||
return options.full || options.raw ? result : renderValidate(result);
|
||||
}
|
||||
if (action === "logs") {
|
||||
const options = parseOpsCommonOptions(rest, { stringOptions: ["--tail"] });
|
||||
const result = logs(options.targetId, parseLogTail(options.tail));
|
||||
return options.full || options.raw ? result : renderLogs(result);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: "unsupported-webterm-command",
|
||||
@@ -121,13 +126,14 @@ export async function runWebtermCommand(config: UniDeskConfig, args: string[]):
|
||||
|
||||
export function webtermHelp(): Record<string, unknown> {
|
||||
return {
|
||||
command: "platform-infra webterm plan|apply|status|validate",
|
||||
command: "platform-infra webterm plan|apply|status|validate|logs",
|
||||
examples: [
|
||||
"bun scripts/cli.ts platform-infra webterm plan",
|
||||
"bun scripts/cli.ts platform-infra webterm apply --dry-run",
|
||||
"bun scripts/cli.ts platform-infra webterm apply --confirm",
|
||||
"bun scripts/cli.ts platform-infra webterm status",
|
||||
"bun scripts/cli.ts platform-infra webterm validate",
|
||||
"bun scripts/cli.ts platform-infra webterm logs --target local-7682 --tail 100",
|
||||
],
|
||||
configPath,
|
||||
description: "Deploy target-scoped host-Docker Web Terminal instances from YAML and optionally expose them through PK01 Caddy HTTPS.",
|
||||
@@ -195,7 +201,7 @@ async function apply(config: UniDeskConfig, targetId: string | null, confirm: bo
|
||||
cwd: target.runtime.workDir,
|
||||
encoding: "utf8",
|
||||
});
|
||||
const localProbe = localHttpProbeWithRetry(target.runtime.hostPort, "/login", 10, 500);
|
||||
const localProbe = providerHealthProbeWithRetry(target.runtime.hostPort, 10, 500);
|
||||
const caddy = target.publicExposure.enabled
|
||||
? await applyPk01CaddySiteBlock(
|
||||
config,
|
||||
@@ -231,7 +237,7 @@ function status(targetId: string | null): Record<string, unknown> {
|
||||
const target = resolveTarget(targetId);
|
||||
const inspect = spawnSync("docker", ["inspect", target.runtime.containerName], { encoding: "utf8" });
|
||||
const ps = spawnSync("docker", ["ps", "--filter", `name=^/${target.runtime.containerName}$`, "--format", "{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"], { encoding: "utf8" });
|
||||
const localProbe = localHttpProbe(target.runtime.hostPort, "/login");
|
||||
const localProbe = providerHealthProbe(target.runtime.hostPort);
|
||||
const publicProbe = target.publicExposure.enabled ? publicHttpProbe(target.publicExposure.publicBaseUrl, "/login") : { ok: true, skipped: true };
|
||||
return {
|
||||
ok: ps.status === 0 && ps.stdout.trim().length > 0 && localProbe.ok === true && publicProbe.ok === true,
|
||||
@@ -254,7 +260,7 @@ function status(targetId: string | null): Record<string, unknown> {
|
||||
function validate(targetId: string | null): Record<string, unknown> {
|
||||
const target = resolveTarget(targetId);
|
||||
const dns = dnsProbe(target.publicExposure.dns.hostname);
|
||||
const localProbe = localHttpProbe(target.runtime.hostPort, "/login");
|
||||
const localProbe = providerHealthProbe(target.runtime.hostPort);
|
||||
const publicProbe = target.publicExposure.enabled ? publicHttpProbe(target.publicExposure.publicBaseUrl, "/login") : { ok: true, skipped: true };
|
||||
return {
|
||||
ok: localProbe.ok === true && publicProbe.ok === true && dns.addresses.includes(target.publicExposure.dns.expectedA),
|
||||
@@ -266,6 +272,28 @@ function validate(targetId: string | null): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function logs(targetId: string | null, tail: number): Record<string, unknown> {
|
||||
const target = resolveTarget(targetId);
|
||||
const result = spawnSync("docker", ["logs", "--tail", String(tail), target.runtime.containerName], { encoding: "utf8" });
|
||||
const output = `${result.stdout}${result.stderr}`.slice(-20000);
|
||||
return {
|
||||
ok: result.status === 0,
|
||||
target: targetSummary(target),
|
||||
tail,
|
||||
logs: output,
|
||||
truncated: result.stdout.length + result.stderr.length > output.length,
|
||||
exitCode: result.status,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function parseLogTail(value: unknown): number {
|
||||
if (value === null || value === undefined) return 100;
|
||||
const tail = Number(value);
|
||||
if (!Number.isInteger(tail) || tail < 1 || tail > 1000) throw new Error("--tail must be an integer from 1 to 1000");
|
||||
return tail;
|
||||
}
|
||||
|
||||
function readWebtermConfig(): WebtermConfig {
|
||||
const yaml = readYamlRecord(configPath, "platform-infra-webterm");
|
||||
const defaults = recordField(yaml, "defaults", "webterm");
|
||||
@@ -511,6 +539,33 @@ function localHttpProbeWithRetry(port: number, path: string, attempts: number, d
|
||||
return httpProbeWithRetry(() => localHttpProbe(port, path), attempts, delayMs);
|
||||
}
|
||||
|
||||
function providerHealthProbe(port: number): Record<string, unknown> {
|
||||
const url = `http://127.0.0.1:${port}/healthz`;
|
||||
const result = spawnSync("curl", ["-fsS", "--connect-timeout", "5", "--max-time", "15", url], { encoding: "utf8" });
|
||||
let payload: Record<string, unknown> = {};
|
||||
try {
|
||||
payload = JSON.parse(result.stdout) as Record<string, unknown>;
|
||||
} catch {
|
||||
payload = {};
|
||||
}
|
||||
const providers = Number(payload.providers);
|
||||
const sessions = Number(payload.sessions);
|
||||
return {
|
||||
ok: result.status === 0 && payload.ok === true && Number.isInteger(providers) && providers >= 1,
|
||||
url,
|
||||
authenticated: false,
|
||||
providers: Number.isInteger(providers) ? providers : null,
|
||||
sessions: Number.isInteger(sessions) ? sessions : null,
|
||||
exitCode: result.status,
|
||||
error: result.status === 0 && providers < 1 ? "no-provider" : undefined,
|
||||
stderrTail: result.status === 0 ? "" : result.stderr.slice(-1000),
|
||||
};
|
||||
}
|
||||
|
||||
function providerHealthProbeWithRetry(port: number, attempts: number, delayMs: number): Record<string, unknown> {
|
||||
return httpProbeWithRetry(() => providerHealthProbe(port), attempts, delayMs);
|
||||
}
|
||||
|
||||
function publicHttpProbeWithRetry(baseUrl: string, path: string, attempts: number, delayMs: number): Record<string, unknown> {
|
||||
return httpProbeWithRetry(() => publicHttpProbe(baseUrl, path), attempts, delayMs);
|
||||
}
|
||||
@@ -730,6 +785,7 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
|
||||
"CHECKS",
|
||||
...table(["CHECK", "OK", "DETAIL"], [
|
||||
["local", bool(localProbe.ok), str(localProbe.url, "-")],
|
||||
["provider", str(localProbe.providers, "-"), `sessions=${str(localProbe.sessions, "-")}`],
|
||||
["pk01Caddy", bool(caddy.ok), str(caddy.action, str(caddy.hostname, "-"))],
|
||||
["public", bool(publicProbe.ok), str(publicProbe.url, "-")],
|
||||
]),
|
||||
@@ -756,6 +812,7 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
|
||||
"CHECKS",
|
||||
...table(["CHECK", "OK", "DETAIL"], [
|
||||
["local", bool(localProbe.ok), str(localProbe.url, "-")],
|
||||
["provider", str(localProbe.providers, "-"), `sessions=${str(localProbe.sessions, "-")}`],
|
||||
["public", bool(publicProbe.ok), str(publicProbe.url, "-")],
|
||||
]),
|
||||
];
|
||||
@@ -771,12 +828,24 @@ function renderValidate(result: Record<string, unknown>): RenderedCliResult {
|
||||
...table(["CHECK", "OK", "DETAIL"], [
|
||||
["dns", bool(dns.ok), values(dns.addresses).map(String).join(",") || "-"],
|
||||
["local", bool(localProbe.ok), str(localProbe.url, "-")],
|
||||
["provider", str(localProbe.providers, "-"), `sessions=${str(localProbe.sessions, "-")}`],
|
||||
["public", bool(publicProbe.ok), str(publicProbe.url, "-")],
|
||||
]),
|
||||
];
|
||||
return rendered(result, "platform-infra webterm validate", lines);
|
||||
}
|
||||
|
||||
function renderLogs(result: Record<string, unknown>): RenderedCliResult {
|
||||
const target = rec(result.target);
|
||||
const lines = [
|
||||
"PLATFORM-INFRA WEBTERM LOGS",
|
||||
...table(["TARGET", "PORT", "TAIL", "STATUS"], [[str(target.id), str(target.hostPort), str(result.tail), result.ok === false ? "failed" : "ok"]]),
|
||||
"",
|
||||
str(result.logs, "-"),
|
||||
];
|
||||
return rendered(result, "platform-infra webterm logs", lines);
|
||||
}
|
||||
|
||||
function rendered(result: Record<string, unknown>, command: string, lines: string[]): RenderedCliResult {
|
||||
return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user