Files
cameleer-server/ui/src/components/ExecutionDiagram/tabs/InfoTab.tsx
hsiegeln e4c66b1311 feat: add DetailPanel with 7 tabs for execution diagram overlay
Implements the bottom detail panel with processor header bar, tab bar
(Info, Headers, Input, Output, Error, Config, Timeline), and all tab
content components. Info shows processor/exchange metadata in a grid,
Headers fetches per-processor snapshots for side-by-side display,
Input/Output render formatted code blocks, Error extracts exception
types, Config is a placeholder, and Timeline renders a Gantt chart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:01:53 +01:00

116 lines
3.8 KiB
TypeScript

import type { ProcessorNode, ExecutionDetail } from '../types';
import styles from '../ExecutionDiagram.module.css';
interface InfoTabProps {
processor: ProcessorNode | null;
executionDetail: ExecutionDetail;
}
function formatTime(iso: string | undefined): string {
if (!iso) return '-';
try {
const d = new Date(iso);
const h = String(d.getHours()).padStart(2, '0');
const m = String(d.getMinutes()).padStart(2, '0');
const s = String(d.getSeconds()).padStart(2, '0');
const ms = String(d.getMilliseconds()).padStart(3, '0');
return `${h}:${m}:${s}.${ms}`;
} catch {
return iso;
}
}
function formatDuration(ms: number | undefined): string {
if (ms === undefined || ms === null) return '-';
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
function statusClass(status: string): string {
const s = status?.toUpperCase();
if (s === 'COMPLETED') return styles.statusCompleted;
if (s === 'FAILED') return styles.statusFailed;
return '';
}
function Field({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
return (
<div>
<div className={styles.fieldLabel}>{label}</div>
<div className={mono ? styles.fieldValueMono : styles.fieldValue}>{value || '-'}</div>
</div>
);
}
function Attributes({ attrs }: { attrs: Record<string, string> | undefined }) {
if (!attrs) return null;
const entries = Object.entries(attrs);
if (entries.length === 0) return null;
return (
<div className={styles.attributesSection}>
<div className={styles.attributesLabel}>Attributes</div>
<div className={styles.attributesList}>
{entries.map(([k, v]) => (
<span key={k} className={styles.attributePill}>
{k}: {v}
</span>
))}
</div>
</div>
);
}
export function InfoTab({ processor, executionDetail }: InfoTabProps) {
if (processor) {
return (
<div>
<div className={styles.infoGrid}>
<Field label="Processor ID" value={processor.processorId} mono />
<Field label="Type" value={processor.processorType} />
<div>
<div className={styles.fieldLabel}>Status</div>
<span className={`${styles.statusBadge} ${statusClass(processor.status)}`}>
{processor.status}
</span>
</div>
<Field label="Start Time" value={formatTime(processor.startTime)} mono />
<Field label="End Time" value={formatTime(processor.endTime)} mono />
<Field label="Duration" value={formatDuration(processor.durationMs)} mono />
<Field label="Endpoint URI" value={processor.processorType} />
<Field label="Resolved URI" value="-" />
<div />
</div>
<Attributes attrs={processor.attributes} />
</div>
);
}
// Exchange-level view
return (
<div>
<div className={styles.infoGrid}>
<Field label="Execution ID" value={executionDetail.executionId} mono />
<Field label="Correlation ID" value={executionDetail.correlationId} mono />
<Field label="Exchange ID" value={executionDetail.exchangeId} mono />
<Field label="Application" value={executionDetail.applicationName} />
<Field label="Route ID" value={executionDetail.routeId} />
<div>
<div className={styles.fieldLabel}>Status</div>
<span className={`${styles.statusBadge} ${statusClass(executionDetail.status)}`}>
{executionDetail.status}
</span>
</div>
<Field label="Start Time" value={formatTime(executionDetail.startTime)} mono />
<Field label="End Time" value={formatTime(executionDetail.endTime)} mono />
<Field label="Duration" value={formatDuration(executionDetail.durationMs)} mono />
</div>
<Attributes attrs={executionDetail.attributes} />
</div>
);
}