@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,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
- }