feat(ui): display business attributes on ExchangeDetail page
Show route-level attributes as Badge strips in the exchange header card, and per-processor attributes above the message IN/OUT panels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,27 @@
|
|||||||
font-style: italic;
|
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
|
TIMELINE SECTION
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
|
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
|
||||||
ProcessorTimeline, Spinner, RouteFlow, useToast,
|
ProcessorTimeline, Spinner, RouteFlow, useToast,
|
||||||
LogViewer, ButtonGroup, SectionHeader, useBreadcrumb,
|
LogViewer, ButtonGroup, SectionHeader, useBreadcrumb,
|
||||||
|
Modal, Tabs, Button, Select, Input, Textarea,
|
||||||
} from '@cameleer/design-system'
|
} from '@cameleer/design-system'
|
||||||
import type { ProcessorStep, RouteNode, NodeBadge, LogEntry, ButtonGroupItem } from '@cameleer/design-system'
|
import type { ProcessorStep, RouteNode, NodeBadge, LogEntry, ButtonGroupItem } from '@cameleer/design-system'
|
||||||
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
|
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
|
||||||
@@ -11,7 +12,8 @@ import { useCorrelationChain } from '../../api/queries/correlation'
|
|||||||
import { useDiagramLayout } from '../../api/queries/diagrams'
|
import { useDiagramLayout } from '../../api/queries/diagrams'
|
||||||
import { mapDiagramToRouteNodes, toFlowSegments } from '../../utils/diagram-mapping'
|
import { mapDiagramToRouteNodes, toFlowSegments } from '../../utils/diagram-mapping'
|
||||||
import { useTracingStore } from '../../stores/tracing-store'
|
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 { useApplicationLogs } from '../../api/queries/logs'
|
||||||
import styles from './ExchangeDetail.module.css'
|
import styles from './ExchangeDetail.module.css'
|
||||||
|
|
||||||
@@ -124,6 +126,17 @@ export default function ExchangeDetail() {
|
|||||||
return result
|
return result
|
||||||
}, [procList, tracedMap])
|
}, [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
|
// Default selected processor: first failed, or 0
|
||||||
const defaultIndex = useMemo(() => {
|
const defaultIndex = useMemo(() => {
|
||||||
if (!processors.length) return 0
|
if (!processors.length) return 0
|
||||||
@@ -359,6 +372,16 @@ export default function ExchangeDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Route-level Attributes */}
|
||||||
|
{detail.attributes && Object.keys(detail.attributes).length > 0 && (
|
||||||
|
<div className={styles.attributesStrip}>
|
||||||
|
<span className={styles.attributesLabel}>Attributes</span>
|
||||||
|
{Object.entries(detail.attributes).map(([key, value]) => (
|
||||||
|
<Badge key={key} label={`${key}: ${value}`} color="auto" variant="filled" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Correlation Chain */}
|
{/* Correlation Chain */}
|
||||||
{correlatedExchanges.length > 1 && (
|
{correlatedExchanges.length > 1 && (
|
||||||
<div className={styles.correlationChain}>
|
<div className={styles.correlationChain}>
|
||||||
@@ -522,6 +545,19 @@ export default function ExchangeDetail() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Processor Attributes */}
|
||||||
|
{selectedProc && (() => {
|
||||||
|
const procNode = flatProcNodes[activeIndex]
|
||||||
|
return procNode?.attributes && Object.keys(procNode.attributes).length > 0 ? (
|
||||||
|
<div className={styles.attributesStrip}>
|
||||||
|
<span className={styles.attributesLabel}>Processor Attributes</span>
|
||||||
|
{Object.entries(procNode.attributes).map(([key, value]) => (
|
||||||
|
<Badge key={key} label={`${key}: ${value}`} color="auto" variant="filled" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Processor Detail Panel (split IN / OUT) */}
|
{/* Processor Detail Panel (split IN / OUT) */}
|
||||||
{selectedProc && snapshot && (
|
{selectedProc && snapshot && (
|
||||||
<div className={styles.detailSplit}>
|
<div className={styles.detailSplit}>
|
||||||
|
|||||||
Reference in New Issue
Block a user