From bdbf868f7515d05fdb5bf4f5119719eb6b0d6540 Mon Sep 17 00:00:00 2001 From: Maofeng Date: Wed, 29 Jul 2026 19:13:45 +0800 Subject: [PATCH] feat(console-ui): show component host capabilities - consume structured capability metadata from service responses - add a stable-width Host Capability column to the full service table - preserve compact overview density and horizontal overflow on narrow screens - cover API typing and capability rendering wiring in SSR source tests --- console/src/api.ts | 8 ++++ console/src/routes/index.tsx | 31 ++++++++++++++- console/src/styles/app.css | 57 ++++++++++++++++++++++++++++ console/tests/rendered-html.test.mjs | 3 ++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/console/src/api.ts b/console/src/api.ts index 2996c7c..0124db3 100644 --- a/console/src/api.ts +++ b/console/src/api.ts @@ -4,11 +4,19 @@ const API_BASE_STORAGE_KEY = "wasmeld-api-base"; export type BackendServiceStatus = "running" | "stopped" | "faulted"; +export type BackendCapability = { + interface: string; + package: string; + name: string; + version: string; +}; + export type BackendService = { id: string; revision: string; artifact: string; world: string; + capabilities: BackendCapability[]; status: BackendServiceStatus; updated_at_ms: number; limits: { diff --git a/console/src/routes/index.tsx b/console/src/routes/index.tsx index b090ba5..8d11143 100644 --- a/console/src/routes/index.tsx +++ b/console/src/routes/index.tsx @@ -31,6 +31,7 @@ import { } from "lucide-react"; import { FormEvent, ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { + BackendCapability, BackendEvent, BackendDeployment, BackendRuntime, @@ -66,6 +67,7 @@ type Service = { deadlineMs: number; mailbox: number; maxInputKb: number; + capabilities: BackendCapability[]; calls: number; errors: number; latencyMs: number | null; @@ -120,6 +122,7 @@ function toService(service: BackendService, activeRevision?: string): Service { deadlineMs: service.limits.deadline_ms, mailbox: service.limits.mailbox_capacity, maxInputKb: Math.round(service.limits.max_input_bytes / 1024), + capabilities: service.capabilities, calls: service.calls, errors: service.errors, latencyMs: service.last_latency_ms, @@ -977,6 +980,26 @@ function StatusBadge({ status }: { status: ServiceStatus }) { ); } +function CapabilitySummary({ capabilities }: { capabilities: BackendCapability[] }) { + if (capabilities.length === 0) { + return ; + } + + const [first, ...remaining] = capabilities; + return ( + capability.interface).join("\n")} + > + + + {first.package}/{first.name} @{first.version} + + {remaining.length > 0 && +{remaining.length}} + + ); +} + function ServiceTable({ services, selectedKey, @@ -1010,12 +1033,13 @@ function ServiceTable({ return (
- +
{!compact && } + {!compact && } } + {!compact && ( + + )}
服务 状态制品Host 能力调用 延迟 @@ -1059,6 +1083,11 @@ function ServiceTable({ {!compact && {service.artifact} + + {service.calls.toLocaleString("zh-CN")} {service.latencyMs ? `${service.latencyMs} ms` : "—"} diff --git a/console/src/styles/app.css b/console/src/styles/app.css index 8f3c764..a0961a7 100644 --- a/console/src/styles/app.css +++ b/console/src/styles/app.css @@ -768,6 +768,31 @@ button:disabled { table-layout: fixed; } +.service-table-full { + min-width: 980px; +} + +.data-table.service-table-full th:first-child { + width: 24%; +} + +.service-table-full th:nth-child(2) { + width: 90px; +} + +.service-table-full th:nth-child(3) { + width: 150px; +} + +.service-table-full th:nth-child(4) { + width: 220px; +} + +.service-table-full th:nth-child(5), +.service-table-full th:nth-child(6) { + width: 72px; +} + .data-table th { height: 34px; padding: 0 12px; @@ -891,6 +916,38 @@ button:disabled { white-space: nowrap; } +.capability-summary { + max-width: 100%; + height: 23px; + padding: 0 7px; + display: inline-flex; + align-items: center; + gap: 5px; + color: #315f52; + background: #edf5f2; + border: 1px solid #d2e4de; + border-radius: 4px; + font-family: var(--font-geist-mono), monospace; + font-size: 8px; +} + +.capability-summary > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.capability-summary > small { + flex: 0 0 auto; + color: var(--ink-faint); + font-size: 7px; +} + +.capability-empty { + color: var(--ink-faint); + font-size: 9px; +} + .numeric-cell { color: var(--ink-soft); font-family: var(--font-geist-mono), monospace; diff --git a/console/tests/rendered-html.test.mjs b/console/tests/rendered-html.test.mjs index 2614f89..df9cd08 100644 --- a/console/tests/rendered-html.test.mjs +++ b/console/tests/rendered-html.test.mjs @@ -97,7 +97,10 @@ test("uses TanStack Start routing and produces Node artifacts", async () => { assert.match(rootRoute, //); assert.match(indexRoute, /createFileRoute\("\/"\)/); assert.match(indexRoute, /切换对外版本/); + assert.match(indexRoute, /Host 能力/); + assert.match(indexRoute, /service\.capabilities/); assert.match(apiClient, /\/api\/v1\/deployments/); + assert.match(apiClient, /capabilities: BackendCapability\[\]/); assert.match(router, /createRouter/); assert.match(packageJson, /"@tanstack\/react-start"/); assert.match(packageJson, /"srvx"/);