feat: redesign exchange detail page with interactive processor inspector
All checks were successful
Build & Publish / publish (push) Successful in 44s
All checks were successful
Build & Publish / publish (push) Successful in 44s
- Rewrite ExchangeDetail with split Message IN/OUT panels that update on processor click, error panel for failed processors, and Timeline/Flow toggle for the processor visualization - Add correlation chain in header with status-colored clickable nodes sorted by start time, labeled "Correlated Exchanges" - Add Exchange ID column and inspect button (↗) to Dashboard table - Add "Open full details" link in the exchange slide-in panel - Add selectedIndex prop to ProcessorTimeline and RouteFlow for highlighting the active processor - Add onNodeClick + selectedIndex to RouteFlow for interactive use - Add correlationGroup field to exchange mock data - Fix sidebar section toggle indentation alignment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -223,3 +223,43 @@
|
||||
font-family: var(--font-mono);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Inspect exchange icon in table */
|
||||
.inspectLink {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-faint);
|
||||
opacity: 0.75;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.15s, opacity 0.15s;
|
||||
}
|
||||
|
||||
.inspectLink:hover {
|
||||
color: var(--text-primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Open full details link in panel */
|
||||
.openDetailLink {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--amber);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
font-family: var(--font-body);
|
||||
transition: color 0.1s;
|
||||
}
|
||||
|
||||
.openDetailLink:hover {
|
||||
color: var(--amber-deep);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import styles from './Dashboard.module.css'
|
||||
|
||||
// Layout
|
||||
@@ -68,8 +68,8 @@ function statusLabel(status: Exchange['status']): string {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Table columns ────────────────────────────────────────────────────────────
|
||||
const COLUMNS: Column<Exchange>[] = [
|
||||
// ─── Table columns (base, without navigate action) ──────────────────────────
|
||||
const BASE_COLUMNS: Column<Exchange>[] = [
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
@@ -97,6 +97,14 @@ const COLUMNS: Column<Exchange>[] = [
|
||||
<span className={styles.appName}>{ROUTE_TO_APP.get(row.route) ?? row.routeGroup}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
header: 'Exchange ID',
|
||||
sortable: true,
|
||||
render: (_, row) => (
|
||||
<MonoText size="xs">{row.id}</MonoText>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'timestamp',
|
||||
header: 'Started',
|
||||
@@ -145,10 +153,34 @@ const SHORTCUTS = [
|
||||
// ─── Dashboard component ──────────────────────────────────────────────────────
|
||||
export function Dashboard() {
|
||||
const { id: appId, routeId } = useParams<{ id: string; routeId: string }>()
|
||||
const navigate = useNavigate()
|
||||
const [selectedId, setSelectedId] = useState<string | undefined>()
|
||||
const [panelOpen, setPanelOpen] = useState(false)
|
||||
const [selectedExchange, setSelectedExchange] = useState<Exchange | null>(null)
|
||||
|
||||
// Build columns with inspect action as second column
|
||||
const COLUMNS: Column<Exchange>[] = useMemo(() => {
|
||||
const inspectCol: Column<Exchange> = {
|
||||
key: 'correlationId' as keyof Exchange,
|
||||
header: '',
|
||||
width: '36px',
|
||||
render: (_, row) => (
|
||||
<button
|
||||
className={styles.inspectLink}
|
||||
title="Inspect exchange"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigate(`/exchanges/${row.id}`)
|
||||
}}
|
||||
>
|
||||
↗
|
||||
</button>
|
||||
),
|
||||
}
|
||||
const [statusCol, ...rest] = BASE_COLUMNS
|
||||
return [statusCol, inspectCol, ...rest]
|
||||
}, [navigate])
|
||||
|
||||
const { isInTimeRange, statusFilters } = useGlobalFilters()
|
||||
|
||||
// Build set of route IDs belonging to the selected app (if any)
|
||||
@@ -232,6 +264,16 @@ export function Dashboard() {
|
||||
onClose={() => setPanelOpen(false)}
|
||||
title={`${selectedExchange.orderId} — ${selectedExchange.route}`}
|
||||
>
|
||||
{/* Link to full detail page */}
|
||||
<div className={styles.panelSection}>
|
||||
<button
|
||||
className={styles.openDetailLink}
|
||||
onClick={() => navigate(`/exchanges/${selectedExchange.id}`)}
|
||||
>
|
||||
Open full details →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Overview */}
|
||||
<div className={styles.panelSection}>
|
||||
<div className={styles.panelSectionTitle}>Overview</div>
|
||||
|
||||
Reference in New Issue
Block a user