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 (
| 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" && ( <> > )} |