@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,378 +0,0 @@
1
- import { useQuery, useQueryClient } from '@tanstack/react-query'
2
- import { AnimatePresence } from 'framer-motion'
3
- import {
4
- Activity,
5
- AlertCircle,
6
- ArrowRight,
7
- Filter,
8
- ListTree,
9
- Pause,
10
- Play,
11
- RefreshCcw,
12
- Search,
13
- XCircle,
14
- } from 'lucide-react'
15
- import React from 'react'
16
- import { JobInspector } from '../components/JobInspector'
17
- import { cn } from '../utils'
18
-
19
- interface QueueStats {
20
- name: string
21
- waiting: number
22
- delayed: number
23
- active: number
24
- failed: number
25
- paused?: boolean
26
- }
27
-
28
- /**
29
- * Queue Management Page.
30
- *
31
- * Provides detailed monitoring and management controls for message queues,
32
- * including job inspection, pausing/resuming queues, and bulk retry/purge actions.
33
- *
34
- * @public
35
- * @since 3.0.0
36
- */
37
- export function QueuesPage() {
38
- const [selectedQueue, setSelectedQueue] = React.useState<string | null>(null)
39
- const [searchQuery, setSearchQuery] = React.useState('')
40
- const [statusFilter, setStatusFilter] = React.useState<'all' | 'active' | 'idle' | 'critical'>(
41
- 'all'
42
- )
43
- const queryClient = useQueryClient()
44
-
45
- const { isPending, error, data } = useQuery<{ queues: QueueStats[] }>({
46
- queryKey: ['queues'],
47
- queryFn: () => fetch('/api/queues').then((res) => res.json()),
48
- staleTime: Infinity, // No auto refetch
49
- })
50
-
51
- // Listen for real-time updates from Layout's global stream
52
- React.useEffect(() => {
53
- const handler = (e: CustomEvent) => {
54
- if (e.detail?.queues) {
55
- queryClient.setQueryData(['queues'], { queues: e.detail.queues })
56
- }
57
- }
58
- window.addEventListener('flux-stats-update', handler as EventListener)
59
- return () => window.removeEventListener('flux-stats-update', handler as EventListener)
60
- }, [queryClient])
61
-
62
- // Note: We intentionally do NOT scroll to top when JobInspector opens
63
- // This allows users to quickly inspect multiple queues without losing their scroll position
64
-
65
- const queues = data?.queues || []
66
-
67
- const filteredQueues = queues.filter((q) => {
68
- const matchesSearch = q.name.toLowerCase().includes(searchQuery.toLowerCase())
69
- const status = q.failed > 0 ? 'critical' : q.active > 0 ? 'active' : 'idle'
70
- const matchesStatus = statusFilter === 'all' || status === statusFilter
71
- return matchesSearch && matchesStatus
72
- })
73
-
74
- const totalWaiting = queues.reduce((acc, q) => acc + q.waiting, 0)
75
- const totalDelayed = queues.reduce((acc, q) => acc + q.delayed, 0)
76
- const totalFailed = queues.reduce((acc, q) => acc + q.failed, 0)
77
- const totalActive = queues.reduce((acc, q) => acc + q.active, 0)
78
-
79
- if (isPending) {
80
- return (
81
- <div className="flex flex-col items-center justify-center p-20 space-y-6">
82
- <RefreshCcw className="animate-spin text-primary" size={48} />
83
- <p className="text-muted-foreground font-bold uppercase tracking-[0.3em] text-xs">
84
- Loading queues...
85
- </p>
86
- </div>
87
- )
88
- }
89
-
90
- if (error) {
91
- return (
92
- <div className="text-center p-20">
93
- <div className="bg-red-500/10 text-red-500 p-10 rounded-3xl border border-red-500/20 max-w-md mx-auto shadow-2xl">
94
- <AlertCircle size={56} className="mx-auto mb-6 opacity-80" />
95
- <h3 className="text-2xl font-black mb-2 uppercase tracking-tight">
96
- Failed to Load Queues
97
- </h3>
98
- <p className="text-sm font-medium opacity-70 mb-8">{error.message}</p>
99
- </div>
100
- </div>
101
- )
102
- }
103
-
104
- return (
105
- <>
106
- {/* JobInspector as full-screen modal overlay */}
107
- <AnimatePresence>
108
- {selectedQueue && (
109
- <JobInspector queueName={selectedQueue} onClose={() => setSelectedQueue(null)} />
110
- )}
111
- </AnimatePresence>
112
-
113
- {/* Main page content */}
114
- <div className="space-y-8">
115
- {/* Header */}
116
- <div className="flex justify-between items-end">
117
- <div>
118
- <h1 className="text-4xl font-black tracking-tighter">Processing Queues</h1>
119
- <p className="text-muted-foreground mt-2 text-sm font-bold opacity-60 uppercase tracking-widest">
120
- Manage and monitor all processing pipelines.
121
- </p>
122
- </div>
123
- <div className="flex items-center gap-2 text-[10px] font-black text-green-500 bg-green-500/10 px-4 py-2 rounded-full border border-green-500/20 uppercase tracking-[0.2em] animate-pulse">
124
- <span className="w-2 h-2 bg-green-500 rounded-full shadow-[0_0_8px_rgba(34,197,94,0.6)]"></span>
125
- {queues.length} Queues
126
- </div>
127
- </div>
128
-
129
- {/* Summary Cards */}
130
- <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
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">
133
- Total Waiting
134
- </p>
135
- <p className="text-3xl font-black font-mono tracking-tighter">
136
- {totalWaiting.toLocaleString()}
137
- </p>
138
- </div>
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">
141
- Total Delayed
142
- </p>
143
- <p className="text-3xl font-black text-amber-500 font-mono tracking-tighter">
144
- {totalDelayed.toLocaleString()}
145
- </p>
146
- </div>
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">
149
- Total Failed
150
- </p>
151
- <p className="text-3xl font-black text-red-500 font-mono tracking-tighter">
152
- {totalFailed.toLocaleString()}
153
- </p>
154
- </div>
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">
157
- Currently Active
158
- </p>
159
- <p className="text-3xl font-black text-emerald-500 font-mono tracking-tighter">
160
- {totalActive.toLocaleString()}
161
- </p>
162
- </div>
163
- </div>
164
-
165
- {/* Filters */}
166
- <div className="card-premium p-3 flex flex-wrap gap-4 items-center">
167
- <div className="relative flex-1 min-w-[240px]">
168
- <Search
169
- className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/40"
170
- size={16}
171
- />
172
- <input
173
- type="text"
174
- aria-label="Filter pipelines"
175
- placeholder="Filter pipelines..."
176
- value={searchQuery}
177
- onChange={(e) => setSearchQuery(e.target.value)}
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"
179
- />
180
- </div>
181
- <div className="flex items-center gap-2">
182
- <Filter size={14} className="text-muted-foreground/40" />
183
- {(['all', 'active', 'idle', 'critical'] as const).map((status) => (
184
- <button
185
- type="button"
186
- key={status}
187
- onClick={() => setStatusFilter(status)}
188
- className={cn(
189
- 'px-3 py-1.5 rounded-lg text-[9px] font-black uppercase tracking-widest transition-all border',
190
- statusFilter === status
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'
193
- )}
194
- >
195
- {status}
196
- </button>
197
- ))}
198
- </div>
199
- </div>
200
-
201
- {/* Queue List */}
202
- <div className="card-premium overflow-hidden">
203
- <div className="overflow-x-auto">
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">
206
- <tr>
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>
214
- </tr>
215
- </thead>
216
- <tbody className="divide-y divide-white/5 text-xs font-mono">
217
- {filteredQueues.map((queue) => {
218
- const status =
219
- queue.failed > 0 ? 'critical' : queue.active > 0 ? 'active' : 'idle'
220
- return (
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} />
226
- </div>
227
- <span className="font-black tracking-tight text-sm font-heading">
228
- {queue.name}
229
- </span>
230
- </div>
231
- </td>
232
- <td className="px-6 py-4 text-center font-bold text-foreground/80">
233
- {queue.waiting.toLocaleString()}
234
- </td>
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">
238
- <span
239
- className={cn(
240
- 'font-black',
241
- queue.failed > 0 ? 'text-red-500' : 'text-muted-foreground/20'
242
- )}
243
- >
244
- {queue.failed}
245
- </span>
246
- </td>
247
- <td className="px-6 py-4 text-center">
248
- <span
249
- className={cn(
250
- 'px-2.5 py-1 rounded-md text-[8px] font-black uppercase tracking-widest border transition-all',
251
- queue.paused
252
- ? 'bg-amber-500/10 text-amber-500 border-amber-500/20'
253
- : status === 'critical'
254
- ? 'bg-red-500 text-white border-red-600 shadow-[0_0_10px_rgba(239,68,68,0.3)] animate-pulse'
255
- : status === 'active'
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'
258
- )}
259
- >
260
- {queue.paused ? 'paused' : status}
261
- </span>
262
- </td>
263
- <td className="px-6 py-4 text-right">
264
- <div className="flex justify-end gap-1.5 items-center">
265
- {/* Pause/Resume button */}
266
- <button
267
- type="button"
268
- onClick={async () => {
269
- const action = queue.paused ? 'resume' : 'pause'
270
- await fetch(`/api/queues/${queue.name}/${action}`, { method: 'POST' })
271
- queryClient.invalidateQueries({ queryKey: ['queues'] })
272
- }}
273
- className={cn(
274
- 'p-2 rounded-lg transition-all border border-transparent hover:border-white/10',
275
- queue.paused
276
- ? 'text-emerald-500 bg-emerald-500/5 hover:bg-emerald-500/10'
277
- : 'text-muted-foreground hover:bg-amber-500/10 hover:text-amber-500'
278
- )}
279
- title={queue.paused ? 'Resume Queue' : 'Pause Queue'}
280
- aria-label={
281
- queue.paused ? `Resume ${queue.name}` : `Pause ${queue.name}`
282
- }
283
- >
284
- {queue.paused ? <Play size={14} /> : <Pause size={14} />}
285
- </button>
286
-
287
- <div className="w-px h-4 bg-white/5 mx-1" />
288
-
289
- {queue.delayed > 0 && (
290
- <button
291
- type="button"
292
- onClick={() =>
293
- fetch(`/api/queues/${queue.name}/retry-all`, {
294
- method: 'POST',
295
- }).then(() =>
296
- queryClient.invalidateQueries({ queryKey: ['queues'] })
297
- )
298
- }
299
- className="p-2 text-amber-500 hover:bg-amber-500/10 rounded-lg transition-all"
300
- title="Retry All Delayed"
301
- aria-label={`Retry all delayed jobs in ${queue.name}`}
302
- >
303
- <RefreshCcw size={14} />
304
- </button>
305
- )}
306
- {queue.failed > 0 && (
307
- <>
308
- <button
309
- type="button"
310
- onClick={() =>
311
- fetch(`/api/queues/${queue.name}/retry-all-failed`, {
312
- method: 'POST',
313
- }).then(() =>
314
- queryClient.invalidateQueries({ queryKey: ['queues'] })
315
- )
316
- }
317
- className="p-2 text-primary hover:bg-primary/10 rounded-lg transition-all"
318
- title="Retry All Failed"
319
- aria-label={`Retry all failed jobs in ${queue.name}`}
320
- >
321
- <RefreshCcw size={14} />
322
- </button>
323
- <button
324
- type="button"
325
- onClick={() => {
326
- if (
327
- confirm(
328
- `Are you sure you want to clear all failed jobs in queue "${queue.name}"?`
329
- )
330
- ) {
331
- fetch(`/api/queues/${queue.name}/clear-failed`, {
332
- method: 'POST',
333
- }).then(() =>
334
- queryClient.invalidateQueries({ queryKey: ['queues'] })
335
- )
336
- }
337
- }}
338
- className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-all"
339
- title="Clear Failed Jobs"
340
- aria-label={`Clear all failed jobs in ${queue.name}`}
341
- >
342
- <XCircle size={14} />
343
- </button>
344
- </>
345
- )}
346
-
347
- <button
348
- type="button"
349
- onClick={() => setSelectedQueue(queue.name)}
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"
351
- >
352
- Inspect <ArrowRight size={10} />
353
- </button>
354
- </div>
355
- </td>
356
- </tr>
357
- )
358
- })}
359
- {filteredQueues.length === 0 && (
360
- <tr>
361
- <td colSpan={7} className="px-6 py-20 text-center text-muted-foreground">
362
- <Activity size={40} className="mx-auto mb-4 opacity-10 animate-pulse" />
363
- <p className="text-sm font-bold opacity-30 italic uppercase tracking-widest">
364
- {searchQuery || statusFilter !== 'all'
365
- ? 'No queues match your filters'
366
- : 'No queues available'}
367
- </p>
368
- </td>
369
- </tr>
370
- )}
371
- </tbody>
372
- </table>
373
- </div>
374
- </div>
375
- </div>
376
- </>
377
- )
378
- }