feat: replace UI with design system example pages wired to real API
Migrate all page components from the @cameleer/design-system v0.0.3 example UI, replacing mock data with real backend API hooks. This brings richer visuals (KpiStrip, GroupCard, RouteFlow, ProcessorTimeline, DateRangePicker, expandable rows) while preserving all existing API integration, auth, and routing infrastructure. Pages migrated: Dashboard, RoutesMetrics, RouteDetail, ExchangeDetail, AgentHealth, AgentInstance, OidcConfig, AuditLog, RBAC (Users/Groups/Roles). Also enhanced LayoutShell CommandPalette with real search data from catalog. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,72 @@
|
||||
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';
|
||||
import type { SidebarApp } from '@cameleer/design-system';
|
||||
|
||||
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 { username, roles, logout } = useAuthStore();
|
||||
const { data: agents } = useAgents();
|
||||
const { username, logout } = useAuthStore();
|
||||
const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
|
||||
|
||||
const sidebarApps: SidebarApp[] = useMemo(() => {
|
||||
@@ -33,6 +90,11 @@ function LayoutContent() {
|
||||
}));
|
||||
}, [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) => ({
|
||||
@@ -47,12 +109,12 @@ function LayoutContent() {
|
||||
}, [logout, navigate]);
|
||||
|
||||
const handlePaletteSelect = useCallback((result: any) => {
|
||||
if (result.path) navigate(result.path);
|
||||
if (result.path) {
|
||||
navigate(result.path, { state: result.path ? { sidebarReveal: result.path } : undefined });
|
||||
}
|
||||
setPaletteOpen(false);
|
||||
}, [navigate, setPaletteOpen]);
|
||||
|
||||
const isAdmin = roles.includes('ADMIN');
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
sidebar={
|
||||
@@ -70,7 +132,7 @@ function LayoutContent() {
|
||||
open={paletteOpen}
|
||||
onClose={() => setPaletteOpen(false)}
|
||||
onSelect={handlePaletteSelect}
|
||||
data={[]}
|
||||
data={searchData}
|
||||
/>
|
||||
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
|
||||
<Outlet />
|
||||
|
||||
Reference in New Issue
Block a user