Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 }>;
};
};
Expand Down Expand Up @@ -521,6 +534,10 @@ function dashboardBaseUrl(config: Record<string, unknown>): string {
return `http://${reachableHost}:${port}`;
}

function dashboardWebappEnabled(config: Record<string, unknown>): boolean {
return recordValue(config.gateway).webapp_enabled !== false;
}

async function fetchDashboardJson<T>(installDir: string, path: string, init?: RequestInit): Promise<T> {
const config = await loadRawConfigForInstall(installDir);
const gateway = recordValue(config.gateway);
Expand Down Expand Up @@ -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 } = {},
Expand Down Expand Up @@ -732,6 +768,7 @@ async function getDesktopDashboardSnapshot(installDir: string): Promise<DesktopD
const octoNode = recordValue(octo.octo);
const octoHealth = recordValue(octo.health);
const systemNode = recordValue(system.system);
const connectivityNode = recordValue(system.connectivity);
const systemLogs = listValue(system.logs) as Array<Record<string, unknown>>;
const recentOctoLog =
systemLogs.find((entry) => stringValue(entry.service).toLowerCase().includes("octo")) ?? systemLogs[0];
Expand Down Expand Up @@ -760,6 +797,7 @@ async function getDesktopDashboardSnapshot(installDir: string): Promise<DesktopD
detail: shouldSurfaceAttention ? attention?.detail || "" : stringValue(overviewHealth.summary, "Dashboard data loaded."),
generatedAt: stringValue(overview.generated_at),
baseUrl: dashboardBaseUrl(config),
dashboardEnabled: dashboardWebappEnabled(config),
starting: startupGrace,
attention: shouldSurfaceAttention ? attention ?? undefined : undefined,
load: {
Expand All @@ -780,6 +818,7 @@ async function getDesktopDashboardSnapshot(installDir: string): Promise<DesktopD
},
system: {
services,
mcpServers: normalizeMcpServers(recordValue(connectivityNode.mcp_servers)),
logs: systemLogs.slice(0, 12).map((entry) => ({
timestamp: stringValue(entry.timestamp),
level: stringValue(entry.level, "info"),
Expand Down
11 changes: 11 additions & 0 deletions desktop/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ type DesktopDashboardSnapshot = {
detail: string;
generatedAt?: string;
baseUrl?: string;
dashboardEnabled?: boolean;
starting?: boolean;
attention?: {
title: string;
Expand Down Expand Up @@ -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 }>;
};
};
Expand Down
38 changes: 34 additions & 4 deletions desktop/src/renderer/src/components/DashboardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -590,7 +593,6 @@ export function DashboardScreen({
</div>
<div className="dashboard-chart-pills">
<span className="dashboard-pill">{copy("lastSamples")}</span>
<span className="dashboard-pill">{copy("chartScale")} 0-{graphMax}</span>
</div>
</div>
<div className="dashboard-load-summary">
Expand Down Expand Up @@ -744,7 +746,7 @@ export function DashboardScreen({
</div>
) : null}
<div className="system-grid">
<div className="dashboard-panel system-card">
<div className="dashboard-panel system-card system-card-half">
<h2>{copy("runtime")}</h2>
<p>{copy("runtimeBody")}</p>
<div className="system-actions">
Expand All @@ -765,7 +767,7 @@ export function DashboardScreen({
{copy("startOctopal")}
</Button>
)}
{snapshot?.baseUrl ? (
{snapshot?.dashboardEnabled && snapshot?.baseUrl ? (
<Button type="button" variant="ghost" onClick={() => window.open(snapshot.baseUrl, "_blank")}>
<ExternalLink data-icon="inline-start" />
{copy("openDashboardUrl")}
Expand All @@ -774,7 +776,7 @@ export function DashboardScreen({
</div>
</div>

<div className="dashboard-panel system-card">
<div className="dashboard-panel system-card system-card-half">
<h2>{copy("updates")}</h2>
<p>{copy("updatesBody")}</p>
<div className="system-actions">
Expand Down Expand Up @@ -804,6 +806,34 @@ export function DashboardScreen({
</div>
</div>

<div className="dashboard-panel system-card">
<h2>{copy("connectedMcpServers")}</h2>
{connectedMcpServers.length === 0 ? (
<p>{copy("noConnectedMcpServers")}</p>
) : (
<div className="mcp-server-list">
{connectedMcpServers.map((server) => (
<article className="mcp-server-card" key={server.id} title={server.reason || server.id}>
<div>
<strong>{server.name}</strong>
<span>{server.id}</span>
</div>
<dl>
<div>
<dt>{copy("availableTools")}</dt>
<dd>{server.toolCount}</dd>
</div>
<div>
<dt>{copy("transport")}</dt>
<dd>{server.transport}</dd>
</div>
</dl>
</article>
))}
</div>
)}
</div>

<div className="dashboard-panel system-card">
<h2>{copy("recentLogs")}</h2>
{logs.length === 0 ? (
Expand Down
20 changes: 16 additions & 4 deletions desktop/src/renderer/src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -977,7 +986,6 @@ export const messages = {
liveLoad: "实时负载",
liveLoadBody: "用同一刻度显示活跃 worker、worker 队列和 Octo 队列。",
lastSamples: "最近 32 个样本",
chartScale: "刻度",
activeWorkers: "活跃 worker",
workerQueue: "Worker 队列",
octoQueue: "Octo 队列",
Expand Down Expand Up @@ -1025,6 +1033,10 @@ export const messages = {
checkRuntimeUpdate: "检查 runtime 更新",
checkDesktopUpdate: "检查桌面 app 更新",
services: "服务",
connectedMcpServers: "已连接的 MCP 服务器",
noConnectedMcpServers: "没有已连接的 MCP 服务器。",
availableTools: "可用工具",
transport: "传输",
recentLogs: "最近日志",
noDashboardData: "Dashboard 数据不可用。",
failedToLoadDashboard: "无法加载 dashboard 数据。",
Expand Down
89 changes: 89 additions & 0 deletions desktop/src/renderer/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2982,18 +2982,107 @@ 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;
gap: 10px;
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;
Expand Down
Loading