import { createElement, type ReactNode } from 'react'; import type { SidebarTreeNode } from '@cameleer/design-system'; import { AlertTriangle, Inbox, List, ScrollText, BellOff } from 'lucide-react'; /* ------------------------------------------------------------------ */ /* Domain types (moved out of DS — no longer exported there) */ /* ------------------------------------------------------------------ */ export interface SidebarRoute { id: string; name: string; exchangeCount: number; routeState?: 'stopped' | 'suspended'; } export interface SidebarApp { id: string; name: string; health: 'live' | 'stale' | 'dead' | 'running' | 'error'; healthTooltip?: string; exchangeCount: number; routes: SidebarRoute[]; } /* ------------------------------------------------------------------ */ /* 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: /exchanges/{appId}, /exchanges/{appId}/{routeId} */ export function buildAppTreeNodes( apps: SidebarApp[], statusDot: (health: string) => ReactNode, chevron: () => ReactNode, stopIcon?: () => ReactNode, pauseIcon?: () => ReactNode, ): SidebarTreeNode[] { return apps.map((app) => ({ id: app.id, label: app.name, icon: app.healthTooltip ? createElement('span', { title: app.healthTooltip }, statusDot(app.health)) : statusDot(app.health), badge: formatCount(app.exchangeCount), path: `/exchanges/${app.id}`, starrable: true, starKey: `app:${app.id}`, children: app.routes.map((r) => ({ id: `${app.id}/${r.id}`, label: r.name, icon: r.routeState === 'stopped' && stopIcon ? stopIcon() : r.routeState === 'suspended' && pauseIcon ? pauseIcon() : chevron(), badge: r.routeState ? `${r.routeState.toUpperCase()} \u00b7 ${formatCount(r.exchangeCount)}` : formatCount(r.exchangeCount), path: `/exchanges/${app.id}/${r.id}`, starrable: true, starKey: `route:${app.id}/${r.id}`, })), })); } /** * Admin tree — static nodes, alphabetically sorted. */ export function buildAdminTreeNodes(opts?: { infrastructureEndpoints?: boolean }): SidebarTreeNode[] { const showInfra = opts?.infrastructureEndpoints !== false; const nodes: SidebarTreeNode[] = [ { id: 'admin:audit', label: 'Audit Log', path: '/admin/audit' }, ...(showInfra ? [{ id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }] : []), ...(showInfra ? [{ id: 'admin:database', label: 'Database', path: '/admin/database' }] : []), { id: 'admin:environments', label: 'Environments', path: '/admin/environments' }, { id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' }, { id: 'admin:outbound-connections', label: 'Outbound Connections', path: '/admin/outbound-connections' }, { id: 'admin:sensitive-keys', label: 'Sensitive Keys', path: '/admin/sensitive-keys' }, { id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' }, ]; return nodes; } /** * Alerts tree — static nodes for the alerting section. * Paths: /alerts/{inbox|all|rules|silences|history} */ export function buildAlertsTreeNodes(): SidebarTreeNode[] { const icon = (el: ReactNode) => el; return [ { id: 'alerts-inbox', label: 'Inbox', path: '/alerts/inbox', icon: icon(createElement(Inbox, { size: 14 })) }, { id: 'alerts-all', label: 'All', path: '/alerts/all', icon: icon(createElement(List, { size: 14 })) }, { id: 'alerts-rules', label: 'Rules', path: '/alerts/rules', icon: icon(createElement(AlertTriangle, { size: 14 })) }, { id: 'alerts-silences', label: 'Silences', path: '/alerts/silences', icon: icon(createElement(BellOff, { size: 14 })) }, { id: 'alerts-history', label: 'History', path: '/alerts/history', icon: icon(createElement(ScrollText, { size: 14 })) }, ]; }