refactor: unify "Executions" → "Exchanges" terminology
Rename all references from "Execution/executions/execCount" to "Exchange/exchanges/exchangeCount" to align with Apache Camel's native Exchange concept. Java class names (CamelExecutionException, HttpOperationFailedException) are preserved as-is. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -184,7 +184,7 @@ src/
|
|||||||
│ ├── ExchangeDetail/
|
│ ├── ExchangeDetail/
|
||||||
│ └── AgentHealth/
|
│ └── AgentHealth/
|
||||||
├── mocks/ # Static TypeScript mock data
|
├── mocks/ # Static TypeScript mock data
|
||||||
│ ├── executions.ts
|
│ ├── exchanges.ts
|
||||||
│ ├── routes.ts
|
│ ├── routes.ts
|
||||||
│ ├── agents.ts
|
│ ├── agents.ts
|
||||||
│ └── metrics.ts
|
│ └── metrics.ts
|
||||||
@@ -361,7 +361,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
|
|||||||
#### CommandPalette
|
#### CommandPalette
|
||||||
- Full-screen overlay modal triggered by Ctrl+K
|
- Full-screen overlay modal triggered by Ctrl+K
|
||||||
- Search input with scoped filter tags (removable, amber-styled)
|
- Search input with scoped filter tags (removable, amber-styled)
|
||||||
- Category tabs with counts (All, Executions, Routes, Exchanges, Agents)
|
- Category tabs with counts (All, Exchanges, Routes, Agents)
|
||||||
- Grouped results by category with section headers
|
- Grouped results by category with section headers
|
||||||
- Result items: icon + title + badges + meta + timestamp
|
- Result items: icon + title + badges + meta + timestamp
|
||||||
- Inline expandable detail (JSON preview with match highlighting)
|
- Inline expandable detail (JSON preview with match highlighting)
|
||||||
@@ -371,7 +371,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
|
|||||||
- Props: `open: boolean`, `onClose`, `onSelect: (result: SearchResult) => void`, `data: SearchResult[]`
|
- Props: `open: boolean`, `onClose`, `onSelect: (result: SearchResult) => void`, `data: SearchResult[]`
|
||||||
- `SearchResult` interface:
|
- `SearchResult` interface:
|
||||||
```ts
|
```ts
|
||||||
type SearchCategory = 'execution' | 'route' | 'exchange' | 'agent'
|
type SearchCategory = 'exchange' | 'route' | 'agent'
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
id: string
|
id: string
|
||||||
category: SearchCategory
|
category: SearchCategory
|
||||||
@@ -451,7 +451,7 @@ All CSS custom properties for both themes. Light values sourced from `mock-v2-li
|
|||||||
#### ProcessorTimeline
|
#### ProcessorTimeline
|
||||||
- Gantt-style horizontal bar timeline for exchange processor steps
|
- Gantt-style horizontal bar timeline for exchange processor steps
|
||||||
- Each bar: processor name (left label), colored bar (green=ok, amber=slow, red=fail), duration label (right)
|
- Each bar: processor name (left label), colored bar (green=ok, amber=slow, red=fail), duration label (right)
|
||||||
- Bar width proportional to duration relative to total execution time
|
- Bar width proportional to duration relative to total exchange time
|
||||||
- Clickable bars (select processor)
|
- Clickable bars (select processor)
|
||||||
- Props: `processors: { name: string; type: string; durationMs: number; status: 'ok' | 'slow' | 'fail'; startMs: number }[]`, `totalMs: number`, `onProcessorClick?`
|
- Props: `processors: { name: string; type: string; durationMs: number; status: 'ok' | 'slow' | 'fail'; startMs: number }[]`, `totalMs: number`, `onProcessorClick?`
|
||||||
|
|
||||||
@@ -518,7 +518,7 @@ Pages are built after the design system layer is complete.
|
|||||||
|
|
||||||
Static TypeScript objects in `src/mocks/` providing realistic data matching the HTML mockups:
|
Static TypeScript objects in `src/mocks/` providing realistic data matching the HTML mockups:
|
||||||
|
|
||||||
- `executions.ts` — execution rows with status, duration, order IDs, error messages
|
- `exchanges.ts` — exchange rows with status, duration, order IDs, error messages
|
||||||
- `routes.ts` — route definitions with processor lists
|
- `routes.ts` — route definitions with processor lists
|
||||||
- `agents.ts` — agent health data (name, version, status, tps, last-seen)
|
- `agents.ts` — agent health data (name, version, status, tps, last-seen)
|
||||||
- `metrics.ts` — KPI values, chart data points
|
- `metrics.ts` — KPI values, chart data points
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import type { SearchResult } from './types'
|
|||||||
const mockData: SearchResult[] = [
|
const mockData: SearchResult[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
category: 'execution',
|
category: 'exchange',
|
||||||
title: 'order-intake execution',
|
title: 'order-intake exchange',
|
||||||
meta: 'cmr-1234 · 142ms',
|
meta: 'cmr-1234 · 142ms',
|
||||||
timestamp: '2s ago',
|
timestamp: '2s ago',
|
||||||
badges: [{ label: 'Completed' }],
|
badges: [{ label: 'Completed' }],
|
||||||
@@ -63,7 +63,7 @@ describe('CommandPalette', () => {
|
|||||||
data={mockData}
|
data={mockData}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
expect(screen.getByText('order-intake execution')).toBeInTheDocument()
|
expect(screen.getByText('order-intake exchange')).toBeInTheDocument()
|
||||||
expect(screen.getByText('content-based-routing')).toBeInTheDocument()
|
expect(screen.getByText('content-based-routing')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ describe('CommandPalette', () => {
|
|||||||
await user.type(screen.getByRole('textbox', { name: 'Search' }), 'routing')
|
await user.type(screen.getByRole('textbox', { name: 'Search' }), 'routing')
|
||||||
// The title is split into fragments by mark highlight, so use a partial text match
|
// The title is split into fragments by mark highlight, so use a partial text match
|
||||||
expect(screen.getByText(/content-based/)).toBeInTheDocument()
|
expect(screen.getByText(/content-based/)).toBeInTheDocument()
|
||||||
expect(screen.queryByText('order-intake execution')).not.toBeInTheDocument()
|
expect(screen.queryByText('order-intake exchange')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls onClose when Escape is pressed', async () => {
|
it('calls onClose when Escape is pressed', async () => {
|
||||||
@@ -143,7 +143,7 @@ describe('CommandPalette', () => {
|
|||||||
)
|
)
|
||||||
await user.click(screen.getByRole('tab', { name: /Routes/i }))
|
await user.click(screen.getByRole('tab', { name: /Routes/i }))
|
||||||
expect(screen.getByText('content-based-routing')).toBeInTheDocument()
|
expect(screen.getByText('content-based-routing')).toBeInTheDocument()
|
||||||
expect(screen.queryByText('order-intake execution')).not.toBeInTheDocument()
|
expect(screen.queryByText('order-intake exchange')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows category tabs with counts', () => {
|
it('shows category tabs with counts', () => {
|
||||||
|
|||||||
@@ -16,17 +16,15 @@ interface CommandPaletteProps {
|
|||||||
|
|
||||||
const CATEGORY_LABELS: Record<SearchCategory | 'all', string> = {
|
const CATEGORY_LABELS: Record<SearchCategory | 'all', string> = {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
execution: 'Executions',
|
|
||||||
route: 'Routes',
|
|
||||||
exchange: 'Exchanges',
|
exchange: 'Exchanges',
|
||||||
|
route: 'Routes',
|
||||||
agent: 'Agents',
|
agent: 'Agents',
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_CATEGORIES: Array<SearchCategory | 'all'> = [
|
const ALL_CATEGORIES: Array<SearchCategory | 'all'> = [
|
||||||
'all',
|
'all',
|
||||||
'execution',
|
|
||||||
'route',
|
|
||||||
'exchange',
|
'exchange',
|
||||||
|
'route',
|
||||||
'agent',
|
'agent',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -203,7 +201,7 @@ export function CommandPalette({ open, onClose, onSelect, data, onOpen }: Comman
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder="Search executions, routes, exchanges, agents…"
|
placeholder="Search exchanges, routes, agents…"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setQuery(e.target.value)
|
setQuery(e.target.value)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
export type SearchCategory = 'execution' | 'route' | 'exchange' | 'agent'
|
export type SearchCategory = 'exchange' | 'route' | 'agent'
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { Tabs } from './Tabs'
|
|||||||
describe('Tabs', () => {
|
describe('Tabs', () => {
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ label: 'All', count: 14, value: 'all' },
|
{ label: 'All', count: 14, value: 'all' },
|
||||||
{ label: 'Executions', count: 8, value: 'executions' },
|
{ label: 'Exchanges', count: 8, value: 'exchanges' },
|
||||||
{ label: 'Routes', count: 3, value: 'routes' },
|
{ label: 'Routes', count: 3, value: 'routes' },
|
||||||
]
|
]
|
||||||
|
|
||||||
it('renders all tab labels', () => {
|
it('renders all tab labels', () => {
|
||||||
render(<Tabs tabs={tabs} active="all" onChange={() => {}} />)
|
render(<Tabs tabs={tabs} active="all" onChange={() => {}} />)
|
||||||
expect(screen.getByText('All')).toBeInTheDocument()
|
expect(screen.getByText('All')).toBeInTheDocument()
|
||||||
expect(screen.getByText('Executions')).toBeInTheDocument()
|
expect(screen.getByText('Exchanges')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows count badges', () => {
|
it('shows count badges', () => {
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ export interface App {
|
|||||||
name: string
|
name: string
|
||||||
agentCount: number
|
agentCount: number
|
||||||
health: 'live' | 'stale' | 'dead'
|
health: 'live' | 'stale' | 'dead'
|
||||||
execCount: number
|
exchangeCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Route {
|
export interface Route {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
execCount: number
|
exchangeCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Agent {
|
export interface Agent {
|
||||||
@@ -163,7 +163,7 @@ export function Sidebar({
|
|||||||
<div className={styles.itemName}>{app.name}</div>
|
<div className={styles.itemName}>{app.name}</div>
|
||||||
<div className={styles.itemMeta}>{app.agentCount} agent{app.agentCount !== 1 ? 's' : ''}</div>
|
<div className={styles.itemMeta}>{app.agentCount} agent{app.agentCount !== 1 ? 's' : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.itemCount}>{app.execCount.toLocaleString()}</span>
|
<span className={styles.itemCount}>{app.exchangeCount.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +195,7 @@ export function Sidebar({
|
|||||||
<div className={styles.itemInfo}>
|
<div className={styles.itemInfo}>
|
||||||
<div className={styles.itemName}>{route.name}</div>
|
<div className={styles.itemName}>{route.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.itemCount}>{route.execCount.toLocaleString()}</span>
|
<span className={styles.itemCount}>{route.exchangeCount.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface ProcessorData {
|
|||||||
startMs: number
|
startMs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Execution {
|
export interface Exchange {
|
||||||
id: string
|
id: string
|
||||||
orderId: string
|
orderId: string
|
||||||
customer: string
|
customer: string
|
||||||
@@ -22,7 +22,7 @@ export interface Execution {
|
|||||||
processors: ProcessorData[]
|
processors: ProcessorData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executions: Execution[] = [
|
export const exchanges: Exchange[] = [
|
||||||
{
|
{
|
||||||
id: 'E-2026-03-18-00201',
|
id: 'E-2026-03-18-00201',
|
||||||
orderId: 'OP-92184',
|
orderId: 'OP-92184',
|
||||||
@@ -44,7 +44,7 @@ function generateTimeSeries(
|
|||||||
// KPI stat cards data
|
// KPI stat cards data
|
||||||
export const kpiMetrics: KpiMetric[] = [
|
export const kpiMetrics: KpiMetric[] = [
|
||||||
{
|
{
|
||||||
label: 'Executions (shift)',
|
label: 'Exchanges (shift)',
|
||||||
value: '3,241',
|
value: '3,241',
|
||||||
trend: 'up',
|
trend: 'up',
|
||||||
trendValue: '+12%',
|
trendValue: '+12%',
|
||||||
@@ -147,7 +147,7 @@ export const errorCountSeries: MetricSeries[] = [
|
|||||||
export interface RouteMetricRow {
|
export interface RouteMetricRow {
|
||||||
routeId: string
|
routeId: string
|
||||||
routeName: string
|
routeName: string
|
||||||
execCount: number
|
exchangeCount: number
|
||||||
successRate: number
|
successRate: number
|
||||||
avgDurationMs: number
|
avgDurationMs: number
|
||||||
p99DurationMs: number
|
p99DurationMs: number
|
||||||
@@ -159,7 +159,7 @@ export const routeMetrics: RouteMetricRow[] = [
|
|||||||
{
|
{
|
||||||
routeId: 'order-intake',
|
routeId: 'order-intake',
|
||||||
routeName: 'order-intake',
|
routeName: 'order-intake',
|
||||||
execCount: 892,
|
exchangeCount: 892,
|
||||||
successRate: 99.2,
|
successRate: 99.2,
|
||||||
avgDurationMs: 88,
|
avgDurationMs: 88,
|
||||||
p99DurationMs: 142,
|
p99DurationMs: 142,
|
||||||
@@ -169,7 +169,7 @@ export const routeMetrics: RouteMetricRow[] = [
|
|||||||
{
|
{
|
||||||
routeId: 'order-enrichment',
|
routeId: 'order-enrichment',
|
||||||
routeName: 'order-enrichment',
|
routeName: 'order-enrichment',
|
||||||
execCount: 541,
|
exchangeCount: 541,
|
||||||
successRate: 97.6,
|
successRate: 97.6,
|
||||||
avgDurationMs: 156,
|
avgDurationMs: 156,
|
||||||
p99DurationMs: 287,
|
p99DurationMs: 287,
|
||||||
@@ -179,7 +179,7 @@ export const routeMetrics: RouteMetricRow[] = [
|
|||||||
{
|
{
|
||||||
routeId: 'payment-process',
|
routeId: 'payment-process',
|
||||||
routeName: 'payment-process',
|
routeName: 'payment-process',
|
||||||
execCount: 414,
|
exchangeCount: 414,
|
||||||
successRate: 96.1,
|
successRate: 96.1,
|
||||||
avgDurationMs: 234,
|
avgDurationMs: 234,
|
||||||
p99DurationMs: 412,
|
p99DurationMs: 412,
|
||||||
@@ -189,7 +189,7 @@ export const routeMetrics: RouteMetricRow[] = [
|
|||||||
{
|
{
|
||||||
routeId: 'shipment-dispatch',
|
routeId: 'shipment-dispatch',
|
||||||
routeName: 'shipment-dispatch',
|
routeName: 'shipment-dispatch',
|
||||||
execCount: 387,
|
exchangeCount: 387,
|
||||||
successRate: 98.4,
|
successRate: 98.4,
|
||||||
avgDurationMs: 118,
|
avgDurationMs: 118,
|
||||||
p99DurationMs: 248,
|
p99DurationMs: 248,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export interface RouteDefinition {
|
|||||||
name: string
|
name: string
|
||||||
group: string
|
group: string
|
||||||
description: string
|
description: string
|
||||||
execCount: number
|
exchangeCount: number
|
||||||
successRate: number
|
successRate: number
|
||||||
avgDurationMs: number
|
avgDurationMs: number
|
||||||
p99DurationMs: number
|
p99DurationMs: number
|
||||||
@@ -17,7 +17,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'order-intake',
|
name: 'order-intake',
|
||||||
group: 'order-flow',
|
group: 'order-flow',
|
||||||
description: 'Ingests new orders from JMS queue, validates schema, enriches with customer data',
|
description: 'Ingests new orders from JMS queue, validates schema, enriches with customer data',
|
||||||
execCount: 892,
|
exchangeCount: 892,
|
||||||
successRate: 99.2,
|
successRate: 99.2,
|
||||||
avgDurationMs: 88,
|
avgDurationMs: 88,
|
||||||
p99DurationMs: 142,
|
p99DurationMs: 142,
|
||||||
@@ -29,7 +29,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'order-enrichment',
|
name: 'order-enrichment',
|
||||||
group: 'order-flow',
|
group: 'order-flow',
|
||||||
description: 'Enriches order with inventory availability, pricing, and shipment options',
|
description: 'Enriches order with inventory availability, pricing, and shipment options',
|
||||||
execCount: 541,
|
exchangeCount: 541,
|
||||||
successRate: 97.6,
|
successRate: 97.6,
|
||||||
avgDurationMs: 156,
|
avgDurationMs: 156,
|
||||||
p99DurationMs: 287,
|
p99DurationMs: 287,
|
||||||
@@ -41,7 +41,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'payment-process',
|
name: 'payment-process',
|
||||||
group: 'payment-flow',
|
group: 'payment-flow',
|
||||||
description: 'Processes payment through gateway, handles retries and partial failures',
|
description: 'Processes payment through gateway, handles retries and partial failures',
|
||||||
execCount: 414,
|
exchangeCount: 414,
|
||||||
successRate: 96.1,
|
successRate: 96.1,
|
||||||
avgDurationMs: 234,
|
avgDurationMs: 234,
|
||||||
p99DurationMs: 412,
|
p99DurationMs: 412,
|
||||||
@@ -53,7 +53,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'payment-validate',
|
name: 'payment-validate',
|
||||||
group: 'payment-flow',
|
group: 'payment-flow',
|
||||||
description: 'Validates payment details and performs fraud check before processing',
|
description: 'Validates payment details and performs fraud check before processing',
|
||||||
execCount: 498,
|
exchangeCount: 498,
|
||||||
successRate: 99.8,
|
successRate: 99.8,
|
||||||
avgDurationMs: 142,
|
avgDurationMs: 142,
|
||||||
p99DurationMs: 198,
|
p99DurationMs: 198,
|
||||||
@@ -65,7 +65,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'shipment-dispatch',
|
name: 'shipment-dispatch',
|
||||||
group: 'shipment-flow',
|
group: 'shipment-flow',
|
||||||
description: 'Dispatches shipment to carrier after order confirmation',
|
description: 'Dispatches shipment to carrier after order confirmation',
|
||||||
execCount: 387,
|
exchangeCount: 387,
|
||||||
successRate: 98.4,
|
successRate: 98.4,
|
||||||
avgDurationMs: 118,
|
avgDurationMs: 118,
|
||||||
p99DurationMs: 248,
|
p99DurationMs: 248,
|
||||||
@@ -77,7 +77,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'shipment-track',
|
name: 'shipment-track',
|
||||||
group: 'shipment-flow',
|
group: 'shipment-flow',
|
||||||
description: 'Polls carrier APIs for shipment status updates and notifies customers',
|
description: 'Polls carrier APIs for shipment status updates and notifies customers',
|
||||||
execCount: 923,
|
exchangeCount: 923,
|
||||||
successRate: 99.5,
|
successRate: 99.5,
|
||||||
avgDurationMs: 94,
|
avgDurationMs: 94,
|
||||||
p99DurationMs: 167,
|
p99DurationMs: 167,
|
||||||
@@ -89,7 +89,7 @@ export const routes: RouteDefinition[] = [
|
|||||||
name: 'notification-dispatch',
|
name: 'notification-dispatch',
|
||||||
group: 'notification-flow',
|
group: 'notification-flow',
|
||||||
description: 'Sends email and SMS notifications to customers for order events',
|
description: 'Sends email and SMS notifications to customers for order events',
|
||||||
execCount: 471,
|
exchangeCount: 471,
|
||||||
successRate: 98.9,
|
successRate: 98.9,
|
||||||
avgDurationMs: 62,
|
avgDurationMs: 62,
|
||||||
p99DurationMs: 124,
|
p99DurationMs: 124,
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import { routes } from '../../mocks/routes'
|
|||||||
|
|
||||||
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
||||||
const APPS = [
|
const APPS = [
|
||||||
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 },
|
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
|
||||||
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 },
|
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
|
||||||
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 },
|
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
|
||||||
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 },
|
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
execCount: r.execCount,
|
exchangeCount: r.exchangeCount,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Build trend data for each agent ─────────────────────────────────────────
|
// ─── Build trend data for each agent ─────────────────────────────────────────
|
||||||
|
|||||||
@@ -24,24 +24,24 @@ import { MonoText } from '../../design-system/primitives/MonoText/MonoText'
|
|||||||
import { Badge } from '../../design-system/primitives/Badge/Badge'
|
import { Badge } from '../../design-system/primitives/Badge/Badge'
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { executions, type Execution } from '../../mocks/executions'
|
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
||||||
import { routes } from '../../mocks/routes'
|
import { routes } from '../../mocks/routes'
|
||||||
import { agents } from '../../mocks/agents'
|
import { agents } from '../../mocks/agents'
|
||||||
import { kpiMetrics } from '../../mocks/metrics'
|
import { kpiMetrics } from '../../mocks/metrics'
|
||||||
|
|
||||||
// ─── Sidebar app list (static) ───────────────────────────────────────────────
|
// ─── Sidebar app list (static) ───────────────────────────────────────────────
|
||||||
const APPS = [
|
const APPS = [
|
||||||
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 },
|
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
|
||||||
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 },
|
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
|
||||||
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 },
|
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
|
||||||
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 },
|
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
|
||||||
]
|
]
|
||||||
|
|
||||||
// ─── Sidebar routes (top 3) ───────────────────────────────────────────────────
|
// ─── Sidebar routes (top 3) ───────────────────────────────────────────────────
|
||||||
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
execCount: r.execCount,
|
exchangeCount: r.exchangeCount,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
@@ -55,7 +55,7 @@ function formatTimestamp(date: Date): string {
|
|||||||
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusToVariant(status: Execution['status']): 'success' | 'error' | 'running' | 'warning' {
|
function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'completed': return 'success'
|
case 'completed': return 'success'
|
||||||
case 'failed': return 'error'
|
case 'failed': return 'error'
|
||||||
@@ -64,7 +64,7 @@ function statusToVariant(status: Execution['status']): 'success' | 'error' | 'ru
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusLabel(status: Execution['status']): string {
|
function statusLabel(status: Exchange['status']): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'completed': return 'OK'
|
case 'completed': return 'OK'
|
||||||
case 'failed': return 'ERR'
|
case 'failed': return 'ERR'
|
||||||
@@ -74,7 +74,7 @@ function statusLabel(status: Execution['status']): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Table columns ────────────────────────────────────────────────────────────
|
// ─── Table columns ────────────────────────────────────────────────────────────
|
||||||
const COLUMNS: Column<Execution>[] = [
|
const COLUMNS: Column<Exchange>[] = [
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
@@ -142,7 +142,7 @@ const COLUMNS: Column<Execution>[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function durationClass(ms: number, status: Execution['status']): string {
|
function durationClass(ms: number, status: Exchange['status']): string {
|
||||||
if (status === 'failed') return styles.durBreach
|
if (status === 'failed') return styles.durBreach
|
||||||
if (ms < 100) return styles.durFast
|
if (ms < 100) return styles.durFast
|
||||||
if (ms < 200) return styles.durNormal
|
if (ms < 200) return styles.durNormal
|
||||||
@@ -154,10 +154,10 @@ function durationClass(ms: number, status: Execution['status']): string {
|
|||||||
function buildSearchData(): SearchResult[] {
|
function buildSearchData(): SearchResult[] {
|
||||||
const results: SearchResult[] = []
|
const results: SearchResult[] = []
|
||||||
|
|
||||||
for (const exec of executions) {
|
for (const exec of exchanges) {
|
||||||
results.push({
|
results.push({
|
||||||
id: exec.id,
|
id: exec.id,
|
||||||
category: 'execution',
|
category: 'exchange',
|
||||||
title: `${exec.orderId} — ${exec.route}`,
|
title: `${exec.orderId} — ${exec.route}`,
|
||||||
badges: [{ label: statusLabel(exec.status), color: statusToVariant(exec.status) }],
|
badges: [{ label: statusLabel(exec.status), color: statusToVariant(exec.status) }],
|
||||||
meta: `${exec.correlationId} · ${formatDuration(exec.durationMs)} · ${exec.customer}`,
|
meta: `${exec.correlationId} · ${formatDuration(exec.durationMs)} · ${exec.customer}`,
|
||||||
@@ -171,7 +171,7 @@ function buildSearchData(): SearchResult[] {
|
|||||||
category: 'route',
|
category: 'route',
|
||||||
title: route.name,
|
title: route.name,
|
||||||
badges: [{ label: route.group }],
|
badges: [{ label: route.group }],
|
||||||
meta: `${route.execCount.toLocaleString()} executions · ${route.successRate}% success`,
|
meta: `${route.exchangeCount.toLocaleString()} exchanges · ${route.successRate}% success`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,11 +192,11 @@ const SEARCH_DATA = buildSearchData()
|
|||||||
|
|
||||||
// ─── Filter options ───────────────────────────────────────────────────────────
|
// ─── Filter options ───────────────────────────────────────────────────────────
|
||||||
const STATUS_FILTERS = [
|
const STATUS_FILTERS = [
|
||||||
{ label: 'All', value: 'all', count: executions.length },
|
{ label: 'All', value: 'all', count: exchanges.length },
|
||||||
{ label: 'OK', value: 'completed', count: executions.filter((e) => e.status === 'completed').length, color: 'success' as const },
|
{ label: 'OK', value: 'completed', count: exchanges.filter((e) => e.status === 'completed').length, color: 'success' as const },
|
||||||
{ label: 'Warn', value: 'warning', count: executions.filter((e) => e.status === 'warning').length },
|
{ label: 'Warn', value: 'warning', count: exchanges.filter((e) => e.status === 'warning').length },
|
||||||
{ label: 'Error', value: 'failed', count: executions.filter((e) => e.status === 'failed').length, color: 'error' as const },
|
{ label: 'Error', value: 'failed', count: exchanges.filter((e) => e.status === 'failed').length, color: 'error' as const },
|
||||||
{ label: 'Running', value: 'running', count: executions.filter((e) => e.status === 'running').length, color: 'running' as const },
|
{ label: 'Running', value: 'running', count: exchanges.filter((e) => e.status === 'running').length, color: 'running' as const },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SHORTCUTS = [
|
const SHORTCUTS = [
|
||||||
@@ -213,12 +213,12 @@ export function Dashboard() {
|
|||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [selectedId, setSelectedId] = useState<string | undefined>()
|
const [selectedId, setSelectedId] = useState<string | undefined>()
|
||||||
const [panelOpen, setPanelOpen] = useState(false)
|
const [panelOpen, setPanelOpen] = useState(false)
|
||||||
const [selectedExecution, setSelectedExecution] = useState<Execution | null>(null)
|
const [selectedExchange, setSelectedExchange] = useState<Exchange | null>(null)
|
||||||
const [paletteOpen, setPaletteOpen] = useState(false)
|
const [paletteOpen, setPaletteOpen] = useState(false)
|
||||||
|
|
||||||
// Filter executions
|
// Filter exchanges
|
||||||
const filteredExecutions = useMemo(() => {
|
const filteredExchanges = useMemo(() => {
|
||||||
let data = executions
|
let data = exchanges
|
||||||
|
|
||||||
const statusFilter = activeFilters.find((f) =>
|
const statusFilter = activeFilters.find((f) =>
|
||||||
['completed', 'failed', 'running', 'warning', 'all'].includes(f.value),
|
['completed', 'failed', 'running', 'warning', 'all'].includes(f.value),
|
||||||
@@ -242,20 +242,20 @@ export function Dashboard() {
|
|||||||
return data
|
return data
|
||||||
}, [activeFilters, search])
|
}, [activeFilters, search])
|
||||||
|
|
||||||
function handleRowClick(row: Execution) {
|
function handleRowClick(row: Exchange) {
|
||||||
setSelectedId(row.id)
|
setSelectedId(row.id)
|
||||||
setSelectedExecution(row)
|
setSelectedExchange(row)
|
||||||
setPanelOpen(true)
|
setPanelOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRowAccent(row: Execution): 'error' | 'warning' | undefined {
|
function handleRowAccent(row: Exchange): 'error' | 'warning' | undefined {
|
||||||
if (row.status === 'failed') return 'error'
|
if (row.status === 'failed') return 'error'
|
||||||
if (row.status === 'warning') return 'warning'
|
if (row.status === 'warning') return 'warning'
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build detail panel tabs for selected execution
|
// Build detail panel tabs for selected exchange
|
||||||
const detailTabs = selectedExecution
|
const detailTabs = selectedExchange
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Overview',
|
label: 'Overview',
|
||||||
@@ -264,43 +264,43 @@ export function Dashboard() {
|
|||||||
<div className={styles.overviewTab}>
|
<div className={styles.overviewTab}>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Order ID</span>
|
<span className={styles.overviewLabel}>Order ID</span>
|
||||||
<MonoText size="sm">{selectedExecution.orderId}</MonoText>
|
<MonoText size="sm">{selectedExchange.orderId}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Route</span>
|
<span className={styles.overviewLabel}>Route</span>
|
||||||
<span>{selectedExecution.route}</span>
|
<span>{selectedExchange.route}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Status</span>
|
<span className={styles.overviewLabel}>Status</span>
|
||||||
<span className={styles.statusCell}>
|
<span className={styles.statusCell}>
|
||||||
<StatusDot variant={statusToVariant(selectedExecution.status)} />
|
<StatusDot variant={statusToVariant(selectedExchange.status)} />
|
||||||
<span>{statusLabel(selectedExecution.status)}</span>
|
<span>{statusLabel(selectedExchange.status)}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Duration</span>
|
<span className={styles.overviewLabel}>Duration</span>
|
||||||
<MonoText size="sm">{formatDuration(selectedExecution.durationMs)}</MonoText>
|
<MonoText size="sm">{formatDuration(selectedExchange.durationMs)}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Customer</span>
|
<span className={styles.overviewLabel}>Customer</span>
|
||||||
<MonoText size="sm">{selectedExecution.customer}</MonoText>
|
<MonoText size="sm">{selectedExchange.customer}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Agent</span>
|
<span className={styles.overviewLabel}>Agent</span>
|
||||||
<MonoText size="sm">{selectedExecution.agent}</MonoText>
|
<MonoText size="sm">{selectedExchange.agent}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Correlation ID</span>
|
<span className={styles.overviewLabel}>Correlation ID</span>
|
||||||
<MonoText size="xs">{selectedExecution.correlationId}</MonoText>
|
<MonoText size="xs">{selectedExchange.correlationId}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.overviewRow}>
|
<div className={styles.overviewRow}>
|
||||||
<span className={styles.overviewLabel}>Timestamp</span>
|
<span className={styles.overviewLabel}>Timestamp</span>
|
||||||
<MonoText size="xs">{selectedExecution.timestamp.toISOString()}</MonoText>
|
<MonoText size="xs">{selectedExchange.timestamp.toISOString()}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
{selectedExecution.errorMessage && (
|
{selectedExchange.errorMessage && (
|
||||||
<div className={styles.errorBlock}>
|
<div className={styles.errorBlock}>
|
||||||
<div className={styles.errorClass}>{selectedExecution.errorClass}</div>
|
<div className={styles.errorClass}>{selectedExchange.errorClass}</div>
|
||||||
<div className={styles.errorMessage}>{selectedExecution.errorMessage}</div>
|
<div className={styles.errorMessage}>{selectedExchange.errorMessage}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -312,8 +312,8 @@ export function Dashboard() {
|
|||||||
content: (
|
content: (
|
||||||
<div className={styles.processorsTab}>
|
<div className={styles.processorsTab}>
|
||||||
<ProcessorTimeline
|
<ProcessorTimeline
|
||||||
processors={selectedExecution.processors}
|
processors={selectedExchange.processors}
|
||||||
totalMs={selectedExecution.durationMs}
|
totalMs={selectedExchange.durationMs}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -332,13 +332,13 @@ export function Dashboard() {
|
|||||||
value: 'error',
|
value: 'error',
|
||||||
content: (
|
content: (
|
||||||
<div className={styles.errorTab}>
|
<div className={styles.errorTab}>
|
||||||
{selectedExecution.errorMessage ? (
|
{selectedExchange.errorMessage ? (
|
||||||
<>
|
<>
|
||||||
<div className={styles.errorClass}>{selectedExecution.errorClass}</div>
|
<div className={styles.errorClass}>{selectedExchange.errorClass}</div>
|
||||||
<pre className={styles.errorPre}>{selectedExecution.errorMessage}</pre>
|
<pre className={styles.errorPre}>{selectedExchange.errorMessage}</pre>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.emptyTabMsg}>No error for this execution.</div>
|
<div className={styles.emptyTabMsg}>No error for this exchange.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -358,11 +358,11 @@ export function Dashboard() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
detail={
|
detail={
|
||||||
selectedExecution ? (
|
selectedExchange ? (
|
||||||
<DetailPanel
|
<DetailPanel
|
||||||
open={panelOpen}
|
open={panelOpen}
|
||||||
onClose={() => setPanelOpen(false)}
|
onClose={() => setPanelOpen(false)}
|
||||||
title={`${selectedExecution.orderId} — ${selectedExecution.route}`}
|
title={`${selectedExchange.orderId} — ${selectedExchange.route}`}
|
||||||
tabs={detailTabs}
|
tabs={detailTabs}
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
@@ -410,13 +410,13 @@ export function Dashboard() {
|
|||||||
className={styles.filterBar}
|
className={styles.filterBar}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Executions table */}
|
{/* Exchanges table */}
|
||||||
<div className={styles.tableSection}>
|
<div className={styles.tableSection}>
|
||||||
<div className={styles.tableHeader}>
|
<div className={styles.tableHeader}>
|
||||||
<span className={styles.tableTitle}>Recent Executions</span>
|
<span className={styles.tableTitle}>Recent Exchanges</span>
|
||||||
<div className={styles.tableRight}>
|
<div className={styles.tableRight}>
|
||||||
<span className={styles.tableMeta}>
|
<span className={styles.tableMeta}>
|
||||||
{filteredExecutions.length.toLocaleString()} of {executions.length.toLocaleString()} executions
|
{filteredExchanges.length.toLocaleString()} of {exchanges.length.toLocaleString()} exchanges
|
||||||
</span>
|
</span>
|
||||||
<Badge label="LIVE" color="success" />
|
<Badge label="LIVE" color="success" />
|
||||||
</div>
|
</div>
|
||||||
@@ -424,7 +424,7 @@ export function Dashboard() {
|
|||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={COLUMNS}
|
columns={COLUMNS}
|
||||||
data={filteredExecutions}
|
data={filteredExchanges}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
sortable
|
sortable
|
||||||
|
|||||||
@@ -20,22 +20,22 @@ import { CodeBlock } from '../../design-system/primitives/CodeBlock/CodeBlock'
|
|||||||
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCallout'
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { executions } from '../../mocks/executions'
|
import { exchanges } from '../../mocks/exchanges'
|
||||||
import { routes } from '../../mocks/routes'
|
import { routes } from '../../mocks/routes'
|
||||||
import { agents } from '../../mocks/agents'
|
import { agents } from '../../mocks/agents'
|
||||||
|
|
||||||
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
||||||
const APPS = [
|
const APPS = [
|
||||||
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 },
|
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
|
||||||
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 },
|
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
|
||||||
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 },
|
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
|
||||||
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 },
|
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
execCount: r.execCount,
|
exchangeCount: r.exchangeCount,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
@@ -121,7 +121,7 @@ export function ExchangeDetail() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [activeItem, setActiveItem] = useState('')
|
const [activeItem, setActiveItem] = useState('')
|
||||||
|
|
||||||
const execution = useMemo(() => executions.find((e) => e.id === id), [id])
|
const exchange = useMemo(() => exchanges.find((e) => e.id === id), [id])
|
||||||
|
|
||||||
function handleItemClick(itemId: string) {
|
function handleItemClick(itemId: string) {
|
||||||
setActiveItem(itemId)
|
setActiveItem(itemId)
|
||||||
@@ -130,7 +130,7 @@ export function ExchangeDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not found state
|
// Not found state
|
||||||
if (!execution) {
|
if (!exchange) {
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
sidebar={
|
sidebar={
|
||||||
@@ -160,8 +160,8 @@ export function ExchangeDetail() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusVariant = statusToVariant(execution.status)
|
const statusVariant = statusToVariant(exchange.status)
|
||||||
const statusLabel = statusToLabel(execution.status)
|
const statusLabel = statusToLabel(exchange.status)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
@@ -179,8 +179,8 @@ export function ExchangeDetail() {
|
|||||||
<TopBar
|
<TopBar
|
||||||
breadcrumb={[
|
breadcrumb={[
|
||||||
{ label: 'Dashboard', href: '/' },
|
{ label: 'Dashboard', href: '/' },
|
||||||
{ label: execution.route, href: `/routes/${execution.route}` },
|
{ label: exchange.route, href: `/routes/${exchange.route}` },
|
||||||
{ label: execution.id },
|
{ label: exchange.id },
|
||||||
]}
|
]}
|
||||||
environment="PRODUCTION"
|
environment="PRODUCTION"
|
||||||
shift="Day (06:00-18:00)"
|
shift="Day (06:00-18:00)"
|
||||||
@@ -197,36 +197,36 @@ export function ExchangeDetail() {
|
|||||||
<StatusDot variant={statusVariant} />
|
<StatusDot variant={statusVariant} />
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.exchangeId}>
|
<div className={styles.exchangeId}>
|
||||||
<MonoText size="md">{execution.id}</MonoText>
|
<MonoText size="md">{exchange.id}</MonoText>
|
||||||
<Badge label={statusLabel} color={statusVariant} variant="filled" />
|
<Badge label={statusLabel} color={statusVariant} variant="filled" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.exchangeRoute}>
|
<div className={styles.exchangeRoute}>
|
||||||
Route: <span className={styles.routeLink} onClick={() => navigate(`/routes/${execution.route}`)}>{execution.route}</span>
|
Route: <span className={styles.routeLink} onClick={() => navigate(`/routes/${exchange.route}`)}>{exchange.route}</span>
|
||||||
<span className={styles.headerDivider}>·</span>
|
<span className={styles.headerDivider}>·</span>
|
||||||
Order: <MonoText size="xs">{execution.orderId}</MonoText>
|
Order: <MonoText size="xs">{exchange.orderId}</MonoText>
|
||||||
<span className={styles.headerDivider}>·</span>
|
<span className={styles.headerDivider}>·</span>
|
||||||
Customer: <MonoText size="xs">{execution.customer}</MonoText>
|
Customer: <MonoText size="xs">{exchange.customer}</MonoText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.headerRight}>
|
<div className={styles.headerRight}>
|
||||||
<div className={styles.headerStat}>
|
<div className={styles.headerStat}>
|
||||||
<div className={styles.headerStatLabel}>Duration</div>
|
<div className={styles.headerStatLabel}>Duration</div>
|
||||||
<div className={styles.headerStatValue}>{formatDuration(execution.durationMs)}</div>
|
<div className={styles.headerStatValue}>{formatDuration(exchange.durationMs)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.headerStat}>
|
<div className={styles.headerStat}>
|
||||||
<div className={styles.headerStatLabel}>Agent</div>
|
<div className={styles.headerStatLabel}>Agent</div>
|
||||||
<div className={styles.headerStatValue}>{execution.agent}</div>
|
<div className={styles.headerStatValue}>{exchange.agent}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.headerStat}>
|
<div className={styles.headerStat}>
|
||||||
<div className={styles.headerStatLabel}>Started</div>
|
<div className={styles.headerStatLabel}>Started</div>
|
||||||
<div className={styles.headerStatValue}>
|
<div className={styles.headerStatValue}>
|
||||||
{execution.timestamp.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
{exchange.timestamp.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.headerStat}>
|
<div className={styles.headerStat}>
|
||||||
<div className={styles.headerStatLabel}>Processors</div>
|
<div className={styles.headerStatLabel}>Processors</div>
|
||||||
<div className={styles.headerStatValue}>{execution.processors.length}</div>
|
<div className={styles.headerStatValue}>{exchange.processors.length}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,12 +236,12 @@ export function ExchangeDetail() {
|
|||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<span className={styles.sectionTitle}>Processor Timeline</span>
|
<span className={styles.sectionTitle}>Processor Timeline</span>
|
||||||
<span className={styles.sectionMeta}>Total: {formatDuration(execution.durationMs)}</span>
|
<span className={styles.sectionMeta}>Total: {formatDuration(exchange.durationMs)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.timelineWrap}>
|
<div className={styles.timelineWrap}>
|
||||||
<ProcessorTimeline
|
<ProcessorTimeline
|
||||||
processors={execution.processors}
|
processors={exchange.processors}
|
||||||
totalMs={execution.durationMs}
|
totalMs={exchange.durationMs}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,11 +250,11 @@ export function ExchangeDetail() {
|
|||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<span className={styles.sectionTitle}>Exchange Inspector</span>
|
<span className={styles.sectionTitle}>Exchange Inspector</span>
|
||||||
<span className={styles.sectionMeta}>{execution.processors.length} processor steps</span>
|
<span className={styles.sectionMeta}>{exchange.processors.length} processor steps</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inspectorSteps}>
|
<div className={styles.inspectorSteps}>
|
||||||
{execution.processors.map((proc, index) => {
|
{exchange.processors.map((proc, index) => {
|
||||||
const snapshot = generateExchangeSnapshot(proc, execution.orderId, execution.customer, index)
|
const snapshot = generateExchangeSnapshot(proc, exchange.orderId, exchange.customer, index)
|
||||||
const stepStatusClass =
|
const stepStatusClass =
|
||||||
proc.status === 'fail'
|
proc.status === 'fail'
|
||||||
? styles.stepFail
|
? styles.stepFail
|
||||||
@@ -307,18 +307,18 @@ export function ExchangeDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error block (if failed) */}
|
{/* Error block (if failed) */}
|
||||||
{execution.status === 'failed' && execution.errorMessage && (
|
{exchange.status === 'failed' && exchange.errorMessage && (
|
||||||
<div className={styles.errorSection}>
|
<div className={styles.errorSection}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<span className={styles.sectionTitle}>Error Details</span>
|
<span className={styles.sectionTitle}>Error Details</span>
|
||||||
<Badge label="FAILED" color="error" />
|
<Badge label="FAILED" color="error" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.errorBody}>
|
<div className={styles.errorBody}>
|
||||||
<div className={styles.errorClass}>{execution.errorClass}</div>
|
<div className={styles.errorClass}>{exchange.errorClass}</div>
|
||||||
<pre className={styles.errorMessage}>{execution.errorMessage}</pre>
|
<pre className={styles.errorMessage}>{exchange.errorMessage}</pre>
|
||||||
<div className={styles.errorHint}>
|
<div className={styles.errorHint}>
|
||||||
Failed at processor: <MonoText size="xs">
|
Failed at processor: <MonoText size="xs">
|
||||||
{execution.processors.find((p) => p.status === 'fail')?.name ?? 'unknown'}
|
{exchange.processors.find((p) => p.status === 'fail')?.name ?? 'unknown'}
|
||||||
</MonoText>
|
</MonoText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,16 +34,16 @@ import { agents } from '../../mocks/agents'
|
|||||||
|
|
||||||
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
||||||
const APPS = [
|
const APPS = [
|
||||||
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 },
|
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
|
||||||
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 },
|
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
|
||||||
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 },
|
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
|
||||||
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 },
|
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
execCount: r.execCount,
|
exchangeCount: r.exchangeCount,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Metrics KPI cards (5 cards per spec) ─────────────────────────────────────
|
// ─── Metrics KPI cards (5 cards per spec) ─────────────────────────────────────
|
||||||
@@ -115,11 +115,11 @@ const ROUTE_COLUMNS: Column<RouteMetricRowWithId>[] = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'execCount',
|
key: 'exchangeCount',
|
||||||
header: 'Executions',
|
header: 'Exchanges',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<MonoText size="sm">{row.execCount.toLocaleString()}</MonoText>
|
<MonoText size="sm">{row.exchangeCount.toLocaleString()}</MonoText>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Executions table */
|
/* Exchanges table */
|
||||||
.tableSection {
|
.tableSection {
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid var(--border-subtle);
|
||||||
|
|||||||
@@ -20,21 +20,21 @@ import { InfoCallout } from '../../design-system/primitives/InfoCallout/InfoCall
|
|||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
import { routes } from '../../mocks/routes'
|
import { routes } from '../../mocks/routes'
|
||||||
import { executions, type Execution } from '../../mocks/executions'
|
import { exchanges, type Exchange } from '../../mocks/exchanges'
|
||||||
import { agents } from '../../mocks/agents'
|
import { agents } from '../../mocks/agents'
|
||||||
|
|
||||||
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
// ─── Sidebar data (shared) ────────────────────────────────────────────────────
|
||||||
const APPS = [
|
const APPS = [
|
||||||
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, execCount: 1433 },
|
{ id: 'order-service', name: 'order-service', agentCount: 2, health: 'live' as const, exchangeCount: 1433 },
|
||||||
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, execCount: 912 },
|
{ id: 'payment-svc', name: 'payment-svc', agentCount: 1, health: 'live' as const, exchangeCount: 912 },
|
||||||
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, execCount: 471 },
|
{ id: 'shipment-tracker', name: 'shipment-tracker', agentCount: 2, health: 'live' as const, exchangeCount: 471 },
|
||||||
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, execCount: 128 },
|
{ id: 'notification-hub', name: 'notification-hub', agentCount: 1, health: 'stale' as const, exchangeCount: 128 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
const SIDEBAR_ROUTES = routes.slice(0, 3).map((r) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
execCount: r.execCount,
|
exchangeCount: r.exchangeCount,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
@@ -48,7 +48,7 @@ function formatTimestamp(date: Date): string {
|
|||||||
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusToVariant(status: Execution['status']): 'success' | 'error' | 'running' | 'warning' {
|
function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'completed': return 'success'
|
case 'completed': return 'success'
|
||||||
case 'failed': return 'error'
|
case 'failed': return 'error'
|
||||||
@@ -57,7 +57,7 @@ function statusToVariant(status: Execution['status']): 'success' | 'error' | 'ru
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusLabel(status: Execution['status']): string {
|
function statusLabel(status: Exchange['status']): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'completed': return 'OK'
|
case 'completed': return 'OK'
|
||||||
case 'failed': return 'ERR'
|
case 'failed': return 'ERR'
|
||||||
@@ -74,7 +74,7 @@ function routeStatusVariant(status: 'healthy' | 'degraded' | 'down'): 'success'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function durationClass(ms: number, status: Execution['status']): string {
|
function durationClass(ms: number, status: Exchange['status']): string {
|
||||||
if (status === 'failed') return styles.durBreach
|
if (status === 'failed') return styles.durBreach
|
||||||
if (ms < 100) return styles.durFast
|
if (ms < 100) return styles.durFast
|
||||||
if (ms < 200) return styles.durNormal
|
if (ms < 200) return styles.durNormal
|
||||||
@@ -82,8 +82,8 @@ function durationClass(ms: number, status: Execution['status']): string {
|
|||||||
return styles.durBreach
|
return styles.durBreach
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Columns for executions table ────────────────────────────────────────────
|
// ─── Columns for exchanges table ────────────────────────────────────────────
|
||||||
const EXEC_COLUMNS: Column<Execution>[] = [
|
const EXCHANGE_COLUMNS: Column<Exchange>[] = [
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
@@ -97,7 +97,7 @@ const EXEC_COLUMNS: Column<Execution>[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'id',
|
key: 'id',
|
||||||
header: 'Execution ID',
|
header: 'Exchange ID',
|
||||||
render: (_, row) => <MonoText size="xs">{row.id}</MonoText>,
|
render: (_, row) => <MonoText size="xs">{row.id}</MonoText>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -146,15 +146,15 @@ export function RouteDetail() {
|
|||||||
const [activeItem, setActiveItem] = useState(id ?? '')
|
const [activeItem, setActiveItem] = useState(id ?? '')
|
||||||
|
|
||||||
const route = useMemo(() => routes.find((r) => r.id === id), [id])
|
const route = useMemo(() => routes.find((r) => r.id === id), [id])
|
||||||
const routeExecutions = useMemo(
|
const routeExchanges = useMemo(
|
||||||
() => executions.filter((e) => e.route === id),
|
() => exchanges.filter((e) => e.route === id),
|
||||||
[id],
|
[id],
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error patterns grouped by exception class
|
// Error patterns grouped by exception class
|
||||||
const errorPatterns = useMemo(() => {
|
const errorPatterns = useMemo(() => {
|
||||||
const patterns: Record<string, { count: number; lastMessage: string; lastTime: Date }> = {}
|
const patterns: Record<string, { count: number; lastMessage: string; lastTime: Date }> = {}
|
||||||
for (const exec of routeExecutions) {
|
for (const exec of routeExchanges) {
|
||||||
if (exec.status === 'failed' && exec.errorClass) {
|
if (exec.status === 'failed' && exec.errorClass) {
|
||||||
if (!patterns[exec.errorClass]) {
|
if (!patterns[exec.errorClass]) {
|
||||||
patterns[exec.errorClass] = {
|
patterns[exec.errorClass] = {
|
||||||
@@ -171,26 +171,26 @@ export function RouteDetail() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Object.entries(patterns)
|
return Object.entries(patterns)
|
||||||
}, [routeExecutions])
|
}, [routeExchanges])
|
||||||
|
|
||||||
// Build aggregate processor timeline from all executions for this route
|
// Build aggregate processor timeline from all exchanges for this route
|
||||||
const aggregateProcessors = useMemo(() => {
|
const aggregateProcessors = useMemo(() => {
|
||||||
if (routeExecutions.length === 0) return []
|
if (routeExchanges.length === 0) return []
|
||||||
// Use the first execution's processors as the template, with averaged durations
|
// Use the first exchange's processors as the template, with averaged durations
|
||||||
const templateExec = routeExecutions[0]
|
const templateExec = routeExchanges[0]
|
||||||
if (!templateExec) return []
|
if (!templateExec) return []
|
||||||
return templateExec.processors.map((proc) => {
|
return templateExec.processors.map((proc) => {
|
||||||
const allDurations = routeExecutions
|
const allDurations = routeExchanges
|
||||||
.flatMap((e) => e.processors)
|
.flatMap((e) => e.processors)
|
||||||
.filter((p) => p.name === proc.name)
|
.filter((p) => p.name === proc.name)
|
||||||
.map((p) => p.durationMs)
|
.map((p) => p.durationMs)
|
||||||
const avgDuration = allDurations.length
|
const avgDuration = allDurations.length
|
||||||
? Math.round(allDurations.reduce((a, b) => a + b, 0) / allDurations.length)
|
? Math.round(allDurations.reduce((a, b) => a + b, 0) / allDurations.length)
|
||||||
: proc.durationMs
|
: proc.durationMs
|
||||||
const hasFailures = routeExecutions.some((e) =>
|
const hasFailures = routeExchanges.some((e) =>
|
||||||
e.processors.some((p) => p.name === proc.name && p.status === 'fail'),
|
e.processors.some((p) => p.name === proc.name && p.status === 'fail'),
|
||||||
)
|
)
|
||||||
const hasSlows = routeExecutions.some((e) =>
|
const hasSlows = routeExchanges.some((e) =>
|
||||||
e.processors.some((p) => p.name === proc.name && p.status === 'slow'),
|
e.processors.some((p) => p.name === proc.name && p.status === 'slow'),
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
@@ -199,15 +199,15 @@ export function RouteDetail() {
|
|||||||
status: hasFailures ? ('fail' as const) : hasSlows ? ('slow' as const) : ('ok' as const),
|
status: hasFailures ? ('fail' as const) : hasSlows ? ('slow' as const) : ('ok' as const),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [routeExecutions])
|
}, [routeExchanges])
|
||||||
|
|
||||||
const totalAggregateMs = aggregateProcessors.reduce((sum, p) => sum + p.durationMs, 0)
|
const totalAggregateMs = aggregateProcessors.reduce((sum, p) => sum + p.durationMs, 0)
|
||||||
|
|
||||||
const inflightCount = routeExecutions.filter((e) => e.status === 'running').length
|
const inflightCount = routeExchanges.filter((e) => e.status === 'running').length
|
||||||
const successCount = routeExecutions.filter((e) => e.status === 'completed').length
|
const successCount = routeExchanges.filter((e) => e.status === 'completed').length
|
||||||
const errorCount = routeExecutions.filter((e) => e.status === 'failed').length
|
const errorCount = routeExchanges.filter((e) => e.status === 'failed').length
|
||||||
const successRate = routeExecutions.length
|
const successRate = routeExchanges.length
|
||||||
? ((successCount / routeExecutions.length) * 100).toFixed(1)
|
? ((successCount / routeExchanges.length) * 100).toFixed(1)
|
||||||
: '0.0'
|
: '0.0'
|
||||||
|
|
||||||
function handleItemClick(itemId: string) {
|
function handleItemClick(itemId: string) {
|
||||||
@@ -298,8 +298,8 @@ export function RouteDetail() {
|
|||||||
{/* KPI strip */}
|
{/* KPI strip */}
|
||||||
<div className={styles.kpiStrip}>
|
<div className={styles.kpiStrip}>
|
||||||
<div className={styles.kpiCard}>
|
<div className={styles.kpiCard}>
|
||||||
<div className={styles.kpiLabel}>Total Executions</div>
|
<div className={styles.kpiLabel}>Total Exchanges</div>
|
||||||
<div className={styles.kpiValue}>{route.execCount.toLocaleString()}</div>
|
<div className={styles.kpiValue}>{route.exchangeCount.toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.kpiCard}>
|
<div className={styles.kpiCard}>
|
||||||
<div className={styles.kpiLabel}>Success Rate</div>
|
<div className={styles.kpiLabel}>Success Rate</div>
|
||||||
@@ -331,7 +331,7 @@ export function RouteDetail() {
|
|||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<span className={styles.sectionTitle}>Processor Performance (aggregate avg)</span>
|
<span className={styles.sectionTitle}>Processor Performance (aggregate avg)</span>
|
||||||
<span className={styles.sectionMeta}>Based on {routeExecutions.length} executions</span>
|
<span className={styles.sectionMeta}>Based on {routeExchanges.length} exchanges</span>
|
||||||
</div>
|
</div>
|
||||||
{aggregateProcessors.length > 0 ? (
|
{aggregateProcessors.length > 0 ? (
|
||||||
<ProcessorTimeline
|
<ProcessorTimeline
|
||||||
@@ -339,17 +339,17 @@ export function RouteDetail() {
|
|||||||
totalMs={totalAggregateMs}
|
totalMs={totalAggregateMs}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.emptyMsg}>No execution data for this route in mock set.</div>
|
<div className={styles.emptyMsg}>No exchange data for this route in mock set.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent executions table */}
|
{/* Recent exchanges table */}
|
||||||
<div className={styles.tableSection}>
|
<div className={styles.tableSection}>
|
||||||
<div className={styles.tableHeader}>
|
<div className={styles.tableHeader}>
|
||||||
<span className={styles.tableTitle}>Recent Executions</span>
|
<span className={styles.tableTitle}>Recent Exchanges</span>
|
||||||
<div className={styles.tableRight}>
|
<div className={styles.tableRight}>
|
||||||
<span className={styles.tableMeta}>
|
<span className={styles.tableMeta}>
|
||||||
{routeExecutions.length} executions · {errorCount} errors
|
{routeExchanges.length} exchanges · {errorCount} errors
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
label={`${successRate}% ok`}
|
label={`${successRate}% ok`}
|
||||||
@@ -358,8 +358,8 @@ export function RouteDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={EXEC_COLUMNS}
|
columns={EXCHANGE_COLUMNS}
|
||||||
data={routeExecutions}
|
data={routeExchanges}
|
||||||
sortable
|
sortable
|
||||||
rowAccent={(row) => {
|
rowAccent={(row) => {
|
||||||
if (row.status === 'failed') return 'error'
|
if (row.status === 'failed') return 'error'
|
||||||
|
|||||||
Reference in New Issue
Block a user