Files
cameleer-server/ui/src/components/sidebar-utils.ts
hsiegeln c8cdd846c0 feat: fetch server capabilities and hide infra tabs when disabled
Adds a useServerCapabilities hook that fetches /api/v1/health once per
session (staleTime: Infinity) and extracts the infrastructureEndpoints
flag. buildAdminTreeNodes now accepts an opts parameter so ClickHouse
and Database tabs are hidden when the server reports infra endpoints as
disabled. LayoutShell wires the hook result into the admin tree memo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 23:12:30 +02:00

114 lines
3.8 KiB
TypeScript

import { createElement, 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;
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: `/apps/${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:rbac', label: 'Users & Roles', path: '/admin/rbac' },
];
return nodes;
}