diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java index 1ab9cb81..eef8fa68 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java @@ -25,7 +25,9 @@ public class DetailService { exec.durationMs() != null ? exec.durationMs() : 0L, exec.correlationId(), exec.exchangeId(), exec.errorMessage(), exec.errorStacktrace(), - exec.diagramContentHash(), roots + exec.diagramContentHash(), roots, + exec.inputBody(), exec.outputBody(), + exec.inputHeaders(), exec.outputHeaders() ); }); } diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java index ba133b2a..e2790cd7 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java @@ -22,6 +22,10 @@ import java.util.List; * @param errorStackTrace error stack trace (empty string if no error) * @param diagramContentHash content hash linking to the active route diagram version * @param processors nested processor execution tree (root nodes) + * @param inputBody exchange input body at route entry (null if not captured) + * @param outputBody exchange output body at route exit (null if not captured) + * @param inputHeaders exchange input headers at route entry (null if not captured) + * @param outputHeaders exchange output headers at route exit (null if not captured) */ public record ExecutionDetail( String executionId, @@ -37,6 +41,10 @@ public record ExecutionDetail( String errorMessage, String errorStackTrace, String diagramContentHash, - List processors + List processors, + String inputBody, + String outputBody, + String inputHeaders, + String outputHeaders ) { } diff --git a/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx b/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx index bf5cfe58..9e32591e 100644 --- a/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx +++ b/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx @@ -171,7 +171,7 @@ export default function ExchangeDetail() { return ids }, [procList]) - // ProcessorId lookup: flow node index → processorId (diagram order) + // ProcessorId lookup: diagram node index → processorId const flowProcessorIds: string[] = useMemo(() => { if (!diagram?.nodes) return processorIds const flatProcs: Array<{ diagramNodeId?: string; processorId?: string }> = [] @@ -188,6 +188,15 @@ export default function ExchangeDetail() { return diagram.nodes.map(node => lookup.get(node.id ?? '') ?? node.id ?? '') }, [diagram, procList, processorIds]) + // Map flow display index → processor tree index (for snapshot API) + const flowToTreeIndex = useMemo(() => + flowIndexMap.map(diagramIdx => { + const pid = flowProcessorIds[diagramIdx] + return pid ? processorIds.indexOf(pid) : -1 + }), + [flowIndexMap, flowProcessorIds, processorIds], + ) + // ── Tracing toggle ────────────────────────────────────────────────────── const { toast } = useToast() const tracingStore = useTracingStore() @@ -395,8 +404,11 @@ export default function ExchangeDetail() { routeFlows.length > 0 ? ( setSelectedProcessorIndex(flowIndexMap[index] ?? index)} - selectedIndex={flowIndexMap.indexOf(activeIndex)} + onNodeClick={(_node, index) => { + const treeIdx = flowToTreeIndex[index] + if (treeIdx >= 0) setSelectedProcessorIndex(treeIdx) + }} + selectedIndex={flowToTreeIndex.indexOf(activeIndex)} getActions={(_node, index) => { const origIdx = flowIndexMap[index] ?? index const pid = flowProcessorIds[origIdx] @@ -415,6 +427,66 @@ export default function ExchangeDetail() { + {/* Exchange-level body (start/end of route) */} + {detail && (detail.inputBody || detail.outputBody) && ( +
+
+
+ + Exchange Input + + at route entry +
+
+ {detail.inputHeaders && ( +
+
Headers
+
+ {Object.entries(parseHeaders(detail.inputHeaders)).map(([key, value]) => ( +
+ {key} + {value} +
+ ))} +
+
+ )} +
+
Body
+ +
+
+
+
+
+ + Exchange Output + + at route exit +
+
+ {detail.outputHeaders && ( +
+
Headers
+
+ {Object.entries(parseHeaders(detail.outputHeaders)).map(([key, value]) => ( +
+ {key} + {value} +
+ ))} +
+
+ )} +
+
Body
+ +
+
+
+
+ )} + {/* Processor Detail Panel (split IN / OUT) */} {selectedProc && snapshot && (