@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,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/60 group/item relative overflow-hidden font-heading',
60
- isActive
61
- ? 'bg-primary/10 text-primary shadow-[0_0_20px_rgba(0,240,255,0.1)] border border-primary/20'
62
- : 'hover:bg-white/[0.03] font-medium hover:text-foreground active:scale-95'
63
- )}
64
- >
65
- <item.icon
66
- size={20}
67
- className={cn(
68
- 'transition-all shrink-0',
69
- isActive ? 'scale-110 text-primary' : '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-black whitespace-nowrap tracking-tight uppercase text-[11px]"
79
- >
80
- {item.label}
81
- </motion.span>
82
- {isActive && (
83
- <motion.div
84
- layoutId="active-pill"
85
- className="absolute left-0 w-1 h-5 bg-primary rounded-r-full shadow-[0_0_10px_#00F0FF]"
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,158 +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-black tracking-tight font-heading">System Throughput</h3>
74
- <div className="flex items-center gap-1.5 px-2 py-0.5 bg-primary/10 text-primary text-[8px] font-black uppercase tracking-widest rounded-full border border-primary/20">
75
- <span className="w-1 h-1 bg-primary rounded-full animate-ping"></span>
76
- Live
77
- </div>
78
- </div>
79
- <p className="text-[10px] text-muted-foreground uppercase tracking-[0.2em] font-black mt-1">
80
- Jobs processed per minute
81
- </p>
82
- </div>
83
- <div className="text-right">
84
- <p className="text-3xl font-black text-foreground font-mono">
85
- {chartData[chartData.length - 1]?.value || 0}
86
- </p>
87
- <p className="text-[8px] text-muted-foreground uppercase font-black tracking-tighter">
88
- Current Rate
89
- </p>
90
- </div>
91
- </div>
92
-
93
- <div className="flex-1 w-full min-h-0">
94
- <ResponsiveContainer width="100%" height="100%">
95
- <AreaChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
96
- <defs>
97
- <linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
98
- <stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.5} />
99
- <stop offset="50%" stopColor="hsl(var(--primary))" stopOpacity={0.1} />
100
- <stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0} />
101
- </linearGradient>
102
- </defs>
103
- <CartesianGrid
104
- strokeDasharray="3 3"
105
- vertical={false}
106
- stroke="hsl(var(--border))"
107
- opacity={0.3}
108
- />
109
- <XAxis
110
- dataKey="time"
111
- axisLine={false}
112
- tickLine={false}
113
- tick={{
114
- fontSize: 9,
115
- fill: 'hsl(var(--muted-foreground))',
116
- fontWeight: 700,
117
- fontFamily: 'Fira Code',
118
- }}
119
- dy={10}
120
- />
121
- <YAxis
122
- axisLine={false}
123
- tickLine={false}
124
- tick={{
125
- fontSize: 9,
126
- fill: 'hsl(var(--muted-foreground))',
127
- fontWeight: 700,
128
- fontFamily: 'Fira Code',
129
- }}
130
- />
131
- <Tooltip
132
- cursor={{ stroke: 'hsl(var(--primary))', strokeWidth: 1, strokeDasharray: '4 4' }}
133
- contentStyle={{
134
- backgroundColor: 'rgba(9, 9, 11, 0.9)',
135
- border: '1px solid rgba(255, 255, 255, 0.1)',
136
- borderRadius: '12px',
137
- fontSize: '11px',
138
- fontFamily: 'Fira Code',
139
- boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.5)',
140
- backdropFilter: 'blur(8px)',
141
- }}
142
- itemStyle={{ fontWeight: 'bold', color: 'hsl(var(--primary))' }}
143
- />
144
- <Area
145
- type="monotone"
146
- dataKey="value"
147
- stroke="hsl(var(--primary))"
148
- fillOpacity={1}
149
- fill="url(#colorValue)"
150
- strokeWidth={3}
151
- animationDuration={1500}
152
- />
153
- </AreaChart>
154
- </ResponsiveContainer>
155
- </div>
156
- </div>
157
- )
158
- }
@@ -1,202 +0,0 @@
1
- import { type ClassValue, clsx } from 'clsx'
2
- import { Activity, Cpu } 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-5 pb-0 flex-none">
77
- <div className="flex justify-between items-center mb-5">
78
- <div>
79
- <h3 className="text-base font-black flex items-center gap-2 tracking-tight font-heading">
80
- <Cpu size={18} className="text-primary" />
81
- Cluster Nodes
82
- </h3>
83
- <p className="text-[9px] text-muted-foreground uppercase font-black tracking-[0.2em] opacity-50 mt-0.5">
84
- Live Infrastructure
85
- </p>
86
- </div>
87
- <span className="text-[9px] font-black text-emerald-500 bg-emerald-500/10 px-2 py-1 rounded-md uppercase tracking-widest border border-emerald-500/20">
88
- {onlineCount} ACTIVE
89
- </span>
90
- </div>
91
- </div>
92
-
93
- <div className="flex-1 overflow-y-auto min-h-0 px-5 space-y-2 scrollbar-thin pb-5">
94
- {workers.length === 0 && (
95
- <div className="py-12 text-center text-muted-foreground/20 flex flex-col items-center gap-2">
96
- <Activity size={24} className="opacity-30 animate-pulse" />
97
- <p className="text-[9px] font-black uppercase tracking-[0.3em]">Awaiting signals...</p>
98
- </div>
99
- )}
100
-
101
- {workers.map((worker) => (
102
- <div
103
- key={worker.id}
104
- className={cn(
105
- 'relative flex items-center gap-3 p-3 rounded-xl border transition-all group overflow-hidden shrink-0',
106
- worker.id === highlightedWorkerId
107
- ? 'bg-primary/10 border-primary/40 shadow-[0_0_20px_rgba(0,240,255,0.1)] -translate-x-1 z-10'
108
- : 'bg-black/20 hover:bg-white/[0.03] border-white/5 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'
116
- ? 'bg-emerald-500 shadow-[0_0_10px_#10B981]'
117
- : 'bg-muted-foreground/20'
118
- )}
119
- />
120
-
121
- {/* Main Info */}
122
- <div className="flex-1 min-w-0 flex flex-col justify-center">
123
- {worker.service && (
124
- <span className="text-[8px] font-black text-primary uppercase tracking-widest mb-0.5 whitespace-nowrap opacity-80">
125
- {worker.service}
126
- </span>
127
- )}
128
- <h4
129
- className="text-xs font-black tracking-tight text-foreground/90 truncate font-heading"
130
- title={worker.id}
131
- >
132
- {getWorkerName(worker.id, worker.pid) || worker.id}
133
- </h4>
134
- <div className="flex items-center gap-2 mt-0.5">
135
- <span className="text-[8px] font-bold text-muted-foreground/40 uppercase font-mono">
136
- PID:{worker.pid}
137
- </span>
138
- {worker.meta?.laravel && worker.meta.laravel.workerCount > 0 && (
139
- <span className="inline-flex items-center gap-1 text-[8px] font-black text-white bg-red-500/80 px-1 rounded shadow-sm uppercase tracking-tighter leading-none whitespace-nowrap">
140
- {worker.meta.laravel.workerCount} PHP
141
- </span>
142
- )}
143
- </div>
144
- </div>
145
-
146
- {/* Metrics (Right Side) */}
147
- <div className="flex items-center gap-4 text-right shrink-0">
148
- {worker.metrics && (
149
- <>
150
- <div className="hidden sm:flex flex-col items-end gap-1 w-10">
151
- <span className="text-[8px] font-black text-muted-foreground/40 uppercase font-mono">
152
- CPU
153
- </span>
154
- <span
155
- className={cn(
156
- 'text-[10px] font-black font-mono tracking-tighter',
157
- worker.metrics.cpu > 80 ? 'text-red-500' : 'text-primary'
158
- )}
159
- >
160
- {worker.metrics.cpu.toFixed(0)}%
161
- </span>
162
- </div>
163
-
164
- <div className="hidden sm:flex flex-col items-end gap-1 w-12">
165
- <span className="text-[8px] font-black text-muted-foreground/40 uppercase font-mono">
166
- RAM
167
- </span>
168
- <span className="text-[10px] font-black font-mono tracking-tighter text-white/80">
169
- {formatBytes(worker.metrics.ram.rss).split(' ')[0]}
170
- </span>
171
- </div>
172
- </>
173
- )}
174
-
175
- <div className="flex flex-col items-end gap-1 w-10">
176
- <span className="text-[8px] font-black text-muted-foreground/40 uppercase font-mono">
177
- UP
178
- </span>
179
- <p className="text-[10px] font-black tracking-tighter font-mono text-foreground/60 tabular-nums">
180
- {worker.uptime > 3600
181
- ? `${(worker.uptime / 3600).toFixed(1)}H`
182
- : worker.uptime > 60
183
- ? `${(worker.uptime / 60).toFixed(0)}M`
184
- : `${worker.uptime.toFixed(0)}S`}
185
- </p>
186
- </div>
187
- </div>
188
- </div>
189
- ))}
190
- </div>
191
-
192
- <div className="p-5 pt-0 flex-none">
193
- <button
194
- type="button"
195
- className="w-full py-2.5 bg-muted/50 text-[9px] font-black rounded-lg hover:bg-primary hover:text-primary-foreground transition-all uppercase tracking-[0.2em] border border-white/5 hover:border-primary/50 active:scale-95 shadow-lg shadow-transparent hover:shadow-primary/10 font-heading"
196
- >
197
- Node Orchestration
198
- </button>
199
- </div>
200
- </div>
201
- )
202
- }
@@ -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
- }