@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,380 +0,0 @@
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 { 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?: {
22
- name: string
23
- size: {
24
- waiting: number
25
- active: number
26
- failed: number
27
- delayed: number
28
- }
29
- }[]
30
- meta?: {
31
- laravel?: {
32
- workerCount: number
33
- roots: string[]
34
- }
35
- }
36
- }
37
-
38
- /**
39
- * Worker Nodes Dashboard Page.
40
- *
41
- * Provides a detailed view of all active and inactive worker nodes in the
42
- * cluster, including their resource usage, monitored queues, and Laravel
43
- * worker status.
44
- *
45
- * @public
46
- * @since 3.0.0
47
- */
48
- export function WorkersPage() {
49
- const queryClient = useQueryClient()
50
- const { isPending, error, data } = useQuery<{ workers: Worker[] }>({
51
- queryKey: ['workers'],
52
- queryFn: async () => {
53
- const res = await fetch('/api/workers')
54
- return res.json()
55
- },
56
- refetchInterval: 5000,
57
- })
58
-
59
- // Listen to real-time stats updates from SSE
60
- useEffect(() => {
61
- const handler = (e: any) => {
62
- if (e.detail?.workers) {
63
- // Optimistically update the query cache with fresh worker data from SSE
64
- queryClient.setQueryData(['workers'], { workers: e.detail.workers })
65
- }
66
- }
67
- window.addEventListener('flux-stats-update', handler)
68
- return () => window.removeEventListener('flux-stats-update', handler)
69
- }, [queryClient])
70
-
71
- const workers = data?.workers || []
72
- const onlineWorkers = workers.filter((w) => w.status === 'online')
73
- const offlineWorkers = workers.filter((w) => w.status !== 'online')
74
-
75
- const totalCpu = workers.reduce((acc, w) => acc + (w.metrics?.cpu || 0), 0)
76
- const avgCpu = workers.length > 0 ? totalCpu / workers.length : 0
77
- const totalRam = workers.reduce((acc, w) => acc + (w.metrics?.ram?.rss || 0), 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 workers...
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 Workers
97
- </h3>
98
- <p className="text-sm font-medium opacity-70">{error.message}</p>
99
- </div>
100
- </div>
101
- )
102
- }
103
-
104
- return (
105
- <div className="space-y-8">
106
- {/* Header */}
107
- <div className="flex justify-between items-end">
108
- <div>
109
- <h1 className="text-4xl font-black tracking-tighter">Worker Nodes</h1>
110
- <p className="text-muted-foreground mt-2 text-sm font-bold opacity-60 uppercase tracking-widest">
111
- Monitor and manage cluster processing nodes.
112
- </p>
113
- </div>
114
- <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]">
115
- <span className="w-2 h-2 bg-green-500 rounded-full shadow-[0_0_8px_rgba(34,197,94,0.6)] animate-pulse"></span>
116
- {onlineWorkers.length} Online
117
- </div>
118
- </div>
119
-
120
- {/* Summary Cards */}
121
- <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
122
- <div className="card-premium p-5 relative overflow-hidden group border-l-4 border-emerald-500">
123
- <div className="relative">
124
- <div className="flex items-center gap-2 mb-3">
125
- <Server size={14} className="text-emerald-500" />
126
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading">
127
- Operational Nodes
128
- </p>
129
- </div>
130
- <p className="text-3xl font-black text-white font-mono tracking-tighter">
131
- {onlineWorkers.length}
132
- </p>
133
- </div>
134
- </div>
135
- <div className="card-premium p-5 relative overflow-hidden group border-l-4 border-white/10">
136
- <div className="relative">
137
- <div className="flex items-center gap-2 mb-3">
138
- <Zap size={14} className="text-white/20" />
139
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading">
140
- Standby Nodes
141
- </p>
142
- </div>
143
- <p className="text-3xl font-black text-white/40 font-mono tracking-tighter">
144
- {offlineWorkers.length}
145
- </p>
146
- </div>
147
- </div>
148
- <div className="card-premium p-5 relative overflow-hidden group border-l-4 border-primary/40">
149
- <div className="relative">
150
- <div className="flex items-center gap-2 mb-3">
151
- <Gauge size={14} className="text-primary" />
152
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading">
153
- Compute Load
154
- </p>
155
- </div>
156
- <p className="text-3xl font-black text-white font-mono tracking-tighter">
157
- {avgCpu.toFixed(2)}
158
- </p>
159
- </div>
160
- </div>
161
- <div className="card-premium p-5 relative overflow-hidden group border-l-4 border-indigo-500/40">
162
- <div className="relative">
163
- <div className="flex items-center gap-2 mb-3">
164
- <MemoryStick size={14} className="text-indigo-400" />
165
- <p className="text-[9px] font-black text-muted-foreground/60 uppercase tracking-[0.2em] font-heading">
166
- Cluster Memory
167
- </p>
168
- </div>
169
- <div className="flex items-baseline gap-1">
170
- <p className="text-3xl font-black text-white font-mono tracking-tighter">
171
- {(totalRam / 1024).toFixed(1)}
172
- </p>
173
- <span className="text-[10px] font-black text-white/20 uppercase tracking-tighter">
174
- GB
175
- </span>
176
- </div>
177
- </div>
178
- </div>
179
- </div>
180
-
181
- {/* Workers Grid */}
182
- <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
183
- {workers.length === 0 && (
184
- <div className="col-span-full py-32 text-center text-muted-foreground/20">
185
- <Cpu size={48} className="mx-auto mb-4 opacity-30 animate-pulse" />
186
- <p className="text-[10px] font-black uppercase tracking-[0.3em]">
187
- Awaiting signal from constellation...
188
- </p>
189
- </div>
190
- )}
191
- {workers.map((worker, index) => (
192
- <motion.div
193
- key={worker.id}
194
- initial={{ opacity: 0, scale: 0.98 }}
195
- animate={{ opacity: 1, scale: 1 }}
196
- transition={{ delay: index * 0.05 }}
197
- className="card-premium p-6 relative overflow-hidden group border-l-4"
198
- style={{ borderLeftColor: worker.status === 'online' ? '#10B981' : '#27272A' }}
199
- >
200
- {/* Header */}
201
- <div className="flex items-start justify-between mb-8">
202
- <div className="flex items-center gap-4">
203
- <div className="relative">
204
- <div
205
- className={cn(
206
- 'w-12 h-12 rounded-xl flex items-center justify-center transition-all border border-white/5',
207
- worker.status === 'online'
208
- ? 'bg-emerald-500/10 text-emerald-500'
209
- : 'bg-zinc-800 text-muted-foreground/40'
210
- )}
211
- >
212
- <Cpu size={24} />
213
- </div>
214
- {worker.status === 'online' && (
215
- <div className="absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-4 border-zinc-950 bg-emerald-500 animate-pulse shadow-[0_0_10px_#10B981]" />
216
- )}
217
- </div>
218
- <div>
219
- <h3 className="font-black tracking-tight text-base group-hover:text-primary transition-colors font-heading uppercase italic">
220
- {worker.id}
221
- </h3>
222
- <p className="text-[9px] font-bold text-muted-foreground/40 uppercase tracking-[0.2em] font-mono mt-1">
223
- PID: {worker.pid}
224
- </p>
225
- </div>
226
- </div>
227
- <span
228
- className={cn(
229
- 'px-2 py-1 rounded-md text-[8px] font-black uppercase tracking-widest border transition-all',
230
- worker.status === 'online'
231
- ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20 shadow-[0_0_10px_rgba(16,185,129,0.1)]'
232
- : 'bg-zinc-800/50 text-muted-foreground/40 border-transparent'
233
- )}
234
- >
235
- {worker.status}
236
- </span>
237
- </div>
238
-
239
- {/* Metrics */}
240
- {worker.metrics && (
241
- <div className="space-y-5 font-mono">
242
- {/* CPU */}
243
- <div>
244
- <div className="flex justify-between text-[9px] font-black uppercase tracking-widest mb-2.5">
245
- <span className="text-muted-foreground/60">CPU Compute Power</span>
246
- <span
247
- className={cn(
248
- worker.metrics.cpu > (worker.metrics.cores || 4)
249
- ? 'text-red-500'
250
- : worker.metrics.cpu > (worker.metrics.cores || 4) * 0.7
251
- ? 'text-amber-500 font-black'
252
- : 'text-primary font-black'
253
- )}
254
- >
255
- {worker.metrics.cpu.toFixed(2)}%
256
- </span>
257
- </div>
258
- <div className="h-1.5 w-full bg-black/40 rounded-full overflow-hidden border border-white/5">
259
- <motion.div
260
- initial={{ width: 0 }}
261
- animate={{
262
- width: `${Math.min(100, (worker.metrics.cpu / (worker.metrics.cores || 1)) * 100)}%`,
263
- }}
264
- transition={{ duration: 1 }}
265
- className={cn(
266
- 'h-full transition-colors relative',
267
- worker.metrics.cpu > (worker.metrics.cores || 4)
268
- ? 'bg-red-500'
269
- : worker.metrics.cpu > (worker.metrics.cores || 4) * 0.7
270
- ? 'bg-amber-500'
271
- : 'bg-primary shadow-[0_0_10px_#00F0FF]'
272
- )}
273
- />
274
- </div>
275
- </div>
276
-
277
- {/* RAM */}
278
- <div>
279
- <div className="flex justify-between text-[9px] font-black uppercase tracking-widest mb-2.5">
280
- <span className="text-muted-foreground/60">Memory Integrity</span>
281
- <span className="text-white/80 font-black">
282
- {(worker.metrics.ram.rss / 1024).toFixed(2)} GB
283
- </span>
284
- </div>
285
- <div className="h-1.5 w-full bg-black/40 rounded-full overflow-hidden border border-white/5">
286
- <motion.div
287
- initial={{ width: 0 }}
288
- animate={{
289
- width: `${Math.min(100, (worker.metrics.ram.rss / (worker.metrics.ram.total || 2048)) * 100)}%`,
290
- }}
291
- transition={{ duration: 1 }}
292
- className="h-full bg-indigo-500 shadow-[0_0_10px_#6366F1]"
293
- />
294
- </div>
295
- </div>
296
- </div>
297
- )}
298
-
299
- {/* Laravel & Queue Info (New) */}
300
- <div className="mt-8 space-y-3">
301
- {/* Monitored Queues */}
302
- {worker.queues && worker.queues.length > 0 && (
303
- <div className="bg-white/[0.02] p-4 rounded-xl border border-white/5">
304
- <div className="flex items-center gap-2 mb-3">
305
- <div className="w-1.5 h-1.5 bg-primary rounded-full shadow-[0_0_8px_#00F0FF]" />
306
- <span className="text-[9px] font-black uppercase tracking-[0.2em] text-muted-foreground/60 font-heading">
307
- Pipeline Access
308
- </span>
309
- </div>
310
- <div className="flex flex-wrap gap-2">
311
- {worker.queues.map((q, i) => (
312
- <div
313
- key={i}
314
- className="flex items-center gap-2 text-[10px] font-black text-foreground/60 bg-black/40 px-2 py-1 rounded border border-white/5 font-mono"
315
- >
316
- <span className="opacity-40">{q.name}</span>
317
- {(q.size.waiting > 0 || q.size.failed > 0) && (
318
- <span
319
- className={cn(
320
- 'px-1 rounded text-[8px] border',
321
- q.size.failed > 0
322
- ? 'text-red-500 bg-red-500/10 border-red-500/20'
323
- : 'text-primary bg-primary/10 border-primary/20'
324
- )}
325
- >
326
- {q.size.failed > 0 ? `! FAIL` : `${q.size.waiting}W`}
327
- </span>
328
- )}
329
- </div>
330
- ))}
331
- </div>
332
- </div>
333
- )}
334
-
335
- {/* Laravel Workers Info */}
336
- {worker.meta?.laravel && (
337
- <div className="flex items-center justify-between p-4 bg-red-500/5 border border-red-500/10 rounded-xl">
338
- <div className="flex items-center gap-2">
339
- <span className="w-1.5 h-1.5 bg-red-500 rounded-full animate-pulse shadow-[0_0_8px_#EF4444]" />
340
- <span className="text-[9px] font-black uppercase tracking-[0.2em] text-red-500/80 font-heading">
341
- Laravel Threading
342
- </span>
343
- </div>
344
- <span className="font-mono text-sm font-black text-red-500 tabular-nums tracking-tighter">
345
- {worker.meta.laravel.workerCount || 0} PHP
346
- </span>
347
- </div>
348
- )}
349
- </div>
350
-
351
- {/* Uptime */}
352
- <div className="mt-6 pt-5 border-t border-white/5 flex items-center justify-between">
353
- <div className="flex items-center gap-2 text-muted-foreground/40 font-heading">
354
- <Clock size={12} />
355
- <span className="text-[9px] font-black uppercase tracking-[0.2em]">
356
- Quantum Uptime
357
- </span>
358
- </div>
359
- <span className="font-mono text-[11px] font-black text-white/60 tabular-nums">
360
- {formatUptime(worker.uptime)}
361
- </span>
362
- </div>
363
- </motion.div>
364
- ))}
365
- </div>
366
- </div>
367
- )
368
- }
369
-
370
- function formatUptime(seconds: number): string {
371
- if (seconds < 60) {
372
- return `${Math.floor(seconds)}s`
373
- }
374
- if (seconds < 3600) {
375
- return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
376
- }
377
- const hours = Math.floor(seconds / 3600)
378
- const minutes = Math.floor((seconds % 3600) / 60)
379
- return `${hours}h ${minutes}m`
380
- }
@@ -1,8 +0,0 @@
1
- export { LoginPage } from './LoginPage'
2
- export { MetricsPage } from './MetricsPage'
3
- export { OverviewPage } from './OverviewPage'
4
- export { PulsePage } from './PulsePage'
5
- export { QueuesPage } from './QueuesPage'
6
- export { SchedulesPage } from './SchedulesPage'
7
- export { SettingsPage } from './SettingsPage'
8
- export { WorkersPage } from './WorkersPage'
@@ -1,15 +0,0 @@
1
- import { type ClassValue, clsx } from 'clsx'
2
- import { twMerge } from 'tailwind-merge'
3
-
4
- /**
5
- * Conditionally join class names and merge Tailwind CSS classes.
6
- *
7
- * @param inputs - A list of class values to be merged.
8
- * @returns The merged class string.
9
- *
10
- * @public
11
- * @since 3.0.0
12
- */
13
- export function cn(...inputs: ClassValue[]) {
14
- return twMerge(clsx(inputs))
15
- }
@@ -1,90 +0,0 @@
1
- import { DB } from '@gravito/atlas'
2
- import { MySQLPersistence, SQLitePersistence } from '@gravito/stream'
3
-
4
- export interface ServerConfig {
5
- port: number
6
- redisUrl: string
7
- queuePrefix: string
8
- dbDriver: 'sqlite' | 'mysql'
9
- dbConfig: {
10
- name?: string
11
- host?: string
12
- port?: number
13
- username?: string
14
- password?: string
15
- }
16
- persistence?: {
17
- adapter: any
18
- archiveCompleted: boolean
19
- archiveFailed: boolean
20
- archiveEnqueued: boolean
21
- }
22
- }
23
-
24
- export class ServerConfigManager {
25
- static load(): ServerConfig {
26
- const dbDriver = (process.env.DB_DRIVER || (process.env.DB_HOST ? 'mysql' : 'sqlite')) as
27
- | 'sqlite'
28
- | 'mysql'
29
-
30
- const dbConfig: ServerConfig['dbConfig'] = {}
31
- if (dbDriver === 'mysql') {
32
- dbConfig.host = process.env.DB_HOST
33
- dbConfig.port = parseInt(process.env.DB_PORT || '3306', 10)
34
- dbConfig.name = process.env.DB_NAME
35
- dbConfig.username = process.env.DB_USER
36
- dbConfig.password = process.env.DB_PASSWORD
37
- } else {
38
- dbConfig.name = process.env.DB_NAME || 'flux.sqlite'
39
- }
40
-
41
- let persistence: ServerConfig['persistence']
42
-
43
- if (dbDriver === 'sqlite' || process.env.DB_HOST) {
44
- this.setupDatabase(dbDriver, dbConfig)
45
-
46
- const adapter =
47
- dbDriver === 'sqlite'
48
- ? new SQLitePersistence(DB.connection())
49
- : new MySQLPersistence(DB.connection())
50
- adapter
51
- .setupTable()
52
- .catch((err) => console.error('[FluxConsole] SQL Archive Setup Error:', err))
53
-
54
- persistence = {
55
- adapter,
56
- archiveCompleted: process.env.PERSIST_ARCHIVE_COMPLETED === 'true',
57
- archiveFailed: process.env.PERSIST_ARCHIVE_FAILED !== 'false',
58
- archiveEnqueued: process.env.PERSIST_ARCHIVE_ENQUEUED === 'true',
59
- }
60
- console.log(`[FluxConsole] SQL Archive enabled via ${dbDriver}`)
61
- }
62
-
63
- return {
64
- port: parseInt(process.env.PORT || '3000', 10),
65
- redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
66
- queuePrefix: process.env.QUEUE_PREFIX || 'queue:',
67
- dbDriver,
68
- dbConfig,
69
- persistence,
70
- }
71
- }
72
-
73
- private static setupDatabase(driver: 'sqlite' | 'mysql', config: ServerConfig['dbConfig']): void {
74
- if (driver === 'sqlite') {
75
- DB.addConnection('default', {
76
- driver: 'sqlite',
77
- database: config.name,
78
- })
79
- } else {
80
- DB.addConnection('default', {
81
- driver,
82
- host: config.host,
83
- port: config.port,
84
- database: config.name,
85
- username: config.username,
86
- password: config.password,
87
- })
88
- }
89
- }
90
- }