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