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

@@ -1,135 +0,0 @@
import { useState, useMemo, useCallback, useEffect } from 'react';
import type { ExecutionDetail, ProcessorNode } from '../api/types';
export interface IterationData {
count: number;
current: number;
}
export interface OverlayState {
isActive: boolean;
toggle: () => void;
executedNodes: Set<string>;
executedEdges: Set<string>;
durations: Map<string, number>;
sequences: Map<string, number>;
statuses: Map<string, string>;
iterationData: Map<string, IterationData>;
selectedNodeId: string | null;
selectNode: (nodeId: string | null) => void;
setIteration: (nodeId: string, iteration: number) => void;
}
/** Walk the processor tree and collect execution data keyed by diagramNodeId */
function collectProcessorData(
processors: ProcessorNode[],
executedNodes: Set<string>,
durations: Map<string, number>,
sequences: Map<string, number>,
statuses: Map<string, string>,
counter: { seq: number },
) {
for (const proc of processors) {
const nodeId = proc.diagramNodeId;
if (nodeId) {
executedNodes.add(nodeId);
durations.set(nodeId, proc.durationMs ?? 0);
sequences.set(nodeId, ++counter.seq);
if (proc.status) statuses.set(nodeId, proc.status);
}
if (proc.children && proc.children.length > 0) {
collectProcessorData(proc.children, executedNodes, durations, sequences, statuses, counter);
}
}
}
/** Determine which edges are executed (both source and target are executed) */
function computeExecutedEdges(
executedNodes: Set<string>,
edges: Array<{ sourceId?: string; targetId?: string }>,
): Set<string> {
const result = new Set<string>();
for (const edge of edges) {
if (edge.sourceId && edge.targetId
&& executedNodes.has(edge.sourceId) && executedNodes.has(edge.targetId)) {
result.add(`${edge.sourceId}->${edge.targetId}`);
}
}
return result;
}
export function useExecutionOverlay(
execution: ExecutionDetail | null | undefined,
edges: Array<{ sourceId?: string; targetId?: string }> = [],
): OverlayState {
const [isActive, setIsActive] = useState(!!execution);
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
const [iterations, setIterations] = useState<Map<string, number>>(new Map());
// Activate overlay when an execution is loaded
useEffect(() => {
if (execution) setIsActive(true);
}, [execution]);
const { executedNodes, durations, sequences, statuses, iterationData } = useMemo(() => {
const en = new Set<string>();
const dur = new Map<string, number>();
const seq = new Map<string, number>();
const st = new Map<string, string>();
const iter = new Map<string, IterationData>();
if (!execution?.processors) {
return { executedNodes: en, durations: dur, sequences: seq, statuses: st, iterationData: iter };
}
collectProcessorData(execution.processors, en, dur, seq, st, { seq: 0 });
return { executedNodes: en, durations: dur, sequences: seq, statuses: st, iterationData: iter };
}, [execution]);
const executedEdges = useMemo(
() => computeExecutedEdges(executedNodes, edges),
[executedNodes, edges],
);
const toggle = useCallback(() => setIsActive((v) => !v), []);
const selectNode = useCallback((nodeId: string | null) => setSelectedNodeId(nodeId), []);
const setIteration = useCallback((nodeId: string, iteration: number) => {
setIterations((prev) => {
const next = new Map(prev);
next.set(nodeId, iteration);
return next;
});
}, []);
// Keyboard shortcut: E to toggle overlay
useEffect(() => {
function handleKey(e: KeyboardEvent) {
if (e.key === 'e' || e.key === 'E') {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') return;
e.preventDefault();
setIsActive((v) => !v);
}
}
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, []);
return {
isActive,
toggle,
executedNodes,
executedEdges,
durations,
sequences,
statuses,
iterationData: new Map([...iterationData].map(([k, v]) => {
const current = iterations.get(k) ?? v.current;
return [k, { ...v, current }];
})),
selectedNodeId,
selectNode,
setIteration,
};
}