@gravito/zenith 1.1.2 → 1.1.3

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 (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +77 -22
  3. package/README.zh-TW.md +88 -0
  4. package/dist/bin.js +64681 -15842
  5. package/dist/client/assets/index-C80c1frR.css +1 -0
  6. package/dist/client/assets/index-CrWem9u3.js +434 -0
  7. package/dist/server/index.js +64681 -15842
  8. package/package.json +9 -7
  9. package/postcss.config.js +4 -4
  10. package/src/client/Layout.tsx +36 -39
  11. package/src/client/Sidebar.tsx +7 -7
  12. package/src/client/ThroughputChart.tsx +31 -17
  13. package/src/client/WorkerStatus.tsx +56 -80
  14. package/src/client/components/ConfirmDialog.tsx +22 -14
  15. package/src/client/components/JobInspector.tsx +95 -162
  16. package/src/client/index.css +29 -31
  17. package/src/client/pages/LoginPage.tsx +33 -31
  18. package/src/client/pages/MetricsPage.tsx +65 -37
  19. package/src/client/pages/OverviewPage.tsx +30 -28
  20. package/src/client/pages/PulsePage.tsx +111 -190
  21. package/src/client/pages/QueuesPage.tsx +82 -83
  22. package/src/client/pages/SchedulesPage.tsx +56 -61
  23. package/src/client/pages/SettingsPage.tsx +118 -137
  24. package/src/client/pages/WorkersPage.tsx +101 -115
  25. package/src/server/services/CommandService.ts +8 -9
  26. package/src/server/services/PulseService.ts +61 -4
  27. package/src/server/services/QueueService.ts +293 -0
  28. package/src/shared/types.ts +38 -13
  29. package/tailwind.config.js +75 -68
  30. package/tsconfig.json +28 -37
  31. package/tsconfig.node.json +9 -11
  32. package/dist/client/assets/index-BSMp8oq_.js +0 -436
  33. package/dist/client/assets/index-BwxlHx-_.css +0 -1
  34. package/dist/client/index.html +0 -13
  35. package/src/client/index.html +0 -12
  36. /package/{ECOSYSTEM_EXPANSION_RFC.md → doc/ECOSYSTEM_EXPANSION_RFC.md} +0 -0
@@ -10,7 +10,6 @@ import {
10
10
  Play,
11
11
  RefreshCcw,
12
12
  Search,
13
- Trash2,
14
13
  XCircle,
15
14
  } from 'lucide-react'
16
15
  import React from 'react'
@@ -129,59 +128,68 @@ export function QueuesPage() {
129
128
 
130
129
  {/* Summary Cards */}
131
130
  <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
132
- <div className="card-premium p-6">
133
- <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest mb-1">
131
+ <div className="card-premium p-5 flex flex-col justify-between">
132
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] mb-2 font-heading">
134
133
  Total Waiting
135
134
  </p>
136
- <p className="text-3xl font-black">{totalWaiting.toLocaleString()}</p>
135
+ <p className="text-3xl font-black font-mono tracking-tighter">
136
+ {totalWaiting.toLocaleString()}
137
+ </p>
137
138
  </div>
138
- <div className="card-premium p-6">
139
- <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest mb-1">
139
+ <div className="card-premium p-5 flex flex-col justify-between">
140
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] mb-2 font-heading">
140
141
  Total Delayed
141
142
  </p>
142
- <p className="text-3xl font-black text-amber-500">{totalDelayed.toLocaleString()}</p>
143
+ <p className="text-3xl font-black text-amber-500 font-mono tracking-tighter">
144
+ {totalDelayed.toLocaleString()}
145
+ </p>
143
146
  </div>
144
- <div className="card-premium p-6">
145
- <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest mb-1">
147
+ <div className="card-premium p-5 flex flex-col justify-between">
148
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] mb-2 font-heading">
146
149
  Total Failed
147
150
  </p>
148
- <p className="text-3xl font-black text-red-500">{totalFailed.toLocaleString()}</p>
151
+ <p className="text-3xl font-black text-red-500 font-mono tracking-tighter">
152
+ {totalFailed.toLocaleString()}
153
+ </p>
149
154
  </div>
150
- <div className="card-premium p-6">
151
- <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest mb-1">
155
+ <div className="card-premium p-5 flex flex-col justify-between">
156
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] mb-2 font-heading">
152
157
  Currently Active
153
158
  </p>
154
- <p className="text-3xl font-black text-green-500">{totalActive.toLocaleString()}</p>
159
+ <p className="text-3xl font-black text-emerald-500 font-mono tracking-tighter">
160
+ {totalActive.toLocaleString()}
161
+ </p>
155
162
  </div>
156
163
  </div>
157
164
 
158
165
  {/* Filters */}
159
- <div className="card-premium p-4 flex flex-wrap gap-4 items-center">
160
- <div className="relative flex-1 min-w-[200px]">
166
+ <div className="card-premium p-3 flex flex-wrap gap-4 items-center">
167
+ <div className="relative flex-1 min-w-[240px]">
161
168
  <Search
162
- className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
163
- size={18}
169
+ className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/40"
170
+ size={16}
164
171
  />
165
172
  <input
166
173
  type="text"
167
- placeholder="Search queues..."
174
+ aria-label="Filter pipelines"
175
+ placeholder="Filter pipelines..."
168
176
  value={searchQuery}
169
177
  onChange={(e) => setSearchQuery(e.target.value)}
170
- className="w-full bg-muted/40 border border-border/50 rounded-xl py-2.5 pl-10 pr-4 text-sm font-medium placeholder:text-muted-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/20"
178
+ className="w-full bg-black/20 border border-white/5 rounded-lg py-2 pl-10 pr-4 text-xs font-bold placeholder:text-muted-foreground/30 focus:outline-none focus:ring-1 focus:ring-primary/30 transition-all font-mono"
171
179
  />
172
180
  </div>
173
181
  <div className="flex items-center gap-2">
174
- <Filter size={16} className="text-muted-foreground" />
182
+ <Filter size={14} className="text-muted-foreground/40" />
175
183
  {(['all', 'active', 'idle', 'critical'] as const).map((status) => (
176
184
  <button
177
185
  type="button"
178
186
  key={status}
179
187
  onClick={() => setStatusFilter(status)}
180
188
  className={cn(
181
- 'px-3 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all',
189
+ 'px-3 py-1.5 rounded-lg text-[9px] font-black uppercase tracking-widest transition-all border',
182
190
  statusFilter === status
183
- ? 'bg-primary text-primary-foreground'
184
- : 'bg-muted text-muted-foreground hover:bg-muted/80'
191
+ ? 'bg-primary text-primary-foreground border-primary shadow-lg shadow-primary/20'
192
+ : 'bg-muted/40 text-muted-foreground border-transparent hover:bg-muted/60'
185
193
  )}
186
194
  >
187
195
  {status}
@@ -193,69 +201,67 @@ export function QueuesPage() {
193
201
  {/* Queue List */}
194
202
  <div className="card-premium overflow-hidden">
195
203
  <div className="overflow-x-auto">
196
- <table className="w-full text-left">
197
- <thead className="bg-muted/10 text-muted-foreground uppercase text-[10px] font-black tracking-[0.2em]">
204
+ <table className="w-full text-left border-collapse">
205
+ <thead className="bg-black/20 text-muted-foreground/60 uppercase text-[9px] font-black tracking-widest border-b border-white/5">
198
206
  <tr>
199
- <th className="px-6 py-5">Queue Name</th>
200
- <th className="px-6 py-5 text-center">Waiting</th>
201
- <th className="px-6 py-5 text-center">Delayed</th>
202
- <th className="px-6 py-5 text-center">Active</th>
203
- <th className="px-6 py-5 text-center">Failed</th>
204
- <th className="px-6 py-5 text-center">Status</th>
205
- <th className="px-6 py-5 text-right">Actions</th>
207
+ <th className="px-6 py-4">Pipeline Architecture</th>
208
+ <th className="px-6 py-4 text-center">Waiting</th>
209
+ <th className="px-6 py-4 text-center">Delayed</th>
210
+ <th className="px-6 py-4 text-center">Active</th>
211
+ <th className="px-6 py-4 text-center">Failed</th>
212
+ <th className="px-6 py-4 text-center">Status</th>
213
+ <th className="px-6 py-4 text-right">Operations</th>
206
214
  </tr>
207
215
  </thead>
208
- <tbody className="divide-y divide-border/30 text-sm">
216
+ <tbody className="divide-y divide-white/5 text-xs font-mono">
209
217
  {filteredQueues.map((queue) => {
210
218
  const status =
211
219
  queue.failed > 0 ? 'critical' : queue.active > 0 ? 'active' : 'idle'
212
220
  return (
213
- <tr key={queue.name} className="hover:bg-muted/5 transition-colors group">
214
- <td className="px-6 py-5">
215
- <div className="flex items-center gap-3">
216
- <div className="w-10 h-10 bg-primary/5 rounded-xl flex items-center justify-center text-primary group-hover:scale-110 transition-transform">
217
- <ListTree size={20} />
221
+ <tr key={queue.name} className="hover:bg-white/[0.02] transition-colors group">
222
+ <td className="px-6 py-4">
223
+ <div className="flex items-center gap-4">
224
+ <div className="w-10 h-10 bg-zinc-800/50 border border-white/5 rounded-xl flex items-center justify-center text-primary group-hover:scale-110 group-hover:shadow-[0_0_15px_rgba(0,240,255,0.2)] transition-all">
225
+ <ListTree size={18} />
218
226
  </div>
219
- <span className="font-black tracking-tight">{queue.name}</span>
227
+ <span className="font-black tracking-tight text-sm font-heading">
228
+ {queue.name}
229
+ </span>
220
230
  </div>
221
231
  </td>
222
- <td className="px-6 py-5 text-center font-mono font-bold">
232
+ <td className="px-6 py-4 text-center font-bold text-foreground/80">
223
233
  {queue.waiting.toLocaleString()}
224
234
  </td>
225
- <td className="px-6 py-5 text-center font-mono text-amber-500">
226
- {queue.delayed}
227
- </td>
228
- <td className="px-6 py-5 text-center font-mono text-green-500">
229
- {queue.active}
230
- </td>
231
- <td className="px-6 py-5 text-center">
235
+ <td className="px-6 py-4 text-center text-amber-500/80">{queue.delayed}</td>
236
+ <td className="px-6 py-4 text-center text-emerald-500/80">{queue.active}</td>
237
+ <td className="px-6 py-4 text-center">
232
238
  <span
233
239
  className={cn(
234
- 'font-mono font-black',
235
- queue.failed > 0 ? 'text-red-500' : 'text-muted-foreground/40'
240
+ 'font-black',
241
+ queue.failed > 0 ? 'text-red-500' : 'text-muted-foreground/20'
236
242
  )}
237
243
  >
238
244
  {queue.failed}
239
245
  </span>
240
246
  </td>
241
- <td className="px-6 py-5 text-center">
247
+ <td className="px-6 py-4 text-center">
242
248
  <span
243
249
  className={cn(
244
- 'px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-widest border',
250
+ 'px-2.5 py-1 rounded-md text-[8px] font-black uppercase tracking-widest border transition-all',
245
251
  queue.paused
246
- ? 'bg-amber-500/20 text-amber-500 border-amber-500/30'
252
+ ? 'bg-amber-500/10 text-amber-500 border-amber-500/20'
247
253
  : status === 'critical'
248
- ? 'bg-red-500 text-white border-red-600'
254
+ ? 'bg-red-500 text-white border-red-600 shadow-[0_0_10px_rgba(239,68,68,0.3)] animate-pulse'
249
255
  : status === 'active'
250
- ? 'bg-green-500/10 text-green-500 border-green-500/20'
251
- : 'bg-muted/40 text-muted-foreground border-transparent'
256
+ ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20 shadow-[0_0_10px_rgba(16,185,129,0.1)]'
257
+ : 'bg-zinc-800/50 text-muted-foreground/40 border-transparent'
252
258
  )}
253
259
  >
254
260
  {queue.paused ? 'paused' : status}
255
261
  </span>
256
262
  </td>
257
- <td className="px-6 py-5">
258
- <div className="flex justify-end gap-2 items-center">
263
+ <td className="px-6 py-4 text-right">
264
+ <div className="flex justify-end gap-1.5 items-center">
259
265
  {/* Pause/Resume button */}
260
266
  <button
261
267
  type="button"
@@ -265,15 +271,21 @@ export function QueuesPage() {
265
271
  queryClient.invalidateQueries({ queryKey: ['queues'] })
266
272
  }}
267
273
  className={cn(
268
- 'p-2 rounded-lg transition-all',
274
+ 'p-2 rounded-lg transition-all border border-transparent hover:border-white/10',
269
275
  queue.paused
270
- ? 'text-green-500 hover:bg-green-500/10'
276
+ ? 'text-emerald-500 bg-emerald-500/5 hover:bg-emerald-500/10'
271
277
  : 'text-muted-foreground hover:bg-amber-500/10 hover:text-amber-500'
272
278
  )}
273
279
  title={queue.paused ? 'Resume Queue' : 'Pause Queue'}
280
+ aria-label={
281
+ queue.paused ? `Resume ${queue.name}` : `Pause ${queue.name}`
282
+ }
274
283
  >
275
- {queue.paused ? <Play size={16} /> : <Pause size={16} />}
284
+ {queue.paused ? <Play size={14} /> : <Pause size={14} />}
276
285
  </button>
286
+
287
+ <div className="w-px h-4 bg-white/5 mx-1" />
288
+
277
289
  {queue.delayed > 0 && (
278
290
  <button
279
291
  type="button"
@@ -286,8 +298,9 @@ export function QueuesPage() {
286
298
  }
287
299
  className="p-2 text-amber-500 hover:bg-amber-500/10 rounded-lg transition-all"
288
300
  title="Retry All Delayed"
301
+ aria-label={`Retry all delayed jobs in ${queue.name}`}
289
302
  >
290
- <RefreshCcw size={16} />
303
+ <RefreshCcw size={14} />
291
304
  </button>
292
305
  )}
293
306
  {queue.failed > 0 && (
@@ -301,10 +314,11 @@ export function QueuesPage() {
301
314
  queryClient.invalidateQueries({ queryKey: ['queues'] })
302
315
  )
303
316
  }
304
- className="p-2 text-blue-500 hover:bg-blue-500/10 rounded-lg transition-all"
317
+ className="p-2 text-primary hover:bg-primary/10 rounded-lg transition-all"
305
318
  title="Retry All Failed"
319
+ aria-label={`Retry all failed jobs in ${queue.name}`}
306
320
  >
307
- <RefreshCcw size={16} />
321
+ <RefreshCcw size={14} />
308
322
  </button>
309
323
  <button
310
324
  type="button"
@@ -323,34 +337,19 @@ export function QueuesPage() {
323
337
  }}
324
338
  className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-all"
325
339
  title="Clear Failed Jobs"
340
+ aria-label={`Clear all failed jobs in ${queue.name}`}
326
341
  >
327
- <XCircle size={16} />
342
+ <XCircle size={14} />
328
343
  </button>
329
344
  </>
330
345
  )}
331
- <button
332
- type="button"
333
- onClick={async () => {
334
- if (
335
- confirm(
336
- `Are you sure you want to purge all jobs in queue "${queue.name}"?`
337
- )
338
- ) {
339
- await fetch(`/api/queues/${queue.name}/purge`, { method: 'POST' })
340
- queryClient.invalidateQueries({ queryKey: ['queues'] })
341
- }
342
- }}
343
- className="p-2 text-muted-foreground hover:bg-red-500/10 hover:text-red-500 rounded-lg transition-all"
344
- title="Purge Queue"
345
- >
346
- <Trash2 size={16} />
347
- </button>
346
+
348
347
  <button
349
348
  type="button"
350
349
  onClick={() => setSelectedQueue(queue.name)}
351
- className="px-4 py-1.5 bg-muted text-foreground rounded-lg transition-all flex items-center gap-2 text-[10px] font-black uppercase tracking-widest border border-border/50 hover:border-primary/50 hover:bg-background"
350
+ className="ml-2 px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-foreground/80 rounded-lg transition-all flex items-center gap-2 text-[9px] font-black uppercase tracking-widest border border-white/5 hover:border-primary/40"
352
351
  >
353
- Inspect <ArrowRight size={12} />
352
+ Inspect <ArrowRight size={10} />
354
353
  </button>
355
354
  </div>
356
355
  </td>
@@ -3,7 +3,6 @@ import { format } from 'date-fns'
3
3
  import { AnimatePresence, motion } from 'framer-motion'
4
4
  import {
5
5
  Activity,
6
- AlertCircle,
7
6
  ArrowRight,
8
7
  Calendar,
9
8
  CheckCircle2,
@@ -176,57 +175,59 @@ export function SchedulesPage() {
176
175
 
177
176
  {/* Stats Overview */}
178
177
  <div className="grid grid-cols-3 gap-6">
179
- <div className="card-premium p-6 flex items-center gap-4">
180
- <div className="w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center text-primary">
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)]">
181
180
  <Activity size={24} />
182
181
  </div>
183
182
  <div>
184
- <p className="text-[10px] font-black text-muted-foreground uppercase tracking-widest">
183
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
185
184
  Active Schedules
186
185
  </p>
187
- <p className="text-2xl font-black">{isLoading ? '...' : schedules.length}</p>
186
+ <p className="text-2xl font-black font-mono tracking-tighter">
187
+ {isLoading ? '...' : schedules.length}
188
+ </p>
188
189
  </div>
189
190
  </div>
190
- <div className="card-premium p-6 flex items-center gap-4">
191
- <div className="w-12 h-12 rounded-2xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
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)]">
192
193
  <Calendar size={24} />
193
194
  </div>
194
195
  <div>
195
- <p className="text-[10px] font-black text-muted-foreground uppercase tracking-widest">
196
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
196
197
  Total Executions
197
198
  </p>
198
- <p className="text-2xl font-black">---</p>
199
+ <p className="text-2xl font-black font-mono tracking-tighter">---</p>
199
200
  </div>
200
201
  </div>
201
- <div className="card-premium p-6 flex items-center gap-4">
202
- <div className="w-12 h-12 rounded-2xl bg-green-500/10 flex items-center justify-center text-green-500">
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)]">
203
204
  <CheckCircle2 size={24} />
204
205
  </div>
205
206
  <div>
206
- <p className="text-[10px] font-black text-muted-foreground uppercase tracking-widest">
207
+ <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading mb-0.5">
207
208
  Health Score
208
209
  </p>
209
- <p className="text-2xl font-black text-green-500">99.8%</p>
210
+ <p className="text-2xl font-black text-emerald-500 font-mono tracking-tighter">99.8%</p>
210
211
  </div>
211
212
  </div>
212
213
  </div>
213
214
 
214
215
  {/* Toolbar */}
215
- <div className="flex gap-4 items-center">
216
+ <div className="card-premium p-3 flex gap-4 items-center">
216
217
  <div className="relative flex-1">
217
218
  <Search
218
- className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground/50"
219
+ className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground/30"
219
220
  size={18}
220
221
  />
221
222
  <input
222
223
  type="text"
223
224
  placeholder="Search schedules by name, cron or ID..."
224
- className="w-full bg-muted/40 border-border/50 rounded-xl pl-12 pr-4 py-3 text-sm focus:ring-1 focus:ring-primary/30 outline-none transition-all"
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"
225
226
  />
226
227
  </div>
227
228
  <button
228
229
  type="button"
229
- className="p-3 bg-muted/40 border border-border/50 rounded-xl hover:bg-muted/60 transition-all"
230
+ className="p-3 bg-zinc-900/40 border border-white/5 rounded-xl hover:bg-zinc-800 transition-all text-muted-foreground/60"
230
231
  >
231
232
  <Filter size={18} />
232
233
  </button>
@@ -259,30 +260,30 @@ export function SchedulesPage() {
259
260
  {schedules.map((schedule: ScheduleInfo) => (
260
261
  <div
261
262
  key={schedule.id}
262
- className="card-premium p-6 group hover:border-primary/30 transition-all"
263
+ className="card-premium p-6 group hover:border-primary/20 transition-all border-l-4 border-l-zinc-800 hover:border-l-primary"
263
264
  >
264
265
  <div className="flex items-start justify-between">
265
- <div className="flex gap-5">
266
- <div className="w-14 h-14 rounded-2xl bg-muted flex items-center justify-center text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary transition-all">
267
- <Clock size={28} />
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} />
268
269
  </div>
269
- <div className="space-y-1">
270
+ <div className="space-y-2">
270
271
  <div className="flex items-center gap-3">
271
- <h3 className="text-lg font-bold">{schedule.id}</h3>
272
- <span className="px-2 py-0.5 bg-green-500/10 text-green-500 text-[10px] font-black uppercase tracking-widest rounded">
273
- Enabled
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
274
277
  </span>
275
278
  </div>
276
- <div className="flex items-center gap-4 text-xs font-bold text-muted-foreground">
277
- <span className="flex items-center gap-1.5">
278
- <Clock size={12} /> {schedule.cron}
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}
279
282
  </span>
280
- <span className="flex items-center gap-1.5">
281
- <ArrowRight size={12} /> {schedule.queue}
282
- </span>
283
- <span className="px-2 py-0.5 bg-muted rounded font-mono text-[10px] uppercase font-black">
284
- {schedule.job.className}
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
285
  </span>
286
+ <span className="text-primary/40">{schedule.job.className}</span>
286
287
  </div>
287
288
  </div>
288
289
  </div>
@@ -292,61 +293,55 @@ export function SchedulesPage() {
292
293
  type="button"
293
294
  disabled={runMutation.isPending}
294
295
  onClick={() => setConfirmRunId(schedule.id)}
295
- className="px-4 py-2 bg-muted hover:bg-primary hover:text-primary-foreground rounded-lg text-[10px] font-black uppercase tracking-widest transition-all active:scale-95 flex items-center gap-2 disabled:opacity-50"
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"
296
297
  >
297
298
  {runMutation.isPending && runMutation.variables === schedule.id ? (
298
299
  <>
299
- <RefreshCcw size={12} className="animate-spin" />
300
- Running...
300
+ <RefreshCcw size={14} className="animate-spin" />
301
+ Triggering...
301
302
  </>
302
303
  ) : (
303
- 'Run Now'
304
+ 'Execute Now'
304
305
  )}
305
306
  </button>
306
- <button
307
- type="button"
308
- className="p-2 hover:bg-muted rounded-lg transition-all text-muted-foreground"
309
- >
310
- <AlertCircle size={18} />
311
- </button>
312
307
  <button
313
308
  type="button"
314
309
  onClick={() => setConfirmDeleteId(schedule.id)}
315
- className="p-2 hover:bg-red-500/10 hover:text-red-500 rounded-lg transition-all text-muted-foreground"
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"
316
311
  >
317
312
  <Trash2 size={18} />
318
313
  </button>
319
314
  </div>
320
315
  </div>
321
316
 
322
- <div className="mt-6 grid grid-cols-2 gap-4 pt-6 border-t border-border/30">
323
- <div className="flex items-center gap-3">
324
- <div className="w-8 h-8 rounded-lg bg-muted flex items-center justify-center text-muted-foreground/60">
325
- <Calendar size={14} />
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} />
326
321
  </div>
327
322
  <div>
328
- <p className="text-[10px] font-black text-muted-foreground/40 uppercase tracking-widest leading-none mb-1">
329
- Last Run
323
+ <p className="text-[9px] font-black text-muted-foreground/40 uppercase tracking-[0.2em] mb-1 font-heading">
324
+ Previous Burst
330
325
  </p>
331
- <p className="text-xs font-bold">
326
+ <p className="text-xs font-black font-mono text-white/60 tabular-nums">
332
327
  {schedule.lastRun
333
- ? format(new Date(schedule.lastRun), 'HH:mm:ss MMM dd')
334
- : 'Never'}
328
+ ? format(new Date(schedule.lastRun), 'HH:mm:ss / MMM dd')
329
+ : 'N/A'}
335
330
  </p>
336
331
  </div>
337
332
  </div>
338
- <div className="flex items-center gap-3">
339
- <div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
340
- <Play size={14} />
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} />
341
336
  </div>
342
337
  <div>
343
- <p className="text-[10px] font-black text-muted-foreground/40 uppercase tracking-widest leading-none mb-1">
344
- Next Run
338
+ <p className="text-[9px] font-black text-primary/40 uppercase tracking-[0.2em] mb-1 font-heading">
339
+ Next Sequence
345
340
  </p>
346
- <p className="text-xs font-bold">
341
+ <p className="text-xs font-black font-mono text-primary/80 tabular-nums uppercase">
347
342
  {schedule.nextRun
348
- ? format(new Date(schedule.nextRun), 'HH:mm:ss MMM dd')
349
- : 'Scheduled'}
343
+ ? format(new Date(schedule.nextRun), 'HH:mm:ss / MMM dd')
344
+ : 'Pending'}
350
345
  </p>
351
346
  </div>
352
347
  </div>