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, Box, MonitorSmartphone, Waypoints, AppWindow, type LucideIcon } from 'lucide-react' import { api, type Resource, type Integration, type Event } from '../lib/api' const subTabs = ['Overview'] const futureSubTabs = ['Network'] const cardBase: React.CSSProperties = { backgroundColor: 'rgba(10, 10, 12, 0.92)', border: '1px solid rgba(200, 164, 52, 0.08)', borderRadius: '12px', padding: '20px', boxShadow: '0 0 20px rgba(200, 164, 52, 0.03)', transition: 'border-color 0.2s ease', position: 'relative', overflow: 'hidden', height: '100%', display: 'flex', flexDirection: 'column', } const sectionTitle: React.CSSProperties = { fontSize: '11px', textTransform: 'uppercase', letterSpacing: '1.5px', color: '#7A7D85', fontWeight: 500, marginBottom: '16px', } function framedCard(bgUrl: string): React.CSSProperties { return { backgroundImage: `url(${bgUrl})`, backgroundSize: '100% 100%', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', position: 'relative', overflow: 'hidden', height: '100%', display: 'flex', flexDirection: 'column', padding: '20px 20px 64px 20px', } } const cardVignette: React.CSSProperties = { position: 'absolute', inset: 0, pointerEvents: 'none', background: 'radial-gradient(ellipse closest-side at center, transparent 70%, var(--color-page) 100%)', } const cardDim: React.CSSProperties = { position: 'absolute', inset: 0, pointerEvents: 'none', backgroundColor: 'rgba(8, 8, 10, 0.45)', } const nodeStatusColor: Record = { healthy: '#2ECC71', warning: '#E67E22', critical: '#E74C3C', unknown: '#7A7D85', } const integrationPalette = ['#C8A434', '#E67E22', '#2ECC71', '#7A7D85', '#3B82F6', '#8B5E3C'] // Every integration except Proxmox collapses its resources into one tile per // integration on Node Status (e.g. 30 EC2 instances under one "AWS" tile, all of // Uptime Kuma's monitors under one "Uptime" tile) — the underlying items are listed // in Node Detail once selected. Proxmox stays ungrouped since its VMs/LXCs are // managed individually elsewhere in the app. See ROADMAP.md for the planned paid-tier // per-integration tabs that will show every node, not just the grouped tile. const ungroupedIntegrationTypes = new Set(['proxmox']) // Icon lookup per integration type, ordered by preference. A user-supplied // icon (Settings → Integrations → "Icon" — URL or upload, stored as // config.iconUrl) always wins; these are the built-in fallback chain when // no custom icon is set. Each entry is tried in order — on a failed image // load (404/network) the next URL in the list is tried — and if every // candidate fails, the per-kind Lucide icon (kindIcon) renders instead. const cdnIconCandidatesByIntegrationType: Record = { uptime_kuma: ['https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png'], aws: [ 'https://samuelsjames.github.io/assets-public/logos/aws-logo.svg', 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/aws.png', ], docker: ['https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/docker.png'], netbird: ['https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/netbird.png'], cloudflare: ['https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/cloudflare.png'], ssh: ['https://samuelsjames.github.io/assets-public/logos/linux-logo.svg'], proxmox: [ 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/proxmox.png', 'https://api.iconify.design/simple-icons/proxmox.svg', ], weather: [ 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/openweathermap.png', 'https://api.iconify.design/mdi/weather-partly-cloudy.svg', ], remote_desktop: [ 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/guacamole.png', 'https://api.iconify.design/simple-icons/apacheguacamole.svg', 'https://api.iconify.design/mdi/remote-desktop.svg', ], } // SSH connections are named by the user after whatever host/service they point at // (e.g. "Linode", "Portainer") — match known keywords in the connection name to a // more specific icon before falling back to the generic Linux icon above. const sshNameIconCandidates: { keyword: string; candidates: string[] }[] = [ { keyword: 'linode', candidates: [ 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/linode.png', 'https://api.iconify.design/simple-icons/linode.svg', ] }, { keyword: 'portainer', candidates: [ 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/portainer.png', 'https://api.iconify.design/simple-icons/portainer.svg', ] }, ] function iconCandidatesForNode(node: NodeTile): string[] { if (node.integrationType === 'ssh') { const name = ('isGroup' in node ? node.integration : node.name).toLowerCase() const match = sshNameIconCandidates.find((m) => name.includes(m.keyword)) if (match) return [...match.candidates, ...cdnIconCandidatesByIntegrationType.ssh] } return cdnIconCandidatesByIntegrationType[node.integrationType] ?? [] } interface NodeGroup { isGroup: true integration: string integrationType: string kind: string status: Resource['status'] iconUrl?: string members: Resource[] } type NodeTile = Resource | NodeGroup const kindIcon: Record = { vm: MonitorSmartphone, container: Box, app: AppWindow, host: Server, network: Waypoints, } // Tries the user's custom icon first, then each built-in candidate URL in // order, falling back to the per-kind Lucide icon if every image 404s. function TileIcon({ customUrl, candidates, fallback: Fallback }: { customUrl?: string; candidates: string[]; fallback: LucideIcon }) { const urls = useMemo(() => [...(customUrl ? [customUrl] : []), ...candidates], [customUrl, candidates]) const [failedCount, setFailedCount] = useState(0) useEffect(() => setFailedCount(0), [urls]) if (failedCount >= urls.length) return return ( setFailedCount((n) => n + 1)} /> ) } function Donut({ data, centerLabel }: { data: { name: string; value: number; color: string }[]; centerLabel?: string }) { const hasData = data.some((d) => d.value > 0) return (
{hasData && ( {data.map((entry) => ( ))} )} {centerLabel && (
{centerLabel}
)}
{data.map((entry) => (
{entry.name} {entry.value}
))}
) } export default function Infrastructure() { const [activeTab, setActiveTab] = useState('Overview') const [resources, setResources] = useState(null) const [integrations, setIntegrations] = useState(null) const [events, setEvents] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const navigate = useNavigate() useEffect(() => { api.listResources().then(({ resources }) => setResources(resources)) api.listIntegrations().then(({ integrations }) => setIntegrations(integrations)) api.listEvents(4).then(({ events }) => setEvents(events)) }, []) const healthy = resources?.filter((r) => r.status === 'healthy').length ?? 0 const warning = resources?.filter((r) => r.status === 'warning').length ?? 0 const critical = resources?.filter((r) => r.status === 'critical').length ?? 0 const total = resources?.length ?? 0 const statusCards = [ { label: 'Total Resources', value: String(total), icon: Server, sub: `${integrations?.filter((i) => i.status === 'connected').length ?? 0} integrations connected` }, { label: 'Healthy', value: String(healthy), icon: Activity, sub: total ? `${Math.round((healthy / total) * 100)}%` : '—', color: '#2ECC71' }, { label: 'Warnings', value: String(warning), icon: AlertTriangle, sub: warning ? 'Needs attention' : 'None', color: '#E67E22' }, { label: 'Critical', value: String(critical), icon: AlertTriangle, sub: critical ? 'Action required' : 'None', color: '#E74C3C' }, ] const distributionData = useMemo(() => { if (!resources) return [] const byIntegration = new Map() for (const r of resources) byIntegration.set(r.integration, (byIntegration.get(r.integration) ?? 0) + 1) return Array.from(byIntegration.entries()).map(([name, value], i) => ({ name, value, color: integrationPalette[i % integrationPalette.length] })) }, [resources]) // An integration can have dozens of resources (e.g. 30 EC2 instances) — collapse // everything except Proxmox into one tile per integration on Node Status, with // members listed in Node Detail. const nodeTiles = useMemo(() => { if (!resources) return [] const groups = new Map() const singles: Resource[] = [] for (const r of resources) { if (!ungroupedIntegrationTypes.has(r.integrationType)) { const arr = groups.get(r.integration) ?? [] arr.push(r) groups.set(r.integration, arr) } else { singles.push(r) } } const groupTiles: NodeGroup[] = Array.from(groups.entries()).map(([integration, members]) => ({ isGroup: true, integration, integrationType: members[0]?.integrationType ?? '', kind: members[0]?.kind ?? '', iconUrl: members[0]?.iconUrl, status: members.some((m) => m.status === 'critical') ? 'critical' : members.some((m) => m.status === 'warning') ? 'warning' : members.every((m) => m.status === 'healthy') ? 'healthy' : 'unknown', members, })) return [...singles, ...groupTiles] }, [resources]) return ( <> {/* Sub-tabs + Add Resource — hero banner is rendered at the layout level behind this */}
{subTabs.map((tab) => { const active = tab === activeTab return ( ) })} {futureSubTabs.map((tab) => ( ))}
{/* Status Cards */}
{statusCards.map((card) => { const Icon = card.icon return (

{card.label}

{card.value}

{card.sub}

) })}
{/* Middle Row */}
{/* Resource Distribution */}

Resource Distribution

{distributionData.length > 0 ? ( ) : (

Connect an integration in Settings to see resource distribution.

)}
{/* Node Status — expanded */}

Node Status

{nodeTiles.length > 0 ? (
{nodeTiles.map((node, i) => { const NodeIcon = kindIcon[node.kind ?? ''] ?? Server const label = 'isGroup' in node ? node.integration : node.name const tooltip = 'isGroup' in node ? `${node.integration}: ${node.members.length} monitored` : `${node.name}: ${node.detail ?? node.status}` return (
setSelectedNode(node)} className="cursor-pointer transition-colors" style={{ backgroundColor: selectedNode === node ? 'rgba(200,164,52,0.1)' : 'rgba(10, 10, 12, 0.55)', border: selectedNode === node ? '1px solid rgba(200,164,52,0.5)' : `1px solid ${nodeStatusColor[node.status]}33`, borderRadius: '8px', padding: '8px 10px', display: 'flex', flexDirection: 'column', gap: '6px', }} >
{label}
) })}
) : (

No resources reported yet. Connect Docker (or another supported integration) in Settings to populate this view.

)}
{/* Bottom Row */}
{/* Integration Health */}

Integration Health

{integrations && integrations.length > 0 ? (
{integrations.map((i) => (
{i.name} {i.status}
))}
) : (

No integrations added yet.

)}
{/* Node Detail — shows the node selected up in Node Status */}

Node Detail

{selectedNode && 'isGroup' in selectedNode ? (
{selectedNode.integration} ({selectedNode.members.length} monitored)
{selectedNode.members.map((m, i) => (
{m.name} {m.detail ?? m.status}
))}
) : selectedNode ? (
{selectedNode.name}
{selectedNode.integration} | {selectedNode.status}
{selectedNode.detail && (

{selectedNode.detail}

)}
) : (

Select a node above to view its details.

)}
{/* Recent Activity */}

Recent Activity

{events && events.length > 0 ? (
{events.map((item) => (

{item.title}

))}
) : (

No activity yet.

)}
{/* Footer stats bar */}
{total} Resources| {integrations?.filter((i) => i.status === 'connected').length ?? 0} Integrations Connected {critical > 0 && ( <> | {critical} Critical )}
) }