import type { ReactNode } from 'react'; import type { SidebarTreeNode } from '@cameleer/design-system'; /* ------------------------------------------------------------------ */ /* Domain types (moved out of DS — no longer exported there) */ /* ------------------------------------------------------------------ */ export interface SidebarRoute { id: string; name: string; exchangeCount: number; } export interface SidebarAgent { id: string; name: string; status: 'live' | 'stale' | 'dead'; tps?: number; } export interface SidebarApp { id: string; name: string; health: 'live' | 'stale' | 'dead'; exchangeCount: number; routes: SidebarRoute[]; agents: SidebarAgent[]; } /* ------------------------------------------------------------------ */ /* Formatting helpers */ /* ------------------------------------------------------------------ */ export function formatCount(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}m`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`; return String(n); } /* ------------------------------------------------------------------ */ /* localStorage collapse helpers */ /* ------------------------------------------------------------------ */ export function readCollapsed(key: string, defaultValue: boolean): boolean { try { const raw = localStorage.getItem(key); if (raw === null) return defaultValue; return raw === 'true'; } catch { return defaultValue; } } export function writeCollapsed(key: string, value: boolean): void { try { localStorage.setItem(key, String(value)); } catch { // ignore quota errors } } /* ------------------------------------------------------------------ */ /* Tree builders */ /* ------------------------------------------------------------------ */ /** * Apps tree — one node per app, routes as children. * Paths: /apps/{appId}, /apps/{appId}/{routeId} */ export function buildAppTreeNodes( apps: SidebarApp[], statusDot: (health: string) => ReactNode, chevron: () => ReactNode, ): SidebarTreeNode[] { return apps.map((app) => ({ id: app.id, label: app.name, icon: statusDot(app.health), badge: formatCount(app.exchangeCount), path: `/apps/${app.id}`, starrable: true, starKey: `app:${app.id}`, children: app.routes.map((r) => ({ id: `${app.id}/${r.id}`, label: r.name, icon: chevron(), badge: formatCount(r.exchangeCount), path: `/apps/${app.id}/${r.id}`, starrable: true, starKey: `route:${app.id}/${r.id}`, })), })); } /** * Agents tree — one node per app, agents as children. * Paths: /agents/{appId}, /agents/{appId}/{agentId} * Badge shows "N/M live". */ export function buildAgentTreeNodes( apps: SidebarApp[], statusDot: (health: string) => ReactNode, ): SidebarTreeNode[] { return apps.map((app) => { const liveCount = app.agents.filter((a) => a.status === 'live').length; return { id: `agent:${app.id}`, label: app.name, icon: statusDot(app.health), badge: `${liveCount}/${app.agents.length} live`, path: `/agents/${app.id}`, children: app.agents.map((a) => ({ id: `agent:${app.id}/${a.id}`, label: a.name, icon: statusDot(a.status), badge: a.tps != null ? `${a.tps.toFixed(1)} msg/s` : undefined, path: `/agents/${app.id}/${a.id}`, })), }; }); } /** * Routes stats tree — one node per app, routes as children. * Paths: /routes/{appId}, /routes/{appId}/{routeId} */ export function buildRouteTreeNodes( apps: SidebarApp[], statusDot: (health: string) => ReactNode, chevron: () => ReactNode, ): SidebarTreeNode[] { return apps.map((app) => ({ id: `route:${app.id}`, label: app.name, icon: statusDot(app.health), badge: `${app.routes.length} routes`, path: `/routes/${app.id}`, children: app.routes.map((r) => ({ id: `route:${app.id}/${r.id}`, label: r.name, icon: chevron(), badge: formatCount(r.exchangeCount), path: `/routes/${app.id}/${r.id}`, })), })); } /** * Admin tree — static 6 nodes. */ export function buildAdminTreeNodes(): SidebarTreeNode[] { return [ { id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' }, { id: 'admin:audit', label: 'Audit Log', path: '/admin/audit' }, { id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' }, { id: 'admin:appconfig', label: 'App Config', path: '/admin/appconfig' }, { id: 'admin:database', label: 'Database', path: '/admin/database' }, { id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }, ]; }