Files
cameleer-server/ui/src/pages/routes/diagram/ProcessorDetailPanel.tsx
hsiegeln 7778793e7b
All checks were successful
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m3s
CI / deploy (push) Successful in 31s
Add route diagram page with execution overlay and group-aware APIs
Backend: Add group filtering to agent list, search, stats, and timeseries
endpoints. Add diagram lookup by group+routeId. Resolve application group
to agent IDs server-side for ClickHouse IN-clause queries.

Frontend: New route detail page at /apps/{group}/routes/{routeId} with
three tabs (Diagram, Performance, Processor Tree). SVG diagram rendering
with panzoom, execution overlay (glow effects, duration/sequence badges,
flow particles, minimap), and processor detail panel. uPlot charts for
performance tab replacing old SVG sparklines. Ctrl+Click from
ExecutionExplorer navigates to route diagram with overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 21:35:42 +01:00

103 lines
3.4 KiB
TypeScript

import { useMemo } from 'react';
import type { ExecutionDetail, ProcessorNode } from '../../../api/types';
import { useProcessorSnapshot } from '../../../api/queries/executions';
import { ExchangeInspector } from './ExchangeInspector';
import styles from './diagram.module.css';
interface ProcessorDetailPanelProps {
execution: ExecutionDetail;
selectedNodeId: string | null;
}
/** Find the processor node matching a diagramNodeId, return its flat index too */
function findProcessor(
processors: ProcessorNode[],
nodeId: string,
indexRef: { idx: number },
): ProcessorNode | null {
for (const proc of processors) {
const currentIdx = indexRef.idx;
indexRef.idx++;
if (proc.diagramNodeId === nodeId) {
return { ...proc, _flatIndex: currentIdx } as ProcessorNode & { _flatIndex: number };
}
if (proc.children && proc.children.length > 0) {
const found = findProcessor(proc.children, nodeId, indexRef);
if (found) return found;
}
}
return null;
}
export function ProcessorDetailPanel({ execution, selectedNodeId }: ProcessorDetailPanelProps) {
const processor = useMemo(() => {
if (!selectedNodeId || !execution.processors) return null;
return findProcessor(execution.processors, selectedNodeId, { idx: 0 });
}, [execution, selectedNodeId]);
// Get flat index for snapshot lookup
const flatIndex = useMemo(() => {
if (!processor) return null;
return (processor as ProcessorNode & { _flatIndex?: number })._flatIndex ?? null;
}, [processor]);
const { data: snapshot } = useProcessorSnapshot(
flatIndex != null ? execution.executionId ?? null : null,
flatIndex,
);
if (!selectedNodeId || !processor) {
return (
<div className={styles.detailPanel}>
<div className={styles.detailEmpty}>
Click a node to view processor details
</div>
</div>
);
}
return (
<div className={styles.detailPanel}>
{/* Processor identity */}
<div className={styles.detailHeader}>
<div className={styles.detailType}>{processor.processorType}</div>
<div className={styles.detailId}>{processor.processorId}</div>
</div>
<div className={styles.detailMeta}>
<div className={styles.detailMetaItem}>
<span className={styles.detailMetaLabel}>Status</span>
<span className={`${styles.detailMetaValue} ${processor.status === 'FAILED' ? styles.statusFailed : styles.statusOk}`}>
{processor.status}
</span>
</div>
<div className={styles.detailMetaItem}>
<span className={styles.detailMetaLabel}>Duration</span>
<span className={styles.detailMetaValue}>{processor.durationMs}ms</span>
</div>
</div>
{/* Error info */}
{processor.errorMessage && (
<div className={styles.detailError}>
<div className={styles.detailErrorLabel}>Error</div>
<div className={styles.detailErrorMessage}>{processor.errorMessage}</div>
</div>
)}
{/* Exchange data */}
{snapshot && <ExchangeInspector snapshot={snapshot} />}
{/* Actions (future) */}
<div className={styles.detailActions}>
<button className={styles.detailActionBtn} disabled title="Coming soon">
Collect Trace Data
</button>
<button className={styles.detailActionBtn} disabled title="Coming soon">
View Logs
</button>
</div>
</div>
);
}