/** * Cron Page * Manage scheduled tasks */ import { useEffect, useState, useCallback } from 'react'; import { Plus, Clock, Play, Pause, Trash2, Edit, RefreshCw, X, Calendar, AlertCircle, CheckCircle2, XCircle, MessageSquare, Loader2, Timer, History, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Switch } from '@/components/ui/switch'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { useCronStore } from '@/stores/cron'; import { useChannelsStore } from '@/stores/channels'; import { useGatewayStore } from '@/stores/gateway'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { formatRelativeTime, cn } from '@/lib/utils'; import { toast } from 'sonner'; import type { CronJob, CronJobCreateInput, ScheduleType } from '@/types/cron'; import { CHANNEL_ICONS } from '@/types/channel'; // Common cron schedule presets const schedulePresets: { label: string; value: string; type: ScheduleType }[] = [ { label: 'Every minute', value: '* * * * *', type: 'interval' }, { label: 'Every 5 minutes', value: '*/5 * * * *', type: 'interval' }, { label: 'Every 15 minutes', value: '*/15 * * * *', type: 'interval' }, { label: 'Every hour', value: '0 * * * *', type: 'interval' }, { label: 'Daily at 9am', value: '0 9 * * *', type: 'daily' }, { label: 'Daily at 6pm', value: '0 18 * * *', type: 'daily' }, { label: 'Weekly (Mon 9am)', value: '0 9 * * 1', type: 'weekly' }, { label: 'Monthly (1st at 9am)', value: '0 9 1 * *', type: 'monthly' }, ]; // Parse cron expression to human-readable format function parseCronSchedule(cron: string): string { const preset = schedulePresets.find((p) => p.value === cron); if (preset) return preset.label; const parts = cron.split(' '); if (parts.length !== 5) return cron; const [minute, hour, dayOfMonth, , dayOfWeek] = parts; if (minute === '*' && hour === '*') return 'Every minute'; if (minute.startsWith('*/')) return `Every ${minute.slice(2)} minutes`; if (hour === '*' && minute === '0') return 'Every hour'; if (dayOfWeek !== '*' && dayOfMonth === '*') { const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return `Weekly on ${days[parseInt(dayOfWeek)]} at ${hour}:${minute.padStart(2, '0')}`; } if (dayOfMonth !== '*') { return `Monthly on day ${dayOfMonth} at ${hour}:${minute.padStart(2, '0')}`; } if (hour !== '*') { return `Daily at ${hour}:${minute.padStart(2, '0')}`; } return cron; } // Create/Edit Task Dialog interface TaskDialogProps { job?: CronJob; onClose: () => void; onSave: (input: CronJobCreateInput) => Promise; } function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { const { channels } = useChannelsStore(); const [saving, setSaving] = useState(false); const [name, setName] = useState(job?.name || ''); const [message, setMessage] = useState(job?.message || ''); const [schedule, setSchedule] = useState(job?.schedule || '0 9 * * *'); const [customSchedule, setCustomSchedule] = useState(''); const [useCustom, setUseCustom] = useState(false); const [channelId, setChannelId] = useState(job?.target.channelId || ''); const [enabled, setEnabled] = useState(job?.enabled ?? true); const selectedChannel = channels.find((c) => c.id === channelId); const handleSubmit = async () => { if (!name.trim()) { toast.error('Please enter a task name'); return; } if (!message.trim()) { toast.error('Please enter a message'); return; } if (!channelId) { toast.error('Please select a channel'); return; } const finalSchedule = useCustom ? customSchedule : schedule; if (!finalSchedule.trim()) { toast.error('Please select or enter a schedule'); return; } setSaving(true); try { await onSave({ name: name.trim(), message: message.trim(), schedule: finalSchedule, target: { channelType: selectedChannel!.type, channelId: selectedChannel!.id, channelName: selectedChannel!.name, }, enabled, }); onClose(); toast.success(job ? 'Task updated' : 'Task created'); } catch (err) { toast.error(String(err)); } finally { setSaving(false); } }; return (
e.stopPropagation()}>
{job ? 'Edit Task' : 'Create Task'} Schedule an automated AI task
{/* Name */}
setName(e.target.value)} />
{/* Message */}