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>
This commit is contained in:
115
ui/src/components/ExecutionDiagram/tabs/InfoTab.tsx
Normal file
115
ui/src/components/ExecutionDiagram/tabs/InfoTab.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user