@wakastellar/ui 2.1.2 → 2.3.2

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 (123) hide show
  1. package/dist/blocks/apm-overview/index.d.ts +58 -0
  2. package/dist/blocks/cicd-builder/index.d.ts +47 -0
  3. package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
  4. package/dist/blocks/container-orchestrator/index.d.ts +63 -0
  5. package/dist/blocks/database-admin/index.d.ts +84 -0
  6. package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
  7. package/dist/blocks/incident-manager/index.d.ts +44 -0
  8. package/dist/blocks/index.d.ts +10 -0
  9. package/dist/blocks/infrastructure-map/index.d.ts +32 -0
  10. package/dist/blocks/on-call-schedule/index.d.ts +43 -0
  11. package/dist/blocks/release-notes/index.d.ts +49 -0
  12. package/dist/components/index.d.ts +34 -0
  13. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  14. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  15. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  16. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  17. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  18. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  19. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  20. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  21. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  22. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  23. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  24. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  25. package/dist/components/waka-container-list/index.d.ts +51 -0
  26. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  27. package/dist/components/waka-database-card/index.d.ts +46 -0
  28. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  29. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  30. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  31. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  32. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  33. package/dist/components/waka-migration-list/index.d.ts +36 -0
  34. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  35. package/dist/components/waka-pod-card/index.d.ts +73 -0
  36. package/dist/components/waka-query-explain/index.d.ts +48 -0
  37. package/dist/components/waka-secret-card/index.d.ts +43 -0
  38. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  39. package/dist/components/waka-service-graph/index.d.ts +44 -0
  40. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  41. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  42. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  43. package/dist/components/waka-test-report/index.d.ts +60 -0
  44. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  45. package/dist/components/waka-video-ad/index.d.ts +32 -0
  46. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  47. package/dist/index.cjs.js +251 -200
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.es.js +47315 -35823
  50. package/dist/utils/security.d.ts +96 -0
  51. package/package.json +4 -4
  52. package/src/blocks/apm-overview/index.tsx +672 -0
  53. package/src/blocks/cicd-builder/index.tsx +738 -0
  54. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  55. package/src/blocks/container-orchestrator/index.tsx +729 -0
  56. package/src/blocks/database-admin/index.tsx +679 -0
  57. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  58. package/src/blocks/incident-manager/index.tsx +586 -0
  59. package/src/blocks/index.ts +119 -0
  60. package/src/blocks/infrastructure-map/index.tsx +638 -0
  61. package/src/blocks/on-call-schedule/index.tsx +615 -0
  62. package/src/blocks/release-notes/index.tsx +643 -0
  63. package/src/blocks/sidebar/index.tsx +6 -6
  64. package/src/components/DataTable/templates/index.tsx +3 -2
  65. package/src/components/index.ts +283 -0
  66. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  67. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  68. package/src/components/waka-ad-banner/index.tsx +275 -0
  69. package/src/components/waka-ad-fallback/index.tsx +181 -0
  70. package/src/components/waka-ad-inline/index.tsx +103 -0
  71. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  72. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  73. package/src/components/waka-ad-provider/index.tsx +329 -0
  74. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  75. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  76. package/src/components/waka-alert-panel/index.tsx +493 -0
  77. package/src/components/waka-artifact-list/index.tsx +416 -0
  78. package/src/components/waka-badge-showcase/index.tsx +12 -11
  79. package/src/components/waka-build-matrix/index.tsx +396 -0
  80. package/src/components/waka-command-bar/index.tsx +2 -1
  81. package/src/components/waka-config-comparator/index.tsx +416 -0
  82. package/src/components/waka-container-list/index.tsx +475 -0
  83. package/src/components/waka-content-recommendation/index.tsx +294 -0
  84. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  85. package/src/components/waka-database-card/index.tsx +473 -0
  86. package/src/components/waka-dependency-tree/index.tsx +542 -0
  87. package/src/components/waka-env-var-editor/index.tsx +417 -0
  88. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  89. package/src/components/waka-funnel-chart/index.tsx +8 -8
  90. package/src/components/waka-health-pulse/index.tsx +6 -6
  91. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  92. package/src/components/waka-leaderboard/index.tsx +9 -9
  93. package/src/components/waka-log-viewer/index.tsx +386 -0
  94. package/src/components/waka-loot-box/index.tsx +20 -20
  95. package/src/components/waka-migration-list/index.tsx +487 -0
  96. package/src/components/waka-outstream-video/index.tsx +240 -0
  97. package/src/components/waka-player-card/index.tsx +5 -5
  98. package/src/components/waka-pod-card/index.tsx +528 -0
  99. package/src/components/waka-query-explain/index.tsx +657 -0
  100. package/src/components/waka-quota-bar/index.tsx +4 -4
  101. package/src/components/waka-radar-score/index.tsx +10 -10
  102. package/src/components/waka-scratch-card/index.tsx +5 -4
  103. package/src/components/waka-secret-card/index.tsx +371 -0
  104. package/src/components/waka-security-scan-result/index.tsx +473 -0
  105. package/src/components/waka-server-rack/index.tsx +28 -27
  106. package/src/components/waka-service-graph/index.tsx +445 -0
  107. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  108. package/src/components/waka-sponsored-card/index.tsx +275 -0
  109. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  110. package/src/components/waka-spotlight/index.tsx +2 -1
  111. package/src/components/waka-success-explosion/index.tsx +4 -4
  112. package/src/components/waka-test-report/index.tsx +469 -0
  113. package/src/components/waka-trace-viewer/index.tsx +490 -0
  114. package/src/components/waka-video-ad/index.tsx +406 -0
  115. package/src/components/waka-video-overlay/index.tsx +257 -0
  116. package/src/components/waka-xp-bar/index.tsx +13 -13
  117. package/src/styles/base.css +16 -0
  118. package/src/styles/tailwind.preset.js +12 -0
  119. package/src/styles/themes/forest.css +16 -0
  120. package/src/styles/themes/monochrome.css +16 -0
  121. package/src/styles/themes/perpetuity.css +16 -0
  122. package/src/styles/themes/sunset.css +16 -0
  123. package/src/styles/themes/twilight.css +16 -0
@@ -0,0 +1,536 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Button } from "../button"
6
+ import { Badge } from "../badge"
7
+ import { Card, CardContent, CardHeader, CardTitle } from "../card"
8
+ import { Progress } from "../progress"
9
+ import { ScrollArea } from "../scroll-area"
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../tabs"
11
+ import {
12
+ Tooltip,
13
+ TooltipContent,
14
+ TooltipProvider,
15
+ TooltipTrigger,
16
+ } from "../tooltip"
17
+ import {
18
+ Server,
19
+ Box,
20
+ Layers,
21
+ Network,
22
+ HardDrive,
23
+ Cpu,
24
+ CheckCircle2,
25
+ XCircle,
26
+ AlertTriangle,
27
+ RefreshCw,
28
+ ChevronRight,
29
+ Zap,
30
+ } from "lucide-react"
31
+
32
+ export type NodeStatus = "Ready" | "NotReady" | "Unknown"
33
+ export type PodPhase = "Running" | "Pending" | "Succeeded" | "Failed" | "Unknown"
34
+ export type DeploymentStatus = "Available" | "Progressing" | "Degraded"
35
+
36
+ export interface K8sNode {
37
+ name: string
38
+ status: NodeStatus
39
+ roles: string[]
40
+ version: string
41
+ cpu: { used: number; total: number }
42
+ memory: { used: number; total: number } // in bytes
43
+ pods: { running: number; total: number }
44
+ conditions?: Array<{ type: string; status: boolean; message?: string }>
45
+ }
46
+
47
+ export interface K8sPod {
48
+ name: string
49
+ namespace: string
50
+ phase: PodPhase
51
+ ready: string // e.g., "2/2"
52
+ restarts: number
53
+ age: string
54
+ node?: string
55
+ labels?: Record<string, string>
56
+ }
57
+
58
+ export interface K8sDeployment {
59
+ name: string
60
+ namespace: string
61
+ replicas: { ready: number; desired: number }
62
+ status: DeploymentStatus
63
+ age: string
64
+ image?: string
65
+ }
66
+
67
+ export interface K8sService {
68
+ name: string
69
+ namespace: string
70
+ type: "ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
71
+ clusterIP: string
72
+ externalIP?: string
73
+ ports: Array<{ port: number; targetPort: number; protocol: string }>
74
+ age: string
75
+ }
76
+
77
+ export interface K8sNamespace {
78
+ name: string
79
+ status: "Active" | "Terminating"
80
+ age: string
81
+ labels?: Record<string, string>
82
+ }
83
+
84
+ export interface WakaKubernetesOverviewProps {
85
+ /** Cluster name */
86
+ clusterName?: string
87
+ /** List of nodes */
88
+ nodes: K8sNode[]
89
+ /** List of pods */
90
+ pods: K8sPod[]
91
+ /** List of deployments */
92
+ deployments: K8sDeployment[]
93
+ /** List of services */
94
+ services: K8sService[]
95
+ /** List of namespaces */
96
+ namespaces: K8sNamespace[]
97
+ /** Selected namespace filter */
98
+ selectedNamespace?: string
99
+ /** Callback when changing namespace */
100
+ onNamespaceChange?: (namespace: string) => void
101
+ /** Callback when clicking on a resource */
102
+ onResourceClick?: (type: string, name: string, namespace?: string) => void
103
+ /** Callback when refreshing */
104
+ onRefresh?: () => void
105
+ /** Whether data is loading */
106
+ isLoading?: boolean
107
+ /** Custom class name */
108
+ className?: string
109
+ }
110
+
111
+ const nodeStatusConfig: Record<NodeStatus, { color: string; icon: React.ElementType }> = {
112
+ Ready: { color: "text-green-500", icon: CheckCircle2 },
113
+ NotReady: { color: "text-red-500", icon: XCircle },
114
+ Unknown: { color: "text-yellow-500", icon: AlertTriangle },
115
+ }
116
+
117
+ const podPhaseConfig: Record<PodPhase, { color: string; bgColor: string }> = {
118
+ Running: { color: "text-green-500", bgColor: "bg-green-500" },
119
+ Pending: { color: "text-yellow-500", bgColor: "bg-yellow-500" },
120
+ Succeeded: { color: "text-blue-500", bgColor: "bg-blue-500" },
121
+ Failed: { color: "text-red-500", bgColor: "bg-red-500" },
122
+ Unknown: { color: "text-gray-500", bgColor: "bg-gray-500" },
123
+ }
124
+
125
+ function formatBytes(bytes: number): string {
126
+ if (bytes === 0) return "0 B"
127
+ const k = 1024
128
+ const sizes = ["B", "KB", "MB", "GB", "TB"]
129
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
130
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]
131
+ }
132
+
133
+ function NodeCard({
134
+ node,
135
+ onClick,
136
+ }: {
137
+ node: K8sNode
138
+ onClick?: () => void
139
+ }) {
140
+ const config = nodeStatusConfig[node.status]
141
+ const StatusIcon = config.icon
142
+ const cpuPercent = (node.cpu.used / node.cpu.total) * 100
143
+ const memPercent = (node.memory.used / node.memory.total) * 100
144
+
145
+ return (
146
+ <Card
147
+ className={cn("cursor-pointer hover:border-primary/50 transition-colors")}
148
+ onClick={onClick}
149
+ >
150
+ <CardHeader className="pb-2">
151
+ <div className="flex items-center justify-between">
152
+ <div className="flex items-center gap-2">
153
+ <Server className="h-4 w-4 text-muted-foreground" />
154
+ <CardTitle className="text-sm font-medium">{node.name}</CardTitle>
155
+ </div>
156
+ <StatusIcon className={cn("h-4 w-4", config.color)} />
157
+ </div>
158
+ <div className="flex items-center gap-1 flex-wrap">
159
+ {node.roles.map((role) => (
160
+ <Badge key={role} variant="outline" className="text-xs">
161
+ {role}
162
+ </Badge>
163
+ ))}
164
+ </div>
165
+ </CardHeader>
166
+ <CardContent className="space-y-3">
167
+ <div className="space-y-1">
168
+ <div className="flex items-center justify-between text-xs">
169
+ <span className="flex items-center gap-1">
170
+ <Cpu className="h-3 w-3" /> CPU
171
+ </span>
172
+ <span>{cpuPercent.toFixed(0)}%</span>
173
+ </div>
174
+ <Progress value={cpuPercent} className="h-1.5" />
175
+ </div>
176
+ <div className="space-y-1">
177
+ <div className="flex items-center justify-between text-xs">
178
+ <span className="flex items-center gap-1">
179
+ <HardDrive className="h-3 w-3" /> Memory
180
+ </span>
181
+ <span>{formatBytes(node.memory.used)} / {formatBytes(node.memory.total)}</span>
182
+ </div>
183
+ <Progress value={memPercent} className="h-1.5" />
184
+ </div>
185
+ <div className="flex items-center justify-between text-xs text-muted-foreground">
186
+ <span>Pods: {node.pods.running}/{node.pods.total}</span>
187
+ <span>v{node.version}</span>
188
+ </div>
189
+ </CardContent>
190
+ </Card>
191
+ )
192
+ }
193
+
194
+ function ClusterSummary({
195
+ nodes,
196
+ pods,
197
+ deployments,
198
+ services,
199
+ }: {
200
+ nodes: K8sNode[]
201
+ pods: K8sPod[]
202
+ deployments: K8sDeployment[]
203
+ services: K8sService[]
204
+ }) {
205
+ const healthyNodes = nodes.filter((n) => n.status === "Ready").length
206
+ const runningPods = pods.filter((p) => p.phase === "Running").length
207
+ const availableDeployments = deployments.filter((d) => d.status === "Available").length
208
+
209
+ const totalCpu = nodes.reduce((acc, n) => acc + n.cpu.total, 0)
210
+ const usedCpu = nodes.reduce((acc, n) => acc + n.cpu.used, 0)
211
+ const totalMem = nodes.reduce((acc, n) => acc + n.memory.total, 0)
212
+ const usedMem = nodes.reduce((acc, n) => acc + n.memory.used, 0)
213
+
214
+ return (
215
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 border-b">
216
+ <div className="space-y-1">
217
+ <div className="text-sm text-muted-foreground">Nodes</div>
218
+ <div className="text-2xl font-bold">
219
+ {healthyNodes}/{nodes.length}
220
+ </div>
221
+ <Progress value={(healthyNodes / nodes.length) * 100} className="h-1" />
222
+ </div>
223
+ <div className="space-y-1">
224
+ <div className="text-sm text-muted-foreground">Pods</div>
225
+ <div className="text-2xl font-bold">
226
+ {runningPods}/{pods.length}
227
+ </div>
228
+ <Progress value={(runningPods / pods.length) * 100} className="h-1" />
229
+ </div>
230
+ <div className="space-y-1">
231
+ <div className="text-sm text-muted-foreground">CPU Usage</div>
232
+ <div className="text-2xl font-bold">{((usedCpu / totalCpu) * 100).toFixed(0)}%</div>
233
+ <Progress value={(usedCpu / totalCpu) * 100} className="h-1" />
234
+ </div>
235
+ <div className="space-y-1">
236
+ <div className="text-sm text-muted-foreground">Memory Usage</div>
237
+ <div className="text-2xl font-bold">{((usedMem / totalMem) * 100).toFixed(0)}%</div>
238
+ <Progress value={(usedMem / totalMem) * 100} className="h-1" />
239
+ </div>
240
+ </div>
241
+ )
242
+ }
243
+
244
+ export function WakaKubernetesOverview({
245
+ clusterName = "kubernetes",
246
+ nodes,
247
+ pods,
248
+ deployments,
249
+ services,
250
+ namespaces,
251
+ selectedNamespace,
252
+ onNamespaceChange,
253
+ onResourceClick,
254
+ onRefresh,
255
+ isLoading = false,
256
+ className,
257
+ }: WakaKubernetesOverviewProps) {
258
+ const [activeTab, setActiveTab] = React.useState("nodes")
259
+
260
+ // Filter by namespace
261
+ const filteredPods = selectedNamespace && selectedNamespace !== "all"
262
+ ? pods.filter((p) => p.namespace === selectedNamespace)
263
+ : pods
264
+ const filteredDeployments = selectedNamespace && selectedNamespace !== "all"
265
+ ? deployments.filter((d) => d.namespace === selectedNamespace)
266
+ : deployments
267
+ const filteredServices = selectedNamespace && selectedNamespace !== "all"
268
+ ? services.filter((s) => s.namespace === selectedNamespace)
269
+ : services
270
+
271
+ return (
272
+ <div className={cn("flex flex-col border rounded-lg bg-background", className)}>
273
+ {/* Header */}
274
+ <div className="flex items-center justify-between gap-4 p-3 border-b">
275
+ <div className="flex items-center gap-3">
276
+ <Layers className="h-5 w-5 text-primary" />
277
+ <h3 className="font-semibold">{clusterName}</h3>
278
+ </div>
279
+
280
+ <div className="flex items-center gap-2">
281
+ {/* Namespace filter */}
282
+ <select
283
+ value={selectedNamespace || "all"}
284
+ onChange={(e) => onNamespaceChange?.(e.target.value)}
285
+ className="h-8 px-2 rounded border bg-background text-sm"
286
+ >
287
+ <option value="all">All Namespaces</option>
288
+ {namespaces.map((ns) => (
289
+ <option key={ns.name} value={ns.name}>
290
+ {ns.name}
291
+ </option>
292
+ ))}
293
+ </select>
294
+
295
+ {onRefresh && (
296
+ <Button
297
+ variant="ghost"
298
+ size="sm"
299
+ onClick={onRefresh}
300
+ disabled={isLoading}
301
+ >
302
+ <RefreshCw className={cn("h-4 w-4", isLoading && "animate-spin")} />
303
+ </Button>
304
+ )}
305
+ </div>
306
+ </div>
307
+
308
+ {/* Summary */}
309
+ <ClusterSummary
310
+ nodes={nodes}
311
+ pods={pods}
312
+ deployments={deployments}
313
+ services={services}
314
+ />
315
+
316
+ {/* Tabs */}
317
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1">
318
+ <TabsList className="w-full justify-start rounded-none border-b bg-transparent h-auto p-0">
319
+ <TabsTrigger
320
+ value="nodes"
321
+ className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
322
+ >
323
+ <Server className="h-4 w-4 mr-2" />
324
+ Nodes ({nodes.length})
325
+ </TabsTrigger>
326
+ <TabsTrigger
327
+ value="pods"
328
+ className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
329
+ >
330
+ <Box className="h-4 w-4 mr-2" />
331
+ Pods ({filteredPods.length})
332
+ </TabsTrigger>
333
+ <TabsTrigger
334
+ value="deployments"
335
+ className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
336
+ >
337
+ <Zap className="h-4 w-4 mr-2" />
338
+ Deployments ({filteredDeployments.length})
339
+ </TabsTrigger>
340
+ <TabsTrigger
341
+ value="services"
342
+ className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
343
+ >
344
+ <Network className="h-4 w-4 mr-2" />
345
+ Services ({filteredServices.length})
346
+ </TabsTrigger>
347
+ </TabsList>
348
+
349
+ <TabsContent value="nodes" className="p-4 m-0">
350
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
351
+ {nodes.map((node) => (
352
+ <NodeCard
353
+ key={node.name}
354
+ node={node}
355
+ onClick={() => onResourceClick?.("node", node.name)}
356
+ />
357
+ ))}
358
+ </div>
359
+ </TabsContent>
360
+
361
+ <TabsContent value="pods" className="m-0">
362
+ <ScrollArea className="h-[400px]">
363
+ <table className="w-full text-sm">
364
+ <thead className="bg-muted/50 sticky top-0">
365
+ <tr>
366
+ <th className="text-left p-2">Name</th>
367
+ <th className="text-left p-2">Namespace</th>
368
+ <th className="text-left p-2">Status</th>
369
+ <th className="text-left p-2">Ready</th>
370
+ <th className="text-left p-2">Restarts</th>
371
+ <th className="text-left p-2">Age</th>
372
+ </tr>
373
+ </thead>
374
+ <tbody>
375
+ {filteredPods.map((pod) => {
376
+ const config = podPhaseConfig[pod.phase]
377
+ return (
378
+ <tr
379
+ key={`${pod.namespace}/${pod.name}`}
380
+ className="border-b hover:bg-muted/30 cursor-pointer"
381
+ onClick={() => onResourceClick?.("pod", pod.name, pod.namespace)}
382
+ >
383
+ <td className="p-2 font-medium">{pod.name}</td>
384
+ <td className="p-2 text-muted-foreground">{pod.namespace}</td>
385
+ <td className="p-2">
386
+ <Badge variant="outline" className={config.color}>
387
+ {pod.phase}
388
+ </Badge>
389
+ </td>
390
+ <td className="p-2">{pod.ready}</td>
391
+ <td className="p-2">{pod.restarts}</td>
392
+ <td className="p-2 text-muted-foreground">{pod.age}</td>
393
+ </tr>
394
+ )
395
+ })}
396
+ </tbody>
397
+ </table>
398
+ </ScrollArea>
399
+ </TabsContent>
400
+
401
+ <TabsContent value="deployments" className="m-0">
402
+ <ScrollArea className="h-[400px]">
403
+ <table className="w-full text-sm">
404
+ <thead className="bg-muted/50 sticky top-0">
405
+ <tr>
406
+ <th className="text-left p-2">Name</th>
407
+ <th className="text-left p-2">Namespace</th>
408
+ <th className="text-left p-2">Status</th>
409
+ <th className="text-left p-2">Replicas</th>
410
+ <th className="text-left p-2">Age</th>
411
+ </tr>
412
+ </thead>
413
+ <tbody>
414
+ {filteredDeployments.map((deploy) => (
415
+ <tr
416
+ key={`${deploy.namespace}/${deploy.name}`}
417
+ className="border-b hover:bg-muted/30 cursor-pointer"
418
+ onClick={() => onResourceClick?.("deployment", deploy.name, deploy.namespace)}
419
+ >
420
+ <td className="p-2 font-medium">{deploy.name}</td>
421
+ <td className="p-2 text-muted-foreground">{deploy.namespace}</td>
422
+ <td className="p-2">
423
+ <Badge
424
+ variant={deploy.status === "Available" ? "default" : "secondary"}
425
+ className={deploy.status === "Available" ? "bg-green-500" : ""}
426
+ >
427
+ {deploy.status}
428
+ </Badge>
429
+ </td>
430
+ <td className="p-2">{deploy.replicas.ready}/{deploy.replicas.desired}</td>
431
+ <td className="p-2 text-muted-foreground">{deploy.age}</td>
432
+ </tr>
433
+ ))}
434
+ </tbody>
435
+ </table>
436
+ </ScrollArea>
437
+ </TabsContent>
438
+
439
+ <TabsContent value="services" className="m-0">
440
+ <ScrollArea className="h-[400px]">
441
+ <table className="w-full text-sm">
442
+ <thead className="bg-muted/50 sticky top-0">
443
+ <tr>
444
+ <th className="text-left p-2">Name</th>
445
+ <th className="text-left p-2">Namespace</th>
446
+ <th className="text-left p-2">Type</th>
447
+ <th className="text-left p-2">Cluster IP</th>
448
+ <th className="text-left p-2">Ports</th>
449
+ <th className="text-left p-2">Age</th>
450
+ </tr>
451
+ </thead>
452
+ <tbody>
453
+ {filteredServices.map((svc) => (
454
+ <tr
455
+ key={`${svc.namespace}/${svc.name}`}
456
+ className="border-b hover:bg-muted/30 cursor-pointer"
457
+ onClick={() => onResourceClick?.("service", svc.name, svc.namespace)}
458
+ >
459
+ <td className="p-2 font-medium">{svc.name}</td>
460
+ <td className="p-2 text-muted-foreground">{svc.namespace}</td>
461
+ <td className="p-2">
462
+ <Badge variant="outline">{svc.type}</Badge>
463
+ </td>
464
+ <td className="p-2 font-mono text-xs">{svc.clusterIP}</td>
465
+ <td className="p-2">
466
+ {svc.ports.map((p) => `${p.port}/${p.protocol}`).join(", ")}
467
+ </td>
468
+ <td className="p-2 text-muted-foreground">{svc.age}</td>
469
+ </tr>
470
+ ))}
471
+ </tbody>
472
+ </table>
473
+ </ScrollArea>
474
+ </TabsContent>
475
+ </Tabs>
476
+ </div>
477
+ )
478
+ }
479
+
480
+ // Default sample data for demo
481
+ export const defaultK8sNodes: K8sNode[] = [
482
+ {
483
+ name: "node-1",
484
+ status: "Ready",
485
+ roles: ["control-plane", "master"],
486
+ version: "1.28.2",
487
+ cpu: { used: 2.5, total: 8 },
488
+ memory: { used: 6 * 1024 * 1024 * 1024, total: 16 * 1024 * 1024 * 1024 },
489
+ pods: { running: 25, total: 110 },
490
+ },
491
+ {
492
+ name: "node-2",
493
+ status: "Ready",
494
+ roles: ["worker"],
495
+ version: "1.28.2",
496
+ cpu: { used: 5.2, total: 8 },
497
+ memory: { used: 12 * 1024 * 1024 * 1024, total: 32 * 1024 * 1024 * 1024 },
498
+ pods: { running: 45, total: 110 },
499
+ },
500
+ {
501
+ name: "node-3",
502
+ status: "Ready",
503
+ roles: ["worker"],
504
+ version: "1.28.2",
505
+ cpu: { used: 3.8, total: 8 },
506
+ memory: { used: 8 * 1024 * 1024 * 1024, total: 32 * 1024 * 1024 * 1024 },
507
+ pods: { running: 38, total: 110 },
508
+ },
509
+ ]
510
+
511
+ export const defaultK8sPods: K8sPod[] = [
512
+ { name: "api-server-7d8f9b6c4d-x2k9l", namespace: "default", phase: "Running", ready: "2/2", restarts: 0, age: "5d" },
513
+ { name: "web-frontend-5c8d7e9f1b-m3n4o", namespace: "default", phase: "Running", ready: "1/1", restarts: 2, age: "3d" },
514
+ { name: "postgres-0", namespace: "database", phase: "Running", ready: "1/1", restarts: 0, age: "14d" },
515
+ { name: "redis-master-0", namespace: "cache", phase: "Running", ready: "1/1", restarts: 0, age: "7d" },
516
+ { name: "worker-6f7g8h9i0j-p5q6r", namespace: "default", phase: "Pending", ready: "0/1", restarts: 0, age: "2m" },
517
+ ]
518
+
519
+ export const defaultK8sDeployments: K8sDeployment[] = [
520
+ { name: "api-server", namespace: "default", replicas: { ready: 3, desired: 3 }, status: "Available", age: "30d" },
521
+ { name: "web-frontend", namespace: "default", replicas: { ready: 2, desired: 2 }, status: "Available", age: "30d" },
522
+ { name: "worker", namespace: "default", replicas: { ready: 1, desired: 2 }, status: "Progressing", age: "15d" },
523
+ ]
524
+
525
+ export const defaultK8sServices: K8sService[] = [
526
+ { name: "api-server", namespace: "default", type: "ClusterIP", clusterIP: "10.96.0.1", ports: [{ port: 80, targetPort: 8080, protocol: "TCP" }], age: "30d" },
527
+ { name: "web-frontend", namespace: "default", type: "LoadBalancer", clusterIP: "10.96.0.2", externalIP: "34.123.45.67", ports: [{ port: 443, targetPort: 3000, protocol: "TCP" }], age: "30d" },
528
+ { name: "postgres", namespace: "database", type: "ClusterIP", clusterIP: "10.96.0.10", ports: [{ port: 5432, targetPort: 5432, protocol: "TCP" }], age: "45d" },
529
+ ]
530
+
531
+ export const defaultK8sNamespaces: K8sNamespace[] = [
532
+ { name: "default", status: "Active", age: "90d" },
533
+ { name: "kube-system", status: "Active", age: "90d" },
534
+ { name: "database", status: "Active", age: "60d" },
535
+ { name: "cache", status: "Active", age: "45d" },
536
+ ]
@@ -77,30 +77,30 @@ const medalConfig = {
77
77
  1: {
78
78
  gradient: "from-yellow-300 via-yellow-400 to-yellow-500",
79
79
  shadow: "shadow-yellow-400/50",
80
- glow: "#fbbf24",
80
+ glow: "hsl(var(--warning))",
81
81
  border: "border-yellow-400",
82
- bg: "bg-yellow-50",
83
- text: "text-yellow-700",
82
+ bg: "bg-yellow-50 dark:bg-yellow-950/30",
83
+ text: "text-yellow-700 dark:text-yellow-400",
84
84
  icon: Crown,
85
85
  label: "Gold",
86
86
  },
87
87
  2: {
88
88
  gradient: "from-gray-300 via-gray-400 to-gray-500",
89
89
  shadow: "shadow-gray-400/50",
90
- glow: "#9ca3af",
90
+ glow: "hsl(var(--muted-foreground))",
91
91
  border: "border-gray-400",
92
- bg: "bg-gray-50",
93
- text: "text-gray-700",
92
+ bg: "bg-gray-50 dark:bg-gray-950/30",
93
+ text: "text-gray-700 dark:text-gray-400",
94
94
  icon: Medal,
95
95
  label: "Silver",
96
96
  },
97
97
  3: {
98
98
  gradient: "from-amber-500 via-amber-600 to-amber-700",
99
99
  shadow: "shadow-amber-500/50",
100
- glow: "#d97706",
100
+ glow: "hsl(var(--chart-4))",
101
101
  border: "border-amber-500",
102
- bg: "bg-amber-50",
103
- text: "text-amber-700",
102
+ bg: "bg-amber-50 dark:bg-amber-950/30",
103
+ text: "text-amber-700 dark:text-amber-400",
104
104
  icon: Medal,
105
105
  label: "Bronze",
106
106
  },