@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,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
+ ]