@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,475 @@
|
|
|
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 { ScrollArea } from "../scroll-area"
|
|
8
|
+
import { Input } from "../input"
|
|
9
|
+
import { Progress } from "../progress"
|
|
10
|
+
import {
|
|
11
|
+
DropdownMenu,
|
|
12
|
+
DropdownMenuContent,
|
|
13
|
+
DropdownMenuItem,
|
|
14
|
+
DropdownMenuSeparator,
|
|
15
|
+
DropdownMenuTrigger,
|
|
16
|
+
} from "../dropdown-menu"
|
|
17
|
+
import {
|
|
18
|
+
Tooltip,
|
|
19
|
+
TooltipContent,
|
|
20
|
+
TooltipProvider,
|
|
21
|
+
TooltipTrigger,
|
|
22
|
+
} from "../tooltip"
|
|
23
|
+
import {
|
|
24
|
+
Box,
|
|
25
|
+
Play,
|
|
26
|
+
Square,
|
|
27
|
+
RotateCcw,
|
|
28
|
+
Trash2,
|
|
29
|
+
MoreVertical,
|
|
30
|
+
Terminal,
|
|
31
|
+
FileText,
|
|
32
|
+
Search,
|
|
33
|
+
RefreshCw,
|
|
34
|
+
Cpu,
|
|
35
|
+
HardDrive,
|
|
36
|
+
Network,
|
|
37
|
+
Clock,
|
|
38
|
+
CheckCircle2,
|
|
39
|
+
XCircle,
|
|
40
|
+
Loader2,
|
|
41
|
+
PauseCircle,
|
|
42
|
+
} from "lucide-react"
|
|
43
|
+
|
|
44
|
+
export type ContainerStatus = "running" | "stopped" | "paused" | "restarting" | "exited" | "created"
|
|
45
|
+
|
|
46
|
+
export interface ContainerResource {
|
|
47
|
+
cpu: number // percentage
|
|
48
|
+
memory: number // bytes
|
|
49
|
+
memoryLimit: number // bytes
|
|
50
|
+
networkRx: number // bytes
|
|
51
|
+
networkTx: number // bytes
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Container {
|
|
55
|
+
id: string
|
|
56
|
+
name: string
|
|
57
|
+
image: string
|
|
58
|
+
status: ContainerStatus
|
|
59
|
+
createdAt: Date
|
|
60
|
+
startedAt?: Date
|
|
61
|
+
ports?: Array<{ host: number; container: number; protocol: string }>
|
|
62
|
+
resources?: ContainerResource
|
|
63
|
+
labels?: Record<string, string>
|
|
64
|
+
command?: string
|
|
65
|
+
exitCode?: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WakaContainerListProps {
|
|
69
|
+
/** List of containers */
|
|
70
|
+
containers: Container[]
|
|
71
|
+
/** Callback when starting a container */
|
|
72
|
+
onStart?: (containerId: string) => void
|
|
73
|
+
/** Callback when stopping a container */
|
|
74
|
+
onStop?: (containerId: string) => void
|
|
75
|
+
/** Callback when restarting a container */
|
|
76
|
+
onRestart?: (containerId: string) => void
|
|
77
|
+
/** Callback when removing a container */
|
|
78
|
+
onRemove?: (containerId: string) => void
|
|
79
|
+
/** Callback when viewing logs */
|
|
80
|
+
onViewLogs?: (containerId: string) => void
|
|
81
|
+
/** Callback when opening terminal */
|
|
82
|
+
onExec?: (containerId: string) => void
|
|
83
|
+
/** Callback when refreshing */
|
|
84
|
+
onRefresh?: () => void
|
|
85
|
+
/** Whether data is loading */
|
|
86
|
+
isLoading?: boolean
|
|
87
|
+
/** Custom class name */
|
|
88
|
+
className?: string
|
|
89
|
+
/** Title */
|
|
90
|
+
title?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const statusConfig: Record<ContainerStatus, { icon: React.ElementType; color: string; label: string }> = {
|
|
94
|
+
running: { icon: CheckCircle2, color: "text-green-500", label: "Running" },
|
|
95
|
+
stopped: { icon: XCircle, color: "text-gray-500", label: "Stopped" },
|
|
96
|
+
paused: { icon: PauseCircle, color: "text-yellow-500", label: "Paused" },
|
|
97
|
+
restarting: { icon: Loader2, color: "text-blue-500", label: "Restarting" },
|
|
98
|
+
exited: { icon: XCircle, color: "text-red-500", label: "Exited" },
|
|
99
|
+
created: { icon: Box, color: "text-gray-400", label: "Created" },
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatBytes(bytes: number): string {
|
|
103
|
+
if (bytes === 0) return "0 B"
|
|
104
|
+
const k = 1024
|
|
105
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"]
|
|
106
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
107
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatUptime(startedAt?: Date): string {
|
|
111
|
+
if (!startedAt) return "-"
|
|
112
|
+
const diff = Date.now() - startedAt.getTime()
|
|
113
|
+
const days = Math.floor(diff / (24 * 3600000))
|
|
114
|
+
const hours = Math.floor((diff % (24 * 3600000)) / 3600000)
|
|
115
|
+
const minutes = Math.floor((diff % 3600000) / 60000)
|
|
116
|
+
|
|
117
|
+
if (days > 0) return `${days}d ${hours}h`
|
|
118
|
+
if (hours > 0) return `${hours}h ${minutes}m`
|
|
119
|
+
return `${minutes}m`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ContainerRow({
|
|
123
|
+
container,
|
|
124
|
+
onStart,
|
|
125
|
+
onStop,
|
|
126
|
+
onRestart,
|
|
127
|
+
onRemove,
|
|
128
|
+
onViewLogs,
|
|
129
|
+
onExec,
|
|
130
|
+
}: {
|
|
131
|
+
container: Container
|
|
132
|
+
onStart?: (id: string) => void
|
|
133
|
+
onStop?: (id: string) => void
|
|
134
|
+
onRestart?: (id: string) => void
|
|
135
|
+
onRemove?: (id: string) => void
|
|
136
|
+
onViewLogs?: (id: string) => void
|
|
137
|
+
onExec?: (id: string) => void
|
|
138
|
+
}) {
|
|
139
|
+
const config = statusConfig[container.status]
|
|
140
|
+
const StatusIcon = config.icon
|
|
141
|
+
const isRunning = container.status === "running"
|
|
142
|
+
const memoryPercent = container.resources
|
|
143
|
+
? (container.resources.memory / container.resources.memoryLimit) * 100
|
|
144
|
+
: 0
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div className="flex items-center gap-4 px-4 py-3 border-b hover:bg-muted/30 transition-colors">
|
|
148
|
+
{/* Status */}
|
|
149
|
+
<TooltipProvider>
|
|
150
|
+
<Tooltip>
|
|
151
|
+
<TooltipTrigger>
|
|
152
|
+
<StatusIcon
|
|
153
|
+
className={cn(
|
|
154
|
+
"h-5 w-5",
|
|
155
|
+
config.color,
|
|
156
|
+
container.status === "restarting" && "animate-spin"
|
|
157
|
+
)}
|
|
158
|
+
/>
|
|
159
|
+
</TooltipTrigger>
|
|
160
|
+
<TooltipContent>{config.label}</TooltipContent>
|
|
161
|
+
</Tooltip>
|
|
162
|
+
</TooltipProvider>
|
|
163
|
+
|
|
164
|
+
{/* Name & Image */}
|
|
165
|
+
<div className="flex-1 min-w-0">
|
|
166
|
+
<div className="flex items-center gap-2">
|
|
167
|
+
<span className="font-medium truncate">{container.name}</span>
|
|
168
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded truncate max-w-[200px]">
|
|
169
|
+
{container.id.slice(0, 12)}
|
|
170
|
+
</code>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="text-sm text-muted-foreground truncate">
|
|
173
|
+
{container.image}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* Ports */}
|
|
178
|
+
<div className="hidden md:block w-32">
|
|
179
|
+
{container.ports && container.ports.length > 0 ? (
|
|
180
|
+
<div className="flex flex-wrap gap-1">
|
|
181
|
+
{container.ports.slice(0, 2).map((port, i) => (
|
|
182
|
+
<Badge key={i} variant="outline" className="text-xs">
|
|
183
|
+
{port.host}:{port.container}
|
|
184
|
+
</Badge>
|
|
185
|
+
))}
|
|
186
|
+
{container.ports.length > 2 && (
|
|
187
|
+
<Badge variant="outline" className="text-xs">
|
|
188
|
+
+{container.ports.length - 2}
|
|
189
|
+
</Badge>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
) : (
|
|
193
|
+
<span className="text-sm text-muted-foreground">-</span>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* Resources */}
|
|
198
|
+
{isRunning && container.resources && (
|
|
199
|
+
<div className="hidden lg:flex items-center gap-4 w-48">
|
|
200
|
+
<TooltipProvider>
|
|
201
|
+
<Tooltip>
|
|
202
|
+
<TooltipTrigger className="flex items-center gap-1.5">
|
|
203
|
+
<Cpu className="h-4 w-4 text-muted-foreground" />
|
|
204
|
+
<span className="text-sm w-12">{container.resources.cpu.toFixed(1)}%</span>
|
|
205
|
+
</TooltipTrigger>
|
|
206
|
+
<TooltipContent>CPU Usage</TooltipContent>
|
|
207
|
+
</Tooltip>
|
|
208
|
+
</TooltipProvider>
|
|
209
|
+
|
|
210
|
+
<TooltipProvider>
|
|
211
|
+
<Tooltip>
|
|
212
|
+
<TooltipTrigger className="flex items-center gap-1.5 flex-1">
|
|
213
|
+
<HardDrive className="h-4 w-4 text-muted-foreground" />
|
|
214
|
+
<Progress value={memoryPercent} className="h-1.5 flex-1" />
|
|
215
|
+
</TooltipTrigger>
|
|
216
|
+
<TooltipContent>
|
|
217
|
+
Memory: {formatBytes(container.resources.memory)} / {formatBytes(container.resources.memoryLimit)}
|
|
218
|
+
</TooltipContent>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
</TooltipProvider>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{/* Uptime */}
|
|
225
|
+
<div className="hidden sm:flex items-center gap-1.5 w-20 text-sm text-muted-foreground">
|
|
226
|
+
<Clock className="h-4 w-4" />
|
|
227
|
+
{formatUptime(container.startedAt)}
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
{/* Actions */}
|
|
231
|
+
<div className="flex items-center gap-1">
|
|
232
|
+
{isRunning ? (
|
|
233
|
+
<>
|
|
234
|
+
<TooltipProvider>
|
|
235
|
+
<Tooltip>
|
|
236
|
+
<TooltipTrigger asChild>
|
|
237
|
+
<Button
|
|
238
|
+
variant="ghost"
|
|
239
|
+
size="sm"
|
|
240
|
+
className="h-8 w-8 p-0"
|
|
241
|
+
onClick={() => onStop?.(container.id)}
|
|
242
|
+
>
|
|
243
|
+
<Square className="h-4 w-4" />
|
|
244
|
+
</Button>
|
|
245
|
+
</TooltipTrigger>
|
|
246
|
+
<TooltipContent>Stop</TooltipContent>
|
|
247
|
+
</Tooltip>
|
|
248
|
+
</TooltipProvider>
|
|
249
|
+
|
|
250
|
+
<TooltipProvider>
|
|
251
|
+
<Tooltip>
|
|
252
|
+
<TooltipTrigger asChild>
|
|
253
|
+
<Button
|
|
254
|
+
variant="ghost"
|
|
255
|
+
size="sm"
|
|
256
|
+
className="h-8 w-8 p-0"
|
|
257
|
+
onClick={() => onRestart?.(container.id)}
|
|
258
|
+
>
|
|
259
|
+
<RotateCcw className="h-4 w-4" />
|
|
260
|
+
</Button>
|
|
261
|
+
</TooltipTrigger>
|
|
262
|
+
<TooltipContent>Restart</TooltipContent>
|
|
263
|
+
</Tooltip>
|
|
264
|
+
</TooltipProvider>
|
|
265
|
+
</>
|
|
266
|
+
) : (
|
|
267
|
+
<TooltipProvider>
|
|
268
|
+
<Tooltip>
|
|
269
|
+
<TooltipTrigger asChild>
|
|
270
|
+
<Button
|
|
271
|
+
variant="ghost"
|
|
272
|
+
size="sm"
|
|
273
|
+
className="h-8 w-8 p-0"
|
|
274
|
+
onClick={() => onStart?.(container.id)}
|
|
275
|
+
>
|
|
276
|
+
<Play className="h-4 w-4" />
|
|
277
|
+
</Button>
|
|
278
|
+
</TooltipTrigger>
|
|
279
|
+
<TooltipContent>Start</TooltipContent>
|
|
280
|
+
</Tooltip>
|
|
281
|
+
</TooltipProvider>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
<DropdownMenu>
|
|
285
|
+
<DropdownMenuTrigger asChild>
|
|
286
|
+
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
|
287
|
+
<MoreVertical className="h-4 w-4" />
|
|
288
|
+
</Button>
|
|
289
|
+
</DropdownMenuTrigger>
|
|
290
|
+
<DropdownMenuContent align="end">
|
|
291
|
+
<DropdownMenuItem onClick={() => onViewLogs?.(container.id)}>
|
|
292
|
+
<FileText className="h-4 w-4 mr-2" />
|
|
293
|
+
View Logs
|
|
294
|
+
</DropdownMenuItem>
|
|
295
|
+
{isRunning && (
|
|
296
|
+
<DropdownMenuItem onClick={() => onExec?.(container.id)}>
|
|
297
|
+
<Terminal className="h-4 w-4 mr-2" />
|
|
298
|
+
Open Terminal
|
|
299
|
+
</DropdownMenuItem>
|
|
300
|
+
)}
|
|
301
|
+
<DropdownMenuSeparator />
|
|
302
|
+
<DropdownMenuItem
|
|
303
|
+
onClick={() => onRemove?.(container.id)}
|
|
304
|
+
className="text-destructive"
|
|
305
|
+
>
|
|
306
|
+
<Trash2 className="h-4 w-4 mr-2" />
|
|
307
|
+
Remove
|
|
308
|
+
</DropdownMenuItem>
|
|
309
|
+
</DropdownMenuContent>
|
|
310
|
+
</DropdownMenu>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function WakaContainerList({
|
|
317
|
+
containers,
|
|
318
|
+
onStart,
|
|
319
|
+
onStop,
|
|
320
|
+
onRestart,
|
|
321
|
+
onRemove,
|
|
322
|
+
onViewLogs,
|
|
323
|
+
onExec,
|
|
324
|
+
onRefresh,
|
|
325
|
+
isLoading = false,
|
|
326
|
+
className,
|
|
327
|
+
title = "Containers",
|
|
328
|
+
}: WakaContainerListProps) {
|
|
329
|
+
const [searchQuery, setSearchQuery] = React.useState("")
|
|
330
|
+
|
|
331
|
+
// Filter containers
|
|
332
|
+
const filteredContainers = React.useMemo(() => {
|
|
333
|
+
if (!searchQuery) return containers
|
|
334
|
+
const query = searchQuery.toLowerCase()
|
|
335
|
+
return containers.filter(
|
|
336
|
+
(c) =>
|
|
337
|
+
c.name.toLowerCase().includes(query) ||
|
|
338
|
+
c.image.toLowerCase().includes(query) ||
|
|
339
|
+
c.id.toLowerCase().includes(query)
|
|
340
|
+
)
|
|
341
|
+
}, [containers, searchQuery])
|
|
342
|
+
|
|
343
|
+
// Count by status
|
|
344
|
+
const statusCounts = React.useMemo(() => {
|
|
345
|
+
return containers.reduce((acc, c) => {
|
|
346
|
+
acc[c.status] = (acc[c.status] || 0) + 1
|
|
347
|
+
return acc
|
|
348
|
+
}, {} as Record<ContainerStatus, number>)
|
|
349
|
+
}, [containers])
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
353
|
+
{/* Header */}
|
|
354
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
355
|
+
<div className="flex items-center gap-3">
|
|
356
|
+
<Box className="h-5 w-5" />
|
|
357
|
+
<h3 className="font-semibold">{title}</h3>
|
|
358
|
+
<Badge variant="secondary">{containers.length}</Badge>
|
|
359
|
+
|
|
360
|
+
<div className="hidden md:flex items-center gap-1">
|
|
361
|
+
{statusCounts.running > 0 && (
|
|
362
|
+
<Badge className="bg-green-500 text-xs">{statusCounts.running} running</Badge>
|
|
363
|
+
)}
|
|
364
|
+
{statusCounts.stopped > 0 && (
|
|
365
|
+
<Badge variant="secondary" className="text-xs">{statusCounts.stopped} stopped</Badge>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
<div className="flex items-center gap-2">
|
|
371
|
+
{onRefresh && (
|
|
372
|
+
<Button
|
|
373
|
+
variant="ghost"
|
|
374
|
+
size="sm"
|
|
375
|
+
onClick={onRefresh}
|
|
376
|
+
disabled={isLoading}
|
|
377
|
+
>
|
|
378
|
+
<RefreshCw className={cn("h-4 w-4", isLoading && "animate-spin")} />
|
|
379
|
+
</Button>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
{/* Search */}
|
|
385
|
+
<div className="p-2 border-b bg-muted/30">
|
|
386
|
+
<div className="relative max-w-sm">
|
|
387
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
388
|
+
<Input
|
|
389
|
+
placeholder="Search containers..."
|
|
390
|
+
value={searchQuery}
|
|
391
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
392
|
+
className="pl-8 h-8"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
{/* Container list */}
|
|
398
|
+
<ScrollArea className="flex-1">
|
|
399
|
+
{filteredContainers.length === 0 ? (
|
|
400
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
401
|
+
<Box className="h-8 w-8 mb-2" />
|
|
402
|
+
<span>No containers found</span>
|
|
403
|
+
</div>
|
|
404
|
+
) : (
|
|
405
|
+
filteredContainers.map((container) => (
|
|
406
|
+
<ContainerRow
|
|
407
|
+
key={container.id}
|
|
408
|
+
container={container}
|
|
409
|
+
onStart={onStart}
|
|
410
|
+
onStop={onStop}
|
|
411
|
+
onRestart={onRestart}
|
|
412
|
+
onRemove={onRemove}
|
|
413
|
+
onViewLogs={onViewLogs}
|
|
414
|
+
onExec={onExec}
|
|
415
|
+
/>
|
|
416
|
+
))
|
|
417
|
+
)}
|
|
418
|
+
</ScrollArea>
|
|
419
|
+
</div>
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Default sample containers for demo
|
|
424
|
+
export const defaultContainers: Container[] = [
|
|
425
|
+
{
|
|
426
|
+
id: "abc123def456",
|
|
427
|
+
name: "api-server",
|
|
428
|
+
image: "myapp/api:v1.2.3",
|
|
429
|
+
status: "running",
|
|
430
|
+
createdAt: new Date(Date.now() - 7 * 24 * 3600000),
|
|
431
|
+
startedAt: new Date(Date.now() - 2 * 24 * 3600000),
|
|
432
|
+
ports: [
|
|
433
|
+
{ host: 8080, container: 80, protocol: "tcp" },
|
|
434
|
+
{ host: 8443, container: 443, protocol: "tcp" },
|
|
435
|
+
],
|
|
436
|
+
resources: {
|
|
437
|
+
cpu: 23.5,
|
|
438
|
+
memory: 256 * 1024 * 1024,
|
|
439
|
+
memoryLimit: 512 * 1024 * 1024,
|
|
440
|
+
networkRx: 1024 * 1024 * 50,
|
|
441
|
+
networkTx: 1024 * 1024 * 25,
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: "def456ghi789",
|
|
446
|
+
name: "postgres-db",
|
|
447
|
+
image: "postgres:15-alpine",
|
|
448
|
+
status: "running",
|
|
449
|
+
createdAt: new Date(Date.now() - 30 * 24 * 3600000),
|
|
450
|
+
startedAt: new Date(Date.now() - 15 * 24 * 3600000),
|
|
451
|
+
ports: [{ host: 5432, container: 5432, protocol: "tcp" }],
|
|
452
|
+
resources: {
|
|
453
|
+
cpu: 5.2,
|
|
454
|
+
memory: 384 * 1024 * 1024,
|
|
455
|
+
memoryLimit: 1024 * 1024 * 1024,
|
|
456
|
+
networkRx: 1024 * 1024 * 10,
|
|
457
|
+
networkTx: 1024 * 1024 * 5,
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: "ghi789jkl012",
|
|
462
|
+
name: "redis-cache",
|
|
463
|
+
image: "redis:7-alpine",
|
|
464
|
+
status: "stopped",
|
|
465
|
+
createdAt: new Date(Date.now() - 14 * 24 * 3600000),
|
|
466
|
+
exitCode: 0,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
id: "jkl012mno345",
|
|
470
|
+
name: "worker-1",
|
|
471
|
+
image: "myapp/worker:v1.2.3",
|
|
472
|
+
status: "restarting",
|
|
473
|
+
createdAt: new Date(Date.now() - 3 * 24 * 3600000),
|
|
474
|
+
},
|
|
475
|
+
]
|