Files
cameleer-server/ui/src/pages/Exchanges/ExchangesPage.tsx

141 lines
4.6 KiB
TypeScript
Raw Normal View History

import { useState, useMemo, useCallback, useRef } from 'react';
import { useParams, useNavigate } from 'react-router';
import { useGlobalFilters } from '@cameleer/design-system';
import { useExecutionDetail } from '../../api/queries/executions';
import { useDiagramByRoute } from '../../api/queries/diagrams';
import { useRouteCatalog } from '../../api/queries/catalog';
import type { NodeAction } from '../../components/ProcessDiagram/types';
import { ExchangeHeader } from './ExchangeHeader';
import { ExecutionDiagram } from '../../components/ExecutionDiagram/ExecutionDiagram';
import { ProcessDiagram } from '../../components/ProcessDiagram';
import styles from './ExchangesPage.module.css';
// The full-width Dashboard table — used at every scope level
import Dashboard from '../Dashboard/Dashboard';
export default function ExchangesPage() {
const { appId, routeId, exchangeId } = useParams<{
appId?: string; routeId?: string; exchangeId?: string;
}>();
// No route scoped: full-width Dashboard
if (!routeId) {
return <Dashboard />;
}
// Route scoped: resizable split — Dashboard table on left, diagram on right
return <SplitExchangeView appId={appId!} routeId={routeId} exchangeId={exchangeId} />;
}
// ─── Resizable split view ───────────────────────────────────────────────────
interface SplitExchangeViewProps {
appId: string;
routeId: string;
exchangeId?: string;
}
function SplitExchangeView({ appId, routeId, exchangeId }: SplitExchangeViewProps) {
const [splitPercent, setSplitPercent] = useState(50);
const containerRef = useRef<HTMLDivElement>(null);
const handleSplitterDown = useCallback((e: React.PointerEvent) => {
e.currentTarget.setPointerCapture(e.pointerId);
const container = containerRef.current;
if (!container) return;
const onMove = (me: PointerEvent) => {
const rect = container.getBoundingClientRect();
const x = me.clientX - rect.left;
const pct = Math.min(80, Math.max(20, (x / rect.width) * 100));
setSplitPercent(pct);
};
const onUp = () => {
document.removeEventListener('pointermove', onMove);
document.removeEventListener('pointerup', onUp);
};
document.addEventListener('pointermove', onMove);
document.addEventListener('pointerup', onUp);
}, []);
return (
<div ref={containerRef} className={styles.splitView}>
<div className={styles.leftPanel} style={{ width: `${splitPercent}%` }}>
<Dashboard />
</div>
<div className={styles.splitter} onPointerDown={handleSplitterDown} />
<div className={styles.rightPanel} style={{ width: `${100 - splitPercent}%` }}>
<DiagramPanel appId={appId} routeId={routeId} exchangeId={exchangeId} />
</div>
</div>
);
}
// ─── Right panel: diagram + optional execution overlay ──────────────────────
interface DiagramPanelProps {
appId: string;
routeId: string;
exchangeId?: string;
}
function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) {
const navigate = useNavigate();
const { timeRange } = useGlobalFilters();
const timeFrom = timeRange.start.toISOString();
const timeTo = timeRange.end.toISOString();
const { data: detail } = useExecutionDetail(exchangeId ?? null);
const diagramQuery = useDiagramByRoute(appId, routeId);
const { data: catalog } = useRouteCatalog(timeFrom, timeTo);
const knownRouteIds = useMemo(() => {
const ids = new Set<string>();
if (catalog) {
for (const app of catalog as any[]) {
for (const r of app.routes || []) {
ids.add(r.routeId);
}
}
}
return ids;
}, [catalog]);
const handleNodeAction = useCallback((nodeId: string, action: NodeAction) => {
if (action === 'configure-tap') {
navigate(`/admin/appconfig?app=${encodeURIComponent(appId)}&processor=${encodeURIComponent(nodeId)}`);
}
}, [appId, navigate]);
if (exchangeId && detail) {
return (
<>
<ExchangeHeader detail={detail} />
<ExecutionDiagram
executionId={exchangeId}
executionDetail={detail}
knownRouteIds={knownRouteIds}
onNodeAction={handleNodeAction}
/>
</>
);
}
if (diagramQuery.data) {
return (
<ProcessDiagram
application={appId}
routeId={routeId}
diagramLayout={diagramQuery.data}
knownRouteIds={knownRouteIds}
onNodeAction={handleNodeAction}
/>
);
}
return (
<div className={styles.emptyRight}>
Loading diagram...
</div>
);
}