refactor: consolidate breadcrumbs to single TopBar instance
Remove duplicate in-page breadcrumbs (ExchangeDetail, AgentHealth scope trail) and improve the global TopBar breadcrumb with semantic labels and a context-based override for pages with richer navigation data. - Add BreadcrumbProvider from design system v0.1.12 - LayoutShell: label map prettifies URL segments (apps→Applications, etc.) - ExchangeDetail: uses useBreadcrumb() to set semantic trail via context - AgentHealth: remove scope trail, keep live-count badge standalone Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "ui",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@cameleer/design-system": "^0.1.11",
|
||||
"@cameleer/design-system": "^0.1.12",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"react": "^19.2.4",
|
||||
@@ -276,9 +276,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cameleer/design-system": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.11/design-system-0.1.11.tgz",
|
||||
"integrity": "sha512-u32cvvxOSwdDkL3WCiHjMZmdT+KxEcVWEYsg0zpm7CZmbwE98mlW1jZmIw7LRHLfdhb5jH6METRrsGsN6ke44g==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.12/design-system-0.1.12.tgz",
|
||||
"integrity": "sha512-7qXwa5UMzkN7OHCP+gVJYE53eeo/F2PYzt2XcmnsQpsoYcqlNdxmaf9Btl7wgkl1g5MbKGUxUlnlqAUkUbusAw==",
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cameleer/design-system": "^0.1.11",
|
||||
"@cameleer/design-system": "^0.1.12",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"react": "^19.2.4",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router';
|
||||
import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, GlobalFilterProvider, ToastProvider, useCommandPalette } from '@cameleer/design-system';
|
||||
import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, GlobalFilterProvider, ToastProvider, BreadcrumbProvider, useCommandPalette } from '@cameleer/design-system';
|
||||
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
|
||||
import { useRouteCatalog } from '../api/queries/catalog';
|
||||
import { useAgents } from '../api/queries/agents';
|
||||
@@ -143,10 +143,23 @@ function LayoutContent() {
|
||||
}, [catalogData, exchangeResults]);
|
||||
|
||||
const breadcrumb = useMemo(() => {
|
||||
const LABELS: Record<string, string> = {
|
||||
apps: 'Applications',
|
||||
agents: 'Agents',
|
||||
exchanges: 'Exchanges',
|
||||
routes: 'Routes',
|
||||
admin: 'Admin',
|
||||
'api-docs': 'API Docs',
|
||||
rbac: 'Users & Roles',
|
||||
audit: 'Audit Log',
|
||||
oidc: 'OIDC',
|
||||
database: 'Database',
|
||||
opensearch: 'OpenSearch',
|
||||
};
|
||||
const parts = location.pathname.split('/').filter(Boolean);
|
||||
return parts.map((part, i) => ({
|
||||
label: part,
|
||||
href: '/' + parts.slice(0, i + 1).join('/'),
|
||||
label: LABELS[part] ?? part,
|
||||
...(i < parts.length - 1 ? { href: '/' + parts.slice(0, i + 1).join('/') } : {}),
|
||||
}));
|
||||
}, [location.pathname]);
|
||||
|
||||
@@ -195,7 +208,9 @@ export function LayoutShell() {
|
||||
<ToastProvider>
|
||||
<CommandPaletteProvider>
|
||||
<GlobalFilterProvider>
|
||||
<LayoutContent />
|
||||
<BreadcrumbProvider>
|
||||
<LayoutContent />
|
||||
</BreadcrumbProvider>
|
||||
</GlobalFilterProvider>
|
||||
</CommandPaletteProvider>
|
||||
</ToastProvider>
|
||||
|
||||
@@ -31,35 +31,6 @@
|
||||
.routesWarning { color: var(--warning); }
|
||||
.routesError { color: var(--error); }
|
||||
|
||||
/* Scope breadcrumb trail */
|
||||
.scopeTrail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.scopeLink {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.scopeLink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.scopeSep {
|
||||
color: var(--text-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.scopeCurrent {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Section header */
|
||||
.sectionTitle {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useParams, Link } from 'react-router';
|
||||
import { useParams } from 'react-router';
|
||||
import {
|
||||
StatCard, StatusDot, Badge, MonoText, ProgressBar,
|
||||
GroupCard, DataTable, LineChart, EventFeed, DetailPanel,
|
||||
@@ -463,15 +463,7 @@ export default function AgentHealth() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Scope trail + badges */}
|
||||
<div className={styles.scopeTrail}>
|
||||
{appId && (
|
||||
<>
|
||||
<Link to="/agents" className={styles.scopeLink}>All Agents</Link>
|
||||
<span className={styles.scopeSep}>▸</span>
|
||||
<span className={styles.scopeCurrent}>{appId}</span>
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Badge
|
||||
label={`${liveCount}/${totalInstances} live`}
|
||||
color={deadCount > 0 ? 'error' : staleCount > 0 ? 'warning' : 'success'}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useState, useMemo, useCallback, useEffect } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router'
|
||||
import {
|
||||
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
|
||||
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow, useToast,
|
||||
LogViewer, ButtonGroup, SectionHeader,
|
||||
ProcessorTimeline, Spinner, RouteFlow, useToast,
|
||||
LogViewer, ButtonGroup, SectionHeader, useBreadcrumb,
|
||||
} from '@cameleer/design-system'
|
||||
import type { ProcessorStep, RouteNode, NodeBadge, LogEntry, ButtonGroupItem } from '@cameleer/design-system'
|
||||
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
|
||||
@@ -256,6 +256,15 @@ export default function ExchangeDetail() {
|
||||
return correlationData.data
|
||||
}, [correlationData])
|
||||
|
||||
// Set semantic breadcrumb in TopBar when detail is loaded
|
||||
const breadcrumbItems = useMemo(() => detail ? [
|
||||
{ label: 'Applications', href: '/apps' },
|
||||
{ label: detail.applicationName || 'App', href: `/apps/${detail.applicationName}` },
|
||||
{ label: detail.routeId, href: `/apps/${detail.applicationName}/${detail.routeId}` },
|
||||
{ label: detail.executionId?.slice(0, 12) || '' },
|
||||
] : null, [detail?.applicationName, detail?.routeId, detail?.executionId])
|
||||
useBreadcrumb(breadcrumbItems)
|
||||
|
||||
// Exchange logs from OpenSearch (filtered by exchangeId via MDC)
|
||||
const { data: rawLogs } = useApplicationLogs(
|
||||
detail?.applicationName,
|
||||
@@ -288,11 +297,6 @@ export default function ExchangeDetail() {
|
||||
if (!detail) {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<Breadcrumb items={[
|
||||
{ label: 'Applications', href: '/apps' },
|
||||
{ label: 'Exchanges' },
|
||||
{ label: id ?? 'Unknown' },
|
||||
]} />
|
||||
<InfoCallout variant="warning">Exchange "{id}" not found.</InfoCallout>
|
||||
</div>
|
||||
)
|
||||
@@ -304,14 +308,6 @@ export default function ExchangeDetail() {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<Breadcrumb items={[
|
||||
{ label: 'Applications', href: '/apps' },
|
||||
{ label: detail.applicationName || 'App', href: `/apps/${detail.applicationName}` },
|
||||
{ label: detail.routeId, href: `/apps/${detail.applicationName}/${detail.routeId}` },
|
||||
{ label: detail.executionId?.slice(0, 12) || '' },
|
||||
]} />
|
||||
|
||||
{/* Exchange header card */}
|
||||
<div className={styles.exchangeHeader}>
|
||||
<div className={styles.headerRow}>
|
||||
|
||||
Reference in New Issue
Block a user