diff --git a/ui/src/pages/Routes/RouteDetail.module.css b/ui/src/pages/Routes/RouteDetail.module.css
index 943834de..692ce6f8 100644
--- a/ui/src/pages/Routes/RouteDetail.module.css
+++ b/ui/src/pages/Routes/RouteDetail.module.css
@@ -286,3 +286,43 @@
font-size: 13px;
padding: 8px 0;
}
+
+/* Recording pill */
+.recordingPill {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ padding: 6px 12px;
+}
+
+.recordingLabel {
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+/* Taps section */
+.tapsSection {
+ margin-top: 16px;
+}
+
+.tapsHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+}
+
+.tapsTitle {
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.emptyState {
+ padding: 40px;
+ text-align: center;
+ color: var(--text-muted);
+ font-size: 13px;
+}
diff --git a/ui/src/pages/Routes/RouteDetail.tsx b/ui/src/pages/Routes/RouteDetail.tsx
index 5dcb116c..1c581f4c 100644
--- a/ui/src/pages/Routes/RouteDetail.tsx
+++ b/ui/src/pages/Routes/RouteDetail.tsx
@@ -13,6 +13,7 @@ import {
Spinner,
MonoText,
Sparkline,
+ Toggle,
} from '@cameleer/design-system';
import type { KpiItem, Column } from '@cameleer/design-system';
import { useGlobalFilters } from '@cameleer/design-system';
@@ -20,6 +21,7 @@ import { useRouteCatalog } from '../../api/queries/catalog';
import { useDiagramByRoute } from '../../api/queries/diagrams';
import { useProcessorMetrics } from '../../api/queries/processor-metrics';
import { useStatsTimeseries, useSearchExecutions, useExecutionStats } from '../../api/queries/executions';
+import { useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands';
import type { ExecutionSummary, AppCatalogEntry, RouteSummary } from '../../api/types';
import { mapDiagramToRouteNodes, toFlowSegments } from '../../utils/diagram-mapping';
import styles from './RouteDetail.module.css';
@@ -294,6 +296,18 @@ export default function RouteDetail() {
limit: 200,
});
+ // ── Application config ──────────────────────────────────────────────────────
+ const config = useApplicationConfig(appId);
+ const updateConfig = useUpdateApplicationConfig();
+
+ const isRecording = config.data?.routeRecording?.[routeId!] !== false;
+
+ function toggleRecording() {
+ if (!config.data) return;
+ const routeRecording = { ...config.data.routeRecording, [routeId!]: !isRecording };
+ updateConfig.mutate({ ...config.data, routeRecording });
+ }
+
// ── Derived data ───────────────────────────────────────────────────────────
const appEntry: AppCatalogEntry | undefined = useMemo(() =>
@@ -408,11 +422,29 @@ export default function RouteDetail() {
.sort((a, b) => b.count - a.count);
}, [errorResult]);
+ // Route taps — cross-reference config taps with diagram processor IDs
+ const routeTaps = useMemo(() => {
+ if (!config.data?.taps || !diagram) return [];
+ const routeProcessorIds = new Set(
+ (diagram.nodes || []).map((n: any) => n.id).filter(Boolean),
+ );
+ return config.data.taps.filter(t => routeProcessorIds.has(t.processorId));
+ }, [config.data?.taps, diagram]);
+
+ const activeTapCount = routeTaps.filter(t => t.enabled).length;
+
// KPI items
- const kpiItems = useMemo(() =>
- buildDetailKpiItems(stats, throughputSparkline, errorSparkline, latencySparkline),
- [stats, throughputSparkline, errorSparkline, latencySparkline],
- );
+ const kpiItems = useMemo(() => {
+ const base = buildDetailKpiItems(stats, throughputSparkline, errorSparkline, latencySparkline);
+ base.push({
+ label: 'Active Taps',
+ value: String(activeTapCount),
+ trend: { label: `${routeTaps.length} total`, variant: 'muted' as const },
+ subtitle: `${activeTapCount} enabled / ${routeTaps.length} configured`,
+ borderColor: 'var(--running)',
+ });
+ return base;
+ }, [stats, throughputSparkline, errorSparkline, latencySparkline, activeTapCount, routeTaps.length]);
const processorColumns = useMemo(() => makeProcessorColumns(styles), []);
@@ -420,6 +452,7 @@ export default function RouteDetail() {
{ label: 'Performance', value: 'performance' },
{ label: 'Recent Executions', value: 'executions', count: exchangeRows.length },
{ label: 'Error Patterns', value: 'errors', count: errorPatterns.length },
+ { label: 'Taps', value: 'taps', count: routeTaps.length },
];
// ── Render ─────────────────────────────────────────────────────────────────
@@ -439,6 +472,10 @@ export default function RouteDetail() {