@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.
- package/README.md +95 -22
- package/README.zh-TW.md +88 -0
- package/dist/bin.js +54699 -39316
- package/dist/client/assets/index-C80c1frR.css +1 -0
- package/dist/client/assets/index-CrWem9u3.js +434 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +54699 -39316
- package/package.json +20 -9
- package/CHANGELOG.md +0 -47
- package/Dockerfile +0 -46
- package/Dockerfile.demo-worker +0 -29
- package/ECOSYSTEM_EXPANSION_RFC.md +0 -130
- package/bin/flux-console.ts +0 -2
- package/dist/client/assets/index-BSMp8oq_.js +0 -436
- package/dist/client/assets/index-BwxlHx-_.css +0 -1
- package/docker-compose.yml +0 -40
- package/docs/ALERTING_GUIDE.md +0 -71
- package/docs/DEPLOYMENT.md +0 -157
- package/docs/DOCS_INTERNAL.md +0 -73
- package/docs/LARAVEL_ZENITH_ROADMAP.md +0 -109
- package/docs/QUASAR_MASTER_PLAN.md +0 -140
- package/docs/QUICK_TEST_GUIDE.md +0 -72
- package/docs/ROADMAP.md +0 -85
- package/docs/integrations/LARAVEL.md +0 -207
- package/postcss.config.js +0 -6
- package/scripts/debug_redis_keys.ts +0 -24
- package/scripts/flood-logs.ts +0 -21
- package/scripts/seed.ts +0 -213
- package/scripts/verify-throttle.ts +0 -49
- package/scripts/worker.ts +0 -124
- package/specs/PULSE_SPEC.md +0 -86
- package/src/bin.ts +0 -6
- package/src/client/App.tsx +0 -72
- package/src/client/Layout.tsx +0 -672
- package/src/client/Sidebar.tsx +0 -112
- package/src/client/ThroughputChart.tsx +0 -144
- package/src/client/WorkerStatus.tsx +0 -226
- package/src/client/components/BrandIcons.tsx +0 -168
- package/src/client/components/ConfirmDialog.tsx +0 -126
- package/src/client/components/JobInspector.tsx +0 -554
- package/src/client/components/LogArchiveModal.tsx +0 -432
- package/src/client/components/NotificationBell.tsx +0 -212
- package/src/client/components/PageHeader.tsx +0 -47
- package/src/client/components/Toaster.tsx +0 -90
- package/src/client/components/UserProfileDropdown.tsx +0 -186
- package/src/client/contexts/AuthContext.tsx +0 -105
- package/src/client/contexts/NotificationContext.tsx +0 -128
- package/src/client/index.css +0 -174
- package/src/client/index.html +0 -12
- package/src/client/main.tsx +0 -15
- package/src/client/pages/LoginPage.tsx +0 -162
- package/src/client/pages/MetricsPage.tsx +0 -417
- package/src/client/pages/OverviewPage.tsx +0 -517
- package/src/client/pages/PulsePage.tsx +0 -488
- package/src/client/pages/QueuesPage.tsx +0 -379
- package/src/client/pages/SchedulesPage.tsx +0 -540
- package/src/client/pages/SettingsPage.tsx +0 -1020
- package/src/client/pages/WorkersPage.tsx +0 -394
- package/src/client/pages/index.ts +0 -8
- package/src/client/utils.ts +0 -15
- package/src/server/config/ServerConfigManager.ts +0 -90
- package/src/server/index.ts +0 -860
- package/src/server/middleware/auth.ts +0 -127
- package/src/server/services/AlertService.ts +0 -321
- package/src/server/services/CommandService.ts +0 -137
- package/src/server/services/LogStreamProcessor.ts +0 -93
- package/src/server/services/MaintenanceScheduler.ts +0 -78
- package/src/server/services/PulseService.ts +0 -91
- package/src/server/services/QueueMetricsCollector.ts +0 -138
- package/src/server/services/QueueService.ts +0 -631
- package/src/shared/types.ts +0 -198
- package/tailwind.config.js +0 -73
- package/tests/placeholder.test.ts +0 -7
- package/tsconfig.json +0 -38
- package/tsconfig.node.json +0 -12
- 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
|
-
}
|