@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,47 +0,0 @@
1
- import type { LucideIcon } from 'lucide-react'
2
- import type { ReactNode } from 'react'
3
- import { cn } from '../utils'
4
-
5
- interface PageHeaderProps {
6
- icon: LucideIcon
7
- title: string
8
- description?: string
9
- children?: ReactNode
10
- className?: string
11
- }
12
-
13
- /**
14
- * Reusable header component for Zenith dashboard pages.
15
- *
16
- * Provides a consistent layout for the page title, icon, description,
17
- * and optional action buttons.
18
- *
19
- * @public
20
- * @since 3.0.0
21
- */
22
- export function PageHeader({
23
- icon: Icon,
24
- title,
25
- description,
26
- children,
27
- className,
28
- }: PageHeaderProps) {
29
- return (
30
- <div className={cn('flex justify-between items-end', className)}>
31
- <div>
32
- <h1 className="text-4xl font-black tracking-tighter flex items-center gap-3">
33
- <div className="p-2 bg-primary/10 rounded-xl text-primary">
34
- <Icon size={32} />
35
- </div>
36
- {title}
37
- </h1>
38
- {description && (
39
- <p className="text-muted-foreground mt-2 text-sm font-bold opacity-60 uppercase tracking-widest pl-[3.75rem]">
40
- {description}
41
- </p>
42
- )}
43
- </div>
44
- <div>{children}</div>
45
- </div>
46
- )
47
- }
@@ -1,90 +0,0 @@
1
- import { AnimatePresence, motion } from 'framer-motion'
2
- import { AlertCircle, CheckCircle2, Info, X } from 'lucide-react'
3
- import { useEffect, useState } from 'react'
4
- import { useNotifications } from '../contexts/NotificationContext'
5
- import { cn } from '../utils'
6
-
7
- /**
8
- * Global toast notification container for Zenith.
9
- *
10
- * Renders an animated stack of floating notifications in the corner of the
11
- * screen. It integrates with the `NotificationContext` to display real-time
12
- * alerts and messages.
13
- *
14
- * @public
15
- * @since 3.0.0
16
- */
17
- export function Toaster() {
18
- const { notifications, removeNotification } = useNotifications()
19
- const [activeIds, setActiveIds] = useState<Set<string>>(new Set())
20
-
21
- useEffect(() => {
22
- const now = Date.now()
23
- // Check for new notifications to add to display
24
- notifications.forEach((n) => {
25
- if (!n.read && now - n.timestamp < 5000 && !activeIds.has(n.id)) {
26
- setActiveIds((prev) => new Set(prev).add(n.id))
27
-
28
- // Set timer to remove this specific ID
29
- setTimeout(() => {
30
- setActiveIds((prev) => {
31
- const next = new Set(prev)
32
- next.delete(n.id)
33
- return next
34
- })
35
- }, 5000)
36
- }
37
- })
38
- }, [notifications, activeIds])
39
-
40
- const visibleNotifications = notifications.filter((n) => activeIds.has(n.id))
41
-
42
- return (
43
- <div className="fixed bottom-8 right-8 z-[2000] flex flex-col gap-3 w-full max-w-sm pointer-events-none">
44
- <AnimatePresence mode="popLayout">
45
- {visibleNotifications.map((n) => (
46
- <motion.div
47
- key={n.id}
48
- layout
49
- initial={{ opacity: 0, x: 50, scale: 0.9 }}
50
- animate={{ opacity: 1, x: 0, scale: 1 }}
51
- exit={{ opacity: 0, scale: 0.8, x: 20 }}
52
- className={cn(
53
- 'pointer-events-auto group relative flex items-start gap-4 p-4 rounded-2xl border shadow-2xl backdrop-blur-xl transition-all',
54
- n.type === 'success' && 'bg-green-500/10 border-green-500/20 text-green-500',
55
- n.type === 'error' && 'bg-red-500/10 border-red-500/20 text-red-500',
56
- n.type === 'warning' && 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500',
57
- n.type === 'info' && 'bg-primary/10 border-primary/20 text-primary'
58
- )}
59
- >
60
- <div className="flex-shrink-0 mt-0.5">
61
- {n.type === 'success' && <CheckCircle2 size={18} />}
62
- {n.type === 'error' && <AlertCircle size={18} />}
63
- {n.type === 'warning' && <AlertCircle size={18} />}
64
- {n.type === 'info' && <Info size={18} />}
65
- </div>
66
- <div className="flex-1 min-w-0">
67
- <h4 className="text-sm font-black tracking-tight leading-none mb-1">{n.title}</h4>
68
- <p className="text-xs font-medium opacity-80 leading-relaxed break-words">
69
- {n.message}
70
- </p>
71
- {n.source && (
72
- <span className="inline-block mt-2 px-1.5 py-0.5 rounded bg-white/10 text-[9px] font-black uppercase tracking-widest">
73
- {n.source}
74
- </span>
75
- )}
76
- </div>
77
- <button
78
- type="button"
79
- onClick={() => removeNotification(n.id)}
80
- className="mt-0.5 opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-white/10 rounded-lg"
81
- >
82
- <X size={14} />
83
- </button>
84
- <div className="absolute left-0 bottom-0 h-1 bg-current opacity-20 animate-toast-progress origin-left" />
85
- </motion.div>
86
- ))}
87
- </AnimatePresence>
88
- </div>
89
- )
90
- }
@@ -1,186 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { AnimatePresence, motion } from 'framer-motion'
3
- import { Activity, ChevronRight, Clock, LogOut, Server, Settings, Shield, User } from 'lucide-react'
4
- import { useEffect, useRef, useState } from 'react'
5
- import { useNavigate } from 'react-router-dom'
6
- import { useAuth } from '../contexts/AuthContext'
7
-
8
- /**
9
- * A user profile dropdown component for the Zenith dashboard.
10
- *
11
- * Displays current user information, system uptime, and provides access
12
- * to settings and logout functionality.
13
- *
14
- * @public
15
- * @since 3.0.0
16
- */
17
- export function UserProfileDropdown() {
18
- const [isOpen, setIsOpen] = useState(false)
19
- const { isAuthEnabled, logout } = useAuth()
20
- const navigate = useNavigate()
21
- const dropdownRef = useRef<HTMLDivElement>(null)
22
-
23
- const { data: systemStatus } = useQuery<any>({
24
- queryKey: ['system-status'],
25
- queryFn: () => fetch('/api/system/status').then((res) => res.json()),
26
- refetchInterval: 30000,
27
- })
28
-
29
- // Close dropdown when clicking outside
30
- useEffect(() => {
31
- const handleClickOutside = (event: MouseEvent) => {
32
- if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
33
- setIsOpen(false)
34
- }
35
- }
36
-
37
- if (isOpen) {
38
- document.addEventListener('mousedown', handleClickOutside)
39
- }
40
-
41
- return () => {
42
- document.removeEventListener('mousedown', handleClickOutside)
43
- }
44
- }, [isOpen])
45
-
46
- const handleLogout = async () => {
47
- await logout()
48
- setIsOpen(false)
49
- }
50
-
51
- const handleNavigate = (path: string) => {
52
- navigate(path)
53
- setIsOpen(false)
54
- }
55
-
56
- const formatUptime = (seconds: number) => {
57
- const hours = Math.floor(seconds / 3600)
58
- const minutes = Math.floor((seconds % 3600) / 60)
59
- if (hours > 0) {
60
- return `${hours}h ${minutes}m`
61
- }
62
- return `${minutes}m`
63
- }
64
-
65
- return (
66
- <div className="relative" ref={dropdownRef}>
67
- {/* Trigger */}
68
- <button
69
- type="button"
70
- className="flex items-center gap-3 cursor-pointer group outline-none"
71
- onClick={() => setIsOpen(!isOpen)}
72
- >
73
- <div className="text-right hidden sm:block">
74
- <p className="text-sm font-black tracking-tight group-hover:text-primary transition-colors">
75
- Admin User
76
- </p>
77
- <p className="text-[10px] font-black text-muted-foreground/60 uppercase tracking-widest">
78
- Architect
79
- </p>
80
- </div>
81
- <div className="w-10 h-10 rounded-2xl bg-gradient-to-br from-primary to-indigo-600 flex items-center justify-center text-primary-foreground font-black shadow-lg shadow-primary/20 group-hover:scale-105 transition-transform">
82
- <User size={20} />
83
- </div>
84
- </button>
85
-
86
- {/* Dropdown */}
87
- <AnimatePresence>
88
- {isOpen && (
89
- <motion.div
90
- initial={{ opacity: 0, y: 10, scale: 0.95 }}
91
- animate={{ opacity: 1, y: 0, scale: 1 }}
92
- exit={{ opacity: 0, y: 10, scale: 0.95 }}
93
- transition={{ duration: 0.2 }}
94
- className="absolute right-0 top-full mt-2 w-72 bg-card border rounded-2xl shadow-2xl overflow-hidden z-50"
95
- >
96
- {/* User Info Header */}
97
- <div className="p-5 bg-gradient-to-br from-primary/10 to-indigo-500/10 border-b">
98
- <div className="flex items-center gap-4">
99
- <div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-primary to-indigo-600 flex items-center justify-center text-primary-foreground shadow-lg">
100
- <User size={28} />
101
- </div>
102
- <div>
103
- <h3 className="font-bold text-lg">Admin User</h3>
104
- <p className="text-xs text-muted-foreground">System Administrator</p>
105
- <div className="flex items-center gap-1 mt-1">
106
- <Shield size={10} className="text-green-500" />
107
- <span className="text-[9px] font-bold text-green-500 uppercase tracking-widest">
108
- Full Access
109
- </span>
110
- </div>
111
- </div>
112
- </div>
113
- </div>
114
-
115
- {/* System Status */}
116
- <div className="p-4 bg-muted/20 border-b">
117
- <p className="text-[9px] font-black text-muted-foreground/50 uppercase tracking-widest mb-3">
118
- System Status
119
- </p>
120
- <div className="grid grid-cols-2 gap-3">
121
- <div className="flex items-center gap-2">
122
- <Activity size={12} className="text-green-500" />
123
- <span className="text-[10px] font-bold">Online</span>
124
- </div>
125
- <div className="flex items-center gap-2">
126
- <Clock size={12} className="text-muted-foreground" />
127
- <span className="text-[10px] font-bold">
128
- {systemStatus?.uptime ? formatUptime(systemStatus.uptime) : '...'}
129
- </span>
130
- </div>
131
- <div className="flex items-center gap-2">
132
- <Server size={12} className="text-muted-foreground" />
133
- <span className="text-[10px] font-bold">{systemStatus?.node || '...'}</span>
134
- </div>
135
- <div className="flex items-center gap-2">
136
- <span className="w-3 h-3 text-[10px] font-black">🔥</span>
137
- <span className="text-[10px] font-bold">
138
- {systemStatus?.memory?.rss || '...'}
139
- </span>
140
- </div>
141
- </div>
142
- </div>
143
-
144
- {/* Menu Items */}
145
- <div className="p-2">
146
- <button
147
- type="button"
148
- onClick={() => handleNavigate('/settings')}
149
- className="w-full flex items-center justify-between px-4 py-3 rounded-xl hover:bg-muted/50 transition-colors group"
150
- >
151
- <div className="flex items-center gap-3">
152
- <Settings
153
- size={18}
154
- className="text-muted-foreground group-hover:text-foreground transition-colors"
155
- />
156
- <span className="text-sm font-semibold">Settings</span>
157
- </div>
158
- <ChevronRight size={16} className="text-muted-foreground/50" />
159
- </button>
160
-
161
- {isAuthEnabled && (
162
- <button
163
- type="button"
164
- onClick={handleLogout}
165
- className="w-full flex items-center justify-between px-4 py-3 rounded-xl hover:bg-red-500/10 transition-colors group"
166
- >
167
- <div className="flex items-center gap-3">
168
- <LogOut size={18} className="text-red-500" />
169
- <span className="text-sm font-semibold text-red-500">Logout</span>
170
- </div>
171
- </button>
172
- )}
173
- </div>
174
-
175
- {/* Footer */}
176
- <div className="px-4 py-3 bg-muted/10 border-t">
177
- <p className="text-[9px] text-center text-muted-foreground/50">
178
- Flux Console v0.1.0-alpha.1
179
- </p>
180
- </div>
181
- </motion.div>
182
- )}
183
- </AnimatePresence>
184
- </div>
185
- )
186
- }
@@ -1,105 +0,0 @@
1
- import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from 'react'
2
-
3
- interface AuthContextType {
4
- isAuthenticated: boolean
5
- isAuthEnabled: boolean
6
- isLoading: boolean
7
- login: (password: string) => Promise<{ success: boolean; error?: string }>
8
- logout: () => Promise<void>
9
- checkAuth: () => Promise<void>
10
- }
11
-
12
- const AuthContext = createContext<AuthContextType | null>(null)
13
-
14
- /**
15
- * React hook to access authentication state and methods.
16
- *
17
- * @public
18
- * @since 3.0.0
19
- */
20
- export function useAuth() {
21
- const context = useContext(AuthContext)
22
- if (!context) {
23
- throw new Error('useAuth must be used within an AuthProvider')
24
- }
25
- return context
26
- }
27
-
28
- interface AuthProviderProps {
29
- children: ReactNode
30
- }
31
-
32
- /**
33
- * Provider component for managing global authentication state in Zenith.
34
- *
35
- * @public
36
- * @since 3.0.0
37
- */
38
- export function AuthProvider({ children }: AuthProviderProps) {
39
- const [isAuthenticated, setIsAuthenticated] = useState(false)
40
- const [isAuthEnabled, setIsAuthEnabled] = useState(false)
41
- const [isLoading, setIsLoading] = useState(true)
42
-
43
- const checkAuth = useCallback(async () => {
44
- try {
45
- const res = await fetch('/api/auth/status')
46
- const data = await res.json()
47
- setIsAuthEnabled(data.enabled)
48
- setIsAuthenticated(data.authenticated)
49
- } catch (err) {
50
- console.error('Failed to check auth status:', err)
51
- setIsAuthenticated(false)
52
- } finally {
53
- setIsLoading(false)
54
- }
55
- }, [])
56
-
57
- const login = async (password: string): Promise<{ success: boolean; error?: string }> => {
58
- try {
59
- const res = await fetch('/api/auth/login', {
60
- method: 'POST',
61
- headers: { 'Content-Type': 'application/json' },
62
- body: JSON.stringify({ password }),
63
- })
64
- const data = await res.json()
65
-
66
- if (data.success) {
67
- setIsAuthenticated(true)
68
- return { success: true }
69
- }
70
-
71
- return { success: false, error: data.error || 'Login failed' }
72
- } catch (_err) {
73
- return { success: false, error: 'Network error' }
74
- }
75
- }
76
-
77
- const logout = async () => {
78
- try {
79
- await fetch('/api/auth/logout', { method: 'POST' })
80
- } catch (err) {
81
- console.error('Logout error:', err)
82
- } finally {
83
- setIsAuthenticated(false)
84
- }
85
- }
86
-
87
- useEffect(() => {
88
- checkAuth()
89
- }, [checkAuth])
90
-
91
- return (
92
- <AuthContext.Provider
93
- value={{
94
- isAuthenticated,
95
- isAuthEnabled,
96
- isLoading,
97
- login,
98
- logout,
99
- checkAuth,
100
- }}
101
- >
102
- {children}
103
- </AuthContext.Provider>
104
- )
105
- }
@@ -1,128 +0,0 @@
1
- import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from 'react'
2
-
3
- /**
4
- * Represents a system notification in the Zenith dashboard.
5
- *
6
- * @public
7
- * @since 3.0.0
8
- */
9
- export interface Notification {
10
- /** Unique notification ID. */
11
- id: string
12
- /** The severity type of the notification. */
13
- type: 'info' | 'success' | 'warning' | 'error'
14
- /** Short title for the notification. */
15
- title: string
16
- /** Detailed notification message. */
17
- message: string
18
- /** Epoch timestamp when the notification was created. */
19
- timestamp: number
20
- /** Whether the notification has been read by the user. */
21
- read: boolean
22
- /** Optional source identifier (e.g., a queue name). */
23
- source?: string
24
- }
25
-
26
- interface NotificationContextType {
27
- notifications: Notification[]
28
- unreadCount: number
29
- addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void
30
- markAsRead: (id: string) => void
31
- markAllAsRead: () => void
32
- clearAll: () => void
33
- removeNotification: (id: string) => void
34
- }
35
-
36
- const NotificationContext = createContext<NotificationContextType | null>(null)
37
-
38
- /**
39
- * React hook to access the notification system.
40
- *
41
- * @public
42
- * @since 3.0.0
43
- */
44
- export function useNotifications() {
45
- const context = useContext(NotificationContext)
46
- if (!context) {
47
- throw new Error('useNotifications must be used within a NotificationProvider')
48
- }
49
- return context
50
- }
51
-
52
- interface NotificationProviderProps {
53
- children: ReactNode
54
- }
55
-
56
- /**
57
- * Provider component for managing global notifications in Zenith.
58
- *
59
- * @public
60
- * @since 3.0.0
61
- */
62
- export function NotificationProvider({ children }: NotificationProviderProps) {
63
- const [notifications, setNotifications] = useState<Notification[]>([])
64
-
65
- const unreadCount = notifications.filter((n) => !n.read).length
66
-
67
- const addNotification = useCallback(
68
- (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => {
69
- const newNotification: Notification = {
70
- ...notification,
71
- id: crypto.randomUUID(),
72
- timestamp: Date.now(),
73
- read: false,
74
- }
75
- setNotifications((prev) => [newNotification, ...prev].slice(0, 50)) // Keep max 50 notifications
76
- },
77
- []
78
- )
79
-
80
- const markAsRead = useCallback((id: string) => {
81
- setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)))
82
- }, [])
83
-
84
- const markAllAsRead = useCallback(() => {
85
- setNotifications((prev) => prev.map((n) => ({ ...n, read: true })))
86
- }, [])
87
-
88
- const clearAll = useCallback(() => {
89
- setNotifications([])
90
- }, [])
91
-
92
- const removeNotification = useCallback((id: string) => {
93
- setNotifications((prev) => prev.filter((n) => n.id !== id))
94
- }, [])
95
-
96
- // Listen to the global log update event dispatched by Layout's SSE stream
97
- useEffect(() => {
98
- const handler = (e: any) => {
99
- const log = e.detail
100
- if (log.level === 'error' || log.level === 'warn') {
101
- addNotification({
102
- type: log.level === 'error' ? 'error' : 'warning',
103
- title: log.level === 'error' ? 'Job Failed' : 'Warning',
104
- message: log.message || 'An event occurred',
105
- source: log.queue || log.source,
106
- })
107
- }
108
- }
109
- window.addEventListener('flux-log-update', handler)
110
- return () => window.removeEventListener('flux-log-update', handler)
111
- }, [addNotification])
112
-
113
- return (
114
- <NotificationContext.Provider
115
- value={{
116
- notifications,
117
- unreadCount,
118
- addNotification,
119
- markAsRead,
120
- markAllAsRead,
121
- clearAll,
122
- removeNotification,
123
- }}
124
- >
125
- {children}
126
- </NotificationContext.Provider>
127
- )
128
- }