feat: show route control bar on topology diagram
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m8s
CI / docker (push) Successful in 59s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s

When no exchange is selected, the topology-only diagram now shows
the RouteControlBar above it (if the agent supports routeControl
or replay and the user has OPERATOR/ADMIN role). This fixes a gap
where suspended routes with no recent exchanges had no way to be
resumed from the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-04 13:49:28 +02:00
parent a5c07b8585
commit 7429b85964

View File

@@ -4,12 +4,15 @@ import { useGlobalFilters, useToast } from '@cameleer/design-system';
import { useExecutionDetail } from '../../api/queries/executions';
import { useDiagramByRoute } from '../../api/queries/diagrams';
import { useRouteCatalog } from '../../api/queries/catalog';
import { useAgents } from '../../api/queries/agents';
import { useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands';
import type { TapDefinition, ConfigUpdateResponse } from '../../api/queries/commands';
import { useAuthStore } from '../../auth/auth-store';
import { useTracingStore } from '../../stores/tracing-store';
import type { NodeAction, NodeConfig } from '../../components/ProcessDiagram/types';
import { TapConfigModal } from '../../components/TapConfigModal';
import { ExchangeHeader } from './ExchangeHeader';
import { RouteControlBar } from './RouteControlBar';
import { ExecutionDiagram } from '../../components/ExecutionDiagram/ExecutionDiagram';
import { ProcessDiagram } from '../../components/ProcessDiagram';
import styles from './ExchangesPage.module.css';
@@ -148,6 +151,30 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
const diagramQuery = useDiagramByRoute(appId, routeId);
const { data: catalog } = useRouteCatalog(timeFrom, timeTo);
// Route state + capabilities for topology-only control bar
const { data: agents } = useAgents(undefined, appId);
const roles = useAuthStore((s) => s.roles);
const canControl = roles.some(r => r === 'OPERATOR' || r === 'ADMIN');
const { hasRouteControl, hasReplay } = useMemo(() => {
if (!agents) return { hasRouteControl: false, hasReplay: false };
const agentList = agents as any[];
return {
hasRouteControl: agentList.some((a: any) => a.capabilities?.routeControl === true),
hasReplay: agentList.some((a: any) => a.capabilities?.replay === true),
};
}, [agents]);
const routeState = useMemo(() => {
if (!catalog) return undefined;
for (const app of catalog as any[]) {
if (app.applicationId !== appId) continue;
for (const r of app.routes || []) {
if (r.routeId === routeId) return (r.routeState ?? 'started') as 'started' | 'stopped' | 'suspended';
}
}
return undefined;
}, [catalog, appId, routeId]);
const knownRouteIds = useMemo(() => {
const ids = new Set<string>();
if (catalog) {
@@ -313,10 +340,19 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
);
}
// No exchange selected: show topology-only diagram
// No exchange selected: show topology-only diagram with route control bar
if (diagramQuery.data) {
return (
<>
{canControl && (hasRouteControl || hasReplay) && (
<RouteControlBar
application={appId}
routeId={routeId}
routeState={routeState}
hasRouteControl={hasRouteControl}
hasReplay={hasReplay}
/>
)}
<ProcessDiagram
application={appId}
routeId={routeId}