From 4a64d0496b91d31846a6180e4116fcd5dc724b52 Mon Sep 17 00:00:00 2001 From: Slava Trofimov Date: Sat, 30 May 2026 08:21:36 -0400 Subject: [PATCH] refine desktop system dashboard --- desktop/src/main/index.ts | 39 ++++++++ desktop/src/preload/index.ts | 11 +++ .../src/components/DashboardScreen.tsx | 38 +++++++- desktop/src/renderer/src/lib/i18n.ts | 20 ++++- desktop/src/renderer/src/styles.css | 89 +++++++++++++++++++ desktop/src/renderer/src/vite-env.d.ts | 11 +++ 6 files changed, 200 insertions(+), 8 deletions(-) diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts index eb6ec47b..fed5e52d 100644 --- a/desktop/src/main/index.ts +++ b/desktop/src/main/index.ts @@ -91,11 +91,23 @@ type DashboardWorkerRun = { }>; }; +type DesktopMcpServer = { + id: string; + name: string; + status: string; + reason: string; + transport: string; + toolCount: number; + reconnectAttempts: number; + error?: string; +}; + type DesktopDashboardSnapshot = { ok: boolean; detail: string; generatedAt?: string; baseUrl?: string; + dashboardEnabled?: boolean; starting?: boolean; attention?: { title: string; @@ -120,6 +132,7 @@ type DesktopDashboardSnapshot = { }; system?: { services: Array<{ id: string; name: string; status: string; reason: string }>; + mcpServers: DesktopMcpServer[]; logs: Array<{ timestamp?: string; level?: string; service?: string; event?: string }>; }; }; @@ -521,6 +534,10 @@ function dashboardBaseUrl(config: Record): string { return `http://${reachableHost}:${port}`; } +function dashboardWebappEnabled(config: Record): boolean { + return recordValue(config.gateway).webapp_enabled !== false; +} + async function fetchDashboardJson(installDir: string, path: string, init?: RequestInit): Promise { const config = await loadRawConfigForInstall(installDir); const gateway = recordValue(config.gateway); @@ -578,6 +595,25 @@ function normalizeStringList(items: unknown): string[] { return out; } +function normalizeMcpServers(value: unknown): DesktopMcpServer[] { + const servers = recordValue(value); + return Object.entries(servers) + .map(([id, payload]) => { + const server = recordValue(payload); + return { + id, + name: stringValue(server.name, id), + status: stringValue(server.status, "unknown"), + reason: stringValue(server.reason), + transport: stringValue(server.transport, "auto"), + toolCount: numberValue(server.tool_count), + reconnectAttempts: numberValue(server.reconnect_attempts), + error: stringValue(server.error) || undefined, + }; + }) + .sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id)); +} + function normalizeDesktopWorkerTemplate( template: DesktopWorkerTemplate, options: { expectedId?: string; updatedAt?: string } = {}, @@ -732,6 +768,7 @@ async function getDesktopDashboardSnapshot(installDir: string): Promise>; const recentOctoLog = systemLogs.find((entry) => stringValue(entry.service).toLowerCase().includes("octo")) ?? systemLogs[0]; @@ -760,6 +797,7 @@ async function getDesktopDashboardSnapshot(installDir: string): Promise ({ timestamp: stringValue(entry.timestamp), level: stringValue(entry.level, "info"), diff --git a/desktop/src/preload/index.ts b/desktop/src/preload/index.ts index bbe77cc1..6ce3b532 100644 --- a/desktop/src/preload/index.ts +++ b/desktop/src/preload/index.ts @@ -178,6 +178,7 @@ type DesktopDashboardSnapshot = { detail: string; generatedAt?: string; baseUrl?: string; + dashboardEnabled?: boolean; starting?: boolean; attention?: { title: string; @@ -232,6 +233,16 @@ type DesktopDashboardSnapshot = { }; system?: { services: Array<{ id: string; name: string; status: string; reason: string }>; + mcpServers: Array<{ + id: string; + name: string; + status: string; + reason: string; + transport: string; + toolCount: number; + reconnectAttempts: number; + error?: string; + }>; logs: Array<{ timestamp?: string; level?: string; service?: string; event?: string }>; }; }; diff --git a/desktop/src/renderer/src/components/DashboardScreen.tsx b/desktop/src/renderer/src/components/DashboardScreen.tsx index 608c18c4..33d0a9bc 100644 --- a/desktop/src/renderer/src/components/DashboardScreen.tsx +++ b/desktop/src/renderer/src/components/DashboardScreen.tsx @@ -456,6 +456,9 @@ export function DashboardScreen({ const systemTitle = attention ? attentionTitleText : runtimeView.title; const systemDetail = attention ? attentionDetail : runtimeView.detail || copy("systemBody"); const services = snapshot?.system?.services ?? []; + const connectedMcpServers = (snapshot?.system?.mcpServers ?? []).filter( + (server) => String(server.status).toLowerCase() === "connected", + ); const logs = snapshot?.system?.logs ?? []; const editingTemplate = editingTemplateId ? templates.find((template) => template.id === editingTemplateId) ?? null @@ -590,7 +593,6 @@ export function DashboardScreen({
{copy("lastSamples")} - {copy("chartScale")} 0-{graphMax}
@@ -744,7 +746,7 @@ export function DashboardScreen({
) : null}
-
+

{copy("runtime")}

{copy("runtimeBody")}

@@ -765,7 +767,7 @@ export function DashboardScreen({ {copy("startOctopal")} )} - {snapshot?.baseUrl ? ( + {snapshot?.dashboardEnabled && snapshot?.baseUrl ? (
-
+

{copy("updates")}

{copy("updatesBody")}

@@ -804,6 +806,34 @@ export function DashboardScreen({
+
+

{copy("connectedMcpServers")}

+ {connectedMcpServers.length === 0 ? ( +

{copy("noConnectedMcpServers")}

+ ) : ( +
+ {connectedMcpServers.map((server) => ( +
+
+ {server.name} + {server.id} +
+
+
+
{copy("availableTools")}
+
{server.toolCount}
+
+
+
{copy("transport")}
+
{server.transport}
+
+
+
+ ))} +
+ )} +
+

{copy("recentLogs")}

{logs.length === 0 ? ( diff --git a/desktop/src/renderer/src/lib/i18n.ts b/desktop/src/renderer/src/lib/i18n.ts index d713b8b6..2223626a 100644 --- a/desktop/src/renderer/src/lib/i18n.ts +++ b/desktop/src/renderer/src/lib/i18n.ts @@ -227,7 +227,6 @@ export const messages = { liveLoad: "Live load", liveLoadBody: "Active workers, worker queue, and Octo queue on one shared scale.", lastSamples: "Last 32 samples", - chartScale: "Scale", activeWorkers: "Active workers", workerQueue: "Worker queue", octoQueue: "Octo queue", @@ -275,6 +274,10 @@ export const messages = { checkRuntimeUpdate: "Check runtime update", checkDesktopUpdate: "Check desktop update", services: "Services", + connectedMcpServers: "Connected MCP servers", + noConnectedMcpServers: "No MCP servers are connected.", + availableTools: "Available tools", + transport: "Transport", recentLogs: "Recent logs", noDashboardData: "Dashboard data is unavailable.", failedToLoadDashboard: "Failed to load dashboard data.", @@ -477,7 +480,6 @@ export const messages = { liveLoad: "Charge en direct", liveLoadBody: "Workers actifs, file workers et file Octo sur une échelle commune.", lastSamples: "32 derniers points", - chartScale: "Échelle", activeWorkers: "Workers actifs", workerQueue: "File workers", octoQueue: "File Octo", @@ -525,6 +527,10 @@ export const messages = { checkRuntimeUpdate: "Vérifier le runtime", checkDesktopUpdate: "Vérifier l'app desktop", services: "Services", + connectedMcpServers: "Serveurs MCP connectés", + noConnectedMcpServers: "Aucun serveur MCP n'est connecté.", + availableTools: "Outils disponibles", + transport: "Transport", recentLogs: "Logs récents", noDashboardData: "Les données du dashboard sont indisponibles.", failedToLoadDashboard: "Impossible de charger les données du dashboard.", @@ -727,7 +733,6 @@ export const messages = { liveLoad: "Carga en vivo", liveLoadBody: "Workers activos, cola de workers y cola de Octo en una escala compartida.", lastSamples: "Últimas 32 muestras", - chartScale: "Escala", activeWorkers: "Workers activos", workerQueue: "Cola workers", octoQueue: "Cola Octo", @@ -775,6 +780,10 @@ export const messages = { checkRuntimeUpdate: "Comprobar runtime", checkDesktopUpdate: "Comprobar app desktop", services: "Servicios", + connectedMcpServers: "Servidores MCP conectados", + noConnectedMcpServers: "No hay servidores MCP conectados.", + availableTools: "Herramientas disponibles", + transport: "Transporte", recentLogs: "Logs recientes", noDashboardData: "Los datos del dashboard no están disponibles.", failedToLoadDashboard: "No se pudieron cargar los datos del dashboard.", @@ -977,7 +986,6 @@ export const messages = { liveLoad: "实时负载", liveLoadBody: "用同一刻度显示活跃 worker、worker 队列和 Octo 队列。", lastSamples: "最近 32 个样本", - chartScale: "刻度", activeWorkers: "活跃 worker", workerQueue: "Worker 队列", octoQueue: "Octo 队列", @@ -1025,6 +1033,10 @@ export const messages = { checkRuntimeUpdate: "检查 runtime 更新", checkDesktopUpdate: "检查桌面 app 更新", services: "服务", + connectedMcpServers: "已连接的 MCP 服务器", + noConnectedMcpServers: "没有已连接的 MCP 服务器。", + availableTools: "可用工具", + transport: "传输", recentLogs: "最近日志", noDashboardData: "Dashboard 数据不可用。", failedToLoadDashboard: "无法加载 dashboard 数据。", diff --git a/desktop/src/renderer/src/styles.css b/desktop/src/renderer/src/styles.css index 067c4e48..27fcae48 100644 --- a/desktop/src/renderer/src/styles.css +++ b/desktop/src/renderer/src/styles.css @@ -2982,11 +2982,25 @@ p { margin-top: 2px; } +.system-card .system-actions .button { + min-height: 44px; +} + .system-grid { display: grid; gap: 16px; } +@media (min-width: 900px) { + .system-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .system-card:not(.system-card-half) { + grid-column: 1 / -1; + } +} + .service-pills { display: flex; flex-wrap: wrap; @@ -2994,6 +3008,81 @@ p { margin-top: 14px; } +.mcp-server-list { + display: grid; + gap: 10px; + margin-top: 12px; +} + +.mcp-server-card { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 14px; + padding: 12px 14px; + border: 1px solid color-mix(in srgb, var(--success) 34%, var(--border)); + border-radius: 14px; + background: color-mix(in srgb, var(--success) 8%, transparent); +} + +.mcp-server-card strong, +.mcp-server-card span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mcp-server-card strong { + color: var(--foreground); + font-size: 14px; +} + +.mcp-server-card span { + margin-top: 3px; + color: var(--muted); + font-size: 12px; +} + +.mcp-server-card dl { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 0; + justify-content: flex-end; +} + +.mcp-server-card dl div { + min-width: 86px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 12px; + background: color-mix(in srgb, var(--surface-strong) 74%, transparent); +} + +.mcp-server-card dt, +.mcp-server-card dd { + margin: 0; +} + +.mcp-server-card dt { + color: var(--muted); + font-size: 10px; + font-weight: 750; + text-transform: uppercase; +} + +.mcp-server-card dd { + margin-top: 3px; + max-width: 140px; + overflow: hidden; + color: var(--foreground); + font-size: 12px; + font-weight: 750; + text-overflow: ellipsis; + white-space: nowrap; +} + .log-list { display: grid; gap: 8px; diff --git a/desktop/src/renderer/src/vite-env.d.ts b/desktop/src/renderer/src/vite-env.d.ts index 6d93aa76..a2c14669 100644 --- a/desktop/src/renderer/src/vite-env.d.ts +++ b/desktop/src/renderer/src/vite-env.d.ts @@ -204,6 +204,7 @@ type DesktopDashboardSnapshot = { detail: string; generatedAt?: string; baseUrl?: string; + dashboardEnabled?: boolean; starting?: boolean; attention?: { title: string; @@ -228,6 +229,16 @@ type DesktopDashboardSnapshot = { }; system?: { services: Array<{ id: string; name: string; status: string; reason: string }>; + mcpServers: Array<{ + id: string; + name: string; + status: string; + reason: string; + transport: string; + toolCount: number; + reconnectAttempts: number; + error?: string; + }>; logs: Array<{ timestamp?: string; level?: string; service?: string; event?: string }>; }; };