import { Outlet, useNavigate, useLocation } from 'react-router';
import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, GlobalFilterProvider, ToastProvider, useCommandPalette } from '@cameleer/design-system';
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
import { useRouteCatalog } from '../api/queries/catalog';
import { useAgents } from '../api/queries/agents';
import { useAuthStore } from '../auth/auth-store';
import { useMemo, useCallback } from 'react';
function healthToColor(health: string): string {
switch (health) {
case 'live': return 'success';
case 'stale': return 'warning';
case 'dead': return 'error';
default: return 'auto';
}
}
function buildSearchData(
catalog: any[] | undefined,
agents: any[] | undefined,
): SearchResult[] {
if (!catalog) return [];
const results: SearchResult[] = [];
for (const app of catalog) {
const liveAgents = (app.agents || []).filter((a: any) => a.status === 'live').length;
results.push({
id: app.appId,
category: 'application',
title: app.appId,
badges: [{ label: (app.health || 'unknown').toUpperCase(), color: healthToColor(app.health) }],
meta: `${(app.routes || []).length} routes · ${(app.agents || []).length} agents (${liveAgents} live) · ${(app.exchangeCount ?? 0).toLocaleString()} exchanges`,
path: `/apps/${app.appId}`,
});
for (const route of (app.routes || [])) {
results.push({
id: route.routeId,
category: 'route',
title: route.routeId,
badges: [{ label: app.appId }],
meta: `${(route.exchangeCount ?? 0).toLocaleString()} exchanges`,
path: `/apps/${app.appId}/${route.routeId}`,
});
}
}
if (agents) {
for (const agent of agents) {
results.push({
id: agent.id,
category: 'agent',
title: agent.name,
badges: [{ label: (agent.state || 'unknown').toUpperCase(), color: healthToColor((agent.state || '').toLowerCase()) }],
meta: `${agent.application} · ${agent.version || ''}${agent.agentTps != null ? ` · ${agent.agentTps.toFixed(1)} msg/s` : ''}`,
path: `/agents/${agent.application}/${agent.id}`,
});
}
}
return results;
}
function LayoutContent() {
const navigate = useNavigate();
const location = useLocation();
const { data: catalog } = useRouteCatalog();
const { data: agents } = useAgents();
const { username, logout } = useAuthStore();
const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
const sidebarApps: SidebarApp[] = useMemo(() => {
if (!catalog) return [];
return catalog.map((app: any) => ({
id: app.appId,
name: app.appId,
health: app.health as 'live' | 'stale' | 'dead',
exchangeCount: app.exchangeCount,
routes: (app.routes || []).map((r: any) => ({
id: r.routeId,
name: r.routeId,
exchangeCount: r.exchangeCount,
})),
agents: (app.agents || []).map((a: any) => ({
id: a.id,
name: a.name,
status: a.status as 'live' | 'stale' | 'dead',
tps: a.tps,
})),
}));
}, [catalog]);
const searchData = useMemo(
() => buildSearchData(catalog, agents as any[]),
[catalog, agents],
);
const breadcrumb = useMemo(() => {
const parts = location.pathname.split('/').filter(Boolean);
return parts.map((part, i) => ({
label: part,
href: '/' + parts.slice(0, i + 1).join('/'),
}));
}, [location.pathname]);
const handleLogout = useCallback(() => {
logout();
navigate('/login');
}, [logout, navigate]);
const handlePaletteSelect = useCallback((result: any) => {
if (result.path) {
navigate(result.path, { state: result.path ? { sidebarReveal: result.path } : undefined });
}
setPaletteOpen(false);
}, [navigate, setPaletteOpen]);
return (
}
>
setPaletteOpen(false)}
onSelect={handlePaletteSelect}
data={searchData}
/>
);
}
export function LayoutShell() {
return (
);
}