feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
import { useState, useMemo } from 'react';
|
|
|
|
|
import { useParams } from 'react-router';
|
|
|
|
|
import {
|
|
|
|
|
StatCard, StatusDot, Badge, MonoText, Sparkline,
|
|
|
|
|
DataTable, DetailPanel, ProcessorTimeline, RouteFlow,
|
|
|
|
|
} from '@cameleer/design-system';
|
|
|
|
|
import type { Column } from '@cameleer/design-system';
|
|
|
|
|
import { useSearchExecutions, useExecutionStats, useStatsTimeseries, useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions';
|
|
|
|
|
import { useGlobalFilters } from '@cameleer/design-system';
|
|
|
|
|
import type { ExecutionSummary } from '../../api/types';
|
2026-03-19 18:16:16 +01:00
|
|
|
import styles from './Dashboard.module.css';
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
|
|
|
|
|
interface Row extends ExecutionSummary { id: string }
|
|
|
|
|
|
|
|
|
|
export default function Dashboard() {
|
|
|
|
|
const { appId, routeId } = useParams();
|
|
|
|
|
const { timeRange } = useGlobalFilters();
|
|
|
|
|
const timeFrom = timeRange.start.toISOString();
|
|
|
|
|
const timeTo = timeRange.end.toISOString();
|
|
|
|
|
|
|
|
|
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
|
|
|
const [detailTab, setDetailTab] = useState('overview');
|
|
|
|
|
const [processorIdx, setProcessorIdx] = useState<number | null>(null);
|
|
|
|
|
|
|
|
|
|
const { data: stats } = useExecutionStats(timeFrom, timeTo, routeId, appId);
|
|
|
|
|
const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, routeId, appId);
|
|
|
|
|
const { data: searchResult } = useSearchExecutions({
|
|
|
|
|
timeFrom, timeTo,
|
|
|
|
|
routeId: routeId || undefined,
|
|
|
|
|
group: appId || undefined,
|
2026-03-19 17:55:27 +01:00
|
|
|
offset: 0, limit: 50,
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
}, true);
|
|
|
|
|
const { data: detail } = useExecutionDetail(selectedId);
|
|
|
|
|
const { data: snapshot } = useProcessorSnapshot(selectedId, processorIdx);
|
|
|
|
|
|
|
|
|
|
const rows: Row[] = useMemo(() =>
|
2026-03-19 18:06:49 +01:00
|
|
|
(searchResult?.data || []).map((e: ExecutionSummary) => ({ ...e, id: e.executionId })),
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
[searchResult],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const sparklineData = useMemo(() =>
|
|
|
|
|
(timeseries?.buckets || []).map((b: any) => b.totalCount as number),
|
|
|
|
|
[timeseries],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const columns: Column<Row>[] = [
|
|
|
|
|
{
|
|
|
|
|
key: 'status', header: 'Status', width: '80px',
|
|
|
|
|
render: (v) => <StatusDot variant={v === 'COMPLETED' ? 'success' : v === 'FAILED' ? 'error' : 'running'} />,
|
|
|
|
|
},
|
|
|
|
|
{ key: 'routeId', header: 'Route', render: (v) => <MonoText size="sm">{String(v)}</MonoText> },
|
|
|
|
|
{ key: 'groupName', header: 'App', render: (v) => <Badge label={String(v)} color="auto" /> },
|
|
|
|
|
{ key: 'executionId', header: 'Exchange ID', render: (v) => <MonoText size="xs">{String(v).slice(0, 12)}</MonoText> },
|
|
|
|
|
{ key: 'startTime', header: 'Started', sortable: true, render: (v) => new Date(v as string).toLocaleTimeString() },
|
|
|
|
|
{
|
|
|
|
|
key: 'durationMs', header: 'Duration', sortable: true,
|
|
|
|
|
render: (v) => `${v}ms`,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const detailTabs = detail ? [
|
|
|
|
|
{
|
|
|
|
|
label: 'Overview', value: 'overview',
|
|
|
|
|
content: (
|
2026-03-19 18:16:16 +01:00
|
|
|
<div className={styles.panelSection}>
|
|
|
|
|
<div className={styles.panelSectionTitle}>Details</div>
|
|
|
|
|
<div className={styles.overviewGrid}>
|
|
|
|
|
<div className={styles.overviewRow}>
|
|
|
|
|
<span className={styles.overviewLabel}>Exchange ID</span>
|
|
|
|
|
<MonoText size="sm">{detail.executionId}</MonoText>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.overviewRow}>
|
|
|
|
|
<span className={styles.overviewLabel}>Status</span>
|
|
|
|
|
<Badge label={detail.status} color={detail.status === 'COMPLETED' ? 'success' : 'error'} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.overviewRow}>
|
|
|
|
|
<span className={styles.overviewLabel}>Route</span>
|
|
|
|
|
<span>{detail.routeId}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.overviewRow}>
|
|
|
|
|
<span className={styles.overviewLabel}>Duration</span>
|
|
|
|
|
<span>{detail.durationMs}ms</span>
|
|
|
|
|
</div>
|
|
|
|
|
{detail.errorMessage && (
|
|
|
|
|
<div className={styles.overviewRow}>
|
|
|
|
|
<span className={styles.overviewLabel}>Error</span>
|
|
|
|
|
<span>{detail.errorMessage}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Processors', value: 'processors',
|
|
|
|
|
content: detail.children ? (
|
|
|
|
|
<ProcessorTimeline
|
|
|
|
|
processors={flattenProcessors(detail.children)}
|
|
|
|
|
totalMs={detail.durationMs}
|
|
|
|
|
onProcessorClick={(_p, i) => setProcessorIdx(i)}
|
|
|
|
|
selectedIndex={processorIdx ?? undefined}
|
|
|
|
|
/>
|
|
|
|
|
) : <div style={{ padding: '1rem' }}>No processor data</div>,
|
|
|
|
|
},
|
|
|
|
|
] : [];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
2026-03-19 18:16:16 +01:00
|
|
|
<div className={styles.healthStrip}>
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
<StatCard label="Total Exchanges" value={stats?.totalCount ?? 0} sparkline={sparklineData} />
|
|
|
|
|
<StatCard label="Failed" value={stats?.failedCount ?? 0} accent="error" />
|
|
|
|
|
<StatCard label="Avg Duration" value={`${stats?.avgDurationMs ?? 0}ms`} />
|
2026-03-19 18:06:49 +01:00
|
|
|
<StatCard label="P99 Duration" value={`${stats?.p99LatencyMs ?? 0}ms`} accent="warning" />
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
<StatCard label="Active" value={stats?.activeCount ?? 0} accent="running" />
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-19 18:16:16 +01:00
|
|
|
<div className={styles.tableSection}>
|
|
|
|
|
<div className={styles.tableHeader}>
|
|
|
|
|
<span className={styles.tableTitle}>Recent Exchanges</span>
|
|
|
|
|
<div className={styles.tableRight}>
|
|
|
|
|
<span className={styles.tableMeta}>{rows.length} results</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<DataTable
|
|
|
|
|
columns={columns}
|
|
|
|
|
data={rows}
|
|
|
|
|
onRowClick={(row) => { setSelectedId(row.id); setProcessorIdx(null); }}
|
|
|
|
|
selectedId={selectedId ?? undefined}
|
|
|
|
|
sortable
|
|
|
|
|
pageSize={25}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
feat: migrate UI to @cameleer/design-system, add backend endpoints
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>
2026-03-19 17:38:39 +01:00
|
|
|
|
|
|
|
|
<DetailPanel
|
|
|
|
|
open={!!selectedId}
|
|
|
|
|
onClose={() => setSelectedId(null)}
|
|
|
|
|
title={selectedId ? `Exchange ${selectedId.slice(0, 12)}...` : ''}
|
|
|
|
|
tabs={detailTabs}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flattenProcessors(nodes: any[]): any[] {
|
|
|
|
|
const result: any[] = [];
|
|
|
|
|
let offset = 0;
|
|
|
|
|
function walk(node: any) {
|
|
|
|
|
result.push({
|
|
|
|
|
name: node.processorId || node.processorType,
|
|
|
|
|
type: node.processorType,
|
|
|
|
|
durationMs: node.durationMs ?? 0,
|
|
|
|
|
status: node.status === 'COMPLETED' ? 'ok' : node.status === 'FAILED' ? 'fail' : 'ok',
|
|
|
|
|
startMs: offset,
|
|
|
|
|
});
|
|
|
|
|
offset += node.durationMs ?? 0;
|
|
|
|
|
if (node.children) node.children.forEach(walk);
|
|
|
|
|
}
|
|
|
|
|
nodes.forEach(walk);
|
|
|
|
|
return result;
|
|
|
|
|
}
|