import cronstrue from "cronstrue"; import { CronExpressionParser } from "cron-parser"; import type { TaskRecord } from "../api/client"; function describeSchedule(task: TaskRecord): string { if (!task.scheduleExpression) return "—"; if (task.scheduleType === "cron") { try { return cronstrue.toString(task.scheduleExpression, { verbose: false }); } catch { return task.scheduleExpression; } } return task.scheduleExpression; } interface NextRun { date: Date; /** true when computed client-side from the cron expression rather than reported by the agent */ estimated: boolean; } function computeNextRun(task: TaskRecord): NextRun | null { if (task.nextRunAt) { return { date: new Date(task.nextRunAt), estimated: false }; } if (task.scheduleType !== "cron" || !task.scheduleExpression || task.isStale || !task.enabled) { return null; } try { const date = CronExpressionParser.parse(task.scheduleExpression).next().toDate(); return { date, estimated: true }; } catch { return null; } } interface Props { tasks: TaskRecord[]; onEdit?: (task: TaskRecord) => void; onDelete?: (task: TaskRecord) => void; } export default function TaskTable({ tasks, onEdit, onDelete }: Props) { return ( {tasks.map((task) => { const nextRun = computeNextRun(task); return ( ); })}
Name Command Schedule Next run Status
{task.name} {task.command ?? "—"}
{describeSchedule(task)}
{task.scheduleExpression && ( {task.scheduleExpression} )}
{nextRun ? ( {nextRun.estimated ? "~" : ""} {nextRun.date.toLocaleString()} ) : ( "—" )} {task.isStale ? ( Missing ) : task.enabled ? ( Enabled ) : ( Disabled )} {task.origin === "manual" && Manual} {task.origin === "manual" && ( <> )}
); }