Improve microservice health availability

This commit is contained in:
Codex
2026-05-13 11:47:08 +00:00
parent 1f811f3c07
commit 7ca313a908
3 changed files with 336 additions and 3 deletions
+12 -1
View File
@@ -619,6 +619,10 @@ function OverviewPage({ data, onRaw, onNavigate }: AnyRecord) {
const pendingCount = overview.pendingTaskCount ?? pendingTasks.length;
const recentTasks = data.tasks.slice(0, 5);
const pgdata = overview.pgdata || {};
const microserviceAvailability = overview.microserviceAvailability || {};
const microserviceTotal = asNumber(microserviceAvailability.totalCount);
const microserviceHealthy = asNumber(microserviceAvailability.healthyCount);
const microserviceUnhealthy = asNumber(microserviceAvailability.unhealthyCount);
return h("div", { className: "page-grid overview-grid", "data-testid": "overview-page" },
h(Panel, { title: "核心指标", eyebrow: "Control" },
h("div", { className: "metric-grid" },
@@ -626,6 +630,7 @@ function OverviewPage({ data, onRaw, onNavigate }: AnyRecord) {
h(MetricCard, { label: "PGDATA", value: fmtBytes(pgdata.databaseBytes), hint: `${pgdata.volumeName || "unidesk_pgdata_10gb"} / ${pgdata.databasePretty || "--"}`, tone: "ok", testId: "pgdata-usage-card" }),
h(MetricCard, { label: "在线节点", value: overview.onlineNodeCount ?? 0, hint: `${overview.nodeCount ?? 0} registered`, tone: "ok" }),
h(MetricCard, { label: "WebSocket", value: overview.activeSocketCount ?? 0, hint: "Provider ingress sockets" }),
h(MetricCard, { label: "用户服务可用", value: microserviceTotal > 0 ? `${microserviceHealthy}/${microserviceTotal}` : "--", hint: microserviceTotal > 0 ? `healthyCount ${microserviceHealthy} · unhealthyCount ${microserviceUnhealthy}` : "strict /health probes", tone: microserviceTotal > 0 && microserviceUnhealthy === 0 ? "ok" : "warn", testId: "microservice-availability-card" }),
h(MetricCard, { label: "待处理任务", value: pendingCount, hint: pendingCount > 0 ? "点击查看具体任务" : `timeout ${fmtDuration(Math.floor((overview.taskPendingTimeoutMs ?? 0) / 1000))}`, tone: pendingCount > 0 ? "warn" : "ok", onClick: () => onNavigate("tasks", "pending"), testId: "pending-task-card" }),
),
),
@@ -1608,6 +1613,8 @@ function MicroserviceCatalogPage({ microservices, onRaw, onNavigate }: AnyRecord
const runtime = microserviceRuntime(service);
const repository = microserviceRepository(service);
const backend = microserviceBackend(service);
const availability = runtime.availability || {};
const availabilityStatus = availability.status || (runtime.providerStatus === "online" ? "unknown" : "unhealthy");
return h("tr", { key: service.id, "data-testid": `microservice-row-${safeId(service.id)}` },
h("td", null, h("strong", null, service.name), h("code", null, service.id)),
h("td", null, h("strong", null, runtime.providerName || service.providerId), h("code", null, service.providerId)),
@@ -1618,7 +1625,11 @@ function MicroserviceCatalogPage({ microservices, onRaw, onNavigate }: AnyRecord
h("code", null, `${backend.nodeBindHost || "--"}:${backend.nodePort || "--"} -> ${backend.proxyMode || "--"}`),
),
h("td", null, h("span", null, service.development?.sshPassthrough ? "SSH 透传" : "未配置"), h("code", null, service.development?.worktreePath || "--")),
h("td", null, h(StatusBadge, { status: runtime.providerStatus === "online" ? "online" : "warn" }, runtime.providerStatus || "unknown"), h(DataSummary, { data: runtime.container, empty: "容器快照未上报" })),
h("td", null,
h(StatusBadge, { status: availabilityStatus === "healthy" ? "online" : availabilityStatus === "unknown" ? "warn" : "failed" }, availabilityStatus),
h("span", null, availability.reason || runtime.providerStatus || "unknown"),
h(DataSummary, { data: runtime.container, empty: "容器快照未上报" }),
),
h("td", null,
h("div", { className: "microservice-actions" },
service.id === "findjob" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "findjob"), "data-testid": "open-findjob-button" }, "打开") : null,
+11 -1
View File
@@ -22,6 +22,16 @@ async function requestJson(path: string, options: AnyRecord = {}): Promise<any>
return requestUniDeskJson(path, { failureFields: ["ok", "success"], ...options });
}
async function requestHealthJson(path: string): Promise<any> {
const response = await fetch(path, { credentials: "same-origin" });
const text = await response.text();
try {
return text ? JSON.parse(text) : { ok: response.ok, status: response.status };
} catch {
return { ok: response.ok, status: response.status, text };
}
}
function StatusBadge({ status, children }: AnyRecord) {
const normalized = String(status || "unknown").toLowerCase();
return h("span", { className: `status-badge ${normalized}` }, children || status || "unknown");
@@ -119,7 +129,7 @@ export function ClaudeQqPage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyR
setState((prev: any) => ({ ...prev, loading: true, error: "" }));
try {
const [health, status, events, subscriptions, sent] = await Promise.all([
requestJson(`${apiBaseUrl}/microservices/claudeqq/health`),
requestHealthJson(`${apiBaseUrl}/microservices/claudeqq/health`),
requestJson(claudeqqApi(apiBaseUrl, "/api/server/status")),
requestJson(claudeqqApi(apiBaseUrl, "/api/events/recent?limit=60")),
requestJson(claudeqqApi(apiBaseUrl, "/api/events/subscriptions")),