feat: add Cron Editor tool
Visual cron expression editor with field-by-field builder, presets select, human-readable description, and live schedule preview showing next occurrences. Registered in tools registry with CronIcon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
91
components/cron/CronPresets.tsx
Normal file
91
components/cron/CronPresets.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
interface Preset {
|
||||
label: string;
|
||||
expr: string;
|
||||
}
|
||||
|
||||
interface PresetGroup {
|
||||
label: string;
|
||||
items: Preset[];
|
||||
}
|
||||
|
||||
const PRESET_GROUPS: PresetGroup[] = [
|
||||
{
|
||||
label: 'Common',
|
||||
items: [
|
||||
{ label: 'Every minute', expr: '* * * * *' },
|
||||
{ label: 'Every 5 min', expr: '*/5 * * * *' },
|
||||
{ label: 'Every 15 min', expr: '*/15 * * * *' },
|
||||
{ label: 'Every 30 min', expr: '*/30 * * * *' },
|
||||
{ label: 'Every hour', expr: '0 * * * *' },
|
||||
{ label: 'Every 6 hours', expr: '0 */6 * * *' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Daily',
|
||||
items: [
|
||||
{ label: 'Midnight', expr: '0 0 * * *' },
|
||||
{ label: '6 AM', expr: '0 6 * * *' },
|
||||
{ label: '9 AM', expr: '0 9 * * *' },
|
||||
{ label: 'Noon', expr: '0 12 * * *' },
|
||||
{ label: 'Twice daily', expr: '0 6,18 * * *' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Weekly',
|
||||
items: [
|
||||
{ label: 'Weekdays 9 AM', expr: '0 9 * * 1-5' },
|
||||
{ label: 'Monday 9 AM', expr: '0 9 * * 1' },
|
||||
{ label: 'Friday 5 PM', expr: '0 17 * * 5' },
|
||||
{ label: 'Sunday 0 AM', expr: '0 0 * * 0' },
|
||||
{ label: 'Weekends 9 AM', expr: '0 9 * * 0,6' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Periodic',
|
||||
items: [
|
||||
{ label: 'Monthly 1st', expr: '0 0 1 * *' },
|
||||
{ label: '1st & 15th', expr: '0 0 1,15 * *' },
|
||||
{ label: 'Quarterly', expr: '0 0 1 */3 *' },
|
||||
{ label: 'Bi-annual', expr: '0 0 1 1,7 *' },
|
||||
{ label: 'January 1st', expr: '0 0 1 1 *' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface CronPresetsProps {
|
||||
onSelect: (expr: string) => void;
|
||||
current: string;
|
||||
}
|
||||
|
||||
export function CronPresets({ onSelect, current }: CronPresetsProps) {
|
||||
const allExprs = PRESET_GROUPS.flatMap(g => g.items.map(i => i.expr));
|
||||
const isPreset = allExprs.includes(current);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<select
|
||||
value={isPreset ? current : ''}
|
||||
onChange={(e) => { if (e.target.value) onSelect(e.target.value); }}
|
||||
className="w-full appearance-none bg-muted/20 border border-border/30 rounded-lg px-3 py-1.5 pr-8 text-xs font-mono text-muted-foreground focus:border-primary/50 focus:outline-none transition-colors cursor-pointer hover:border-border/50"
|
||||
>
|
||||
<option value="" disabled>
|
||||
{isPreset ? '' : 'Quick preset…'}
|
||||
</option>
|
||||
{PRESET_GROUPS.map((group) => (
|
||||
<optgroup key={group.label} label={group.label}>
|
||||
{group.items.map((preset) => (
|
||||
<option key={preset.expr} value={preset.expr}>
|
||||
{preset.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 w-3 h-3 pointer-events-none text-muted-foreground/40" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user