import React, { useMemo } from 'react'; import '@shared/styles/theme.css'; import { Badge } from '@shared/components'; import { useToolData } from '@shared/hooks/useToolData'; import type { ExecutionHistoryData } from '@shared/types'; type ExecStatus = 'success' | 'error' | 'waiting' | 'running' | 'unknown'; function getStatusInfo(status?: string): { variant: 'success' | 'error' | 'warning' | 'info'; label: string } { switch (status) { case 'success': return { variant: 'success', label: 'Success' }; case 'error': case 'failed': case 'crashed': return { variant: 'error', label: 'Error' }; case 'waiting': return { variant: 'warning', label: 'Waiting' }; case 'running': return { variant: 'info', label: 'Running' }; default: return { variant: 'info', label: status ?? 'Unknown' }; } } function formatDuration(startedAt?: string, stoppedAt?: string): string { if (!startedAt || !stoppedAt) return '–'; try { const ms = new Date(stoppedAt).getTime() - new Date(startedAt).getTime(); if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`; } catch { return '–'; } } function formatTime(dateStr?: string): string { if (!dateStr) return ''; try { const d = new Date(dateStr); return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch { return dateStr; } } function classifyStatus(status?: string): ExecStatus { switch (status) { case 'success': return 'success'; case 'error': case 'failed': case 'crashed': return 'error'; case 'waiting': return 'waiting'; case 'running': return 'running'; default: return 'unknown'; } } export default function App() { const { data, error, isConnected } = useToolData(); const executions = data?.data?.executions ?? []; const summary = useMemo(() => { const counts: Record = { success: 0, error: 0, waiting: 0, running: 0, unknown: 0 }; for (const ex of executions) { counts[classifyStatus(ex.status)]++; } return counts; }, [executions]); if (error) { return
Error: {error}
; } if (!isConnected) { return
Connecting...
; } if (!data) { return
Waiting for data...
; } if (!data.success && data.error) { return (
Error
{data.error}
); } const total = executions.length; const barSegments: { color: string; pct: number }[] = []; if (total > 0) { if (summary.success > 0) barSegments.push({ color: 'var(--n8n-success)', pct: (summary.success / total) * 100 }); if (summary.error > 0) barSegments.push({ color: 'var(--n8n-error)', pct: (summary.error / total) * 100 }); if (summary.waiting > 0) barSegments.push({ color: 'var(--n8n-warning)', pct: (summary.waiting / total) * 100 }); if (summary.running > 0) barSegments.push({ color: 'var(--n8n-info)', pct: (summary.running / total) * 100 }); if (summary.unknown > 0) barSegments.push({ color: 'var(--n8n-border)', pct: (summary.unknown / total) * 100 }); } return (
{/* Summary bar */} {total > 0 && (
{barSegments.map((seg, i) => (
))}
{summary.success > 0 && <>{summary.success} succeeded} {summary.error > 0 && <>{summary.success > 0 && ', '}{summary.error} failed} {summary.waiting > 0 && <>{(summary.success > 0 || summary.error > 0) && ', '}{summary.waiting} waiting} {summary.running > 0 && <>{(summary.success > 0 || summary.error > 0 || summary.waiting > 0) && ', '}{summary.running} running}
)} {/* Table */}
ID Workflow Status Started Duration
{executions.length === 0 && (
No executions found
)} {executions.map((ex) => { const statusInfo = getStatusInfo(ex.status); return (
{ex.id.length > 8 ? ex.id.slice(0, 8) + '…' : ex.id} {ex.workflowName || ex.workflowId || '–'} {statusInfo.label} {formatTime(ex.startedAt)} {formatDuration(ex.startedAt, ex.stoppedAt)}
); })}
{data.data?.hasMore && (
More executions available
)}
); }