@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.
- package/dist/blocks/apm-overview/index.d.ts +58 -0
- package/dist/blocks/cicd-builder/index.d.ts +47 -0
- package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
- package/dist/blocks/container-orchestrator/index.d.ts +63 -0
- package/dist/blocks/database-admin/index.d.ts +84 -0
- package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
- package/dist/blocks/incident-manager/index.d.ts +44 -0
- package/dist/blocks/index.d.ts +10 -0
- package/dist/blocks/infrastructure-map/index.d.ts +32 -0
- package/dist/blocks/on-call-schedule/index.d.ts +43 -0
- package/dist/blocks/release-notes/index.d.ts +49 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-alert-panel/index.d.ts +45 -0
- package/dist/components/waka-artifact-list/index.d.ts +32 -0
- package/dist/components/waka-build-matrix/index.d.ts +36 -0
- package/dist/components/waka-config-comparator/index.d.ts +37 -0
- package/dist/components/waka-container-list/index.d.ts +51 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-database-card/index.d.ts +46 -0
- package/dist/components/waka-dependency-tree/index.d.ts +38 -0
- package/dist/components/waka-env-var-editor/index.d.ts +30 -0
- package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
- package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
- package/dist/components/waka-log-viewer/index.d.ts +38 -0
- package/dist/components/waka-migration-list/index.d.ts +36 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-pod-card/index.d.ts +73 -0
- package/dist/components/waka-query-explain/index.d.ts +48 -0
- package/dist/components/waka-secret-card/index.d.ts +43 -0
- package/dist/components/waka-security-scan-result/index.d.ts +45 -0
- package/dist/components/waka-service-graph/index.d.ts +44 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +251 -200
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +47315 -35823
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/apm-overview/index.tsx +672 -0
- package/src/blocks/cicd-builder/index.tsx +738 -0
- package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
- package/src/blocks/container-orchestrator/index.tsx +729 -0
- package/src/blocks/database-admin/index.tsx +679 -0
- package/src/blocks/gitops-sync-status/index.tsx +557 -0
- package/src/blocks/incident-manager/index.tsx +586 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/infrastructure-map/index.tsx +638 -0
- package/src/blocks/on-call-schedule/index.tsx +615 -0
- package/src/blocks/release-notes/index.tsx +643 -0
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +283 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-alert-panel/index.tsx +493 -0
- package/src/components/waka-artifact-list/index.tsx +416 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-build-matrix/index.tsx +396 -0
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-database-card/index.tsx +473 -0
- package/src/components/waka-dependency-tree/index.tsx +542 -0
- package/src/components/waka-env-var-editor/index.tsx +417 -0
- package/src/components/waka-feature-flag-row/index.tsx +386 -0
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-secret-card/index.tsx +371 -0
- package/src/components/waka-security-scan-result/index.tsx +473 -0
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-service-graph/index.tsx +445 -0
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- 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: "
|
|
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: "
|
|
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: "
|
|
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
|
},
|