diff --git a/ui/src/components/ExecutionDiagram/DetailPanel.tsx b/ui/src/components/ExecutionDiagram/DetailPanel.tsx
index 74c90411..d2b28c7e 100644
--- a/ui/src/components/ExecutionDiagram/DetailPanel.tsx
+++ b/ui/src/components/ExecutionDiagram/DetailPanel.tsx
@@ -7,6 +7,7 @@ import { BodyTab } from './tabs/BodyTab';
import { ErrorTab } from './tabs/ErrorTab';
import { ConfigTab } from './tabs/ConfigTab';
import { TimelineTab } from './tabs/TimelineTab';
+import { LogTab } from './tabs/LogTab';
import styles from './ExecutionDiagram.module.css';
interface DetailPanelProps {
@@ -24,6 +25,7 @@ const TABS: { key: DetailTab; label: string }[] = [
{ key: 'error', label: 'Error' },
{ key: 'config', label: 'Config' },
{ key: 'timeline', label: 'Timeline' },
+ { key: 'log', label: 'Log' },
];
function formatDuration(ms: number | undefined): string {
@@ -158,6 +160,13 @@ export function DetailPanel({
onSelectProcessor={onSelectProcessor}
/>
)}
+ {activeTab === 'log' && (
+
+ )}
);
diff --git a/ui/src/components/ExecutionDiagram/tabs/LogTab.tsx b/ui/src/components/ExecutionDiagram/tabs/LogTab.tsx
new file mode 100644
index 00000000..6495c540
--- /dev/null
+++ b/ui/src/components/ExecutionDiagram/tabs/LogTab.tsx
@@ -0,0 +1,117 @@
+import { useState, useMemo } from 'react';
+import { useApplicationLogs } from '../../../api/queries/logs';
+import type { LogEntryResponse } from '../../../api/queries/logs';
+import styles from '../ExecutionDiagram.module.css';
+
+interface LogTabProps {
+ applicationName: string;
+ exchangeId?: string;
+ processorId: string | null;
+}
+
+function levelColor(level: string): string {
+ switch (level?.toUpperCase()) {
+ case 'ERROR': return 'var(--error)';
+ case 'WARN': return 'var(--warning)';
+ case 'DEBUG': return 'var(--text-muted)';
+ case 'TRACE': return 'var(--text-faint, var(--text-muted))';
+ default: return 'var(--text-secondary)';
+ }
+}
+
+function formatTime(iso: string): string {
+ 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}`;
+}
+
+export function LogTab({ applicationName, exchangeId, processorId }: LogTabProps) {
+ const [filter, setFilter] = useState('');
+
+ const { data: logs, isLoading } = useApplicationLogs(
+ applicationName,
+ undefined,
+ { exchangeId, limit: 500 },
+ );
+
+ const entries: LogEntryResponse[] = useMemo(() => {
+ if (!logs) return [];
+ let items = logs as LogEntryResponse[];
+
+ // If a processor is selected, filter logs by logger name containing the processor ID
+ if (processorId) {
+ items = items.filter((e) =>
+ e.message?.includes(processorId) ||
+ e.loggerName?.includes(processorId)
+ );
+ }
+
+ // Text filter
+ if (filter) {
+ const q = filter.toLowerCase();
+ items = items.filter((e) =>
+ e.message?.toLowerCase().includes(q) ||
+ e.level?.toLowerCase().includes(q) ||
+ e.loggerName?.toLowerCase().includes(q)
+ );
+ }
+
+ return items;
+ }, [logs, processorId, filter]);
+
+ if (isLoading) {
+ return
Loading logs...
;
+ }
+
+ return (
+
+
+ setFilter(e.target.value)}
+ style={{
+ width: '100%',
+ padding: '4px 8px',
+ fontSize: '11px',
+ border: '1px solid var(--border-subtle)',
+ borderRadius: 'var(--radius-sm)',
+ background: 'var(--bg-surface)',
+ color: 'var(--text-primary)',
+ outline: 'none',
+ fontFamily: 'var(--font-body)',
+ }}
+ />
+
+
+ {entries.length === 0 ? (
+
+ {processorId ? 'No logs for this processor' : 'No logs available'}
+
+ ) : (
+
+
+ {entries.map((entry, i) => (
+
+ |
+ {formatTime(entry.timestamp)}
+ |
+
+ {entry.level}
+ |
+
+ {entry.message}
+ |
+
+ ))}
+
+
+ )}
+
+
+ );
+}
diff --git a/ui/src/components/ExecutionDiagram/types.ts b/ui/src/components/ExecutionDiagram/types.ts
index 5837fbd3..7cb52f74 100644
--- a/ui/src/components/ExecutionDiagram/types.ts
+++ b/ui/src/components/ExecutionDiagram/types.ts
@@ -21,4 +21,4 @@ export interface IterationInfo {
type: 'loop' | 'split' | 'multicast';
}
-export type DetailTab = 'info' | 'headers' | 'input' | 'output' | 'error' | 'config' | 'timeline';
+export type DetailTab = 'info' | 'headers' | 'input' | 'output' | 'error' | 'config' | 'timeline' | 'log';