@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.
- package/README.md +28 -10
- package/dist/bin.js +43235 -76691
- package/dist/client/index.html +13 -0
- package/dist/server/index.js +43235 -76691
- package/package.json +16 -7
- package/CHANGELOG.md +0 -62
- package/Dockerfile +0 -46
- package/Dockerfile.demo-worker +0 -29
- package/bin/flux-console.ts +0 -2
- package/doc/ECOSYSTEM_EXPANSION_RFC.md +0 -130
- package/docker-compose.yml +0 -40
- package/docs/ALERTING_GUIDE.md +0 -71
- package/docs/DEPLOYMENT.md +0 -157
- package/docs/DOCS_INTERNAL.md +0 -73
- package/docs/LARAVEL_ZENITH_ROADMAP.md +0 -109
- package/docs/QUASAR_MASTER_PLAN.md +0 -140
- package/docs/QUICK_TEST_GUIDE.md +0 -72
- package/docs/ROADMAP.md +0 -85
- package/docs/integrations/LARAVEL.md +0 -207
- package/postcss.config.js +0 -6
- package/scripts/debug_redis_keys.ts +0 -24
- package/scripts/flood-logs.ts +0 -21
- package/scripts/seed.ts +0 -213
- package/scripts/verify-throttle.ts +0 -49
- package/scripts/worker.ts +0 -124
- package/specs/PULSE_SPEC.md +0 -86
- package/src/bin.ts +0 -6
- package/src/client/App.tsx +0 -72
- package/src/client/Layout.tsx +0 -669
- package/src/client/Sidebar.tsx +0 -112
- package/src/client/ThroughputChart.tsx +0 -158
- package/src/client/WorkerStatus.tsx +0 -202
- package/src/client/components/BrandIcons.tsx +0 -168
- package/src/client/components/ConfirmDialog.tsx +0 -134
- package/src/client/components/JobInspector.tsx +0 -487
- package/src/client/components/LogArchiveModal.tsx +0 -432
- package/src/client/components/NotificationBell.tsx +0 -212
- package/src/client/components/PageHeader.tsx +0 -47
- package/src/client/components/Toaster.tsx +0 -90
- package/src/client/components/UserProfileDropdown.tsx +0 -186
- package/src/client/contexts/AuthContext.tsx +0 -105
- package/src/client/contexts/NotificationContext.tsx +0 -128
- package/src/client/index.css +0 -172
- package/src/client/main.tsx +0 -15
- package/src/client/pages/LoginPage.tsx +0 -164
- package/src/client/pages/MetricsPage.tsx +0 -445
- package/src/client/pages/OverviewPage.tsx +0 -519
- package/src/client/pages/PulsePage.tsx +0 -409
- package/src/client/pages/QueuesPage.tsx +0 -378
- package/src/client/pages/SchedulesPage.tsx +0 -535
- package/src/client/pages/SettingsPage.tsx +0 -1001
- package/src/client/pages/WorkersPage.tsx +0 -380
- package/src/client/pages/index.ts +0 -8
- package/src/client/utils.ts +0 -15
- package/src/server/config/ServerConfigManager.ts +0 -90
- package/src/server/index.ts +0 -860
- package/src/server/middleware/auth.ts +0 -127
- package/src/server/services/AlertService.ts +0 -321
- package/src/server/services/CommandService.ts +0 -136
- package/src/server/services/LogStreamProcessor.ts +0 -93
- package/src/server/services/MaintenanceScheduler.ts +0 -78
- package/src/server/services/PulseService.ts +0 -148
- package/src/server/services/QueueMetricsCollector.ts +0 -138
- package/src/server/services/QueueService.ts +0 -924
- package/src/shared/types.ts +0 -223
- package/tailwind.config.js +0 -80
- package/tests/placeholder.test.ts +0 -7
- package/tsconfig.json +0 -29
- package/tsconfig.node.json +0 -10
- 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
|
-
}
|