import { useCallback, useEffect, useState } from "react"; import { Clock, Pause, Play, Plus, Trash2, Zap } from "lucide-react"; import { Badge } from "@nous-research/ui/ui/components/badge"; import { Button } from "@nous-research/ui/ui/components/button"; import { Select, SelectOption } from "@nous-research/ui/ui/components/select"; import { Spinner } from "@nous-research/ui/ui/components/spinner"; import { H2 } from "@/components/NouiTypography"; import { api } from "@/lib/api"; import type { CronJob } from "@/lib/api"; import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog"; import { useToast } from "@/hooks/useToast"; import { useConfirmDelete } from "@/hooks/useConfirmDelete"; import { Toast } from "@/components/Toast"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useI18n } from "@/i18n"; import { PluginSlot } from "@/plugins"; function formatTime(iso?: string | null): string { if (!iso) return "—"; const d = new Date(iso); return d.toLocaleString(); } const STATUS_TONE: Record = { enabled: "success", scheduled: "success", paused: "warning", error: "destructive", completed: "destructive", }; export default function CronPage() { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(true); const { toast, showToast } = useToast(); const { t } = useI18n(); // New job form state const [prompt, setPrompt] = useState(""); const [schedule, setSchedule] = useState(""); const [name, setName] = useState(""); const [deliver, setDeliver] = useState("local"); const [creating, setCreating] = useState(false); const loadJobs = useCallback(() => { api .getCronJobs() .then(setJobs) .catch(() => showToast(t.common.loading, "error")) .finally(() => setLoading(false)); }, [showToast, t.common.loading]); useEffect(() => { loadJobs(); }, [loadJobs]); const handleCreate = async () => { if (!prompt.trim() || !schedule.trim()) { showToast(`${t.cron.prompt} & ${t.cron.schedule} required`, "error"); return; } setCreating(true); try { await api.createCronJob({ prompt: prompt.trim(), schedule: schedule.trim(), name: name.trim() || undefined, deliver, }); showToast(t.common.create + " ✓", "success"); setPrompt(""); setSchedule(""); setName(""); setDeliver("local"); loadJobs(); } catch (e) { showToast(`${t.config.failedToSave}: ${e}`, "error"); } finally { setCreating(false); } }; const handlePauseResume = async (job: CronJob) => { try { const isPaused = job.state === "paused"; if (isPaused) { await api.resumeCronJob(job.id); showToast( `${t.cron.resume}: "${job.name || job.prompt.slice(0, 30)}"`, "success", ); } else { await api.pauseCronJob(job.id); showToast( `${t.cron.pause}: "${job.name || job.prompt.slice(0, 30)}"`, "success", ); } loadJobs(); } catch (e) { showToast(`${t.status.error}: ${e}`, "error"); } }; const handleTrigger = async (job: CronJob) => { try { await api.triggerCronJob(job.id); showToast( `${t.cron.triggerNow}: "${job.name || job.prompt.slice(0, 30)}"`, "success", ); loadJobs(); } catch (e) { showToast(`${t.status.error}: ${e}`, "error"); } }; const jobDelete = useConfirmDelete({ onDelete: useCallback( async (id: string) => { const job = jobs.find((j) => j.id === id); try { await api.deleteCronJob(id); showToast( `${t.common.delete}: "${job?.name || (job?.prompt ?? "").slice(0, 30) || id}"`, "success", ); loadJobs(); } catch (e) { showToast(`${t.status.error}: ${e}`, "error"); throw e; } }, [jobs, loadJobs, showToast, t.common.delete, t.status.error], ), }); if (loading) { return (
); } const pendingJob = jobDelete.pendingId ? jobs.find((j) => j.id === jobDelete.pendingId) : null; return (
{t.cron.newJob}
setName(e.target.value)} />