feat: migrate UI to @cameleer/design-system, add backend endpoints
Some checks failed
CI / build (push) Failing after 47s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped

Backend:
- Add agent_events table (V5) and lifecycle event recording
- Add route catalog endpoint (GET /routes/catalog)
- Add route metrics endpoint (GET /routes/metrics)
- Add agent events endpoint (GET /agents/events-log)
- Enrich AgentInstanceResponse with tps, errorRate, activeRoutes, uptimeSeconds
- Add TimescaleDB retention/compression policies (V6)

Frontend:
- Replace custom Mission Control UI with @cameleer/design-system components
- Rebuild all pages: Dashboard, ExchangeDetail, RoutesMetrics, AgentHealth,
  AgentInstance, RBAC, AuditLog, OIDC, DatabaseAdmin, OpenSearchAdmin, Swagger
- New LayoutShell with design system AppShell, Sidebar, TopBar, CommandPalette
- Consume design system from Gitea npm registry (@cameleer/design-system@0.0.1)
- Add .npmrc for scoped registry, update Dockerfile with REGISTRY_TOKEN arg

CI:
- Pass REGISTRY_TOKEN build-arg to UI Docker build step

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-19 17:38:39 +01:00
parent 82124c3145
commit 2b111c603c
150 changed files with 2750 additions and 21779 deletions

View File

@@ -0,0 +1,84 @@
import { Outlet, useNavigate, useLocation } from 'react-router';
import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, GlobalFilterProvider, useCommandPalette } from '@cameleer/design-system';
import { useRouteCatalog } from '../api/queries/catalog';
import { useAuthStore } from '../auth/auth-store';
import { useMemo, useCallback } from 'react';
import type { SidebarApp } from '@cameleer/design-system';
function LayoutContent() {
const navigate = useNavigate();
const location = useLocation();
const { data: catalog } = useRouteCatalog();
const { username, roles } = 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 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 handlePaletteSelect = useCallback((result: any) => {
if (result.path) navigate(result.path);
setPaletteOpen(false);
}, [navigate, setPaletteOpen]);
const isAdmin = roles.includes('ADMIN');
return (
<AppShell
sidebar={
<Sidebar
apps={sidebarApps}
/>
}
>
<TopBar
breadcrumb={breadcrumb}
user={username ? { name: username } : undefined}
/>
<CommandPalette
open={paletteOpen}
onClose={() => setPaletteOpen(false)}
onSelect={handlePaletteSelect}
data={[]}
/>
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
<Outlet />
</main>
</AppShell>
);
}
export function LayoutShell() {
return (
<CommandPaletteProvider>
<GlobalFilterProvider>
<LayoutContent />
</GlobalFilterProvider>
</CommandPaletteProvider>
);
}