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>
103 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|