diff --git a/ui/src/pages/Exchanges/ExchangeHeader.module.css b/ui/src/pages/Exchanges/ExchangeHeader.module.css index 28d2bb13..ce2d3c0a 100644 --- a/ui/src/pages/Exchanges/ExchangeHeader.module.css +++ b/ui/src/pages/Exchanges/ExchangeHeader.module.css @@ -1,61 +1,95 @@ +/* ── Header bar above diagram ─────────────────────────────────────────────── */ + .header { display: flex; flex-direction: column; - gap: 0.5rem; - padding: 0.75rem; + gap: 0; border-bottom: 1px solid var(--border); - background: var(--surface); - font-size: 0.8125rem; + background: var(--bg-raised, var(--surface)); + flex-shrink: 0; } -.summary { +/* ── Exchange info row ────────────────────────────────────────────────────── */ + +.info { display: flex; align-items: center; gap: 0.5rem; - flex-wrap: wrap; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + border-bottom: 1px solid var(--border-subtle); +} + +.separator { + width: 1px; + height: 14px; + background: var(--border); + flex-shrink: 0; +} + +.route { + font-weight: 600; + color: var(--text-primary); +} + +.app { + color: var(--text-muted); } .duration { margin-left: auto; font-family: var(--font-mono); - font-size: 0.75rem; + font-size: 0.6875rem; + font-weight: 600; + color: var(--text-secondary); } -/* ── Correlation chain ────────────────────────────────────────────────────── */ +/* ── Correlation chain row ────────────────────────────────────────────────── */ .chain { display: flex; - flex-direction: row; align-items: center; - gap: 8px; - padding-top: 8px; - margin-top: 4px; - border-top: 1px solid var(--border-subtle); - flex-wrap: wrap; + gap: 0; + padding: 0.375rem 0.75rem; + overflow-x: auto; } .chainLabel { - font-size: 10px; - font-weight: 600; + font-size: 9px; + font-weight: 700; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.8px; color: var(--text-muted); - margin-right: 4px; + margin-right: 0.5rem; + flex-shrink: 0; +} + +.chainEntry { + display: inline-flex; + align-items: center; +} + +.chainArrow { + color: var(--text-faint, var(--text-muted)); + font-size: 10px; + margin: 0 4px; + flex-shrink: 0; } .chainNode { display: inline-flex; align-items: center; gap: 4px; - padding: 4px 10px; + padding: 3px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); - font-size: 11px; + font-size: 10px; font-family: var(--font-mono); cursor: pointer; background: var(--bg-surface); color: var(--text-secondary); transition: all 0.12s; + white-space: nowrap; } .chainNode:hover { @@ -68,6 +102,7 @@ border-color: var(--amber-light); color: var(--amber-deep); font-weight: 600; + cursor: default; } .chainNodeSuccess { @@ -86,8 +121,11 @@ border-left: 3px solid var(--warning); } -.chainMore { - color: var(--text-muted); - font-size: 11px; - font-style: italic; +.chainRoute { + font-weight: 500; +} + +.chainDuration { + color: var(--text-muted); + font-size: 9px; } diff --git a/ui/src/pages/Exchanges/ExchangeHeader.tsx b/ui/src/pages/Exchanges/ExchangeHeader.tsx index 8ba9c887..c1665189 100644 --- a/ui/src/pages/Exchanges/ExchangeHeader.tsx +++ b/ui/src/pages/Exchanges/ExchangeHeader.tsx @@ -1,3 +1,4 @@ +import { useNavigate } from 'react-router'; import { StatusDot, MonoText, Badge } from '@cameleer/design-system'; import { useCorrelationChain } from '../../api/queries/correlation'; import type { ExecutionDetail } from '../../components/ExecutionDiagram/types'; @@ -5,7 +6,6 @@ import styles from './ExchangeHeader.module.css'; interface ExchangeHeaderProps { detail: ExecutionDetail; - onExchangeClick?: (executionId: string) => void; } type StatusVariant = 'success' | 'error' | 'running' | 'warning'; @@ -19,25 +19,45 @@ function statusVariant(s: string): StatusVariant { } } +function statusLabel(s: string): string { + switch (s) { + case 'COMPLETED': return 'OK'; + case 'FAILED': return 'ERR'; + case 'RUNNING': return 'RUN'; + default: return s; + } +} + function formatDuration(ms: number): string { if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s`; if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`; return `${ms}ms`; } -export function ExchangeHeader({ detail, onExchangeClick }: ExchangeHeaderProps) { +export function ExchangeHeader({ detail }: ExchangeHeaderProps) { + const navigate = useNavigate(); const { data: chainResult } = useCorrelationChain(detail.correlationId ?? null); const chain = chainResult?.data; const showChain = chain && chain.length > 1; - if (!showChain) return null; - return (
+ {/* Exchange info — always shown */} +
+ + + {(detail.exchangeId || detail.executionId).slice(0, 16)} + + {detail.routeId} + {detail.applicationName} + {formatDuration(detail.durationMs)} +
+ + {/* Correlation chain — only when multiple correlated exchanges exist */} {showChain && (
- Correlated Exchanges - {chain.map((ce: any) => { + Correlated + {chain.map((ce: any, i: number) => { const isCurrent = ce.executionId === detail.executionId; const variant = statusVariant(ce.status); const statusCls = @@ -46,15 +66,22 @@ export function ExchangeHeader({ detail, onExchangeClick }: ExchangeHeaderProps) : variant === 'running' ? styles.chainNodeRunning : styles.chainNodeWarning; return ( - + + {i > 0 && } + + ); })}