@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
package/src/client/Sidebar.tsx
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { motion } from 'framer-motion'
|
|
2
|
-
import {
|
|
3
|
-
Activity,
|
|
4
|
-
ChevronLeft,
|
|
5
|
-
ChevronRight,
|
|
6
|
-
Clock,
|
|
7
|
-
HardDrive,
|
|
8
|
-
LayoutDashboard,
|
|
9
|
-
ListTree,
|
|
10
|
-
Settings,
|
|
11
|
-
} from 'lucide-react'
|
|
12
|
-
import { NavLink, useLocation } from 'react-router-dom'
|
|
13
|
-
import { cn } from './utils'
|
|
14
|
-
|
|
15
|
-
interface SidebarProps {
|
|
16
|
-
className?: string
|
|
17
|
-
collapsed: boolean
|
|
18
|
-
toggleCollapse: () => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Sidebar navigation component for the Zenith dashboard.
|
|
23
|
-
*
|
|
24
|
-
* Provides links to various sections of the dashboard and supports
|
|
25
|
-
* a collapsible state to maximize screen space.
|
|
26
|
-
*
|
|
27
|
-
* @public
|
|
28
|
-
* @since 3.0.0
|
|
29
|
-
*/
|
|
30
|
-
export function Sidebar({ className, collapsed, toggleCollapse }: SidebarProps) {
|
|
31
|
-
const location = useLocation()
|
|
32
|
-
|
|
33
|
-
const navItems = [
|
|
34
|
-
{ icon: LayoutDashboard, label: 'Overview', path: '/' },
|
|
35
|
-
{ icon: Activity, label: 'Pulse', path: '/pulse' },
|
|
36
|
-
{ icon: ListTree, label: 'Queues', path: '/queues' },
|
|
37
|
-
{ icon: Clock, label: 'Schedules', path: '/schedules' },
|
|
38
|
-
{ icon: HardDrive, label: 'Workers', path: '/workers' },
|
|
39
|
-
// { icon: Activity, label: 'Metrics', path: '/metrics' },
|
|
40
|
-
{ icon: Settings, label: 'Settings', path: '/settings' },
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'flex-1 flex flex-col justify-between transition-all duration-300 ease-in-out relative group z-20 overflow-hidden',
|
|
47
|
-
className
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
{/* Nav Items */}
|
|
51
|
-
<nav className="flex-1 px-4 py-6 space-y-2">
|
|
52
|
-
{navItems.map((item, i) => {
|
|
53
|
-
const isActive = location.pathname === item.path
|
|
54
|
-
return (
|
|
55
|
-
<NavLink
|
|
56
|
-
key={i}
|
|
57
|
-
to={item.path}
|
|
58
|
-
className={cn(
|
|
59
|
-
'w-full flex items-center gap-4 px-4 py-3 rounded-xl transition-all text-muted-foreground group/item relative overflow-hidden',
|
|
60
|
-
isActive
|
|
61
|
-
? 'bg-primary text-primary-foreground shadow-lg shadow-primary/20 hover:scale-[1.02]'
|
|
62
|
-
: 'hover:bg-muted font-medium hover:text-foreground active:scale-95'
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
<item.icon
|
|
66
|
-
size={22}
|
|
67
|
-
className={cn(
|
|
68
|
-
'transition-all shrink-0',
|
|
69
|
-
isActive ? 'scale-110' : 'group-hover/item:scale-110'
|
|
70
|
-
)}
|
|
71
|
-
/>
|
|
72
|
-
<motion.span
|
|
73
|
-
initial={false}
|
|
74
|
-
animate={{
|
|
75
|
-
opacity: collapsed ? 0 : 1,
|
|
76
|
-
display: collapsed ? 'none' : 'block',
|
|
77
|
-
}}
|
|
78
|
-
className="font-semibold whitespace-nowrap tracking-tight"
|
|
79
|
-
>
|
|
80
|
-
{item.label}
|
|
81
|
-
</motion.span>
|
|
82
|
-
{isActive && (
|
|
83
|
-
<motion.div
|
|
84
|
-
layoutId="active-pill"
|
|
85
|
-
className="absolute left-0 w-1 h-6 bg-primary-foreground rounded-r-full"
|
|
86
|
-
/>
|
|
87
|
-
)}
|
|
88
|
-
</NavLink>
|
|
89
|
-
)
|
|
90
|
-
})}
|
|
91
|
-
</nav>
|
|
92
|
-
|
|
93
|
-
{/* Footer / Toggle */}
|
|
94
|
-
<div className="p-4 border-t border-border/50">
|
|
95
|
-
<button
|
|
96
|
-
type="button"
|
|
97
|
-
onClick={toggleCollapse}
|
|
98
|
-
className="w-full flex items-center justify-center h-10 rounded-xl hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90"
|
|
99
|
-
>
|
|
100
|
-
{collapsed ? (
|
|
101
|
-
<ChevronRight size={20} />
|
|
102
|
-
) : (
|
|
103
|
-
<div className="flex items-center gap-2 px-2">
|
|
104
|
-
<ChevronLeft size={18} />{' '}
|
|
105
|
-
<span className="text-xs font-black uppercase tracking-widest">Collapse Sidebar</span>
|
|
106
|
-
</div>
|
|
107
|
-
)}
|
|
108
|
-
</button>
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
)
|
|
112
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import {
|
|
4
|
-
Area,
|
|
5
|
-
AreaChart,
|
|
6
|
-
CartesianGrid,
|
|
7
|
-
ResponsiveContainer,
|
|
8
|
-
Tooltip,
|
|
9
|
-
XAxis,
|
|
10
|
-
YAxis,
|
|
11
|
-
} from 'recharts'
|
|
12
|
-
|
|
13
|
-
interface ThroughputPoint {
|
|
14
|
-
timestamp: string
|
|
15
|
-
count: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Real-time throughput visualization component.
|
|
20
|
-
*
|
|
21
|
-
* Displays a live-updating area chart of the number of jobs processed
|
|
22
|
-
* per minute across the system.
|
|
23
|
-
*
|
|
24
|
-
* @public
|
|
25
|
-
* @since 3.0.0
|
|
26
|
-
*/
|
|
27
|
-
export function ThroughputChart() {
|
|
28
|
-
// Initial fetch via React Query
|
|
29
|
-
const { data: initialData } = useQuery({
|
|
30
|
-
queryKey: ['throughput'],
|
|
31
|
-
queryFn: async () => {
|
|
32
|
-
const res = await fetch('/api/throughput')
|
|
33
|
-
const json = await res.json()
|
|
34
|
-
return json.data || []
|
|
35
|
-
},
|
|
36
|
-
staleTime: Infinity, // Don't refetch automatically
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const [throughputData, setThroughputData] = React.useState<ThroughputPoint[]>([])
|
|
40
|
-
|
|
41
|
-
// Sync with initial data
|
|
42
|
-
React.useEffect(() => {
|
|
43
|
-
if (initialData) {
|
|
44
|
-
setThroughputData(initialData)
|
|
45
|
-
}
|
|
46
|
-
}, [initialData])
|
|
47
|
-
|
|
48
|
-
// Listen for live updates
|
|
49
|
-
React.useEffect(() => {
|
|
50
|
-
const handler = (e: Event) => {
|
|
51
|
-
const customEvent = e as CustomEvent
|
|
52
|
-
if (customEvent.detail?.throughput) {
|
|
53
|
-
setThroughputData(customEvent.detail.throughput)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
window.addEventListener('flux-stats-update', handler)
|
|
57
|
-
return () => window.removeEventListener('flux-stats-update', handler)
|
|
58
|
-
}, [])
|
|
59
|
-
|
|
60
|
-
const chartData =
|
|
61
|
-
throughputData?.map((d: ThroughputPoint) => ({
|
|
62
|
-
time: d.timestamp,
|
|
63
|
-
value: d.count,
|
|
64
|
-
})) || []
|
|
65
|
-
return (
|
|
66
|
-
<div className="card-premium h-[350px] w-full p-6 flex flex-col relative overflow-hidden group">
|
|
67
|
-
{/* Background Accent */}
|
|
68
|
-
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl" />
|
|
69
|
-
|
|
70
|
-
<div className="flex justify-between items-start mb-6 z-10">
|
|
71
|
-
<div>
|
|
72
|
-
<div className="flex items-center gap-2">
|
|
73
|
-
<h3 className="text-xl font-bold tracking-tight">System Throughput</h3>
|
|
74
|
-
<div className="flex items-center gap-1.5 px-2 py-0.5 bg-green-500/10 text-green-500 text-[8px] font-black uppercase tracking-widest rounded-full border border-green-500/20">
|
|
75
|
-
<span className="w-1 h-1 bg-green-500 rounded-full animate-ping"></span>
|
|
76
|
-
Live
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
<p className="text-[10px] text-muted-foreground uppercase tracking-[0.2em] font-bold mt-1">
|
|
80
|
-
Jobs processed per minute
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
<div className="text-right">
|
|
84
|
-
<p className="text-2xl font-black text-foreground">
|
|
85
|
-
{chartData[chartData.length - 1]?.value || 0}
|
|
86
|
-
</p>
|
|
87
|
-
<p className="text-[8px] text-muted-foreground uppercase font-bold">Current Rate</p>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div className="flex-1 w-full min-h-0">
|
|
92
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
93
|
-
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
94
|
-
<defs>
|
|
95
|
-
<linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
|
|
96
|
-
<stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.4} />
|
|
97
|
-
<stop offset="50%" stopColor="hsl(var(--primary))" stopOpacity={0.1} />
|
|
98
|
-
<stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0} />
|
|
99
|
-
</linearGradient>
|
|
100
|
-
</defs>
|
|
101
|
-
<CartesianGrid
|
|
102
|
-
strokeDasharray="3 3"
|
|
103
|
-
vertical={false}
|
|
104
|
-
stroke="hsl(var(--border))"
|
|
105
|
-
opacity={0.5}
|
|
106
|
-
/>
|
|
107
|
-
<XAxis
|
|
108
|
-
dataKey="time"
|
|
109
|
-
axisLine={false}
|
|
110
|
-
tickLine={false}
|
|
111
|
-
tick={{ fontSize: 9, fill: 'hsl(var(--muted-foreground))', fontWeight: 600 }}
|
|
112
|
-
dy={10}
|
|
113
|
-
/>
|
|
114
|
-
<YAxis
|
|
115
|
-
axisLine={false}
|
|
116
|
-
tickLine={false}
|
|
117
|
-
tick={{ fontSize: 9, fill: 'hsl(var(--muted-foreground))', fontWeight: 600 }}
|
|
118
|
-
/>
|
|
119
|
-
<Tooltip
|
|
120
|
-
cursor={{ stroke: 'hsl(var(--primary))', strokeWidth: 1, strokeDasharray: '4 4' }}
|
|
121
|
-
contentStyle={{
|
|
122
|
-
backgroundColor: 'hsl(var(--card))',
|
|
123
|
-
border: '1px solid hsl(var(--border))',
|
|
124
|
-
borderRadius: '16px',
|
|
125
|
-
fontSize: '12px',
|
|
126
|
-
boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)',
|
|
127
|
-
}}
|
|
128
|
-
itemStyle={{ fontWeight: 'bold', color: 'hsl(var(--primary))' }}
|
|
129
|
-
/>
|
|
130
|
-
<Area
|
|
131
|
-
type="stepAfter"
|
|
132
|
-
dataKey="value"
|
|
133
|
-
stroke="hsl(var(--primary))"
|
|
134
|
-
fillOpacity={1}
|
|
135
|
-
fill="url(#colorValue)"
|
|
136
|
-
strokeWidth={2.5}
|
|
137
|
-
animationDuration={1500}
|
|
138
|
-
/>
|
|
139
|
-
</AreaChart>
|
|
140
|
-
</ResponsiveContainer>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from 'clsx'
|
|
2
|
-
import { Activity, Cpu, Terminal } from 'lucide-react'
|
|
3
|
-
import { twMerge } from 'tailwind-merge'
|
|
4
|
-
|
|
5
|
-
function cn(...inputs: ClassValue[]) {
|
|
6
|
-
return twMerge(clsx(inputs))
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function formatBytes(bytes: number) {
|
|
10
|
-
if (bytes === 0) {
|
|
11
|
-
return '0 B'
|
|
12
|
-
}
|
|
13
|
-
const k = 1024
|
|
14
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
15
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
16
|
-
return `${parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getWorkerName(id: string, pid: number) {
|
|
20
|
-
// If ID contains Hostname+PID, try to simplify it
|
|
21
|
-
// Example: CarldeMacBook-Air.local-99401
|
|
22
|
-
const complexIdMatch = id.match(/^(.*)-(\d+)$/)
|
|
23
|
-
if (complexIdMatch && parseInt(complexIdMatch[2], 10) === pid) {
|
|
24
|
-
// Return just the hostname part, and maybe truncate if too long
|
|
25
|
-
let hostname = complexIdMatch[1]
|
|
26
|
-
if (hostname.endsWith('.local')) {
|
|
27
|
-
hostname = hostname.replace('.local', '')
|
|
28
|
-
}
|
|
29
|
-
return hostname
|
|
30
|
-
}
|
|
31
|
-
// Fallback
|
|
32
|
-
return id.replace('.local', '')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface WorkerInfo {
|
|
36
|
-
id: string
|
|
37
|
-
service?: string
|
|
38
|
-
status: 'online' | 'offline'
|
|
39
|
-
pid: number
|
|
40
|
-
uptime: number
|
|
41
|
-
metrics?: {
|
|
42
|
-
cpu: number
|
|
43
|
-
cores: number
|
|
44
|
-
ram: {
|
|
45
|
-
rss: number
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
meta?: {
|
|
49
|
-
laravel?: {
|
|
50
|
-
workerCount: number
|
|
51
|
-
roots: string[]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Real-time worker node status visualization component.
|
|
58
|
-
*
|
|
59
|
-
* Displays a list of active worker nodes with their CPU, RAM, uptime metrics,
|
|
60
|
-
* and highlights a specific worker if provided.
|
|
61
|
-
*
|
|
62
|
-
* @public
|
|
63
|
-
* @since 3.0.0
|
|
64
|
-
*/
|
|
65
|
-
export function WorkerStatus({
|
|
66
|
-
highlightedWorkerId,
|
|
67
|
-
workers = [],
|
|
68
|
-
}: {
|
|
69
|
-
highlightedWorkerId?: string | null
|
|
70
|
-
workers?: WorkerInfo[]
|
|
71
|
-
}) {
|
|
72
|
-
const onlineCount = workers.filter((w) => w.status === 'online').length
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="card-premium h-full flex flex-col overflow-hidden">
|
|
76
|
-
<div className="p-6 pb-0 flex-none">
|
|
77
|
-
<div className="flex justify-between items-center mb-6">
|
|
78
|
-
<div>
|
|
79
|
-
<h3 className="text-lg font-black flex items-center gap-2 tracking-tight">
|
|
80
|
-
<Cpu size={20} className="text-primary" />
|
|
81
|
-
Cluster Nodes
|
|
82
|
-
</h3>
|
|
83
|
-
<p className="text-[10px] text-muted-foreground uppercase font-black tracking-widest opacity-60">
|
|
84
|
-
Real-time load
|
|
85
|
-
</p>
|
|
86
|
-
</div>
|
|
87
|
-
<span className="text-[10px] font-black text-green-500 bg-green-500/10 px-3 py-1 rounded-full uppercase tracking-widest border border-green-500/20">
|
|
88
|
-
{onlineCount} ACTIVE
|
|
89
|
-
</span>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<div className="flex-1 overflow-y-auto min-h-0 px-6 space-y-3 scrollbar-thin pb-6">
|
|
94
|
-
{workers.length === 0 && (
|
|
95
|
-
<div className="py-12 text-center text-muted-foreground/30 flex flex-col items-center gap-2">
|
|
96
|
-
<Activity size={24} className="opacity-20 animate-pulse" />
|
|
97
|
-
<p className="text-[10px] font-black uppercase tracking-widest">No nodes connected</p>
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{workers.map((worker) => (
|
|
102
|
-
<div
|
|
103
|
-
key={worker.id}
|
|
104
|
-
className={cn(
|
|
105
|
-
'relative flex items-center gap-4 p-4 rounded-2xl border transition-all group overflow-hidden shrink-0',
|
|
106
|
-
worker.id === highlightedWorkerId
|
|
107
|
-
? 'bg-primary/5 border-primary/50 shadow-[0_0_20px_rgba(var(--primary-rgb),0.1)] -translate-y-1 scale-[1.02] z-10'
|
|
108
|
-
: 'bg-card hover:bg-muted/10 border-border/50 hover:border-primary/20'
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
{/* Status bar */}
|
|
112
|
-
<div
|
|
113
|
-
className={cn(
|
|
114
|
-
'absolute left-0 top-0 bottom-0 w-1 transition-all',
|
|
115
|
-
worker.status === 'online' ? 'bg-green-500' : 'bg-muted-foreground/30'
|
|
116
|
-
)}
|
|
117
|
-
/>
|
|
118
|
-
|
|
119
|
-
{/* Icon/Dot */}
|
|
120
|
-
<div className="relative shrink-0 ml-1">
|
|
121
|
-
<div
|
|
122
|
-
className={cn(
|
|
123
|
-
'w-3 h-3 rounded-full',
|
|
124
|
-
worker.status === 'online'
|
|
125
|
-
? 'bg-green-500 animate-pulse shadow-[0_0_12px_rgba(34,197,94,0.6)]'
|
|
126
|
-
: 'bg-muted-foreground/40'
|
|
127
|
-
)}
|
|
128
|
-
/>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
{/* Main Info */}
|
|
132
|
-
<div className="flex-1 min-w-0 flex flex-col justify-center mr-2">
|
|
133
|
-
{worker.service && (
|
|
134
|
-
<span className="text-[10px] font-black text-primary/80 uppercase tracking-widest mb-0.5 whitespace-nowrap">
|
|
135
|
-
{worker.service}
|
|
136
|
-
</span>
|
|
137
|
-
)}
|
|
138
|
-
<h4
|
|
139
|
-
className="text-sm font-black tracking-tight text-foreground truncate"
|
|
140
|
-
title={worker.id}
|
|
141
|
-
>
|
|
142
|
-
{getWorkerName(worker.id, worker.pid) || worker.id}
|
|
143
|
-
</h4>
|
|
144
|
-
<div className="flex items-center gap-2 mt-1">
|
|
145
|
-
<span className="text-[9px] font-bold text-muted-foreground uppercase tracking-wider whitespace-nowrap">
|
|
146
|
-
PID {worker.pid}
|
|
147
|
-
</span>
|
|
148
|
-
{worker.meta?.laravel && worker.meta.laravel.workerCount > 0 && (
|
|
149
|
-
<span className="inline-flex items-center gap-1 text-[9px] font-black text-white bg-red-500 px-1.5 py-0.5 rounded shadow-sm uppercase tracking-widest leading-none whitespace-nowrap">
|
|
150
|
-
<Terminal size={8} />
|
|
151
|
-
{worker.meta.laravel.workerCount} PHP
|
|
152
|
-
</span>
|
|
153
|
-
)}
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
{/* Metrics (Right Side) */}
|
|
158
|
-
<div className="flex items-center gap-3 text-right shrink-0">
|
|
159
|
-
{worker.metrics && (
|
|
160
|
-
<>
|
|
161
|
-
<div className="hidden sm:block space-y-1 w-12">
|
|
162
|
-
<div className="flex justify-between text-[8px] font-black text-muted-foreground uppercase tracking-tighter">
|
|
163
|
-
<span>CPU</span>
|
|
164
|
-
<span
|
|
165
|
-
className={cn(
|
|
166
|
-
worker.metrics.cpu > (worker.metrics.cores || 1) * 100 && 'text-red-500'
|
|
167
|
-
)}
|
|
168
|
-
>
|
|
169
|
-
{worker.metrics.cpu.toFixed(0)}%
|
|
170
|
-
</span>
|
|
171
|
-
</div>
|
|
172
|
-
<div className="h-1 w-full bg-muted rounded-full overflow-hidden">
|
|
173
|
-
<div
|
|
174
|
-
className="h-full bg-foreground transition-all duration-700"
|
|
175
|
-
style={{ width: `${Math.min(100, worker.metrics.cpu)}%` }}
|
|
176
|
-
></div>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
<div className="hidden sm:block space-y-1 w-12">
|
|
181
|
-
<div className="flex justify-between text-[8px] font-black text-muted-foreground uppercase tracking-tighter">
|
|
182
|
-
<span>RAM</span>
|
|
183
|
-
<span className="truncate ml-1">
|
|
184
|
-
{formatBytes(worker.metrics.ram.rss).split(' ')[0]}
|
|
185
|
-
</span>
|
|
186
|
-
</div>
|
|
187
|
-
<div className="h-1 w-full bg-muted rounded-full overflow-hidden">
|
|
188
|
-
<div
|
|
189
|
-
className="h-full bg-indigo-500 transition-all duration-700"
|
|
190
|
-
style={{
|
|
191
|
-
width: `${Math.min(100, (worker.metrics.ram.rss / 2000000000) * 100)}%`,
|
|
192
|
-
}}
|
|
193
|
-
></div>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
</>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
<div className="w-12">
|
|
200
|
-
<p className="text-xs font-black tracking-tighter tabular-nums text-foreground">
|
|
201
|
-
{worker.uptime > 3600
|
|
202
|
-
? `${(worker.uptime / 3600).toFixed(1)}h`
|
|
203
|
-
: worker.uptime > 60
|
|
204
|
-
? `${(worker.uptime / 60).toFixed(0)}m`
|
|
205
|
-
: `${worker.uptime.toFixed(0)}s`}
|
|
206
|
-
</p>
|
|
207
|
-
<p className="text-[8px] text-muted-foreground uppercase font-black tracking-widest opacity-50">
|
|
208
|
-
UP
|
|
209
|
-
</p>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
))}
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div className="p-6 pt-0 flex-none">
|
|
217
|
-
<button
|
|
218
|
-
type="button"
|
|
219
|
-
className="w-full py-3 bg-muted text-[10px] font-black rounded-xl hover:bg-primary hover:text-primary-foreground transition-all uppercase tracking-[0.2em] opacity-60 hover:opacity-100 active:scale-95 shadow-lg shadow-transparent hover:shadow-primary/20"
|
|
220
|
-
>
|
|
221
|
-
Manage Nodes
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
)
|
|
226
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from 'react'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SVG icon for Node.js runtime.
|
|
5
|
-
* @public
|
|
6
|
-
* @since 3.0.0
|
|
7
|
-
*/
|
|
8
|
-
export function NodeIcon(props: SVGProps<SVGSVGElement>) {
|
|
9
|
-
return (
|
|
10
|
-
<svg
|
|
11
|
-
viewBox="0 0 32 32"
|
|
12
|
-
fill="none"
|
|
13
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
-
role="img"
|
|
15
|
-
aria-label="Node.js"
|
|
16
|
-
{...props}
|
|
17
|
-
>
|
|
18
|
-
<path
|
|
19
|
-
d="M16 2L2.1 9.9v12.2L16 30l13.9-7.9V9.9L16 2zm11.9 19.1L16 27.8l-11.9-6.7V11.1L16 4.2l11.9 6.9v10z"
|
|
20
|
-
fill="#339933"
|
|
21
|
-
/>
|
|
22
|
-
<path d="M16 22.5l-6-3.4v-6.8l6-3.4 6 3.4v6.8l-6 3.4z" fill="#339933" />
|
|
23
|
-
</svg>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* SVG icon for Bun runtime.
|
|
29
|
-
* @public
|
|
30
|
-
* @since 3.0.0
|
|
31
|
-
*/
|
|
32
|
-
export function BunIcon(props: SVGProps<SVGSVGElement>) {
|
|
33
|
-
return (
|
|
34
|
-
<svg
|
|
35
|
-
viewBox="0 0 32 32"
|
|
36
|
-
fill="none"
|
|
37
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
-
role="img"
|
|
39
|
-
aria-label="Bun"
|
|
40
|
-
{...props}
|
|
41
|
-
>
|
|
42
|
-
{/* Outer Outline/Shadow for contrast on light bg */}
|
|
43
|
-
<path
|
|
44
|
-
d="M30 17.045a9.8 9.8 0 0 0-.32-2.306l-.004.034a11.2 11.2 0 0 0-5.762-6.786c-3.495-1.89-5.243-3.326-6.8-3.811h.003c-1.95-.695-3.949.82-5.825 1.927-4.52 2.481-9.573 5.45-9.28 11.417.008-.029.017-.052.026-.08a9.97 9.97 0 0 0 3.934 7.257l-.01-.006C13.747 31.473 30.05 27.292 30 17.045"
|
|
45
|
-
fill="#fbf0df"
|
|
46
|
-
stroke="#4a4a4a"
|
|
47
|
-
strokeWidth="1.5"
|
|
48
|
-
/>
|
|
49
|
-
|
|
50
|
-
<path
|
|
51
|
-
fill="#37474f"
|
|
52
|
-
d="M19.855 20.236A.8.8 0 0 0 19.26 20h-6.514a.8.8 0 0 0-.596.236.51.51 0 0 0-.137.463 4.37 4.37 0 0 0 1.641 2.339 4.2 4.2 0 0 0 2.349.926 4.2 4.2 0 0 0 2.343-.926 4.37 4.37 0 0 0 1.642-2.339.5.5 0 0 0-.132-.463Z"
|
|
53
|
-
/>
|
|
54
|
-
<ellipse cx="22.5" cy="18.5" fill="#f8bbd0" rx="2.5" ry="1.5" />
|
|
55
|
-
<ellipse cx="9.5" cy="18.5" fill="#f8bbd0" rx="2.5" ry="1.5" />
|
|
56
|
-
<circle cx="10" cy="16" r="2" fill="#37474f" />
|
|
57
|
-
<circle cx="22" cy="16" r="2" fill="#37474f" />
|
|
58
|
-
</svg>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* SVG icon for Deno runtime.
|
|
64
|
-
* @public
|
|
65
|
-
* @since 3.0.0
|
|
66
|
-
*/
|
|
67
|
-
export function DenoIcon(props: SVGProps<SVGSVGElement>) {
|
|
68
|
-
return (
|
|
69
|
-
<svg
|
|
70
|
-
viewBox="0 0 32 32"
|
|
71
|
-
fill="none"
|
|
72
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
-
role="img"
|
|
74
|
-
aria-label="Deno"
|
|
75
|
-
{...props}
|
|
76
|
-
>
|
|
77
|
-
<circle cx="16" cy="16" r="14" fill="currentColor" />
|
|
78
|
-
<path
|
|
79
|
-
d="M16 6C16 6 24 10 24 18C24 23.5228 19.5228 28 14 28C8.47715 28 4 23.5228 4 18C4 10 16 6 16 6Z"
|
|
80
|
-
fill="white"
|
|
81
|
-
/>
|
|
82
|
-
<circle cx="12" cy="18" r="2" fill="black" />
|
|
83
|
-
</svg>
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* SVG icon for PHP runtime.
|
|
89
|
-
* @public
|
|
90
|
-
* @since 3.0.0
|
|
91
|
-
*/
|
|
92
|
-
export function PhpIcon(props: SVGProps<SVGSVGElement>) {
|
|
93
|
-
return (
|
|
94
|
-
<svg
|
|
95
|
-
viewBox="0 0 32 32"
|
|
96
|
-
fill="none"
|
|
97
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
98
|
-
role="img"
|
|
99
|
-
aria-label="PHP"
|
|
100
|
-
{...props}
|
|
101
|
-
>
|
|
102
|
-
<ellipse cx="16" cy="16" rx="14" ry="10" fill="#777BB4" />
|
|
103
|
-
<text
|
|
104
|
-
x="50%"
|
|
105
|
-
y="54%"
|
|
106
|
-
dominantBaseline="middle"
|
|
107
|
-
textAnchor="middle"
|
|
108
|
-
fill="white"
|
|
109
|
-
fontSize="9"
|
|
110
|
-
fontWeight="bold"
|
|
111
|
-
>
|
|
112
|
-
PHP
|
|
113
|
-
</text>
|
|
114
|
-
</svg>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* SVG icon for Go runtime.
|
|
120
|
-
* @public
|
|
121
|
-
* @since 3.0.0
|
|
122
|
-
*/
|
|
123
|
-
export function GoIcon(props: SVGProps<SVGSVGElement>) {
|
|
124
|
-
return (
|
|
125
|
-
<svg
|
|
126
|
-
viewBox="0 0 32 32"
|
|
127
|
-
fill="none"
|
|
128
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
-
role="img"
|
|
130
|
-
aria-label="Go"
|
|
131
|
-
{...props}
|
|
132
|
-
>
|
|
133
|
-
<path
|
|
134
|
-
d="M5 16C5 10 10 5 16 5H24V13H16C14.3431 13 13 14.3431 13 16C13 17.6569 14.3431 19 16 19H27V27H16C10 27 5 22 5 16Z"
|
|
135
|
-
fill="#00ADD8"
|
|
136
|
-
/>
|
|
137
|
-
<circle cx="9" cy="16" r="2" fill="white" />
|
|
138
|
-
<circle cx="23" cy="9" r="2" fill="white" />
|
|
139
|
-
</svg>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* SVG icon for Python runtime.
|
|
145
|
-
* @public
|
|
146
|
-
* @since 3.0.0
|
|
147
|
-
*/
|
|
148
|
-
export function PythonIcon(props: SVGProps<SVGSVGElement>) {
|
|
149
|
-
return (
|
|
150
|
-
<svg
|
|
151
|
-
viewBox="0 0 32 32"
|
|
152
|
-
fill="none"
|
|
153
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
154
|
-
role="img"
|
|
155
|
-
aria-label="Python"
|
|
156
|
-
{...props}
|
|
157
|
-
>
|
|
158
|
-
<path
|
|
159
|
-
d="M16 2C10 2 10 5 10 5L10 9L18 9L18 11L8 11L8 20L12 20L12 14L22 14C22 14 22 12 16 2Z"
|
|
160
|
-
fill="#3776AB"
|
|
161
|
-
/>
|
|
162
|
-
<path
|
|
163
|
-
d="M16 30C22 30 22 27 22 27L22 23L14 23L14 21L24 21L24 12L20 12L20 18L10 18C10 18 10 20 16 30Z"
|
|
164
|
-
fill="#FFD43B"
|
|
165
|
-
/>
|
|
166
|
-
</svg>
|
|
167
|
-
)
|
|
168
|
-
}
|