From 9c9063dc1b15e76a4f063eb2f83223014624930a Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:39:45 +0100 Subject: [PATCH] refactor: unify /apps routing with application and route filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Table columns: Status, Route, Application, Started (yyyy-mm-dd hh:mm:ss), Duration, Agent (removed Order ID and Customer) - /apps shows all exchanges, /apps/:id filters by application, /apps/:id/:routeId filters by application and route - Route paths changed from /routes/:id to /apps/:appId/:routeId across sidebar, search, breadcrumbs, metrics, and exchange detail - Added buildRouteToAppMap utility for route→application lookup Co-Authored-By: Claude Opus 4.6 (1M context) --- src/App.tsx | 14 +++--- src/design-system/layout/Sidebar/Sidebar.tsx | 4 +- src/mocks/searchData.tsx | 6 ++- src/mocks/sidebar.ts | 11 +++++ src/pages/Dashboard/Dashboard.module.css | 11 ++--- src/pages/Dashboard/Dashboard.tsx | 47 +++++++++++--------- src/pages/ExchangeDetail/ExchangeDetail.tsx | 8 ++-- src/pages/Metrics/Metrics.tsx | 6 ++- 8 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a4c9b32..a8e814b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,6 @@ import { useMemo, useCallback } from 'react' import { Routes, Route, Navigate, useNavigate } from 'react-router-dom' import { Dashboard } from './pages/Dashboard/Dashboard' import { Metrics } from './pages/Metrics/Metrics' -import { RouteDetail } from './pages/RouteDetail/RouteDetail' import { ExchangeDetail } from './pages/ExchangeDetail/ExchangeDetail' import { AgentHealth } from './pages/AgentHealth/AgentHealth' import { AgentInstance } from './pages/AgentInstance/AgentInstance' @@ -20,32 +19,31 @@ import { buildSearchData } from './mocks/searchData' import { exchanges } from './mocks/exchanges' import { routes } from './mocks/routes' import { agents } from './mocks/agents' -import { SIDEBAR_APPS } from './mocks/sidebar' +import { SIDEBAR_APPS, buildRouteToAppMap } from './mocks/sidebar' + +const routeToApp = buildRouteToAppMap() /** Compute which sidebar path to reveal for a given search result */ function computeSidebarRevealPath(result: SearchResult): string | undefined { if (!result.path) return undefined if (result.category === 'application') { - // /apps/:id — already a sidebar node path return result.path } if (result.category === 'route') { - // /routes/:id — already a sidebar node path return result.path } if (result.category === 'agent') { - // /agents/:appId/:agentId — already a sidebar node path return result.path } if (result.category === 'exchange') { - // /exchanges/:id — no sidebar entry; resolve to the parent route const exchange = exchanges.find((e) => e.id === result.id) if (exchange) { - return `/routes/${exchange.route}` + const appId = routeToApp.get(exchange.route) + if (appId) return `/apps/${appId}/${exchange.route}` } } @@ -83,8 +81,8 @@ export default function App() { } /> } /> } /> + } /> } /> - } /> } /> } /> } /> diff --git a/src/design-system/layout/Sidebar/Sidebar.tsx b/src/design-system/layout/Sidebar/Sidebar.tsx index 59547fc..1cb1848 100644 --- a/src/design-system/layout/Sidebar/Sidebar.tsx +++ b/src/design-system/layout/Sidebar/Sidebar.tsx @@ -57,7 +57,7 @@ function buildAppTreeNodes(apps: SidebarApp[]): SidebarTreeNode[] { label: route.name, icon: , badge: formatCount(route.exchangeCount), - path: `/routes/${route.id}`, + path: `/apps/${app.id}/${route.id}`, starrable: true, })), })) @@ -118,7 +118,7 @@ function collectStarredItems(apps: SidebarApp[], starredIds: Set): Starr items.push({ starKey: key, label: route.name, - path: `/routes/${route.id}`, + path: `/apps/${app.id}/${route.id}`, type: 'route', parentApp: app.name, }) diff --git a/src/mocks/searchData.tsx b/src/mocks/searchData.tsx index a0da27a..e46bb70 100644 --- a/src/mocks/searchData.tsx +++ b/src/mocks/searchData.tsx @@ -2,7 +2,7 @@ import type { SearchResult } from '../design-system/composites/CommandPalette/ty import { exchanges, type Exchange } from './exchanges' import { routes } from './routes' import { agents } from './agents' -import { SIDEBAR_APPS, type SidebarApp } from './sidebar' +import { SIDEBAR_APPS, buildRouteToAppMap, type SidebarApp } from './sidebar' function formatDuration(ms: number): string { if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` @@ -72,14 +72,16 @@ export function buildSearchData( }) } + const routeToApp = buildRouteToAppMap(apps) for (const route of rts) { + const appIdForRoute = routeToApp.get(route.id) results.push({ id: route.id, category: 'route', title: route.name, badges: [{ label: route.group }], meta: `${route.exchangeCount.toLocaleString()} exchanges · ${route.successRate}% success`, - path: `/routes/${route.id}`, + path: appIdForRoute ? `/apps/${appIdForRoute}/${route.id}` : `/apps/${route.id}`, }) } diff --git a/src/mocks/sidebar.ts b/src/mocks/sidebar.ts index 37f5ee0..0b74172 100644 --- a/src/mocks/sidebar.ts +++ b/src/mocks/sidebar.ts @@ -20,6 +20,17 @@ export interface SidebarApp { agents: SidebarAgent[] } +/** Build a routeId → appId lookup from the sidebar tree */ +export function buildRouteToAppMap(apps: SidebarApp[] = SIDEBAR_APPS): Map { + const map = new Map() + for (const app of apps) { + for (const route of app.routes) { + map.set(route.id, app.id) + } + } + return map +} + export const SIDEBAR_APPS: SidebarApp[] = [ { id: 'order-service', diff --git a/src/pages/Dashboard/Dashboard.module.css b/src/pages/Dashboard/Dashboard.module.css index a90c856..33fac93 100644 --- a/src/pages/Dashboard/Dashboard.module.css +++ b/src/pages/Dashboard/Dashboard.module.css @@ -69,14 +69,9 @@ color: var(--text-primary); } -.routeGroup { - font-size: 10px; - color: var(--text-muted); - font-family: var(--font-mono); -} - -/* Customer text */ -.customerText { +/* Application column */ +.appName { + font-size: 12px; color: var(--text-secondary); } diff --git a/src/pages/Dashboard/Dashboard.tsx b/src/pages/Dashboard/Dashboard.tsx index 482f7a5..e7c2f82 100644 --- a/src/pages/Dashboard/Dashboard.tsx +++ b/src/pages/Dashboard/Dashboard.tsx @@ -28,7 +28,10 @@ import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProv // Mock data import { exchanges, type Exchange } from '../../mocks/exchanges' import { kpiMetrics } from '../../mocks/metrics' -import { SIDEBAR_APPS } from '../../mocks/sidebar' +import { SIDEBAR_APPS, buildRouteToAppMap } from '../../mocks/sidebar' + +// Route → Application lookup +const ROUTE_TO_APP = buildRouteToAppMap() // ─── Helpers ───────────────────────────────────────────────────────────────── function formatDuration(ms: number): string { @@ -38,7 +41,13 @@ function formatDuration(ms: number): string { } function formatTimestamp(date: Date): string { - return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + const y = date.getFullYear() + const mo = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + const h = String(date.getHours()).padStart(2, '0') + const mi = String(date.getMinutes()).padStart(2, '0') + const s = String(date.getSeconds()).padStart(2, '0') + return `${y}-${mo}-${d} ${h}:${mi}:${s}` } function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' { @@ -77,25 +86,15 @@ const COLUMNS: Column[] = [ header: 'Route', sortable: true, render: (_, row) => ( -
-
{row.route}
-
{row.routeGroup}
-
+ {row.route} ), }, { - key: 'orderId', - header: 'Order ID', + key: 'routeGroup', + header: 'Application', sortable: true, render: (_, row) => ( - {row.orderId} - ), - }, - { - key: 'customer', - header: 'Customer', - render: (_, row) => ( - {row.customer} + {ROUTE_TO_APP.get(row.route) ?? row.routeGroup} ), }, { @@ -145,7 +144,7 @@ const SHORTCUTS = [ // ─── Dashboard component ────────────────────────────────────────────────────── export function Dashboard() { - const { id: appId } = useParams<{ id: string }>() + const { id: appId, routeId } = useParams<{ id: string; routeId: string }>() const [selectedId, setSelectedId] = useState() const [panelOpen, setPanelOpen] = useState(false) const [selectedExchange, setSelectedExchange] = useState(null) @@ -160,11 +159,12 @@ export function Dashboard() { return new Set(app.routes.map((r) => r.id)) }, [appId]) - // Scope all data to the selected app + // Scope all data to the selected app (and optionally route) const scopedExchanges = useMemo(() => { + if (routeId) return exchanges.filter((e) => e.route === routeId) if (!appRouteIds) return exchanges return exchanges.filter((e) => appRouteIds.has(e.route)) - }, [appRouteIds]) + }, [appRouteIds, routeId]) // Filter exchanges (scoped + global filters) const filteredExchanges = useMemo(() => { @@ -313,9 +313,12 @@ export function Dashboard() { > {/* Top bar */}
- Route: navigate(`/routes/${exchange.route}`)}>{exchange.route} + Route: navigate(`/apps/${ROUTE_TO_APP.get(exchange.route) ?? exchange.route}/${exchange.route}`)}>{exchange.route} · Order: {exchange.orderId} · diff --git a/src/pages/Metrics/Metrics.tsx b/src/pages/Metrics/Metrics.tsx index 7d47c00..f371b8c 100644 --- a/src/pages/Metrics/Metrics.tsx +++ b/src/pages/Metrics/Metrics.tsx @@ -27,7 +27,9 @@ import { routeMetrics, type RouteMetricRow, } from '../../mocks/metrics' -import { SIDEBAR_APPS } from '../../mocks/sidebar' +import { SIDEBAR_APPS, buildRouteToAppMap } from '../../mocks/sidebar' + +const ROUTE_TO_APP = buildRouteToAppMap() // ─── Metrics KPI cards (5 cards per spec) ───────────────────────────────────── const METRIC_KPIS = [ @@ -245,7 +247,7 @@ export function Metrics() { columns={ROUTE_COLUMNS} data={routeMetricsWithId} sortable - onRowClick={(row) => navigate(`/routes/${row.routeId}`)} + onRowClick={(row) => navigate(`/apps/${ROUTE_TO_APP.get(row.routeId) ?? row.routeId}/${row.routeId}`)} />