fix: surface database volume in docker view

This commit is contained in:
Codex
2026-05-04 13:51:31 +00:00
parent 2a03f6343c
commit a16fedd9e4
8 changed files with 169 additions and 29 deletions
+23 -20
View File
@@ -1,4 +1,5 @@
import { mkdirSync } from "node:fs";
import { connect } from "node:net";
import { join } from "node:path";
import { chromium } from "playwright";
import { runCommand } from "./command";
@@ -48,6 +49,23 @@ async function fetchProbe(url: string, timeoutMs = 8000): Promise<unknown> {
}
}
function tcpProbe(host: string, port: number, timeoutMs = 2500): Promise<unknown> {
return new Promise((resolve) => {
const socket = connect({ host, port });
let settled = false;
const finish = (detail: unknown): void => {
if (settled) return;
settled = true;
socket.destroy();
resolve(detail);
};
socket.setTimeout(timeoutMs);
socket.once("connect", () => finish({ reachable: true, ok: true, host, port }));
socket.once("timeout", () => finish({ reachable: false, ok: false, host, port, error: "timeout" }));
socket.once("error", (error) => finish({ reachable: false, ok: false, host, port, error: error.message }));
});
}
function addCheck(checks: E2ECheck[], name: string, passed: boolean, detail: unknown): void {
checks.push({ name, status: passed ? "passed" : "failed", detail });
}
@@ -178,26 +196,10 @@ async function exposureChecks(config: UniDeskConfig, urls: PublicUrls, checks: E
const portSummary = dockerPortSummary() as { rows?: Array<{ name: string; ports: string }> };
const portsText = (portSummary.rows ?? []).map((row) => `${row.name} ${row.ports}`).join("\n");
const corePublic = await fetchProbe(`${urls.blockedCoreUrl}/health`, 2500);
const databasePublic = runCommand([
"docker",
"run",
"--rm",
"postgres:16-alpine",
"pg_isready",
"-h",
urls.blockedDatabaseHost,
"-p",
String(urls.blockedDatabasePort),
"-U",
config.database.user,
], repoRoot);
const databasePublic = await tcpProbe(urls.blockedDatabaseHost, urls.blockedDatabasePort);
addCheck(checks, "network:only-frontend-provider-ports", !portsText.includes(`:${config.network.core.port}->`) && !portsText.includes(`:${config.network.database.port}->`), portSummary);
addCheck(checks, "network:core-public-blocked", (corePublic as { reachable?: boolean }).reachable === false, corePublic);
addCheck(checks, "network:database-public-blocked", databasePublic.exitCode !== 0, {
exitCode: databasePublic.exitCode,
stdout: databasePublic.stdout.trim(),
stderr: databasePublic.stderr.trim(),
});
addCheck(checks, "network:database-public-blocked", (databasePublic as { reachable?: boolean }).reachable === false, databasePublic);
}
async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2ECheck[]): Promise<void> {
@@ -292,9 +294,10 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
await page.getByRole("button", { name: /Docker 状态/ }).click();
await page.waitForSelector('[data-testid="docker-status-page"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="docker-container-table"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="database-volume-card"]', { timeout: 10000 });
await page.waitForFunction(() => {
const text = document.body.innerText.toLowerCase();
return text.includes("docker desktop 视图") && text.includes("containers");
return text.includes("docker desktop 视图") && text.includes("containers") && text.includes("unidesk_pgdata_10gb");
}, undefined, { timeout: 10000 });
const dockerText = await page.locator('[data-testid="docker-status-page"]').innerText({ timeout: 5000 });
addCheck(checks, "frontend:login-provider-visible", bodyText.includes(config.providerGateway.id) && bodyText.includes(config.providerGateway.name) && bodyText.includes("核心在线"), { screenshotPath });
@@ -302,7 +305,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
addCheck(checks, "frontend:raw-json-explicit-button", rawText.includes('"providerId"') && rawText.includes(config.providerGateway.id), { rawTextPreview: rawText.slice(0, 400) });
addCheck(checks, "frontend:system-monitor-visible", monitorText.includes("任务管理器视图") && monitorText.includes("CPU") && monitorText.includes("Memory") && monitorText.includes("Disk"), { monitorTextPreview: monitorText.slice(0, 800) });
addCheck(checks, "frontend:upgrade-plan-dispatch", upgradeControlText.includes("预检升级 已下发"), { providerId: config.providerGateway.id, upgradeControlPreview: upgradeControlText.slice(0, 500) });
addCheck(checks, "frontend:docker-status-visible", dockerText.toLowerCase().includes("docker desktop 视图") && dockerText.toLowerCase().includes("containers") && (dockerText.includes("unidesk-frontend") || dockerText.includes("unidesk-backend-core")), { dockerTextPreview: dockerText.slice(0, 800) });
addCheck(checks, "frontend:docker-status-visible", dockerText.toLowerCase().includes("docker desktop 视图") && dockerText.toLowerCase().includes("containers") && dockerText.includes("unidesk_pgdata_10gb") && (dockerText.includes("unidesk-frontend") || dockerText.includes("unidesk-backend-core")), { dockerTextPreview: dockerText.slice(0, 800) });
addCheck(checks, "frontend:no-console-errors", consoleErrors.length === 0, { consoleErrors });
return { screenshotPath, bodyText, consoleErrors };
} finally {