@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
package/scripts/seed.ts DELETED
@@ -1,213 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { Job, QueueManager } from '@gravito/stream'
3
- import Redis from 'ioredis'
4
-
5
- /**
6
- * Flux Console Unified Seed Script
7
- *
8
- * Usage:
9
- * bun scripts/seed.ts [mode]
10
- *
11
- * Modes:
12
- * standard - Small set of diverse jobs (Waiting, Delayed, Failed)
13
- * stress - Many queues and many jobs for performance testing
14
- * batch - Setup for batch operation testing (100+ jobs)
15
- * cron - Register recurring schedules
16
- * cleanup - Flush Redis and clear logs
17
- */
18
-
19
- const mode = process.argv[2] || 'standard'
20
- const redis = new Redis('redis://localhost:6379')
21
- const prefix = 'queue:'
22
-
23
- // Simple Job class for testing
24
- class GenericJob extends Job {
25
- constructor(
26
- id: any = null,
27
- public data: any = {}
28
- ) {
29
- super()
30
- this.id = id
31
- }
32
- async handle() {}
33
- }
34
-
35
- const manager = new QueueManager({
36
- default: 'redis',
37
- connections: {
38
- redis: {
39
- driver: 'redis',
40
- client: redis,
41
- prefix,
42
- },
43
- },
44
- })
45
-
46
- manager.registerJobClasses([GenericJob])
47
-
48
- async function cleanup() {
49
- console.log('🧹 Cleaning up Redis...')
50
- const keys = await redis.keys(`${prefix}*`)
51
- const internalKeys = await redis.keys('flux_console:*')
52
- const allKeys = [...keys, ...internalKeys]
53
-
54
- if (allKeys.length > 0) {
55
- await redis.del(...allKeys)
56
- }
57
- console.log(`✅ Removed ${allKeys.length} keys.`)
58
- }
59
-
60
- async function seedStandard() {
61
- console.log('🚀 Seeding standard data...')
62
-
63
- // Orders Queue
64
- for (let i = 1; i <= 5; i++) {
65
- const job = new GenericJob(`ORD-${1000 + i}`, { amount: Math.random() * 100 })
66
- job.queueName = 'orders'
67
- await manager.push(job)
68
- }
69
-
70
- // Failed Jobs
71
- for (let i = 1; i <= 3; i++) {
72
- const jobInstance = new GenericJob(`FAIL-${i}`, { error: 'Payment Timeout' })
73
- jobInstance.queueName = 'orders'
74
- const serialized = manager.getSerializer().serialize(jobInstance)
75
-
76
- await redis.lpush(
77
- `${prefix}orders:failed`,
78
- JSON.stringify({
79
- ...serialized,
80
- status: 'failed',
81
- failedReason: 'Payment Timeout',
82
- failedAt: Date.now(),
83
- })
84
- )
85
- }
86
-
87
- // Delayed Jobs
88
- for (let i = 1; i <= 3; i++) {
89
- const job = new GenericJob(`DLY-${i}`, { type: 'reminder' })
90
- job.queueName = 'notifications'
91
- job.delay(3600 * 1000) // 1 hour
92
- await manager.push(job)
93
- }
94
- }
95
-
96
- async function seedStress() {
97
- console.log('🔥 Stress Mode: Creating 15 queues with jobs...')
98
- const queues = [
99
- 'billing',
100
- 'shipping',
101
- 'inventory',
102
- 'marketing',
103
- 'crm',
104
- 'auth',
105
- 'logs',
106
- 'backups',
107
- 'indexing',
108
- 'cache',
109
- 'sync',
110
- 'webhooks',
111
- 'api',
112
- 'metrics',
113
- 'events',
114
- ]
115
-
116
- for (const q of queues) {
117
- const count = 10 + Math.floor(Math.random() * 40)
118
- for (let i = 0; i < count; i++) {
119
- const job = new GenericJob(`JOB-${q}-${i}`, { timestamp: Date.now() })
120
- job.queueName = q
121
- await manager.push(job)
122
- }
123
- console.log(` - ${q}: ${count} jobs`)
124
- }
125
- }
126
-
127
- async function seedBatch() {
128
- console.log('📦 Batch Mode: Setting up specialized data for batch testing...')
129
-
130
- // 100 Waiting jobs
131
- for (let i = 1; i <= 100; i++) {
132
- const job = new GenericJob(`BATCH-WAIT-${i}`)
133
- job.queueName = 'test-batch'
134
- await manager.push(job)
135
- }
136
-
137
- // 50 Failed jobs
138
- for (let i = 1; i <= 50; i++) {
139
- const jobInstance = new GenericJob(`BATCH-FAIL-${i}`, { error: 'Database Connection Lost' })
140
- jobInstance.queueName = 'test-batch-fail'
141
- const serialized = manager.getSerializer().serialize(jobInstance)
142
-
143
- await redis.lpush(
144
- `${prefix}test-batch-fail:failed`,
145
- JSON.stringify({
146
- ...serialized,
147
- status: 'failed',
148
- attempts: 3,
149
- failedAt: Date.now(),
150
- })
151
- )
152
- }
153
- }
154
-
155
- async function seedCron() {
156
- console.log('⏰ Cron Mode: Registering recurring schedules...')
157
- const scheduler = manager.getScheduler()
158
- const serializer = manager.getSerializer()
159
-
160
- const rawSchedules = [
161
- { id: 'cleanup-tmp', cron: '*/1 * * * *', queue: 'system', name: 'CleanupTmp' },
162
- { id: 'daily-report', cron: '0 0 * * *', queue: 'reports', name: 'DailyReport' },
163
- { id: 'health-check', cron: '*/5 * * * *', queue: 'monitoring', name: 'HealthCheck' },
164
- { id: 'high-frequency', cron: '*/1 * * * *', queue: 'fast', name: 'Pulse' },
165
- ]
166
-
167
- for (const s of rawSchedules) {
168
- const jobInstance = new GenericJob(s.id, { task: s.name })
169
- jobInstance.queueName = s.queue
170
- const serialized = serializer.serialize(jobInstance)
171
-
172
- await scheduler.register({
173
- id: s.id,
174
- cron: s.cron,
175
- queue: s.queue,
176
- job: serialized,
177
- })
178
- console.log(` - Registered: ${s.id} (${s.cron})`)
179
- }
180
- }
181
-
182
- async function main() {
183
- try {
184
- if (mode === 'cleanup') {
185
- await cleanup()
186
- } else if (mode === 'standard') {
187
- await cleanup()
188
- await seedStandard()
189
- } else if (mode === 'stress') {
190
- await seedStress()
191
- } else if (mode === 'batch') {
192
- await seedBatch()
193
- } else if (mode === 'cron') {
194
- await seedCron()
195
- } else if (mode === 'all') {
196
- await cleanup()
197
- await seedStandard()
198
- await seedStress()
199
- await seedBatch()
200
- await seedCron()
201
- } else {
202
- console.log('❌ Unknown mode. Try: standard, stress, batch, cron, cleanup, all')
203
- }
204
- } catch (err) {
205
- console.error('💥 Error:', err)
206
- } finally {
207
- redis.disconnect()
208
- console.log('\n🏁 Done.')
209
- process.exit(0)
210
- }
211
- }
212
-
213
- main()
@@ -1,49 +0,0 @@
1
- console.log('🎧 Connecting to log stream...')
2
- const req = await fetch('http://localhost:3000/api/logs/stream')
3
- if (!req.body) {
4
- throw new Error('No body')
5
- }
6
-
7
- const reader = req.body.getReader()
8
- const decoder = new TextDecoder()
9
-
10
- let logCount = 0
11
- const start = Date.now()
12
-
13
- // Count for 2 seconds
14
- const DURATION = 2000
15
-
16
- console.log(`⏳ Measuring received logs for ${DURATION}ms...`)
17
-
18
- async function readStream() {
19
- while (true) {
20
- const { done, value } = await reader.read()
21
- if (done) {
22
- break
23
- }
24
-
25
- const chunk = decoder.decode(value)
26
- // SSE format: event: log\ndata: ...\n\n
27
- const matches = chunk.match(/event: log/g)
28
- if (matches) {
29
- logCount += matches.length
30
- }
31
-
32
- if (Date.now() - start > DURATION) {
33
- break
34
- }
35
- }
36
-
37
- console.log(`📊 Result: Received ${logCount} logs in ${(Date.now() - start) / 1000}s`)
38
- console.log(`ℹ️ Expected max: ~50-60 logs (50/sec limit + potential buffer/history)`)
39
-
40
- if (logCount > 150) {
41
- console.error('❌ Throttling FAILED! Too many logs received.')
42
- process.exit(1)
43
- } else {
44
- console.log('✅ Throttling PASSED!')
45
- process.exit(0)
46
- }
47
- }
48
-
49
- readStream()
package/scripts/worker.ts DELETED
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { Consumer, Job, QueueManager } from '@gravito/stream'
3
- import Redis from 'ioredis'
4
-
5
- /**
6
- * Flux Console Unified Worker Script
7
- *
8
- * Usage:
9
- * bun scripts/worker.ts [queues] [--fail=rate] [--delay=ms]
10
- *
11
- * Example:
12
- * bun scripts/worker.ts orders,reports --fail=0.1 --delay=200
13
- * bun scripts/worker.ts all
14
- */
15
-
16
- const redis = new Redis('redis://localhost:6379')
17
- const prefix = 'queue:'
18
-
19
- const queuesRaw = process.argv[2] || 'orders,notifications,test-batch,billing,analytics'
20
- let queues: string[] = []
21
-
22
- if (queuesRaw === 'all') {
23
- console.log('🔍 Discovering all queues...')
24
- const keys = await redis.keys(`${prefix}*`)
25
- const set = new Set<string>()
26
- for (const k of keys) {
27
- const name = k.slice(prefix.length).split(':')[0]
28
- // Exclude internal management keys
29
- if (
30
- name &&
31
- ![
32
- 'active',
33
- 'schedules',
34
- 'schedule',
35
- 'worker',
36
- 'lock',
37
- 'logs',
38
- 'metrics',
39
- 'throughput',
40
- ].includes(name)
41
- ) {
42
- set.add(name)
43
- }
44
- }
45
- queues = Array.from(set)
46
- if (queues.length === 0) {
47
- console.log('⚠️ No active queues found. Defaulting to standard set...')
48
- queues = ['orders', 'notifications', 'test-batch', 'billing', 'analytics']
49
- }
50
- } else {
51
- queues = queuesRaw.split(',')
52
- }
53
-
54
- const failRate = parseFloat(process.argv.find((a) => a.startsWith('--fail='))?.split('=')[1] || '0')
55
- const processDelay = parseInt(
56
- process.argv.find((a) => a.startsWith('--delay='))?.split('=')[1] || '100',
57
- 10
58
- )
59
-
60
- // Simple Job class for testing
61
- class GenericJob extends Job {
62
- constructor(
63
- id: any = null,
64
- public data: any = {}
65
- ) {
66
- super()
67
- this.id = id
68
- }
69
- async handle() {
70
- // Simulated work
71
- if (processDelay > 0) {
72
- await new Promise((r) => setTimeout(r, processDelay))
73
- }
74
-
75
- // Simulated failure
76
- if (Math.random() < failRate) {
77
- throw new Error('Simulated random failure')
78
- }
79
- }
80
- }
81
-
82
- const manager = new QueueManager({
83
- default: 'redis',
84
- connections: {
85
- redis: {
86
- driver: 'redis',
87
- client: redis,
88
- prefix,
89
- },
90
- },
91
- })
92
-
93
- manager.registerJobClasses([GenericJob])
94
-
95
- console.log('🚀 Starting Flux Unified Worker...')
96
- console.log(`📡 Queues: ${queues.join(', ')}`)
97
- console.log(`⚠️ Fail Rate: ${(failRate * 100).toFixed(0)}%`)
98
- console.log(`⏱️ Sim Delay: ${processDelay}ms\n`)
99
-
100
- const consumer = new Consumer(manager, {
101
- queues,
102
- connection: 'redis',
103
- pollInterval: 100,
104
- monitor: {
105
- prefix: 'flux_console:', // Critical: match the prefix the Console looks for
106
- },
107
- workerOptions: {
108
- maxAttempts: 3,
109
- },
110
- })
111
-
112
- console.log('✅ Worker started. Monitoring enabled (check the Workers page in Console).')
113
-
114
- consumer.start().catch((err) => {
115
- console.error('💥 Consumer Error:', err)
116
- process.exit(1)
117
- })
118
-
119
- process.on('SIGINT', async () => {
120
- console.log('\n🛑 Shutting down worker...')
121
- await consumer.stop()
122
- redis.disconnect()
123
- process.exit(0)
124
- })
@@ -1,86 +0,0 @@
1
- # Gravito Pulse Implementation Spec
2
-
3
- ## Overview
4
- Gravito Pulse is a lightweight APM (Application Performance Monitoring) system integrated into Zenith. It follows the philosophy: *"If you can connect to Redis, you are monitored."*
5
-
6
- ## 1. Gravito Pulse Protocol (GPP)
7
-
8
- ### Data Structure
9
- Pulse uses Redis keys with specific TTLs to represent live services.
10
-
11
- - **Key Pattern**: `gravito:quasar:node:{service}:{node_id}`
12
- - **TTL**: 30 seconds (Agents should heartbeat every 10-15s).
13
- - **Data Type**: String (JSON)
14
-
15
- ### Payload Schema
16
- ```json
17
- {
18
- "id": "string", // Unique Instance ID (e.g., UUID or Hostname-PID)
19
- "service": "string", // Group name (e.g., "worker-billing", "api-gateway")
20
- "language": "string", // "node" | "bun" | "deno" | "php" | "go" | "python" | "other"
21
- "version": "string", // Language/Runtime Version
22
- "pid": "number", // Process ID
23
- "hostname": "string", // Machine Hostname or Custom Name
24
- "platform": "string", // OS Platform (linux, darwin, win32)
25
- "cpu": {
26
- "system": "number", // System Load % (0-100)
27
- "process": "number", // Process Usage % (0-100)
28
- "cores": "number" // Core count
29
- },
30
- "memory": {
31
- "system": {
32
- "total": "number", // System Total Memory (bytes)
33
- "free": "number", // System Free Memory (bytes)
34
- "used": "number" // System Used Memory (bytes)
35
- },
36
- "process": {
37
- "rss": "number", // Resident Set Size (bytes)
38
- "heapTotal": "number",// Heap Total (bytes)
39
- "heapUsed": "number" // Heap Used (bytes)
40
- }
41
- },
42
- "runtime": {
43
- "uptime": "number", // Process uptime in seconds
44
- "framework": "string" // Optional framework info
45
- },
46
- "timestamp": "number" // Unix Ms Timestamp
47
- }
48
- ```
49
-
50
- ## 2. Implementation Modules
51
-
52
- ### A. Client SDK (`@gravito/pulse-node`)
53
- A lightweight agent to collect metrics and publish to Redis.
54
- - **Dependencies**: `ioredis`, `pidusage` (optional, or use native `os`/`process`).
55
- - **Functionality**:
56
- - `startPulse({ service: string })`: Starts the heartbeat loop.
57
- - Collects CPU/RAM usage.
58
- - Publishes to Redis.
59
-
60
- ### B. Server Collector (Zenith Console)
61
- - **Service**: `PulseService`
62
- - **Method**: `getNodes()`
63
- - Performs `SCAN 0 MATCH pulse:* COUNT 100`.
64
- - Returns grouped nodes by `service`.
65
- - **API**: `GET /api/pulse/nodes`
66
-
67
- ### C. Frontend Dashboard (Zenith UI)
68
- - **Route**: `/pulse`
69
- - **Components**:
70
- - `ServiceGroup`: A container for nodes of a specific service.
71
- - `NodeCard`: Displays CPU/RAM sparklines (optional) and current health.
72
- - `HealthBadge`: Green (Fresh), Yellow (>15s ago), Red (Dead/Gone - though Redis TTL handles removal, frontend can handle stale UI).
73
-
74
- ## 3. Alerts (Phase 2)
75
- - Server-side checker that monitors values from `PulseService`.
76
- - Triggers `AlertService` if:
77
- - CPU > 90% for 2 mins.
78
- - Memory > 90% for 5 mins.
79
- - Disk < 10% free.
80
-
81
- ## 4. Work Plan
82
- 1. **Define Types**: Add `PulseNode` interface to `@gravito/custom-types` or `flux-console` shared types.
83
- 2. **Implement Server Collector**: Create `PulseService` in `packages/flux-console/server/services`.
84
- 3. **Implement API**: Add route in `packages/flux-console/server/routes.ts`.
85
- 4. **Implement UI**: Create `PulsePage` and components.
86
- 5. **Implement Node Client**: Add `startPulse` to `packages/stream` (or separate package) to verify "dogfooding" by having the server monitor itself.
package/src/bin.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bun
2
- import server from './server/index'
3
-
4
- console.log(`[Flux Console] CLI wrapper initialized.`)
5
-
6
- export default server
@@ -1,72 +0,0 @@
1
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
- import { RefreshCcw } from 'lucide-react'
3
- import { BrowserRouter, Route, Routes } from 'react-router-dom'
4
- import { AuthProvider, useAuth } from './contexts/AuthContext'
5
- import { NotificationProvider } from './contexts/NotificationContext'
6
- import { Layout } from './Layout'
7
- import {
8
- LoginPage,
9
- MetricsPage,
10
- OverviewPage,
11
- PulsePage,
12
- QueuesPage,
13
- SchedulesPage,
14
- SettingsPage,
15
- WorkersPage,
16
- } from './pages'
17
-
18
- const queryClient = new QueryClient()
19
-
20
- function AuthenticatedRoutes() {
21
- const { isAuthenticated, isAuthEnabled, isLoading } = useAuth()
22
-
23
- // Show loading spinner while checking auth
24
- if (isLoading) {
25
- return (
26
- <div className="min-h-screen flex items-center justify-center bg-background">
27
- <div className="flex flex-col items-center gap-4">
28
- <RefreshCcw className="animate-spin text-primary" size={48} />
29
- <p className="text-muted-foreground font-bold uppercase tracking-[0.3em] text-xs">
30
- Initializing...
31
- </p>
32
- </div>
33
- </div>
34
- )
35
- }
36
-
37
- // If auth is enabled and user is not authenticated, show login
38
- if (isAuthEnabled && !isAuthenticated) {
39
- return <LoginPage />
40
- }
41
-
42
- // Otherwise, show the main app
43
- return (
44
- <NotificationProvider>
45
- <Layout>
46
- <Routes>
47
- <Route path="/" element={<OverviewPage />} />
48
- <Route path="/queues" element={<QueuesPage />} />
49
- <Route path="/schedules" element={<SchedulesPage />} />
50
- <Route path="/workers" element={<WorkersPage />} />
51
- <Route path="/metrics" element={<MetricsPage />} />
52
- <Route path="/pulse" element={<PulsePage />} />
53
- <Route path="/settings" element={<SettingsPage />} />
54
- </Routes>
55
- </Layout>
56
- </NotificationProvider>
57
- )
58
- }
59
-
60
- function App() {
61
- return (
62
- <QueryClientProvider client={queryClient}>
63
- <BrowserRouter>
64
- <AuthProvider>
65
- <AuthenticatedRoutes />
66
- </AuthProvider>
67
- </BrowserRouter>
68
- </QueryClientProvider>
69
- )
70
- }
71
-
72
- export default App