"use client"; import { useState, useEffect } from "react"; import { Timer, Play, RefreshCw, Loader2 } from "lucide-react"; import Link from "next/link"; import { timeAgo } from "@/lib/utils"; interface CronJob { id: string; name?: string; prompt: string; schedule: string; status: string; deliver: string; skill?: string; skills?: string[]; model?: string; run_count?: number; last_run?: string; next_run?: string; } export default function CronPage() { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(true); const [triggering, setTriggering] = useState(null); const load = () => { setLoading(true); fetch("/cron/jobs") .then((r) => r.json()) .then((d) => setJobs(d.jobs || [])) .catch(() => {}) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, []); const trigger = async (e: React.MouseEvent, id: string) => { e.preventDefault(); e.stopPropagation(); setTriggering(id); await fetch("/cron/jobs/" + id + "/run", { method: "POST" }); setTriggering(null); setTimeout(load, 1000); }; const active = jobs.filter((j) => j.status === "active" || j.status === "enabled"); const paused = jobs.filter((j) => j.status === "paused"); const other = jobs.filter((j) => !["active", "enabled", "paused"].includes(j.status)); return (

Cron Jobs

{loading ? (
{[...Array(3)].map((_, i) => (
))}
) : jobs.length === 0 ? (

No cron jobs

) : (
{active.length > 0 && } {paused.length > 0 && } {other.length > 0 && }
)}
); } function JobSection({ label, jobs, onTrigger, triggering }: { label: string; jobs: CronJob[]; onTrigger: (e: React.MouseEvent, id: string) => void; triggering: string | null }) { return (
{label} ({jobs.length})
{jobs.map((j) => (
{j.name || j.prompt.slice(0, 60)}
{j.schedule}
→ {j.deliver} {j.skills && j.skills.length > 0 && ( {j.skills.join(", ")} )} {j.last_run && last: {timeAgo(new Date(j.last_run).getTime() / 1000)}}
))}
); }