From fcc4d3f13eb4a73fac61b8d6df62a87017f8dc1f Mon Sep 17 00:00:00 2001 From: eleven_qy <64311644+alph-cmky@users.noreply.github.com> Date: Fri, 29 May 2026 13:59:38 +0800 Subject: [PATCH] feat: add runtime dashboard ui --- .../runtimes/runtime-dashboard.test.ts | 91 +++++++++++++++++++ .../features/runtimes/runtime-dashboard.tsx | 29 ++++++ .../features/runtimes/runtime-status-row.tsx | 73 +++++++++++++++ apps/web/src/index.ts | 6 ++ docs/development/task-backlog.md | 8 +- 5 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/features/runtimes/runtime-dashboard.test.ts create mode 100644 apps/web/src/features/runtimes/runtime-dashboard.tsx create mode 100644 apps/web/src/features/runtimes/runtime-status-row.tsx diff --git a/apps/web/src/features/runtimes/runtime-dashboard.test.ts b/apps/web/src/features/runtimes/runtime-dashboard.test.ts new file mode 100644 index 0000000..1a8b481 --- /dev/null +++ b/apps/web/src/features/runtimes/runtime-dashboard.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; + +import { renderRuntimeDashboardHtml } from "./runtime-dashboard.js"; +import type { RuntimeDashboardResult } from "./runtime-status-row.js"; + +const runtimeRows: RuntimeDashboardResult[] = [ + runtime("codex", "OpenAI Codex CLI", "ready", { + path: "/usr/local/bin/codex", + version: "0.128.0", + }), + runtime("claude", "Claude Code", "configured", { + path: "/usr/local/bin/claude", + version: "1.2.3", + }), + runtime("ollama", "Ollama", "localProviderReady", { + path: "/usr/local/bin/ollama", + version: "0.9.0", + }), + runtime("lmstudio", "LM Studio", "ready", { path: "/usr/local/bin/lms", version: "0.3.0" }), + runtime("node", "Node.js", "ready", { path: "/usr/local/bin/node", version: "v24.1.0" }), + runtime("git", "Git", "ready", { path: "/usr/bin/git", version: "git version 2.50.0" }), + runtime("code", "Visual Studio Code CLI", "missing", { + warnings: ["missing with token=abc123"], + }), +]; + +describe("renderRuntimeDashboardHtml", () => { + it("renders Codex, Claude, Ollama, LM Studio, Node, Git, and VS Code in one table", () => { + const html = renderRuntimeDashboardHtml(runtimeRows); + + for (const label of [ + "OpenAI Codex CLI", + "Claude Code", + "Ollama", + "LM Studio", + "Node.js", + "Git", + "Visual Studio Code CLI", + ]) { + expect(html).toContain(label); + } + expect(html).toContain(" { + const html = renderRuntimeDashboardHtml(runtimeRows); + + expect(html).toContain("localProviderReady"); + expect(html).toContain("/usr/local/bin/codex"); + expect(html).toContain("0.128.0"); + expect(html).toContain("exec"); + expect(html).toContain("chat"); + expect(html).toContain("missing with token=[REDACTED]"); + }); + + it("shows missing runtime guidance and avoids secret-like values", () => { + const html = renderRuntimeDashboardHtml(runtimeRows); + + expect(html).toContain("Install or configure Visual Studio Code CLI"); + expect(html).not.toContain("abc123"); + }); +}); + +function runtime( + type: RuntimeDashboardResult["type"], + name: string, + status: RuntimeDashboardResult["status"], + overrides: Partial = {}, +): RuntimeDashboardResult { + return { + id: type, + name, + type, + status, + detected: status !== "missing", + scope: + status === "localProviderReady" + ? ["global", "localProvider"] + : status === "missing" + ? [] + : ["global"], + capabilities: { + exec: type === "codex" || type === "claude" || type === "node", + chat: type === "ollama" || type === "lmstudio", + versionCommand: status !== "missing", + }, + warnings: [], + lastDetectedAt: "2026-05-29T00:00:00.000Z", + ...overrides, + }; +} diff --git a/apps/web/src/features/runtimes/runtime-dashboard.tsx b/apps/web/src/features/runtimes/runtime-dashboard.tsx new file mode 100644 index 0000000..eb9ce85 --- /dev/null +++ b/apps/web/src/features/runtimes/runtime-dashboard.tsx @@ -0,0 +1,29 @@ +import { renderRuntimeStatusRowHtml, type RuntimeDashboardResult } from "./runtime-status-row.js"; + +export type { RuntimeDashboardResult } from "./runtime-status-row.js"; + +export function renderRuntimeDashboardHtml(runtimes: readonly RuntimeDashboardResult[]): string { + return `
+
+
+

Runtimes

+

Local runtime detection across CLIs and local model providers.

+
+
+ + + + + + + + + + + + + ${runtimes.map(renderRuntimeStatusRowHtml).join("")} + +
RuntimeStatusPathVersionCapabilitiesWarnings
+
`; +} diff --git a/apps/web/src/features/runtimes/runtime-status-row.tsx b/apps/web/src/features/runtimes/runtime-status-row.tsx new file mode 100644 index 0000000..79bb17a --- /dev/null +++ b/apps/web/src/features/runtimes/runtime-status-row.tsx @@ -0,0 +1,73 @@ +export interface RuntimeDashboardCapabilities { + readonly exec?: boolean; + readonly mcp?: boolean; + readonly chat?: boolean; + readonly versionCommand?: boolean; + readonly localProviders?: readonly string[]; +} + +export interface RuntimeDashboardResult { + readonly id: string; + readonly name: string; + readonly type: "codex" | "claude" | "ollama" | "lmstudio" | "node" | "git" | "code"; + readonly status: "missing" | "ready" | "configured" | "projectActive" | "localProviderReady"; + readonly detected: boolean; + readonly path?: string; + readonly version?: string; + readonly scope: readonly string[]; + readonly capabilities: RuntimeDashboardCapabilities; + readonly warnings: readonly string[]; + readonly lastDetectedAt: string; +} + +export function renderRuntimeStatusRowHtml(runtime: RuntimeDashboardResult): string { + const capabilities = formatCapabilities(runtime.capabilities); + const warnings = runtime.warnings.map(redactSensitiveValues); + const guidance = renderMissingGuidance(runtime); + const warningContent = [...warnings, guidance].filter(Boolean); + + return ` + ${escapeHtml(runtime.name)} + ${runtime.status} + ${escapeHtml(runtime.path ?? "Not detected")} + ${escapeHtml(runtime.version ?? "Unknown")} + ${escapeHtml(capabilities || "None")} + ${warningContent.map(escapeHtml).join("
")} +`; +} + +function formatCapabilities(capabilities: RuntimeDashboardCapabilities): string { + return [ + capabilities.exec ? "exec" : undefined, + capabilities.mcp ? "mcp" : undefined, + capabilities.chat ? "chat" : undefined, + capabilities.versionCommand ? "version" : undefined, + ...(capabilities.localProviders ?? []), + ] + .filter(Boolean) + .join(", "); +} + +function renderMissingGuidance(runtime: RuntimeDashboardResult): string { + if (runtime.status !== "missing") { + return ""; + } + + return `Install or configure ${runtime.name}`; +} + +function redactSensitiveValues(value: string): string { + return value + .replace(/\b(api[_-]?key\s*[=:]\s*)([^\s]+)/gi, "$1[REDACTED]") + .replace(/\b(token\s*[=:]\s*)([^\s]+)/gi, "$1[REDACTED]") + .replace(/\b(password\s*[=:]\s*)([^\s]+)/gi, "$1[REDACTED]") + .replace(/\b(secret\s*[=:]\s*)([^\s]+)/gi, "$1[REDACTED]"); +} + +function escapeHtml(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} diff --git a/apps/web/src/index.ts b/apps/web/src/index.ts index 8af70a2..4852018 100644 --- a/apps/web/src/index.ts +++ b/apps/web/src/index.ts @@ -9,3 +9,9 @@ export { export { renderInspectorHtml } from "./components/inspector.js"; export { renderSidebarHtml } from "./components/sidebar.js"; export type { NavItem } from "./components/sidebar.js"; +export { renderRuntimeDashboardHtml } from "./features/runtimes/runtime-dashboard.js"; +export { renderRuntimeStatusRowHtml } from "./features/runtimes/runtime-status-row.js"; +export type { + RuntimeDashboardCapabilities, + RuntimeDashboardResult, +} from "./features/runtimes/runtime-status-row.js"; diff --git a/docs/development/task-backlog.md b/docs/development/task-backlog.md index e879e56..204d98d 100644 --- a/docs/development/task-backlog.md +++ b/docs/development/task-backlog.md @@ -445,10 +445,10 @@ Status markers: **Tasks:** -- [ ] Render runtime name, status, path, version, capabilities, and warnings. -- [ ] Show install/configuration guidance for missing runtimes. -- [ ] Avoid rendering secret-like values. -- [ ] Commit with message `feat: add runtime dashboard ui`. +- [x] Render runtime name, status, path, version, capabilities, and warnings. +- [x] Show install/configuration guidance for missing runtimes. +- [x] Avoid rendering secret-like values. +- [x] Commit with message `feat: add runtime dashboard ui`. **Acceptance Criteria:**