@gravito/zenith 1.1.3 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +28 -10
  2. package/dist/bin.js +43235 -76691
  3. package/dist/client/index.html +13 -0
  4. package/dist/server/index.js +43235 -76691
  5. package/package.json +16 -7
  6. package/CHANGELOG.md +0 -62
  7. package/Dockerfile +0 -46
  8. package/Dockerfile.demo-worker +0 -29
  9. package/bin/flux-console.ts +0 -2
  10. package/doc/ECOSYSTEM_EXPANSION_RFC.md +0 -130
  11. package/docker-compose.yml +0 -40
  12. package/docs/ALERTING_GUIDE.md +0 -71
  13. package/docs/DEPLOYMENT.md +0 -157
  14. package/docs/DOCS_INTERNAL.md +0 -73
  15. package/docs/LARAVEL_ZENITH_ROADMAP.md +0 -109
  16. package/docs/QUASAR_MASTER_PLAN.md +0 -140
  17. package/docs/QUICK_TEST_GUIDE.md +0 -72
  18. package/docs/ROADMAP.md +0 -85
  19. package/docs/integrations/LARAVEL.md +0 -207
  20. package/postcss.config.js +0 -6
  21. package/scripts/debug_redis_keys.ts +0 -24
  22. package/scripts/flood-logs.ts +0 -21
  23. package/scripts/seed.ts +0 -213
  24. package/scripts/verify-throttle.ts +0 -49
  25. package/scripts/worker.ts +0 -124
  26. package/specs/PULSE_SPEC.md +0 -86
  27. package/src/bin.ts +0 -6
  28. package/src/client/App.tsx +0 -72
  29. package/src/client/Layout.tsx +0 -669
  30. package/src/client/Sidebar.tsx +0 -112
  31. package/src/client/ThroughputChart.tsx +0 -158
  32. package/src/client/WorkerStatus.tsx +0 -202
  33. package/src/client/components/BrandIcons.tsx +0 -168
  34. package/src/client/components/ConfirmDialog.tsx +0 -134
  35. package/src/client/components/JobInspector.tsx +0 -487
  36. package/src/client/components/LogArchiveModal.tsx +0 -432
  37. package/src/client/components/NotificationBell.tsx +0 -212
  38. package/src/client/components/PageHeader.tsx +0 -47
  39. package/src/client/components/Toaster.tsx +0 -90
  40. package/src/client/components/UserProfileDropdown.tsx +0 -186
  41. package/src/client/contexts/AuthContext.tsx +0 -105
  42. package/src/client/contexts/NotificationContext.tsx +0 -128
  43. package/src/client/index.css +0 -172
  44. package/src/client/main.tsx +0 -15
  45. package/src/client/pages/LoginPage.tsx +0 -164
  46. package/src/client/pages/MetricsPage.tsx +0 -445
  47. package/src/client/pages/OverviewPage.tsx +0 -519
  48. package/src/client/pages/PulsePage.tsx +0 -409
  49. package/src/client/pages/QueuesPage.tsx +0 -378
  50. package/src/client/pages/SchedulesPage.tsx +0 -535
  51. package/src/client/pages/SettingsPage.tsx +0 -1001
  52. package/src/client/pages/WorkersPage.tsx +0 -380
  53. package/src/client/pages/index.ts +0 -8
  54. package/src/client/utils.ts +0 -15
  55. package/src/server/config/ServerConfigManager.ts +0 -90
  56. package/src/server/index.ts +0 -860
  57. package/src/server/middleware/auth.ts +0 -127
  58. package/src/server/services/AlertService.ts +0 -321
  59. package/src/server/services/CommandService.ts +0 -136
  60. package/src/server/services/LogStreamProcessor.ts +0 -93
  61. package/src/server/services/MaintenanceScheduler.ts +0 -78
  62. package/src/server/services/PulseService.ts +0 -148
  63. package/src/server/services/QueueMetricsCollector.ts +0 -138
  64. package/src/server/services/QueueService.ts +0 -924
  65. package/src/shared/types.ts +0 -223
  66. package/tailwind.config.js +0 -80
  67. package/tests/placeholder.test.ts +0 -7
  68. package/tsconfig.json +0 -29
  69. package/tsconfig.node.json +0 -10
  70. package/vite.config.ts +0 -27
@@ -1,535 +0,0 @@
1
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2
- import { format } from 'date-fns'
3
- import { AnimatePresence, motion } from 'framer-motion'
4
- import {
5
- Activity,
6
- ArrowRight,
7
- Calendar,
8
- CheckCircle2,
9
- ChevronDown,
10
- Clock,
11
- Filter,
12
- Play,
13
- Plus,
14
- RefreshCcw,
15
- Search,
16
- Trash2,
17
- X,
18
- } from 'lucide-react'
19
- import { useState } from 'react'
20
- import { ConfirmDialog } from '../components/ConfirmDialog'
21
- import { useNotifications } from '../contexts/NotificationContext'
22
-
23
- // Update interface to match actual API response
24
- interface ScheduleInfo {
25
- id: string
26
- cron: string
27
- queue: string
28
- job: {
29
- className?: string
30
- name?: string
31
- data?: any
32
- payload?: any
33
- }
34
- lastRun?: number | string
35
- nextRun?: number | string
36
- }
37
-
38
- interface QueueListItem {
39
- name: string
40
- waiting: number
41
- delayed: number
42
- failed: number
43
- active: number
44
- paused: boolean
45
- }
46
-
47
- /**
48
- * Task Schedules Page.
49
- *
50
- * View and manage all recurring jobs and cron tasks. Users can register new
51
- * schedules, manually trigger execution, or delete existing schedules.
52
- *
53
- * @public
54
- * @since 3.0.0
55
- */
56
- export function SchedulesPage() {
57
- const queryClient = useQueryClient()
58
- const [isModalOpen, setIsModalOpen] = useState(false)
59
- const [formData, setFormData] = useState({
60
- id: '',
61
- cron: '',
62
- queue: 'default',
63
- className: '',
64
- payload: '{}',
65
- })
66
-
67
- const [confirmRunId, setConfirmRunId] = useState<string | null>(null)
68
- const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null)
69
-
70
- const { addNotification } = useNotifications()
71
-
72
- const { data: queueData } = useQuery<{ queues: QueueListItem[] }>({
73
- queryKey: ['queues'],
74
- queryFn: () => fetch('/api/queues').then((res) => res.json()),
75
- })
76
-
77
- const { data, isLoading } = useQuery<{ schedules: ScheduleInfo[] }>({
78
- queryKey: ['schedules'],
79
- queryFn: () => fetch('/api/schedules').then((res) => res.json()),
80
- })
81
-
82
- const runMutation = useMutation({
83
- mutationFn: (id: string) => fetch(`/api/schedules/run/${id}`, { method: 'POST' }),
84
- onSuccess: (_, id) => {
85
- queryClient.invalidateQueries({ queryKey: ['queues'] })
86
- addNotification({
87
- type: 'success',
88
- title: 'Schedule Triggered',
89
- message: `Job ${id} has been manually pushed to the queue.`,
90
- })
91
- setConfirmRunId(null)
92
- },
93
- onError: (err: any) => {
94
- addNotification({
95
- type: 'error',
96
- title: 'Trigger Failed',
97
- message: err.message || 'Failed to run schedule manually.',
98
- })
99
- setConfirmRunId(null)
100
- },
101
- })
102
-
103
- const deleteMutation = useMutation({
104
- mutationFn: (id: string) => fetch(`/api/schedules/${id}`, { method: 'DELETE' }),
105
- onSuccess: (_, id) => {
106
- queryClient.invalidateQueries({ queryKey: ['schedules'] })
107
- addNotification({
108
- type: 'info',
109
- title: 'Schedule Deleted',
110
- message: `Schedule ${id} was removed.`,
111
- })
112
- setConfirmDeleteId(null)
113
- },
114
- })
115
-
116
- const registerMutation = useMutation({
117
- mutationFn: (body: any) =>
118
- fetch('/api/schedules', {
119
- method: 'POST',
120
- headers: { 'Content-Type': 'application/json' },
121
- body: JSON.stringify(body),
122
- }),
123
- onSuccess: () => {
124
- queryClient.invalidateQueries({ queryKey: ['schedules'] })
125
- setIsModalOpen(false)
126
- setFormData({ id: '', cron: '', queue: 'default', className: '', payload: '{}' })
127
- addNotification({
128
- type: 'success',
129
- title: 'Schedule Registered',
130
- message: 'New recurring task is now active.',
131
- })
132
- },
133
- })
134
-
135
- const schedules = data?.schedules || []
136
- const queues = queueData?.queues || []
137
-
138
- const handleSubmit = (e: React.FormEvent) => {
139
- e.preventDefault()
140
- try {
141
- const payload = JSON.parse(formData.payload)
142
- registerMutation.mutate({
143
- id: formData.id,
144
- cron: formData.cron,
145
- queue: formData.queue,
146
- job: {
147
- className: formData.className,
148
- payload,
149
- },
150
- })
151
- } catch (_err) {
152
- alert('Invalid JSON payload')
153
- }
154
- }
155
-
156
- return (
157
- <div className="space-y-8 max-w-6xl">
158
- {/* Header */}
159
- <div className="flex justify-between items-end">
160
- <div>
161
- <h1 className="text-4xl font-black tracking-tighter">Schedules</h1>
162
- <p className="text-muted-foreground mt-2 text-sm font-bold opacity-60 uppercase tracking-widest text-[10px]">
163
- Manage recurring tasks and cron workflows.
164
- </p>
165
- </div>
166
- <button
167
- type="button"
168
- onClick={() => setIsModalOpen(true)}
169
- className="px-6 py-3 bg-primary text-primary-foreground rounded-xl flex items-center gap-2 text-sm font-black uppercase tracking-widest shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-all"
170
- >
171
- <Plus size={18} />
172
- New Schedule
173
- </button>
174
- </div>
175
-
176
- {/* Stats Overview */}
177
- <div className="grid grid-cols-3 gap-6">
178
- <div className="card-premium p-5 flex items-center gap-5 border-l-4 border-primary">
179
- <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-[0_0_15px_rgba(0,240,255,0.1)]">
180
- <Activity size={24} />
181
- </div>
182
- <div>
183
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
184
- Active Schedules
185
- </p>
186
- <p className="text-2xl font-black font-mono tracking-tighter">
187
- {isLoading ? '...' : schedules.length}
188
- </p>
189
- </div>
190
- </div>
191
- <div className="card-premium p-5 flex items-center gap-5 border-l-4 border-indigo-500/40">
192
- <div className="w-12 h-12 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-400 border border-indigo-500/20 shadow-[0_0_15px_rgba(99,102,241,0.1)]">
193
- <Calendar size={24} />
194
- </div>
195
- <div>
196
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
197
- Total Executions
198
- </p>
199
- <p className="text-2xl font-black font-mono tracking-tighter">---</p>
200
- </div>
201
- </div>
202
- <div className="card-premium p-5 flex items-center gap-5 border-l-4 border-emerald-500/40">
203
- <div className="w-12 h-12 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-400 border border-emerald-500/20 shadow-[0_0_15px_rgba(16,185,129,0.1)]">
204
- <CheckCircle2 size={24} />
205
- </div>
206
- <div>
207
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
208
- Health Score
209
- </p>
210
- <p className="text-2xl font-black text-emerald-500 font-mono tracking-tighter">99.8%</p>
211
- </div>
212
- </div>
213
- </div>
214
-
215
- {/* Toolbar */}
216
- <div className="card-premium p-3 flex gap-4 items-center">
217
- <div className="relative flex-1">
218
- <Search
219
- className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground/30"
220
- size={18}
221
- />
222
- <input
223
- type="text"
224
- placeholder="Search schedules by name, cron or ID..."
225
- className="w-full bg-black/20 border border-white/5 rounded-xl pl-12 pr-4 py-3 text-xs font-bold font-mono placeholder:text-white/10 focus:ring-1 focus:ring-primary/30 outline-none transition-all"
226
- />
227
- </div>
228
- <button
229
- type="button"
230
- className="p-3 bg-zinc-900/40 border border-white/5 rounded-xl hover:bg-zinc-800 transition-all text-muted-foreground/60"
231
- >
232
- <Filter size={18} />
233
- </button>
234
- </div>
235
-
236
- {/* Loading State */}
237
- {isLoading && (
238
- <div className="space-y-4">
239
- {[1, 2, 3].map((i) => (
240
- <div key={i} className="card-premium p-6 h-32 animate-pulse bg-muted/20" />
241
- ))}
242
- </div>
243
- )}
244
-
245
- {/* Implementation Empty State if zero */}
246
- {schedules.length === 0 && !isLoading && (
247
- <div className="card-premium p-20 flex flex-col items-center justify-center text-center opacity-60">
248
- <div className="w-20 h-20 rounded-3xl bg-muted flex items-center justify-center mb-6">
249
- <Clock size={32} className="text-muted-foreground/30" />
250
- </div>
251
- <h3 className="text-xl font-bold mb-2">No Scheduled Tasks Yet</h3>
252
- <p className="text-sm text-muted-foreground max-w-sm mb-8">
253
- Register recurring jobs using the @gravito/stream scheduler to see them appear here.
254
- </p>
255
- </div>
256
- )}
257
-
258
- {/* Schedules Grid */}
259
- <div className="grid grid-cols-1 gap-4">
260
- {schedules.map((schedule: ScheduleInfo) => (
261
- <div
262
- key={schedule.id}
263
- className="card-premium p-6 group hover:border-primary/20 transition-all border-l-4 border-l-zinc-800 hover:border-l-primary"
264
- >
265
- <div className="flex items-start justify-between">
266
- <div className="flex gap-6">
267
- <div className="w-16 h-16 rounded-2xl bg-zinc-900 border border-white/5 flex items-center justify-center text-muted-foreground/40 group-hover:text-primary group-hover:border-primary/20 group-hover:shadow-[0_0_20px_rgba(0,240,255,0.1)] transition-all">
268
- <Clock size={32} />
269
- </div>
270
- <div className="space-y-2">
271
- <div className="flex items-center gap-3">
272
- <h3 className="text-xl font-black font-heading tracking-tight text-white/90">
273
- {schedule.id}
274
- </h3>
275
- <span className="px-2 py-0.5 bg-emerald-500/10 text-emerald-500 text-[9px] font-black uppercase tracking-widest rounded border border-emerald-500/20">
276
- Active
277
- </span>
278
- </div>
279
- <div className="flex items-center gap-5 text-[11px] font-bold text-muted-foreground/60 font-mono uppercase tracking-tighter">
280
- <span className="flex items-center gap-2 bg-black/40 px-2 py-0.5 rounded border border-white/5">
281
- <Clock size={12} className="text-primary/60" /> {schedule.cron}
282
- </span>
283
- <span className="flex items-center gap-2 bg-black/40 px-2 py-0.5 rounded border border-white/5">
284
- <ArrowRight size={12} className="text-primary/60" /> {schedule.queue}
285
- </span>
286
- <span className="text-primary/40">{schedule.job.className}</span>
287
- </div>
288
- </div>
289
- </div>
290
-
291
- <div className="flex items-center gap-2">
292
- <button
293
- type="button"
294
- disabled={runMutation.isPending}
295
- onClick={() => setConfirmRunId(schedule.id)}
296
- className="px-5 py-2.5 bg-primary/10 hover:bg-primary text-primary hover:text-black rounded-xl text-[10px] font-black uppercase tracking-[0.2em] transition-all active:scale-95 flex items-center gap-2 disabled:opacity-50 border border-primary/20 font-heading shadow-lg shadow-transparent hover:shadow-primary/20"
297
- >
298
- {runMutation.isPending && runMutation.variables === schedule.id ? (
299
- <>
300
- <RefreshCcw size={14} className="animate-spin" />
301
- Triggering...
302
- </>
303
- ) : (
304
- 'Execute Now'
305
- )}
306
- </button>
307
- <button
308
- type="button"
309
- onClick={() => setConfirmDeleteId(schedule.id)}
310
- className="p-2.5 bg-red-500/5 hover:bg-red-500 hover:text-white rounded-xl transition-all text-red-500/60 border border-transparent hover:border-red-500/20"
311
- >
312
- <Trash2 size={18} />
313
- </button>
314
- </div>
315
- </div>
316
-
317
- <div className="mt-8 grid grid-cols-2 gap-6 pt-6 border-t border-white/5">
318
- <div className="flex items-center gap-4 group/stat">
319
- <div className="w-10 h-10 rounded-xl bg-zinc-900 border border-white/5 flex items-center justify-center text-muted-foreground/20 group-hover/stat:text-primary transition-colors">
320
- <Calendar size={16} />
321
- </div>
322
- <div>
323
- <p className="text-[9px] font-black text-muted-foreground/40 uppercase tracking-[0.2em] mb-1 font-heading">
324
- Previous Burst
325
- </p>
326
- <p className="text-xs font-black font-mono text-white/60 tabular-nums">
327
- {schedule.lastRun
328
- ? format(new Date(schedule.lastRun), 'HH:mm:ss / MMM dd')
329
- : 'N/A'}
330
- </p>
331
- </div>
332
- </div>
333
- <div className="flex items-center gap-4 group/stat">
334
- <div className="w-10 h-10 rounded-xl bg-primary/5 border border-primary/10 flex items-center justify-center text-primary/40 group-hover/stat:text-primary transition-colors">
335
- <Play size={16} />
336
- </div>
337
- <div>
338
- <p className="text-[9px] font-black text-primary/40 uppercase tracking-[0.2em] mb-1 font-heading">
339
- Next Sequence
340
- </p>
341
- <p className="text-xs font-black font-mono text-primary/80 tabular-nums uppercase">
342
- {schedule.nextRun
343
- ? format(new Date(schedule.nextRun), 'HH:mm:ss / MMM dd')
344
- : 'Pending'}
345
- </p>
346
- </div>
347
- </div>
348
- </div>
349
- </div>
350
- ))}
351
- </div>
352
-
353
- {/* New Schedule Modal */}
354
- <AnimatePresence>
355
- {isModalOpen && (
356
- <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
357
- <motion.div
358
- initial={{ opacity: 0 }}
359
- animate={{ opacity: 1 }}
360
- exit={{ opacity: 0 }}
361
- onClick={() => setIsModalOpen(false)}
362
- className="absolute inset-0 bg-black/60 backdrop-blur-sm"
363
- />
364
- <motion.div
365
- initial={{ opacity: 0, scale: 0.95, y: 20 }}
366
- animate={{ opacity: 1, scale: 1, y: 0 }}
367
- exit={{ opacity: 0, scale: 0.95, y: 20 }}
368
- className="relative w-full max-w-lg bg-card border border-border/50 rounded-3xl shadow-2xl overflow-hidden"
369
- >
370
- <div className="p-6 border-b flex items-center justify-between bg-muted/5">
371
- <div className="flex items-center gap-3">
372
- <div className="w-10 h-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary">
373
- <Plus size={20} />
374
- </div>
375
- <div>
376
- <h2 className="text-lg font-bold leading-none">Register Schedule</h2>
377
- <p className="text-[10px] font-black text-muted-foreground uppercase tracking-widest mt-1">
378
- Add new recurring job
379
- </p>
380
- </div>
381
- </div>
382
- <button
383
- type="button"
384
- onClick={() => setIsModalOpen(false)}
385
- className="p-2 hover:bg-muted rounded-full transition-all"
386
- >
387
- <X size={20} />
388
- </button>
389
- </div>
390
-
391
- <form onSubmit={handleSubmit} className="p-6 space-y-5">
392
- <div className="space-y-2">
393
- <label
394
- htmlFor="schedule-id"
395
- className="text-[10px] font-black uppercase tracking-widest text-muted-foreground ml-1"
396
- >
397
- Schedule Identifier (ID)
398
- </label>
399
- <input
400
- id="schedule-id"
401
- required
402
- type="text"
403
- placeholder="daily-cleanup-job"
404
- className="w-full bg-muted/40 border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-primary/30 outline-none transition-all"
405
- value={formData.id}
406
- onChange={(e) => setFormData({ ...formData, id: e.target.value })}
407
- />
408
- </div>
409
-
410
- <div className="grid grid-cols-2 gap-4">
411
- <div className="space-y-2">
412
- <label
413
- htmlFor="cron-expression"
414
- className="text-[10px] font-black uppercase tracking-widest text-muted-foreground ml-1"
415
- >
416
- Cron Expression
417
- </label>
418
- <input
419
- id="cron-expression"
420
- required
421
- type="text"
422
- placeholder="* * * * *"
423
- className="w-full bg-muted/40 border-border/50 rounded-xl px-4 py-3 text-sm font-mono focus:ring-1 focus:ring-primary/30 outline-none transition-all"
424
- value={formData.cron}
425
- onChange={(e) => setFormData({ ...formData, cron: e.target.value })}
426
- />
427
- </div>
428
- <div className="space-y-2">
429
- <label
430
- htmlFor="target-queue"
431
- className="text-[10px] font-black uppercase tracking-widest text-muted-foreground ml-1"
432
- >
433
- Target Queue
434
- </label>
435
- <div className="relative">
436
- <select
437
- id="target-queue"
438
- className="w-full bg-muted/40 border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-primary/30 outline-none transition-all appearance-none cursor-pointer"
439
- value={formData.queue}
440
- onChange={(e) => setFormData({ ...formData, queue: e.target.value })}
441
- >
442
- {queues.map((q: any) => (
443
- <option key={q.name} value={q.name}>
444
- {q.name}
445
- </option>
446
- ))}
447
- </select>
448
- <ChevronDown
449
- className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none"
450
- size={16}
451
- />
452
- </div>
453
- </div>
454
- </div>
455
-
456
- <div className="space-y-2">
457
- <label
458
- htmlFor="job-class-name"
459
- className="text-[10px] font-black uppercase tracking-widest text-muted-foreground ml-1"
460
- >
461
- Job Class Name
462
- </label>
463
- <input
464
- id="job-class-name"
465
- required
466
- type="text"
467
- placeholder="CleanupJob"
468
- className="w-full bg-muted/40 border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-primary/30 outline-none transition-all font-mono"
469
- value={formData.className}
470
- onChange={(e) => setFormData({ ...formData, className: e.target.value })}
471
- />
472
- </div>
473
-
474
- <div className="space-y-2">
475
- <label
476
- htmlFor="job-payload"
477
- className="text-[10px] font-black uppercase tracking-widest text-muted-foreground ml-1"
478
- >
479
- Job Payload (JSON)
480
- </label>
481
- <textarea
482
- id="job-payload"
483
- rows={3}
484
- className="w-full bg-muted/40 border-border/50 rounded-xl px-4 py-3 text-sm font-mono focus:ring-1 focus:ring-primary/30 outline-none transition-all resize-none"
485
- value={formData.payload}
486
- onChange={(e) => setFormData({ ...formData, payload: e.target.value })}
487
- />
488
- </div>
489
-
490
- <div className="pt-4 flex gap-3">
491
- <button
492
- type="button"
493
- onClick={() => setIsModalOpen(false)}
494
- className="flex-1 py-3 border border-border/50 rounded-xl text-xs font-black uppercase tracking-widest hover:bg-muted transition-all"
495
- >
496
- Cancel
497
- </button>
498
- <button
499
- type="submit"
500
- disabled={registerMutation.isPending}
501
- className="flex-1 py-3 bg-primary text-primary-foreground rounded-xl text-xs font-black uppercase tracking-widest shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-all disabled:opacity-50"
502
- >
503
- {registerMutation.isPending ? 'Registering...' : 'Register Schedule'}
504
- </button>
505
- </div>
506
- </form>
507
- </motion.div>
508
- </div>
509
- )}
510
- </AnimatePresence>
511
-
512
- <ConfirmDialog
513
- open={!!confirmRunId}
514
- title="Manual trigger confirmation"
515
- message={`Are you sure you want to run "${confirmRunId}" immediately?\nThis will bypass the next scheduled time and push a job to the "${schedules.find((s) => s.id === confirmRunId)?.queue}" queue.`}
516
- confirmText="Run Now"
517
- variant="info"
518
- isProcessing={runMutation.isPending}
519
- onConfirm={() => confirmRunId && runMutation.mutate(confirmRunId)}
520
- onCancel={() => setConfirmRunId(null)}
521
- />
522
-
523
- <ConfirmDialog
524
- open={!!confirmDeleteId}
525
- title="Delete Schedule"
526
- message={`Are you sure you want to delete "${confirmDeleteId}"?\nThis action cannot be undone and recurring jobs for this schedule will stop.`}
527
- confirmText="Delete"
528
- variant="danger"
529
- isProcessing={deleteMutation.isPending}
530
- onConfirm={() => confirmDeleteId && deleteMutation.mutate(confirmDeleteId)}
531
- onCancel={() => setConfirmDeleteId(null)}
532
- />
533
- </div>
534
- )
535
- }