From 58f007e6dbadd7dc5610b8a097f0e9c77eebf6a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 21 Jun 2026 12:03:25 +0000 Subject: [PATCH] Add per-resource kind labeling and proper Node Status icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each adapter now tags its Resources with a kind (vm, container, app, host, network) so Node Status tiles show the right icon instead of a generic server glyph — Proxmox LXCs/Docker containers get a container icon, VMs get a VM icon, Uptime Kuma monitors get an app icon, etc. Also stops silently swallowing listResources() failures — they're now logged as warnings, since a connected-but-empty integration (e.g. Uptime Kuma reporting zero monitors) was previously indistinguishable from a real adapter error. --- backend/src/integrations/aws.ts | 1 + backend/src/integrations/cloudflare.ts | 1 + backend/src/integrations/docker.ts | 1 + backend/src/integrations/netbird.ts | 1 + backend/src/integrations/proxmox.ts | 1 + backend/src/integrations/ssh.ts | 1 + backend/src/integrations/types.ts | 1 + backend/src/integrations/uptimeKuma.ts | 2 +- backend/src/routes/integrations.ts | 4 ++-- src/lib/api.ts | 1 + src/pages/Infrastructure.tsx | 19 +++++++++++++++---- 11 files changed, 26 insertions(+), 7 deletions(-) diff --git a/backend/src/integrations/aws.ts b/backend/src/integrations/aws.ts index 00d8623..057efc2 100644 --- a/backend/src/integrations/aws.ts +++ b/backend/src/integrations/aws.ts @@ -39,6 +39,7 @@ export const aws: IntegrationAdapter = { name: nameTag || instance.InstanceId || 'unknown', status: state === 'running' ? 'healthy' : state === 'stopped' || state === 'terminated' ? 'critical' : 'warning', detail: `${instance.InstanceType ?? 'unknown type'} — ${state}`, + kind: 'vm', }) } } diff --git a/backend/src/integrations/cloudflare.ts b/backend/src/integrations/cloudflare.ts index 0480212..10701bd 100644 --- a/backend/src/integrations/cloudflare.ts +++ b/backend/src/integrations/cloudflare.ts @@ -40,6 +40,7 @@ export const cloudflare: IntegrationAdapter = { name: body.result.name, status: body.result.status === 'active' ? 'healthy' : body.result.status === 'pending' || body.result.status === 'initializing' ? 'warning' : 'critical', detail: `Zone status: ${body.result.status}`, + kind: 'network', }, ] }, diff --git a/backend/src/integrations/docker.ts b/backend/src/integrations/docker.ts index b2e3a3e..bde2e5d 100644 --- a/backend/src/integrations/docker.ts +++ b/backend/src/integrations/docker.ts @@ -23,6 +23,7 @@ export const docker: IntegrationAdapter = { name: c.Names[0]?.replace(/^\//, '') ?? 'unknown', status: c.State === 'running' ? 'healthy' : c.State === 'restarting' ? 'warning' : 'critical', detail: c.State, + kind: 'container', })) }, } diff --git a/backend/src/integrations/netbird.ts b/backend/src/integrations/netbird.ts index 13c4152..b58ff9b 100644 --- a/backend/src/integrations/netbird.ts +++ b/backend/src/integrations/netbird.ts @@ -39,6 +39,7 @@ export const netbird: IntegrationAdapter = { name: p.name || p.hostname || p.ip || 'unknown peer', status: p.connected ? 'healthy' : 'critical', detail: p.connected ? `Online — ${p.ip ?? ''}`.trim() : 'Offline', + kind: 'network', })) }, } diff --git a/backend/src/integrations/proxmox.ts b/backend/src/integrations/proxmox.ts index 8144430..88f6461 100644 --- a/backend/src/integrations/proxmox.ts +++ b/backend/src/integrations/proxmox.ts @@ -59,6 +59,7 @@ export const proxmox: IntegrationAdapter = { name: entry.name ?? `vm-${entry.vmid}`, status: entry.status === 'running' ? 'healthy' : entry.status === 'stopped' ? 'unknown' : 'warning', detail: `${entry.type} on ${entry.node} — ${entry.status}`, + kind: entry.type === 'lxc' ? 'container' : 'vm', })) }, } diff --git a/backend/src/integrations/ssh.ts b/backend/src/integrations/ssh.ts index 028e6ba..e84996c 100644 --- a/backend/src/integrations/ssh.ts +++ b/backend/src/integrations/ssh.ts @@ -82,6 +82,7 @@ export const ssh: IntegrationAdapter = { name: hostname, status: critical ? 'critical' : warning ? 'warning' : 'healthy', detail: parts.join(' · ') || undefined, + kind: 'host', }, ] } catch { diff --git a/backend/src/integrations/types.ts b/backend/src/integrations/types.ts index 01ecce9..6fbe7d3 100644 --- a/backend/src/integrations/types.ts +++ b/backend/src/integrations/types.ts @@ -22,6 +22,7 @@ export interface Resource { name: string status: 'healthy' | 'warning' | 'critical' | 'unknown' detail?: string + kind?: 'vm' | 'container' | 'app' | 'host' | 'network' } export interface IntegrationAdapter { diff --git a/backend/src/integrations/uptimeKuma.ts b/backend/src/integrations/uptimeKuma.ts index f50be26..496b845 100644 --- a/backend/src/integrations/uptimeKuma.ts +++ b/backend/src/integrations/uptimeKuma.ts @@ -108,7 +108,7 @@ export const uptimeKuma: IntegrationAdapter = { : beat.status === 2 ? 'warning' : 'unknown' - resources.push({ name: monitor.name, status, detail: beat?.msg || undefined }) + resources.push({ name: monitor.name, status, detail: beat?.msg || undefined, kind: 'app' }) } return resources } finally { diff --git a/backend/src/routes/integrations.ts b/backend/src/routes/integrations.ts index 0ced90a..f535ade 100644 --- a/backend/src/routes/integrations.ts +++ b/backend/src/routes/integrations.ts @@ -152,8 +152,8 @@ export async function integrationRoutes(app: FastifyInstance) { try { const found = await adapter.listResources(config, secrets) for (const r of found) resources.push({ ...r, integration: row.name }) - } catch { - // adapter unreachable — skip, connection test already surfaces this + } catch (err) { + app.log.warn(`listResources failed for integration "${row.name}" (${row.type}): ${err instanceof Error ? err.message : err}`) } } return { resources } diff --git a/src/lib/api.ts b/src/lib/api.ts index dc76c9c..f4c7257 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -443,6 +443,7 @@ export interface Resource { status: 'healthy' | 'warning' | 'critical' | 'unknown' detail?: string integration: string + kind?: 'vm' | 'container' | 'app' | 'host' | 'network' } export interface TransferProgress { diff --git a/src/pages/Infrastructure.tsx b/src/pages/Infrastructure.tsx index a824104..09307ef 100644 --- a/src/pages/Infrastructure.tsx +++ b/src/pages/Infrastructure.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts' -import { Plus, Server, Activity, AlertTriangle, CircleCheck } from 'lucide-react' +import { Plus, Server, Activity, AlertTriangle, CircleCheck, Box, MonitorSmartphone, Waypoints, AppWindow, type LucideIcon } from 'lucide-react' import { api, type Resource, type Integration, type Event } from '../lib/api' const subTabs = ['Overview'] @@ -68,6 +68,14 @@ const nodeStatusColor: Record = { const integrationPalette = ['#C8A434', '#E67E22', '#2ECC71', '#7A7D85', '#3B82F6', '#8B5E3C'] +const kindIcon: Record = { + vm: MonitorSmartphone, + container: Box, + app: AppWindow, + host: Server, + network: Waypoints, +} + function Donut({ data, centerLabel }: { data: { name: string; value: number; color: string }[]; centerLabel?: string }) { const hasData = data.some((d) => d.value > 0) return ( @@ -248,7 +256,9 @@ export default function Infrastructure() {

Node Status

{resources && resources.length > 0 ? (
- {resources.map((node, i) => ( + {resources.map((node, i) => { + const NodeIcon = kindIcon[node.kind ?? ''] ?? Server + return (
- +
{node.name}
- ))} + ) + })}
) : (