From 4fe418cc898cbe4ebd1fed61901ac59cbcce0143 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:01:52 +0100 Subject: [PATCH] feat(ui): integrate ContentTabs, ScopeTrail, and sidebar scope interception Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/components/LayoutShell.tsx | 83 +++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index 7112f01f..10dcb64f 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -6,6 +6,9 @@ import { useAgents } from '../api/queries/agents'; import { useSearchExecutions } from '../api/queries/executions'; import { useAuthStore } from '../auth/auth-store'; import { useState, useMemo, useCallback, useEffect } from 'react'; +import { ContentTabs } from './ContentTabs'; +import { ScopeTrail } from './ScopeTrail'; +import { useScope } from '../hooks/useScope'; function healthToColor(health: string): string { switch (health) { @@ -31,7 +34,7 @@ function buildSearchData( title: app.appId, badges: [{ label: (app.health || 'unknown').toUpperCase(), color: healthToColor(app.health) }], meta: `${(app.routes || []).length} routes · ${(app.agents || []).length} agents (${liveAgents} live) · ${(app.exchangeCount ?? 0).toLocaleString()} exchanges`, - path: `/apps/${app.appId}`, + path: `/exchanges/${app.appId}`, }); for (const route of (app.routes || [])) { @@ -41,7 +44,7 @@ function buildSearchData( title: route.routeId, badges: [{ label: app.appId }], meta: `${(route.exchangeCount ?? 0).toLocaleString()} exchanges`, - path: `/apps/${app.appId}/${route.routeId}`, + path: `/exchanges/${app.appId}/${route.routeId}`, }); } } @@ -54,7 +57,7 @@ function buildSearchData( title: agent.name, badges: [{ label: (agent.state || 'unknown').toUpperCase(), color: healthToColor((agent.state || '').toLowerCase()) }], meta: `${agent.application} · ${agent.version || ''}${agent.agentTps != null ? ` · ${agent.agentTps.toFixed(1)} msg/s` : ''}`, - path: `/agents/${agent.application}/${agent.id}`, + path: `/runtime/${agent.application}/${agent.id}`, }); } } @@ -94,6 +97,7 @@ function LayoutContent() { const { data: agents } = useAgents(); const { username, logout } = useAuthStore(); const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette(); + const { scope, setTab } = useScope(); // Exchange full-text search via command palette const [paletteQuery, setPaletteQuery] = useState(''); @@ -115,12 +119,7 @@ function LayoutContent() { name: r.routeId, exchangeCount: r.exchangeCount, })), - agents: (app.agents || []).map((a: any) => ({ - id: a.id, - name: a.name, - status: a.status as 'live' | 'stale' | 'dead', - tps: a.tps, - })), + agents: [], })); }, [catalog]); @@ -136,7 +135,7 @@ function LayoutContent() { title: e.executionId, badges: [{ label: e.status, color: statusToColor(e.status) }], meta: `${e.routeId} · ${e.applicationName ?? ''} · ${formatDuration(e.durationMs)}`, - path: `/exchanges/${e.executionId}`, + path: `/exchanges/${e.applicationName ?? ''}/${e.routeId}/${e.executionId}`, serverFiltered: true, matchContext: e.highlight ?? undefined, })); @@ -154,7 +153,7 @@ function LayoutContent() { title: `${key} = "${value}"`, badges: [{ label: e.status, color: statusToColor(e.status) }], meta: `${e.executionId} · ${e.routeId} · ${e.applicationName ?? ''}`, - path: `/exchanges/${e.executionId}`, + path: `/exchanges/${e.applicationName ?? ''}/${e.routeId}/${e.executionId}`, serverFiltered: true, }); } @@ -165,14 +164,11 @@ function LayoutContent() { return [...catalogData, ...exchangeItems, ...attributeItems]; }, [catalogData, exchangeResults, debouncedQuery]); + const isAdminPage = location.pathname.startsWith('/admin'); const breadcrumb = useMemo(() => { + if (!isAdminPage) return []; const LABELS: Record = { - apps: 'Applications', - agents: 'Agents', - exchanges: 'Exchanges', - routes: 'Routes', admin: 'Admin', - 'api-docs': 'API Docs', rbac: 'Users & Roles', audit: 'Audit Log', oidc: 'OIDC', @@ -185,7 +181,7 @@ function LayoutContent() { label: LABELS[part] ?? part, ...(i < parts.length - 1 ? { href: '/' + parts.slice(0, i + 1).join('/') } : {}), })); - }, [location.pathname]); + }, [location.pathname, isAdminPage]); const handleLogout = useCallback(() => { logout(); @@ -200,19 +196,42 @@ function LayoutContent() { }, [navigate, setPaletteOpen]); const handlePaletteSubmit = useCallback((query: string) => { - // Navigate to dashboard with full-text search applied - const currentPath = location.pathname; - // Stay on the current app/route context if we're already there - const basePath = currentPath.startsWith('/apps/') ? currentPath.split('/').slice(0, 4).join('/') : '/apps'; - navigate(`${basePath}?text=${encodeURIComponent(query)}`); - }, [navigate, location.pathname]); + const baseParts = ['/exchanges']; + if (scope.appId) baseParts.push(scope.appId); + if (scope.routeId) baseParts.push(scope.routeId); + navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`); + }, [navigate, scope.appId, scope.routeId]); + + // Intercept Sidebar's internal navigation to re-route through current tab + const handleSidebarClick = useCallback((e: React.MouseEvent) => { + const anchor = (e.target as HTMLElement).closest('a[href]'); + if (!anchor) return; + const href = anchor.getAttribute('href') || ''; + + // Intercept /apps/:appId and /apps/:appId/:routeId links + const appMatch = href.match(/^\/apps\/([^/]+)(?:\/(.+))?$/); + if (appMatch) { + e.preventDefault(); + const [, sAppId, sRouteId] = appMatch; + navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`); + return; + } + + // Intercept /agents/* links — redirect to runtime tab + const agentMatch = href.match(/^\/agents\/([^/]+)(?:\/(.+))?$/); + if (agentMatch) { + e.preventDefault(); + const [, sAppId, sInstanceId] = agentMatch; + navigate(sInstanceId ? `/runtime/${sAppId}/${sInstanceId}` : `/runtime/${sAppId}`); + } + }, [navigate, scope.tab]); return ( +
+ +
} > -
+ + {!isAdminPage && ( + <> + +
+ navigate(path)} /> +
+ + )} + +