diff --git a/ui/src/pages/ExchangeDetail/ExchangeDetail.module.css b/ui/src/pages/ExchangeDetail/ExchangeDetail.module.css index ee9df5ea..ff553ecb 100644 --- a/ui/src/pages/ExchangeDetail/ExchangeDetail.module.css +++ b/ui/src/pages/ExchangeDetail/ExchangeDetail.module.css @@ -168,6 +168,27 @@ font-style: italic; } +/* ========================================================================== + ATTRIBUTES STRIP + ========================================================================== */ +.attributesStrip { + display: flex; + gap: 8px; + flex-wrap: wrap; + align-items: center; + padding: 10px 14px; + background: var(--bg-surface); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + margin-bottom: 16px; +} + +.attributesLabel { + font-size: 11px; + color: var(--text-muted); + margin-right: 4px; +} + /* ========================================================================== TIMELINE SECTION ========================================================================== */ diff --git a/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx b/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx index 5af273c6..be671c50 100644 --- a/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx +++ b/ui/src/pages/ExchangeDetail/ExchangeDetail.tsx @@ -4,6 +4,7 @@ import { Badge, StatusDot, MonoText, CodeBlock, InfoCallout, ProcessorTimeline, Spinner, RouteFlow, useToast, LogViewer, ButtonGroup, SectionHeader, useBreadcrumb, + Modal, Tabs, Button, Select, Input, Textarea, } from '@cameleer/design-system' import type { ProcessorStep, RouteNode, NodeBadge, LogEntry, ButtonGroupItem } from '@cameleer/design-system' import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions' @@ -11,7 +12,8 @@ import { useCorrelationChain } from '../../api/queries/correlation' import { useDiagramLayout } from '../../api/queries/diagrams' import { mapDiagramToRouteNodes, toFlowSegments } from '../../utils/diagram-mapping' import { useTracingStore } from '../../stores/tracing-store' -import { useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands' +import { useApplicationConfig, useUpdateApplicationConfig, useReplayExchange } from '../../api/queries/commands' +import { useAgents } from '../../api/queries/agents' import { useApplicationLogs } from '../../api/queries/logs' import styles from './ExchangeDetail.module.css' @@ -124,6 +126,17 @@ export default function ExchangeDetail() { return result }, [procList, tracedMap]) + // Flatten processor tree into raw node objects (for attribute access) + const flatProcNodes = useMemo(() => { + const nodes: any[] = [] + function walk(node: any) { + nodes.push(node) + if (node.children) node.children.forEach(walk) + } + procList.forEach(walk) + return nodes + }, [procList]) + // Default selected processor: first failed, or 0 const defaultIndex = useMemo(() => { if (!processors.length) return 0 @@ -359,6 +372,16 @@ export default function ExchangeDetail() { + {/* Route-level Attributes */} + {detail.attributes && Object.keys(detail.attributes).length > 0 && ( +