diff --git a/client/src/components/dynamic-groups/cron-job-builder.tsx b/client/src/components/dynamic-groups/cron-job-builder.tsx index 30d3e2d..6ed2a58 100644 --- a/client/src/components/dynamic-groups/cron-job-builder.tsx +++ b/client/src/components/dynamic-groups/cron-job-builder.tsx @@ -7,7 +7,11 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Switch } from '@/components/ui/switch'; import { Badge } from '@/components/ui/badge'; -import { Trash, Plus, Calendar, Clock, RotateCcw } from 'lucide-react'; +import { Trash, Plus, Calendar, Clock, RotateCcw, HelpCircle, Copy, Edit, AlertCircle } from 'lucide-react'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Slider } from '@/components/ui/slider'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; import { ScheduleFrequency, Weekday, @@ -15,6 +19,7 @@ import { weekdays, InsertScheduleRule } from '@shared/schema'; +import { useToast } from '@/hooks/use-toast'; interface CronJobBuilderProps { schedules: ScheduleItem[]; @@ -34,6 +39,14 @@ export interface ScheduleItem { hour: number; minute: number; enabled: boolean; + priority?: number; + customSchedule?: boolean; + months?: number[]; + daysOfWeek?: Weekday[]; + daysOfMonth?: number[]; + hours?: number[]; + minutes?: number[]; + mode?: 'basic' | 'advanced'; } const initialSchedule: ScheduleItem = { @@ -43,7 +56,15 @@ const initialSchedule: ScheduleItem = { cronExpression: '0 0 * * *', hour: 0, minute: 0, - enabled: true + enabled: true, + priority: 0, + customSchedule: false, + months: [], + daysOfWeek: [], + daysOfMonth: [], + hours: [], + minutes: [], + mode: 'basic' }; export const CronJobBuilder: React.FC = ({ @@ -51,6 +72,7 @@ export const CronJobBuilder: React.FC = ({ onSchedulesChange, ruleId }) => { + const { toast } = useToast(); const [activeSchedules, setActiveSchedules] = useState( schedules.length > 0 ? schedules : [initialSchedule] ); @@ -131,9 +153,112 @@ export const CronJobBuilder: React.FC = ({ setActiveSchedules(newSchedules); onSchedulesChange(newSchedules); }; + + // Toggle between basic and advanced mode + const toggleMode = (index: number) => { + const schedule = activeSchedules[index]; + const newMode = schedule.mode === 'basic' ? 'advanced' : 'basic'; + + // If switching to advanced mode, initialize the advanced properties + if (newMode === 'advanced') { + updateSchedule(index, { + mode: newMode, + customSchedule: true, + months: [], + daysOfWeek: schedule.dayOfWeek ? [schedule.dayOfWeek] : [], + daysOfMonth: schedule.dayOfMonth ? [schedule.dayOfMonth] : [], + hours: [schedule.hour], + minutes: [schedule.minute] + }); + } else { + // Switching back to basic mode + updateSchedule(index, { + mode: newMode, + customSchedule: false + }); + } + }; + + // Copy cron expression to clipboard + const copyCronExpression = (expression: string) => { + navigator.clipboard.writeText(expression) + .then(() => { + toast({ + title: "Copied to clipboard", + description: "Cron expression copied successfully", + }); + }) + .catch((err) => { + toast({ + title: "Failed to copy", + description: "Could not copy to clipboard: " + err, + variant: "destructive" + }); + }); + }; + + // Handle updating priority of schedule + const updatePriority = (index: number, priority: number) => { + updateSchedule(index, { priority }); + }; + + // Generate cron expression from advanced settings + const generateAdvancedCronExpression = (schedule: ScheduleItem): string => { + const minutePart = schedule.minutes && schedule.minutes.length > 0 + ? schedule.minutes.join(',') + : '*'; + + const hourPart = schedule.hours && schedule.hours.length > 0 + ? schedule.hours.join(',') + : '*'; + + const dayOfMonthPart = schedule.daysOfMonth && schedule.daysOfMonth.length > 0 + ? schedule.daysOfMonth.join(',') + : '*'; + + const monthPart = schedule.months && schedule.months.length > 0 + ? schedule.months.join(',') + : '*'; + + const dayOfWeekPart = schedule.daysOfWeek && schedule.daysOfWeek.length > 0 + ? schedule.daysOfWeek.map(day => weekdays.indexOf(day)).join(',') + : '*'; + + return `${minutePart} ${hourPart} ${dayOfMonthPart} ${monthPart} ${dayOfWeekPart}`; + }; // Function to generate a human-readable description of the schedule const getScheduleDescription = (schedule: ScheduleItem): string => { + if (schedule.mode === 'advanced' && schedule.customSchedule) { + const parts = []; + + if (schedule.minutes && schedule.minutes.length > 0) { + parts.push(`at minute(s): ${schedule.minutes.join(', ')}`); + } + + if (schedule.hours && schedule.hours.length > 0) { + parts.push(`hour(s): ${schedule.hours.map(h => formatTime(h, 0).replace(':00', '')).join(', ')}`); + } + + if (schedule.daysOfWeek && schedule.daysOfWeek.length > 0) { + parts.push(`on ${schedule.daysOfWeek.join(', ')}`); + } + + if (schedule.daysOfMonth && schedule.daysOfMonth.length > 0) { + parts.push(`on day(s) ${schedule.daysOfMonth.join(', ')}`); + } + + if (schedule.months && schedule.months.length > 0) { + const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December']; + parts.push(`in ${schedule.months.map(m => monthNames[m-1]).join(', ')}`); + } + + return parts.length > 0 + ? `Runs ${parts.join(', ')}` + : 'Custom advanced schedule'; + } + switch (schedule.frequency) { case 'minutely': return 'Runs every minute'; @@ -145,6 +270,8 @@ export const CronJobBuilder: React.FC = ({ return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`; case 'monthly': return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`; + case 'custom': + return 'Custom schedule'; default: return 'Custom schedule'; } @@ -326,9 +453,19 @@ export const CronJobBuilder: React.FC = ({
- +
+ + +
- {schedule.cronExpression} + {schedule.cronExpression}
@@ -338,28 +475,311 @@ export const CronJobBuilder: React.FC = ({

{getScheduleDescription(schedule)}

- -
-
- toggleScheduleStatus(index)} - /> - + +
+
+
+ toggleScheduleStatus(index)} + /> + +
+ +
+ + + + + + + {schedule.mode === 'basic' + ? 'Switch to advanced mode for more options' + : 'Switch back to basic mode'} + + + + + {activeSchedules.length > 1 && ( + + )} +
- - {activeSchedules.length > 1 && ( - + + {schedule.mode === 'advanced' && ( +
+ + + +
+ + Priority Settings +
+
+ +
+
+ + updatePriority(index, value[0])} + /> +
+
+
+
+ + + +
+ + Minutes +
+
+ +
+
+ {Array.from({ length: 60 }, (_, i) => i).map((minute) => ( + + ))} +
+
+
+
+ + + +
+ + Hours +
+
+ +
+
+ {Array.from({ length: 24 }, (_, i) => i).map((hour) => ( + + ))} +
+
+
+
+ + + +
+ + Days of Week +
+
+ +
+
+ {weekdays.map((day) => ( + + ))} +
+
+
+
+ + + +
+ + Days of Month +
+
+ +
+
+ {Array.from({ length: 31 }, (_, i) => i + 1).map((day) => ( + + ))} +
+
+
+
+ + + +
+ + Months +
+
+ +
+
+ {[ + 'January', 'February', 'March', 'April', + 'May', 'June', 'July', 'August', + 'September', 'October', 'November', 'December' + ].map((month, idx) => ( + + ))} +
+
+
+
+
+
)}