import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import { executionsApi } from '../api/workflows'
import { useExecutionListUpdates } from '../hooks/useExecutionListUpdates'
import { formatDistanceToNow } from 'date-fns'
import { CheckCircle, XCircle, Clock, Ban, RefreshCw, ChevronLeft, ChevronRight, AlertCircle, Octagon } from 'lucide-react'
import clsx from 'clsx'
// Helper to safely format timestamps that might be invalid or too large
function safeFormatDistanceToNow(timestamp: number): string {
if (!timestamp || timestamp < 0 || timestamp > 8640000000000000) {
return 'unknown'
}
try {
return formatDistanceToNow(timestamp, { addSuffix: true })
} catch {
return 'unknown'
}
}
const PAGE_SIZE = 20
export default function ExecutionsPage() {
const navigate = useNavigate()
const [page, setPage] = useState(1)
// The workflow editor already renders an execution across the canvas with each
// node's input and output, so open it there rather than building a second
// viewer that would show less.
const openExecution = (execution: { id: string; workflowId: string }) => {
if (!execution.workflowId) {
return
}
navigate(`/workflows/${execution.workflowId}?execution=${encodeURIComponent(execution.id)}`)
}
const { data, isLoading, refetch } = useQuery({
queryKey: ['executions', page],
queryFn: () => executionsApi.list(undefined, undefined, page, PAGE_SIZE),
})
// Subscribe to WebSocket for real-time updates instead of polling
useExecutionListUpdates()
const executions = data?.executions || []
const total = data?.total || 0
const totalPages = Math.ceil(total / PAGE_SIZE)
const hasMore = data?.hasMore || false
const getStatusIcon = (status: string) => {
switch (status) {
case 'completed':
return
No executions yet
| Status | Workflow | Trigger | Runner | Started | Duration |
|---|---|---|---|---|---|
|
{getStatusIcon(execution.status)}
{execution.status}
|
{execution.workflowName || execution.workflowId}
{execution.error && (
|
{execution.triggerType} | {execution.runnerId || '-'} | {execution.startedAt ? safeFormatDistanceToNow(execution.startedAt) : '-'} | {execution.finishedAt && execution.startedAt ? `${((execution.finishedAt - execution.startedAt) / 1000).toFixed(2)}s` : execution.status === 'running' ? 'Running...' : '-'} |