@gravito/zenith 1.1.2 → 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 (76) hide show
  1. package/README.md +95 -22
  2. package/README.zh-TW.md +88 -0
  3. package/dist/bin.js +54699 -39316
  4. package/dist/client/assets/index-C80c1frR.css +1 -0
  5. package/dist/client/assets/index-CrWem9u3.js +434 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/server/index.js +54699 -39316
  8. package/package.json +20 -9
  9. package/CHANGELOG.md +0 -47
  10. package/Dockerfile +0 -46
  11. package/Dockerfile.demo-worker +0 -29
  12. package/ECOSYSTEM_EXPANSION_RFC.md +0 -130
  13. package/bin/flux-console.ts +0 -2
  14. package/dist/client/assets/index-BSMp8oq_.js +0 -436
  15. package/dist/client/assets/index-BwxlHx-_.css +0 -1
  16. package/docker-compose.yml +0 -40
  17. package/docs/ALERTING_GUIDE.md +0 -71
  18. package/docs/DEPLOYMENT.md +0 -157
  19. package/docs/DOCS_INTERNAL.md +0 -73
  20. package/docs/LARAVEL_ZENITH_ROADMAP.md +0 -109
  21. package/docs/QUASAR_MASTER_PLAN.md +0 -140
  22. package/docs/QUICK_TEST_GUIDE.md +0 -72
  23. package/docs/ROADMAP.md +0 -85
  24. package/docs/integrations/LARAVEL.md +0 -207
  25. package/postcss.config.js +0 -6
  26. package/scripts/debug_redis_keys.ts +0 -24
  27. package/scripts/flood-logs.ts +0 -21
  28. package/scripts/seed.ts +0 -213
  29. package/scripts/verify-throttle.ts +0 -49
  30. package/scripts/worker.ts +0 -124
  31. package/specs/PULSE_SPEC.md +0 -86
  32. package/src/bin.ts +0 -6
  33. package/src/client/App.tsx +0 -72
  34. package/src/client/Layout.tsx +0 -672
  35. package/src/client/Sidebar.tsx +0 -112
  36. package/src/client/ThroughputChart.tsx +0 -144
  37. package/src/client/WorkerStatus.tsx +0 -226
  38. package/src/client/components/BrandIcons.tsx +0 -168
  39. package/src/client/components/ConfirmDialog.tsx +0 -126
  40. package/src/client/components/JobInspector.tsx +0 -554
  41. package/src/client/components/LogArchiveModal.tsx +0 -432
  42. package/src/client/components/NotificationBell.tsx +0 -212
  43. package/src/client/components/PageHeader.tsx +0 -47
  44. package/src/client/components/Toaster.tsx +0 -90
  45. package/src/client/components/UserProfileDropdown.tsx +0 -186
  46. package/src/client/contexts/AuthContext.tsx +0 -105
  47. package/src/client/contexts/NotificationContext.tsx +0 -128
  48. package/src/client/index.css +0 -174
  49. package/src/client/index.html +0 -12
  50. package/src/client/main.tsx +0 -15
  51. package/src/client/pages/LoginPage.tsx +0 -162
  52. package/src/client/pages/MetricsPage.tsx +0 -417
  53. package/src/client/pages/OverviewPage.tsx +0 -517
  54. package/src/client/pages/PulsePage.tsx +0 -488
  55. package/src/client/pages/QueuesPage.tsx +0 -379
  56. package/src/client/pages/SchedulesPage.tsx +0 -540
  57. package/src/client/pages/SettingsPage.tsx +0 -1020
  58. package/src/client/pages/WorkersPage.tsx +0 -394
  59. package/src/client/pages/index.ts +0 -8
  60. package/src/client/utils.ts +0 -15
  61. package/src/server/config/ServerConfigManager.ts +0 -90
  62. package/src/server/index.ts +0 -860
  63. package/src/server/middleware/auth.ts +0 -127
  64. package/src/server/services/AlertService.ts +0 -321
  65. package/src/server/services/CommandService.ts +0 -137
  66. package/src/server/services/LogStreamProcessor.ts +0 -93
  67. package/src/server/services/MaintenanceScheduler.ts +0 -78
  68. package/src/server/services/PulseService.ts +0 -91
  69. package/src/server/services/QueueMetricsCollector.ts +0 -138
  70. package/src/server/services/QueueService.ts +0 -631
  71. package/src/shared/types.ts +0 -198
  72. package/tailwind.config.js +0 -73
  73. package/tests/placeholder.test.ts +0 -7
  74. package/tsconfig.json +0 -38
  75. package/tsconfig.node.json +0 -12
  76. package/vite.config.ts +0 -27
@@ -1,78 +0,0 @@
1
- import type { Redis } from 'ioredis'
2
-
3
- export interface MaintenanceConfig {
4
- autoCleanup: boolean
5
- retentionDays: number
6
- lastRun?: number
7
- }
8
-
9
- export class MaintenanceScheduler {
10
- private static readonly ONE_DAY = 24 * 60 * 60 * 1000
11
- private static readonly CHECK_INTERVAL = 3600000 // 1 hour
12
-
13
- constructor(
14
- private redis: Redis,
15
- private cleanupCallback: (retentionDays: number) => Promise<number>
16
- ) {}
17
-
18
- /**
19
- * Start the maintenance loop
20
- */
21
- start(initialDelay = 30000): void {
22
- setTimeout(() => {
23
- const loop = async () => {
24
- try {
25
- await this.checkMaintenance()
26
- } catch (err) {
27
- console.error('[Maintenance] Task Error:', err)
28
- }
29
- setTimeout(loop, MaintenanceScheduler.CHECK_INTERVAL)
30
- }
31
- loop()
32
- }, initialDelay)
33
- }
34
-
35
- /**
36
- * Check and run maintenance if needed
37
- */
38
- private async checkMaintenance(): Promise<void> {
39
- const config = await this.getConfig()
40
- if (!config.autoCleanup) {
41
- return
42
- }
43
-
44
- const now = Date.now()
45
- const lastRun = config.lastRun || 0
46
-
47
- if (now - lastRun >= MaintenanceScheduler.ONE_DAY) {
48
- console.log(
49
- `[Maintenance] Starting Auto-Cleanup (Retention: ${config.retentionDays} days)...`
50
- )
51
- const deleted = await this.cleanupCallback(config.retentionDays)
52
- console.log(`[Maintenance] Cleanup Complete. Removed ${deleted} records.`)
53
-
54
- await this.saveConfig({
55
- ...config,
56
- lastRun: now,
57
- })
58
- }
59
- }
60
-
61
- /**
62
- * Get maintenance configuration
63
- */
64
- async getConfig(): Promise<MaintenanceConfig> {
65
- const data = await this.redis.get('gravito:zenith:maintenance:config')
66
- if (data) {
67
- return JSON.parse(data)
68
- }
69
- return { autoCleanup: false, retentionDays: 30 }
70
- }
71
-
72
- /**
73
- * Save maintenance configuration
74
- */
75
- async saveConfig(config: MaintenanceConfig): Promise<void> {
76
- await this.redis.set('gravito:zenith:maintenance:config', JSON.stringify(config))
77
- }
78
- }
@@ -1,91 +0,0 @@
1
- import { Redis } from 'ioredis'
2
- import type { PulseNode } from '../../shared/types'
3
-
4
- /**
5
- * PulseService manages the discovery and health monitoring of system nodes.
6
- *
7
- * It scans Redis for heartbeat keys emitted by Quasar agents and groups
8
- * them by service name for the Zenith dashboard.
9
- *
10
- * @public
11
- * @since 3.0.0
12
- */
13
- export class PulseService {
14
- private redis: Redis
15
- private prefix = 'gravito:quasar:node:'
16
-
17
- constructor(redisUrl: string) {
18
- this.redis = new Redis(redisUrl, {
19
- lazyConnect: true,
20
- })
21
- }
22
-
23
- async connect() {
24
- await this.redis.connect()
25
- }
26
-
27
- /**
28
- * Discovers active Pulse nodes using SCAN.
29
- */
30
- async getNodes(): Promise<Record<string, PulseNode[]>> {
31
- const nodes: PulseNode[] = []
32
- let cursor = '0'
33
- const now = Date.now()
34
-
35
- do {
36
- // Scan for pulse keys
37
- const result = await this.redis.scan(cursor, 'MATCH', `${this.prefix}*`, 'COUNT', 100)
38
- cursor = result[0]
39
- const keys = result[1]
40
-
41
- if (keys.length > 0) {
42
- // Fetch values
43
- const values = await this.redis.mget(...keys)
44
-
45
- values.forEach((v) => {
46
- if (v) {
47
- try {
48
- const node = JSON.parse(v) as PulseNode
49
- // Filter out stale nodes if TTL didn't catch them yet (grace period 60s)
50
- if (now - node.timestamp < 60000) {
51
- nodes.push(node)
52
- }
53
- } catch (_e) {
54
- // Ignore malformed
55
- }
56
- }
57
- })
58
- }
59
- } while (cursor !== '0')
60
-
61
- // Group by service
62
- const grouped: Record<string, PulseNode[]> = {}
63
-
64
- // Sort nodes by service name, then by node id for stable UI positions
65
- nodes.sort((a, b) => {
66
- const sComp = a.service.localeCompare(b.service)
67
- if (sComp !== 0) {
68
- return sComp
69
- }
70
- return a.id.localeCompare(b.id)
71
- })
72
-
73
- for (const node of nodes) {
74
- if (!grouped[node.service]) {
75
- grouped[node.service] = []
76
- }
77
- grouped[node.service].push(node)
78
- }
79
-
80
- return grouped
81
- }
82
-
83
- /**
84
- * Manually record a heartbeat (for this Zenith server itself).
85
- */
86
- async recordHeartbeat(node: PulseNode): Promise<void> {
87
- const key = `${this.prefix}${node.service}:${node.id}`
88
- // TTL 30 seconds
89
- await this.redis.set(key, JSON.stringify(node), 'EX', 30)
90
- }
91
- }
@@ -1,138 +0,0 @@
1
- import type { Redis } from 'ioredis'
2
- import type { GlobalStats, QueueStats, WorkerReport } from './QueueService'
3
-
4
- export class QueueMetricsCollector {
5
- constructor(
6
- private redis: Redis,
7
- private prefix = 'queue:'
8
- ) {}
9
-
10
- /**
11
- * Discover all queues using SCAN to avoid blocking Redis
12
- */
13
- async listQueues(): Promise<QueueStats[]> {
14
- const queues = new Set<string>()
15
- let cursor = '0'
16
- let iterations = 0
17
- const MAX_ITERATIONS = 1000
18
-
19
- do {
20
- const result = await this.redis.scan(cursor, 'MATCH', `${this.prefix}*`, 'COUNT', 100)
21
- cursor = result[0]
22
- const keys = result[1]
23
-
24
- for (const key of keys) {
25
- const relative = key.slice(this.prefix.length)
26
- const parts = relative.split(':')
27
- const candidateName = parts[0]
28
-
29
- if (candidateName && !this.isSystemKey(candidateName)) {
30
- queues.add(candidateName)
31
- }
32
- }
33
-
34
- iterations++
35
- } while (cursor !== '0' && iterations < MAX_ITERATIONS)
36
-
37
- return this.collectQueueStats(Array.from(queues).sort())
38
- }
39
-
40
- /**
41
- * Collect statistics for a list of queues
42
- */
43
- private async collectQueueStats(queueNames: string[]): Promise<QueueStats[]> {
44
- const stats: QueueStats[] = []
45
- const BATCH_SIZE = 10
46
-
47
- for (let i = 0; i < queueNames.length; i += BATCH_SIZE) {
48
- const batch = queueNames.slice(i, i + BATCH_SIZE)
49
- const batchResults = await Promise.all(batch.map(async (name) => this.getQueueStats(name)))
50
- stats.push(...batchResults)
51
- }
52
-
53
- return stats
54
- }
55
-
56
- /**
57
- * Get statistics for a single queue
58
- */
59
- private async getQueueStats(name: string): Promise<QueueStats> {
60
- const waiting = await this.redis.llen(`${this.prefix}${name}`)
61
- const delayed = await this.redis.zcard(`${this.prefix}${name}:delayed`)
62
- const failed = await this.redis.llen(`${this.prefix}${name}:failed`)
63
- const active = await this.redis.scard(`${this.prefix}${name}:active`)
64
- const paused = await this.redis.get(`${this.prefix}${name}:paused`)
65
-
66
- return {
67
- name,
68
- waiting,
69
- delayed,
70
- failed,
71
- active,
72
- paused: paused === '1',
73
- }
74
- }
75
-
76
- /**
77
- * Get global statistics including throughput
78
- */
79
- async getGlobalStats(): Promise<GlobalStats> {
80
- const queues = await this.listQueues()
81
- const throughput = await this.getThroughputData()
82
-
83
- return {
84
- queues,
85
- throughput,
86
- workers: [],
87
- }
88
- }
89
-
90
- /**
91
- * Get throughput data for the last hour
92
- */
93
- private async getThroughputData(): Promise<{ timestamp: string; count: number }[]> {
94
- const data: { timestamp: string; count: number }[] = []
95
- const currentMinute = Math.floor(Date.now() / 60000)
96
-
97
- for (let i = 0; i < 60; i++) {
98
- const minute = currentMinute - i
99
- const count = await this.redis.get(`flux_console:throughput:${minute}`)
100
- data.push({
101
- timestamp: new Date(minute * 60000).toISOString(),
102
- count: Number(count) || 0,
103
- })
104
- }
105
-
106
- return data.reverse()
107
- }
108
-
109
- /**
110
- * Get reports from all active workers
111
- */
112
- async listWorkers(): Promise<WorkerReport[]> {
113
- const workers = await this.redis.smembers('flux_console:workers')
114
-
115
- return Promise.all(workers.map(async (workerId) => this.getWorkerReport(workerId)))
116
- }
117
-
118
- /**
119
- * Get report from a specific worker
120
- */
121
- private async getWorkerReport(workerId: string): Promise<WorkerReport> {
122
- const data = await this.redis.get(`flux_console:worker:${workerId}`)
123
-
124
- if (!data) {
125
- throw new Error(`Worker ${workerId} not found`)
126
- }
127
-
128
- return JSON.parse(data) as WorkerReport
129
- }
130
-
131
- /**
132
- * Check if a key is a system key
133
- */
134
- private isSystemKey(key: string): boolean {
135
- const systemKeys = ['active', 'schedules', 'schedule', 'lock']
136
- return systemKeys.includes(key)
137
- }
138
- }