diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java index 9934d2dc..b3bec6ec 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java @@ -53,7 +53,7 @@ public class ElkDiagramRenderer implements DiagramRenderer { } private static final int PADDING = 20; - private static final int NODE_HEIGHT = 50; + private static final int NODE_HEIGHT = 56; private static final int NODE_WIDTH = 220; private static final int COMPOUND_TOP_PADDING = 30; private static final int COMPOUND_SIDE_PADDING = 10; diff --git a/ui/src/components/ExecutionDiagram/types.ts b/ui/src/components/ExecutionDiagram/types.ts index 7cb52f74..47db9b42 100644 --- a/ui/src/components/ExecutionDiagram/types.ts +++ b/ui/src/components/ExecutionDiagram/types.ts @@ -10,6 +10,8 @@ export interface NodeExecutionState { subRouteFailed?: boolean; /** True if trace data is available for this processor */ hasTraceData?: boolean; + /** Runtime-resolved endpoint URI (for TO_DYNAMIC, etc.) */ + resolvedEndpointUri?: string; } export interface IterationInfo { diff --git a/ui/src/components/ExecutionDiagram/useExecutionOverlay.ts b/ui/src/components/ExecutionDiagram/useExecutionOverlay.ts index 65dbfa2e..5fa029d8 100644 --- a/ui/src/components/ExecutionDiagram/useExecutionOverlay.ts +++ b/ui/src/components/ExecutionDiagram/useExecutionOverlay.ts @@ -60,6 +60,7 @@ function buildOverlay( durationMs: proc.durationMs ?? 0, subRouteFailed: subRouteFailed || undefined, hasTraceData: !!proc.hasTraceData, + resolvedEndpointUri: proc.resolvedEndpointUri || undefined, }); // Recurse into children diff --git a/ui/src/components/ProcessDiagram/DiagramNode.tsx b/ui/src/components/ProcessDiagram/DiagramNode.tsx index 907c0c39..3600cbe2 100644 --- a/ui/src/components/ProcessDiagram/DiagramNode.tsx +++ b/ui/src/components/ProcessDiagram/DiagramNode.tsx @@ -42,6 +42,7 @@ export function DiagramNode({ // Extract label parts: type name and detail const typeName = node.type?.replace(/^EIP_/, '').replace(/_/g, ' ') ?? ''; const detail = node.label || ''; + const resolvedUri = executionState?.resolvedEndpointUri; // Overlay state derivation const isCompleted = executionState?.status === 'COMPLETED'; @@ -125,15 +126,33 @@ export function DiagramNode({ )} - {/* Type name + detail (clipped to available width) */} + {/* Type name + detail + resolved URI (clipped to available width) */} - - {typeName} - - {detail && detail !== typeName && ( - - {detail} - + {resolvedUri ? ( + <> + + {typeName} + + {detail && detail !== typeName && ( + + {detail} + + )} + + → {resolvedUri.split('?')[0]} + + + ) : ( + <> + + {typeName} + + {detail && detail !== typeName && ( + + {detail} + + )} + )} diff --git a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx index d612ef45..cf1540d1 100644 --- a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx +++ b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx @@ -16,7 +16,7 @@ import styles from './ProcessDiagram.module.css'; const PADDING = 40; /** Types that support drill-down — double-click navigates to the target route */ -const DRILLDOWN_TYPES = new Set(['DIRECT', 'SEDA']); +const DRILLDOWN_TYPES = new Set(['DIRECT', 'SEDA', 'TO', 'TO_DYNAMIC']); export function ProcessDiagram({ application, @@ -168,17 +168,26 @@ export function ProcessDiagram({ const node = findNodeById(sections, nodeId); if (!node || !DRILLDOWN_TYPES.has(node.type ?? '')) return; - // Resolve via endpointUri → endpointRouteMap (exact match, no heuristics) - const uri = node.endpointUri; - if (!uri) return; - const stripped = uri.split('?')[0]; - const resolved = endpointRouteMap?.get(stripped); + // Try static endpointUri first, then runtime-resolved URI from execution overlay + const staticUri = node.endpointUri; + const runtimeUri = executionOverlay?.get(nodeId)?.resolvedEndpointUri; + + function normalize(uri: string): string { + return uri.split('?')[0]; + } + + function tryResolve(uri: string | undefined): string | undefined { + if (!uri) return undefined; + return endpointRouteMap?.get(normalize(uri)); + } + + const resolved = tryResolve(staticUri) ?? tryResolve(runtimeUri); if (resolved) { onNodeSelect?.(''); setRouteStack(prev => [...prev, resolved]); } }, - [sections, onNodeSelect, endpointRouteMap], + [sections, onNodeSelect, endpointRouteMap, executionOverlay], ); const handleBreadcrumbClick = useCallback( @@ -201,7 +210,6 @@ export function ProcessDiagram({ (e: React.KeyboardEvent) => { if (e.key === 'Escape') { if (routeStack.length > 1) { - // Go back one level setRouteStack(prev => prev.slice(0, -1)); } else { onNodeSelect?.('');