@gravito/zenith 0.1.0-beta.1

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 (63) hide show
  1. package/ARCHITECTURE.md +88 -0
  2. package/BATCH_OPERATIONS_IMPLEMENTATION.md +159 -0
  3. package/DEMO.md +156 -0
  4. package/DEPLOYMENT.md +157 -0
  5. package/DOCS_INTERNAL.md +73 -0
  6. package/Dockerfile +46 -0
  7. package/Dockerfile.demo-worker +29 -0
  8. package/EVOLUTION_BLUEPRINT.md +112 -0
  9. package/JOBINSPECTOR_SCROLL_FIX.md +152 -0
  10. package/PULSE_IMPLEMENTATION_PLAN.md +111 -0
  11. package/QUICK_TEST_GUIDE.md +72 -0
  12. package/README.md +33 -0
  13. package/ROADMAP.md +85 -0
  14. package/TESTING_BATCH_OPERATIONS.md +252 -0
  15. package/bin/flux-console.ts +2 -0
  16. package/dist/bin.js +108196 -0
  17. package/dist/client/assets/index-DGYEwTDL.css +1 -0
  18. package/dist/client/assets/index-oyTdySX0.js +421 -0
  19. package/dist/client/index.html +13 -0
  20. package/dist/server/index.js +108191 -0
  21. package/docker-compose.yml +40 -0
  22. package/docs/integrations/LARAVEL.md +207 -0
  23. package/package.json +50 -0
  24. package/postcss.config.js +6 -0
  25. package/scripts/flood-logs.ts +21 -0
  26. package/scripts/seed.ts +213 -0
  27. package/scripts/verify-throttle.ts +45 -0
  28. package/scripts/worker.ts +123 -0
  29. package/src/bin.ts +6 -0
  30. package/src/client/App.tsx +70 -0
  31. package/src/client/Layout.tsx +644 -0
  32. package/src/client/Sidebar.tsx +102 -0
  33. package/src/client/ThroughputChart.tsx +135 -0
  34. package/src/client/WorkerStatus.tsx +170 -0
  35. package/src/client/components/ConfirmDialog.tsx +103 -0
  36. package/src/client/components/JobInspector.tsx +524 -0
  37. package/src/client/components/LogArchiveModal.tsx +383 -0
  38. package/src/client/components/NotificationBell.tsx +203 -0
  39. package/src/client/components/Toaster.tsx +80 -0
  40. package/src/client/components/UserProfileDropdown.tsx +177 -0
  41. package/src/client/contexts/AuthContext.tsx +93 -0
  42. package/src/client/contexts/NotificationContext.tsx +103 -0
  43. package/src/client/index.css +174 -0
  44. package/src/client/index.html +12 -0
  45. package/src/client/main.tsx +15 -0
  46. package/src/client/pages/LoginPage.tsx +153 -0
  47. package/src/client/pages/MetricsPage.tsx +408 -0
  48. package/src/client/pages/OverviewPage.tsx +511 -0
  49. package/src/client/pages/QueuesPage.tsx +372 -0
  50. package/src/client/pages/SchedulesPage.tsx +531 -0
  51. package/src/client/pages/SettingsPage.tsx +449 -0
  52. package/src/client/pages/WorkersPage.tsx +316 -0
  53. package/src/client/pages/index.ts +7 -0
  54. package/src/client/utils.ts +6 -0
  55. package/src/server/index.ts +556 -0
  56. package/src/server/middleware/auth.ts +127 -0
  57. package/src/server/services/AlertService.ts +160 -0
  58. package/src/server/services/QueueService.ts +828 -0
  59. package/tailwind.config.js +73 -0
  60. package/tests/placeholder.test.ts +7 -0
  61. package/tsconfig.json +38 -0
  62. package/tsconfig.node.json +12 -0
  63. package/vite.config.ts +27 -0
@@ -0,0 +1,316 @@
1
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import { motion } from 'framer-motion'
3
+ import { AlertCircle, Clock, Cpu, Gauge, MemoryStick, RefreshCcw, Server, Zap } from 'lucide-react'
4
+ import React, { useEffect } from 'react'
5
+ import { cn } from '../utils'
6
+
7
+ interface Worker {
8
+ id: string
9
+ status: string
10
+ pid: number
11
+ uptime: number
12
+ metrics?: {
13
+ cpu: number
14
+ cores?: number
15
+ ram: {
16
+ rss: number
17
+ heapUsed: number
18
+ total?: number
19
+ }
20
+ }
21
+ queues?: string[]
22
+ }
23
+
24
+ export function WorkersPage() {
25
+ const queryClient = useQueryClient()
26
+ const { isPending, error, data } = useQuery<{ workers: Worker[] }>({
27
+ queryKey: ['workers'],
28
+ queryFn: async () => {
29
+ const res = await fetch('/api/workers')
30
+ return res.json()
31
+ },
32
+ refetchInterval: 5000,
33
+ })
34
+
35
+ // Listen to real-time stats updates from SSE
36
+ useEffect(() => {
37
+ const handler = (e: any) => {
38
+ if (e.detail?.workers) {
39
+ // Optimistically update the query cache with fresh worker data from SSE
40
+ queryClient.setQueryData(['workers'], { workers: e.detail.workers })
41
+ }
42
+ }
43
+ window.addEventListener('flux-stats-update', handler)
44
+ return () => window.removeEventListener('flux-stats-update', handler)
45
+ }, [queryClient])
46
+
47
+ const workers = data?.workers || []
48
+ const onlineWorkers = workers.filter((w) => w.status === 'online')
49
+ const offlineWorkers = workers.filter((w) => w.status !== 'online')
50
+
51
+ const totalCpu = workers.reduce((acc, w) => acc + (w.metrics?.cpu || 0), 0)
52
+ const avgCpu = workers.length > 0 ? totalCpu / workers.length : 0
53
+ const totalRam = workers.reduce((acc, w) => acc + (w.metrics?.ram?.rss || 0), 0)
54
+ const totalCapacity = workers.reduce((acc, w) => acc + (w.metrics?.ram?.total || 0), 0)
55
+
56
+ if (isPending) {
57
+ return (
58
+ <div className="flex flex-col items-center justify-center p-20 space-y-6">
59
+ <RefreshCcw className="animate-spin text-primary" size={48} />
60
+ <p className="text-muted-foreground font-bold uppercase tracking-[0.3em] text-xs">
61
+ Loading workers...
62
+ </p>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ if (error) {
68
+ return (
69
+ <div className="text-center p-20">
70
+ <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">
71
+ <AlertCircle size={56} className="mx-auto mb-6 opacity-80" />
72
+ <h3 className="text-2xl font-black mb-2 uppercase tracking-tight">
73
+ Failed to Load Workers
74
+ </h3>
75
+ <p className="text-sm font-medium opacity-70">{error.message}</p>
76
+ </div>
77
+ </div>
78
+ )
79
+ }
80
+
81
+ return (
82
+ <div className="space-y-8">
83
+ {/* Header */}
84
+ <div className="flex justify-between items-end">
85
+ <div>
86
+ <h1 className="text-4xl font-black tracking-tighter">Worker Nodes</h1>
87
+ <p className="text-muted-foreground mt-2 text-sm font-bold opacity-60 uppercase tracking-widest">
88
+ Monitor and manage cluster processing nodes.
89
+ </p>
90
+ </div>
91
+ <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]">
92
+ <span className="w-2 h-2 bg-green-500 rounded-full shadow-[0_0_8px_rgba(34,197,94,0.6)] animate-pulse"></span>
93
+ {onlineWorkers.length} Online
94
+ </div>
95
+ </div>
96
+
97
+ {/* Summary Cards */}
98
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
99
+ <div className="card-premium p-6 relative overflow-hidden group">
100
+ <div className="absolute top-0 right-0 w-20 h-20 bg-green-500/10 rounded-full -translate-y-1/2 translate-x-1/2 group-hover:scale-150 transition-transform duration-500" />
101
+ <div className="relative">
102
+ <div className="flex items-center gap-2 mb-2">
103
+ <Server size={16} className="text-green-500" />
104
+ <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest">
105
+ Online Nodes
106
+ </p>
107
+ </div>
108
+ <p className="text-3xl font-black text-green-500">{onlineWorkers.length}</p>
109
+ </div>
110
+ </div>
111
+ <div className="card-premium p-6 relative overflow-hidden group">
112
+ <div className="absolute top-0 right-0 w-20 h-20 bg-muted/20 rounded-full -translate-y-1/2 translate-x-1/2 group-hover:scale-150 transition-transform duration-500" />
113
+ <div className="relative">
114
+ <div className="flex items-center gap-2 mb-2">
115
+ <Zap size={16} className="text-muted-foreground" />
116
+ <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest">
117
+ Offline Nodes
118
+ </p>
119
+ </div>
120
+ <p className="text-3xl font-black text-muted-foreground">{offlineWorkers.length}</p>
121
+ </div>
122
+ </div>
123
+ <div className="card-premium p-6 relative overflow-hidden group">
124
+ <div className="absolute top-0 right-0 w-20 h-20 bg-primary/10 rounded-full -translate-y-1/2 translate-x-1/2 group-hover:scale-150 transition-transform duration-500" />
125
+ <div className="relative">
126
+ <div className="flex items-center gap-2 mb-2">
127
+ <Gauge size={16} className="text-primary" />
128
+ <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest">
129
+ Avg Load
130
+ </p>
131
+ </div>
132
+ <p className="text-3xl font-black">{avgCpu.toFixed(2)}</p>
133
+ </div>
134
+ </div>
135
+ <div className="card-premium p-6 relative overflow-hidden group">
136
+ <div className="absolute top-0 right-0 w-20 h-20 bg-indigo-500/10 rounded-full -translate-y-1/2 translate-x-1/2 group-hover:scale-150 transition-transform duration-500" />
137
+ <div className="relative">
138
+ <div className="flex items-center gap-2 mb-2">
139
+ <MemoryStick size={16} className="text-indigo-500" />
140
+ <p className="text-[10px] font-black text-muted-foreground/50 uppercase tracking-widest">
141
+ Cluster RAM
142
+ </p>
143
+ </div>
144
+ <div className="flex items-baseline gap-1">
145
+ <p className="text-3xl font-black text-indigo-500">{(totalRam / 1024).toFixed(2)}</p>
146
+ {totalCapacity > 0 && (
147
+ <span className="text-sm font-bold text-muted-foreground opacity-50">
148
+ / {(totalCapacity / 1024).toFixed(0)} GB
149
+ </span>
150
+ )}
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ {/* Workers Grid */}
157
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
158
+ {workers.length === 0 && (
159
+ <div className="col-span-full py-20 text-center text-muted-foreground/30">
160
+ <Cpu size={48} className="mx-auto mb-4 opacity-20 animate-pulse" />
161
+ <p className="text-sm font-bold uppercase tracking-widest">No worker nodes connected</p>
162
+ <p className="text-xs opacity-60 mt-2">Start a worker to see it appear here</p>
163
+ </div>
164
+ )}
165
+ {workers.map((worker, index) => (
166
+ <motion.div
167
+ key={worker.id}
168
+ initial={{ opacity: 0, y: 20 }}
169
+ animate={{ opacity: 1, y: 0 }}
170
+ transition={{ delay: index * 0.1 }}
171
+ className="card-premium p-6 relative overflow-hidden group"
172
+ >
173
+ {/* Status indicator bar */}
174
+ <div
175
+ className={cn(
176
+ 'absolute left-0 top-0 bottom-0 w-1.5 transition-all',
177
+ worker.status === 'online' ? 'bg-green-500' : 'bg-muted-foreground/30'
178
+ )}
179
+ />
180
+
181
+ {/* Header */}
182
+ <div className="flex items-start justify-between mb-6">
183
+ <div className="flex items-center gap-4">
184
+ <div className="relative">
185
+ <div
186
+ className={cn(
187
+ 'w-12 h-12 rounded-2xl flex items-center justify-center transition-all',
188
+ worker.status === 'online'
189
+ ? 'bg-green-500/10 text-green-500'
190
+ : 'bg-muted text-muted-foreground'
191
+ )}
192
+ >
193
+ <Cpu size={24} />
194
+ </div>
195
+ <div
196
+ className={cn(
197
+ 'absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-2 border-card',
198
+ worker.status === 'online'
199
+ ? 'bg-green-500 animate-pulse'
200
+ : 'bg-muted-foreground'
201
+ )}
202
+ />
203
+ </div>
204
+ <div>
205
+ <h3 className="font-black tracking-tight text-lg group-hover:text-primary transition-colors">
206
+ {worker.id}
207
+ </h3>
208
+ <p className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">
209
+ PID: {worker.pid}
210
+ </p>
211
+ </div>
212
+ </div>
213
+ <span
214
+ className={cn(
215
+ 'px-3 py-1 rounded-full text-[9px] font-black uppercase tracking-widest border',
216
+ worker.status === 'online'
217
+ ? 'bg-green-500/10 text-green-500 border-green-500/20'
218
+ : 'bg-muted/40 text-muted-foreground border-transparent'
219
+ )}
220
+ >
221
+ {worker.status}
222
+ </span>
223
+ </div>
224
+
225
+ {/* Metrics */}
226
+ {worker.metrics && (
227
+ <div className="space-y-4">
228
+ {/* CPU */}
229
+ <div>
230
+ <div className="flex justify-between text-[10px] font-black uppercase tracking-widest mb-2">
231
+ <span className="text-muted-foreground">
232
+ Load (Cap: {worker.metrics.cores || '-'})
233
+ </span>
234
+ <span
235
+ className={cn(
236
+ worker.metrics.cpu > (worker.metrics.cores || 4)
237
+ ? 'text-red-500'
238
+ : worker.metrics.cpu > (worker.metrics.cores || 4) * 0.7
239
+ ? 'text-amber-500'
240
+ : 'text-green-500'
241
+ )}
242
+ >
243
+ {worker.metrics.cpu.toFixed(2)}
244
+ </span>
245
+ </div>
246
+ <div className="h-2 w-full bg-muted rounded-full overflow-hidden">
247
+ <motion.div
248
+ initial={{ width: 0 }}
249
+ animate={{
250
+ width: `${Math.min(100, (worker.metrics.cpu / (worker.metrics.cores || 1)) * 100)}%`,
251
+ }}
252
+ transition={{ duration: 0.5 }}
253
+ className={cn(
254
+ 'h-full transition-colors',
255
+ worker.metrics.cpu > (worker.metrics.cores || 4)
256
+ ? 'bg-red-500'
257
+ : worker.metrics.cpu > (worker.metrics.cores || 4) * 0.7
258
+ ? 'bg-amber-500'
259
+ : 'bg-green-500'
260
+ )}
261
+ />
262
+ </div>
263
+ </div>
264
+
265
+ {/* RAM */}
266
+ <div>
267
+ <div className="flex justify-between text-[10px] font-black uppercase tracking-widest mb-2">
268
+ <span className="text-muted-foreground">Memory (RSS / Total)</span>
269
+ <span className="text-indigo-500">
270
+ {(worker.metrics.ram.rss / 1024).toFixed(2)} GB /{' '}
271
+ {worker.metrics.ram.total
272
+ ? (worker.metrics.ram.total / 1024).toFixed(0)
273
+ : '-'}{' '}
274
+ GB
275
+ </span>
276
+ </div>
277
+ <div className="h-2 w-full bg-muted rounded-full overflow-hidden">
278
+ <motion.div
279
+ initial={{ width: 0 }}
280
+ animate={{
281
+ width: `${Math.min(100, (worker.metrics.ram.rss / (worker.metrics.ram.total || 2048)) * 100)}%`,
282
+ }}
283
+ transition={{ duration: 0.5 }}
284
+ className="h-full bg-indigo-500"
285
+ />
286
+ </div>
287
+ </div>
288
+ </div>
289
+ )}
290
+
291
+ {/* Uptime */}
292
+ <div className="mt-6 pt-4 border-t border-border/30 flex items-center justify-between">
293
+ <div className="flex items-center gap-2 text-muted-foreground">
294
+ <Clock size={14} />
295
+ <span className="text-[10px] font-bold uppercase tracking-widest">Uptime</span>
296
+ </div>
297
+ <span className="font-mono text-sm font-bold">{formatUptime(worker.uptime)}</span>
298
+ </div>
299
+ </motion.div>
300
+ ))}
301
+ </div>
302
+ </div>
303
+ )
304
+ }
305
+
306
+ function formatUptime(seconds: number): string {
307
+ if (seconds < 60) {
308
+ return `${Math.floor(seconds)}s`
309
+ }
310
+ if (seconds < 3600) {
311
+ return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
312
+ }
313
+ const hours = Math.floor(seconds / 3600)
314
+ const minutes = Math.floor((seconds % 3600) / 60)
315
+ return `${hours}h ${minutes}m`
316
+ }
@@ -0,0 +1,7 @@
1
+ export { LoginPage } from './LoginPage'
2
+ export { MetricsPage } from './MetricsPage'
3
+ export { OverviewPage } from './OverviewPage'
4
+ export { QueuesPage } from './QueuesPage'
5
+ export { SchedulesPage } from './SchedulesPage'
6
+ export { SettingsPage } from './SettingsPage'
7
+ export { WorkersPage } from './WorkersPage'
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }