@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,638 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../../components/badge"
6
+ import { Button } from "../../components/button"
7
+ import { Card, CardContent, CardHeader, CardTitle } from "../../components/card"
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from "../../components/tooltip"
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from "../../components/select"
21
+ import {
22
+ Globe,
23
+ Server,
24
+ Database,
25
+ Cloud,
26
+ Shield,
27
+ Activity,
28
+ AlertTriangle,
29
+ CheckCircle2,
30
+ Layers,
31
+ ZoomIn,
32
+ ZoomOut,
33
+ Maximize2,
34
+ RefreshCw,
35
+ Filter,
36
+ HardDrive,
37
+ Cpu,
38
+ Network,
39
+ Lock,
40
+ Box,
41
+ Container,
42
+ } from "lucide-react"
43
+
44
+ export type ResourceType = "region" | "vpc" | "subnet" | "server" | "database" | "loadbalancer" | "cache" | "storage" | "container" | "function" | "cdn" | "firewall"
45
+ export type ResourceStatus = "healthy" | "warning" | "critical" | "unknown" | "maintenance"
46
+ export type CloudProvider = "aws" | "gcp" | "azure" | "custom"
47
+
48
+ export interface InfraResource {
49
+ id: string
50
+ name: string
51
+ type: ResourceType
52
+ status: ResourceStatus
53
+ provider?: CloudProvider
54
+ region?: string
55
+ metadata?: Record<string, string | number>
56
+ children?: InfraResource[]
57
+ connections?: string[] // IDs of connected resources
58
+ metrics?: {
59
+ cpu?: number
60
+ memory?: number
61
+ network?: number
62
+ requests?: number
63
+ latency?: number
64
+ errors?: number
65
+ }
66
+ }
67
+
68
+ export interface InfrastructureMapProps {
69
+ resources: InfraResource[]
70
+ onResourceClick?: (resource: InfraResource) => void
71
+ onRefresh?: () => void
72
+ showConnections?: boolean
73
+ viewMode?: "tree" | "grid" | "topology"
74
+ className?: string
75
+ }
76
+
77
+ const typeConfig: Record<ResourceType, { icon: React.ElementType; color: string; label: string }> = {
78
+ region: { icon: Globe, color: "text-blue-500", label: "Region" },
79
+ vpc: { icon: Network, color: "text-purple-500", label: "VPC" },
80
+ subnet: { icon: Layers, color: "text-indigo-500", label: "Subnet" },
81
+ server: { icon: Server, color: "text-gray-500", label: "Server" },
82
+ database: { icon: Database, color: "text-green-500", label: "Database" },
83
+ loadbalancer: { icon: Activity, color: "text-cyan-500", label: "Load Balancer" },
84
+ cache: { icon: HardDrive, color: "text-red-500", label: "Cache" },
85
+ storage: { icon: HardDrive, color: "text-orange-500", label: "Storage" },
86
+ container: { icon: Container, color: "text-blue-400", label: "Container" },
87
+ function: { icon: Cpu, color: "text-yellow-500", label: "Function" },
88
+ cdn: { icon: Globe, color: "text-teal-500", label: "CDN" },
89
+ firewall: { icon: Shield, color: "text-red-400", label: "Firewall" },
90
+ }
91
+
92
+ const statusConfig: Record<ResourceStatus, { color: string; bgColor: string; label: string }> = {
93
+ healthy: { color: "text-green-500", bgColor: "bg-green-500", label: "Healthy" },
94
+ warning: { color: "text-yellow-500", bgColor: "bg-yellow-500", label: "Warning" },
95
+ critical: { color: "text-red-500", bgColor: "bg-red-500", label: "Critical" },
96
+ unknown: { color: "text-gray-500", bgColor: "bg-gray-500", label: "Unknown" },
97
+ maintenance: { color: "text-blue-500", bgColor: "bg-blue-500", label: "Maintenance" },
98
+ }
99
+
100
+ const providerConfig: Record<CloudProvider, { label: string; color: string }> = {
101
+ aws: { label: "AWS", color: "text-orange-500" },
102
+ gcp: { label: "GCP", color: "text-blue-500" },
103
+ azure: { label: "Azure", color: "text-cyan-500" },
104
+ custom: { label: "Custom", color: "text-gray-500" },
105
+ }
106
+
107
+ function ResourceNode({
108
+ resource,
109
+ depth = 0,
110
+ onClick,
111
+ isExpanded,
112
+ onToggle,
113
+ }: {
114
+ resource: InfraResource
115
+ depth?: number
116
+ onClick?: () => void
117
+ isExpanded?: boolean
118
+ onToggle?: () => void
119
+ }) {
120
+ const typeConf = typeConfig[resource.type]
121
+ const statusConf = statusConfig[resource.status]
122
+ const Icon = typeConf.icon
123
+ const hasChildren = resource.children && resource.children.length > 0
124
+
125
+ return (
126
+ <div className={cn("border-l-2", depth > 0 && "ml-6", statusConf.color.replace("text-", "border-"))}>
127
+ <div
128
+ className={cn(
129
+ "flex items-center gap-3 p-3 cursor-pointer rounded-r-lg transition-all",
130
+ "hover:bg-muted/50",
131
+ resource.status === "critical" && "bg-red-500/5",
132
+ resource.status === "warning" && "bg-yellow-500/5"
133
+ )}
134
+ onClick={onClick}
135
+ >
136
+ {/* Expand toggle */}
137
+ {hasChildren && (
138
+ <button
139
+ className="p-1 hover:bg-muted rounded"
140
+ onClick={(e) => {
141
+ e.stopPropagation()
142
+ onToggle?.()
143
+ }}
144
+ >
145
+ <Layers className={cn("h-4 w-4", isExpanded && "rotate-90")} />
146
+ </button>
147
+ )}
148
+
149
+ {/* Icon */}
150
+ <div className={cn("p-2 rounded-lg bg-muted/50")}>
151
+ <Icon className={cn("h-5 w-5", typeConf.color)} />
152
+ </div>
153
+
154
+ {/* Info */}
155
+ <div className="flex-1 min-w-0">
156
+ <div className="flex items-center gap-2">
157
+ <span className="font-medium truncate">{resource.name}</span>
158
+ <Badge variant="outline" className="text-xs shrink-0">
159
+ {typeConf.label}
160
+ </Badge>
161
+ {resource.provider && (
162
+ <Badge variant="secondary" className={cn("text-xs shrink-0", providerConfig[resource.provider].color)}>
163
+ {providerConfig[resource.provider].label}
164
+ </Badge>
165
+ )}
166
+ </div>
167
+ {resource.region && (
168
+ <div className="text-xs text-muted-foreground mt-0.5">
169
+ {resource.region}
170
+ </div>
171
+ )}
172
+ </div>
173
+
174
+ {/* Metrics preview */}
175
+ {resource.metrics && (
176
+ <div className="flex items-center gap-3 text-xs text-muted-foreground">
177
+ {resource.metrics.cpu !== undefined && (
178
+ <span className={cn(resource.metrics.cpu > 80 && "text-red-500")}>
179
+ CPU: {resource.metrics.cpu}%
180
+ </span>
181
+ )}
182
+ {resource.metrics.memory !== undefined && (
183
+ <span className={cn(resource.metrics.memory > 80 && "text-red-500")}>
184
+ MEM: {resource.metrics.memory}%
185
+ </span>
186
+ )}
187
+ {resource.metrics.latency !== undefined && (
188
+ <span className={cn(resource.metrics.latency > 500 && "text-yellow-500")}>
189
+ {resource.metrics.latency}ms
190
+ </span>
191
+ )}
192
+ </div>
193
+ )}
194
+
195
+ {/* Status indicator */}
196
+ <TooltipProvider>
197
+ <Tooltip>
198
+ <TooltipTrigger>
199
+ <div className={cn("w-3 h-3 rounded-full shrink-0", statusConf.bgColor)} />
200
+ </TooltipTrigger>
201
+ <TooltipContent>{statusConf.label}</TooltipContent>
202
+ </Tooltip>
203
+ </TooltipProvider>
204
+ </div>
205
+ </div>
206
+ )
207
+ }
208
+
209
+ function ResourceTree({
210
+ resources,
211
+ depth = 0,
212
+ expandedIds,
213
+ onToggle,
214
+ onResourceClick,
215
+ }: {
216
+ resources: InfraResource[]
217
+ depth?: number
218
+ expandedIds: Set<string>
219
+ onToggle: (id: string) => void
220
+ onResourceClick?: (resource: InfraResource) => void
221
+ }) {
222
+ return (
223
+ <div className={cn(depth > 0 && "ml-4")}>
224
+ {resources.map((resource) => (
225
+ <React.Fragment key={resource.id}>
226
+ <ResourceNode
227
+ resource={resource}
228
+ depth={depth}
229
+ onClick={() => onResourceClick?.(resource)}
230
+ isExpanded={expandedIds.has(resource.id)}
231
+ onToggle={() => onToggle(resource.id)}
232
+ />
233
+ {expandedIds.has(resource.id) && resource.children && (
234
+ <ResourceTree
235
+ resources={resource.children}
236
+ depth={depth + 1}
237
+ expandedIds={expandedIds}
238
+ onToggle={onToggle}
239
+ onResourceClick={onResourceClick}
240
+ />
241
+ )}
242
+ </React.Fragment>
243
+ ))}
244
+ </div>
245
+ )
246
+ }
247
+
248
+ function ResourceGrid({
249
+ resources,
250
+ onResourceClick,
251
+ }: {
252
+ resources: InfraResource[]
253
+ onResourceClick?: (resource: InfraResource) => void
254
+ }) {
255
+ // Flatten resources for grid view
256
+ const flattenResources = (items: InfraResource[]): InfraResource[] => {
257
+ return items.reduce<InfraResource[]>((acc, item) => {
258
+ acc.push(item)
259
+ if (item.children) {
260
+ acc.push(...flattenResources(item.children))
261
+ }
262
+ return acc
263
+ }, [])
264
+ }
265
+
266
+ const allResources = flattenResources(resources)
267
+
268
+ return (
269
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
270
+ {allResources.map((resource) => {
271
+ const typeConf = typeConfig[resource.type]
272
+ const statusConf = statusConfig[resource.status]
273
+ const Icon = typeConf.icon
274
+
275
+ return (
276
+ <Card
277
+ key={resource.id}
278
+ className={cn(
279
+ "cursor-pointer transition-all hover:shadow-md",
280
+ resource.status === "critical" && "border-red-500/50",
281
+ resource.status === "warning" && "border-yellow-500/50"
282
+ )}
283
+ onClick={() => onResourceClick?.(resource)}
284
+ >
285
+ <CardContent className="p-4">
286
+ <div className="flex items-start justify-between">
287
+ <div className={cn("p-2 rounded-lg", `${typeConf.color.replace("text-", "bg-")}/10`)}>
288
+ <Icon className={cn("h-5 w-5", typeConf.color)} />
289
+ </div>
290
+ <div className={cn("w-2 h-2 rounded-full", statusConf.bgColor)} />
291
+ </div>
292
+ <div className="mt-3">
293
+ <div className="font-medium truncate">{resource.name}</div>
294
+ <div className="text-xs text-muted-foreground">{typeConf.label}</div>
295
+ </div>
296
+ {resource.metrics && (
297
+ <div className="flex items-center gap-2 mt-2 text-xs">
298
+ {resource.metrics.cpu !== undefined && (
299
+ <span className={cn(resource.metrics.cpu > 80 && "text-red-500")}>
300
+ {resource.metrics.cpu}%
301
+ </span>
302
+ )}
303
+ </div>
304
+ )}
305
+ </CardContent>
306
+ </Card>
307
+ )
308
+ })}
309
+ </div>
310
+ )
311
+ }
312
+
313
+ function InfraStats({ resources }: { resources: InfraResource[] }) {
314
+ const stats = React.useMemo(() => {
315
+ let total = 0
316
+ let healthy = 0
317
+ let warning = 0
318
+ let critical = 0
319
+
320
+ const count = (items: InfraResource[]) => {
321
+ items.forEach((item) => {
322
+ total++
323
+ if (item.status === "healthy") healthy++
324
+ if (item.status === "warning") warning++
325
+ if (item.status === "critical") critical++
326
+ if (item.children) count(item.children)
327
+ })
328
+ }
329
+ count(resources)
330
+
331
+ return { total, healthy, warning, critical }
332
+ }, [resources])
333
+
334
+ return (
335
+ <div className="flex items-center gap-6">
336
+ <div className="flex items-center gap-2">
337
+ <Box className="h-4 w-4 text-muted-foreground" />
338
+ <span className="font-medium">{stats.total}</span>
339
+ <span className="text-muted-foreground text-sm">Resources</span>
340
+ </div>
341
+ <div className="flex items-center gap-2">
342
+ <CheckCircle2 className="h-4 w-4 text-green-500" />
343
+ <span className="font-medium text-green-500">{stats.healthy}</span>
344
+ </div>
345
+ <div className="flex items-center gap-2">
346
+ <AlertTriangle className="h-4 w-4 text-yellow-500" />
347
+ <span className="font-medium text-yellow-500">{stats.warning}</span>
348
+ </div>
349
+ <div className="flex items-center gap-2">
350
+ <AlertTriangle className="h-4 w-4 text-red-500" />
351
+ <span className="font-medium text-red-500">{stats.critical}</span>
352
+ </div>
353
+ </div>
354
+ )
355
+ }
356
+
357
+ export function InfrastructureMap({
358
+ resources,
359
+ onResourceClick,
360
+ onRefresh,
361
+ showConnections = false,
362
+ viewMode: initialViewMode = "tree",
363
+ className,
364
+ }: InfrastructureMapProps) {
365
+ const [viewMode, setViewMode] = React.useState(initialViewMode)
366
+ const [expandedIds, setExpandedIds] = React.useState<Set<string>>(() => {
367
+ // Expand first level by default
368
+ return new Set(resources.map((r) => r.id))
369
+ })
370
+ const [statusFilter, setStatusFilter] = React.useState<ResourceStatus | "all">("all")
371
+
372
+ const toggleExpand = (id: string) => {
373
+ setExpandedIds((prev) => {
374
+ const next = new Set(prev)
375
+ if (next.has(id)) {
376
+ next.delete(id)
377
+ } else {
378
+ next.add(id)
379
+ }
380
+ return next
381
+ })
382
+ }
383
+
384
+ const expandAll = () => {
385
+ const ids = new Set<string>()
386
+ const collectIds = (items: InfraResource[]) => {
387
+ items.forEach((item) => {
388
+ ids.add(item.id)
389
+ if (item.children) collectIds(item.children)
390
+ })
391
+ }
392
+ collectIds(resources)
393
+ setExpandedIds(ids)
394
+ }
395
+
396
+ const collapseAll = () => {
397
+ setExpandedIds(new Set())
398
+ }
399
+
400
+ // Filter resources by status
401
+ const filteredResources = React.useMemo(() => {
402
+ if (statusFilter === "all") return resources
403
+
404
+ const filterByStatus = (items: InfraResource[]): InfraResource[] => {
405
+ return items
406
+ .filter((item) => {
407
+ const hasMatchingChild = item.children?.some((c) => {
408
+ const filtered = filterByStatus([c])
409
+ return filtered.length > 0
410
+ })
411
+ return item.status === statusFilter || hasMatchingChild
412
+ })
413
+ .map((item) => ({
414
+ ...item,
415
+ children: item.children ? filterByStatus(item.children) : undefined,
416
+ }))
417
+ }
418
+
419
+ return filterByStatus(resources)
420
+ }, [resources, statusFilter])
421
+
422
+ return (
423
+ <Card className={className}>
424
+ <CardHeader className="pb-3">
425
+ <div className="flex items-center justify-between">
426
+ <div className="flex items-center gap-3">
427
+ <Cloud className="h-5 w-5" />
428
+ <CardTitle>Infrastructure Map</CardTitle>
429
+ </div>
430
+ <InfraStats resources={resources} />
431
+ </div>
432
+ </CardHeader>
433
+
434
+ <CardContent className="space-y-4">
435
+ {/* Toolbar */}
436
+ <div className="flex items-center justify-between gap-3">
437
+ <div className="flex items-center gap-2">
438
+ <Select value={viewMode} onValueChange={(v) => setViewMode(v as "tree" | "grid")}>
439
+ <SelectTrigger className="w-28">
440
+ <SelectValue />
441
+ </SelectTrigger>
442
+ <SelectContent>
443
+ <SelectItem value="tree">Tree View</SelectItem>
444
+ <SelectItem value="grid">Grid View</SelectItem>
445
+ </SelectContent>
446
+ </Select>
447
+
448
+ <Select value={statusFilter} onValueChange={(v) => setStatusFilter(v as ResourceStatus | "all")}>
449
+ <SelectTrigger className="w-32">
450
+ <Filter className="h-4 w-4 mr-2" />
451
+ <SelectValue placeholder="Status" />
452
+ </SelectTrigger>
453
+ <SelectContent>
454
+ <SelectItem value="all">All Status</SelectItem>
455
+ <SelectItem value="healthy">Healthy</SelectItem>
456
+ <SelectItem value="warning">Warning</SelectItem>
457
+ <SelectItem value="critical">Critical</SelectItem>
458
+ <SelectItem value="maintenance">Maintenance</SelectItem>
459
+ </SelectContent>
460
+ </Select>
461
+ </div>
462
+
463
+ <div className="flex items-center gap-1">
464
+ {viewMode === "tree" && (
465
+ <>
466
+ <Button variant="ghost" size="sm" onClick={expandAll}>
467
+ <Maximize2 className="h-4 w-4 mr-1" />
468
+ Expand
469
+ </Button>
470
+ <Button variant="ghost" size="sm" onClick={collapseAll}>
471
+ Collapse
472
+ </Button>
473
+ </>
474
+ )}
475
+ {onRefresh && (
476
+ <Button variant="ghost" size="sm" onClick={onRefresh}>
477
+ <RefreshCw className="h-4 w-4" />
478
+ </Button>
479
+ )}
480
+ </div>
481
+ </div>
482
+
483
+ {/* Content */}
484
+ <div className="min-h-[400px]">
485
+ {filteredResources.length === 0 ? (
486
+ <div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
487
+ <Cloud className="h-12 w-12 mb-4" />
488
+ <p>No resources found</p>
489
+ </div>
490
+ ) : viewMode === "tree" ? (
491
+ <ResourceTree
492
+ resources={filteredResources}
493
+ expandedIds={expandedIds}
494
+ onToggle={toggleExpand}
495
+ onResourceClick={onResourceClick}
496
+ />
497
+ ) : (
498
+ <ResourceGrid
499
+ resources={filteredResources}
500
+ onResourceClick={onResourceClick}
501
+ />
502
+ )}
503
+ </div>
504
+ </CardContent>
505
+ </Card>
506
+ )
507
+ }
508
+
509
+ // Default sample data
510
+ export const defaultInfraResources: InfraResource[] = [
511
+ {
512
+ id: "region-us-east-1",
513
+ name: "us-east-1",
514
+ type: "region",
515
+ status: "healthy",
516
+ provider: "aws",
517
+ region: "N. Virginia",
518
+ children: [
519
+ {
520
+ id: "vpc-prod",
521
+ name: "production-vpc",
522
+ type: "vpc",
523
+ status: "healthy",
524
+ provider: "aws",
525
+ metadata: { cidr: "10.0.0.0/16" },
526
+ children: [
527
+ {
528
+ id: "subnet-public-1",
529
+ name: "public-subnet-1a",
530
+ type: "subnet",
531
+ status: "healthy",
532
+ metadata: { az: "us-east-1a", cidr: "10.0.1.0/24" },
533
+ children: [
534
+ {
535
+ id: "alb-1",
536
+ name: "api-load-balancer",
537
+ type: "loadbalancer",
538
+ status: "healthy",
539
+ metrics: { requests: 15000, latency: 45 },
540
+ },
541
+ {
542
+ id: "nat-1",
543
+ name: "nat-gateway-1a",
544
+ type: "firewall",
545
+ status: "healthy",
546
+ },
547
+ ],
548
+ },
549
+ {
550
+ id: "subnet-private-1",
551
+ name: "private-subnet-1a",
552
+ type: "subnet",
553
+ status: "warning",
554
+ metadata: { az: "us-east-1a", cidr: "10.0.10.0/24" },
555
+ children: [
556
+ {
557
+ id: "ec2-api-1",
558
+ name: "api-server-1",
559
+ type: "server",
560
+ status: "healthy",
561
+ metrics: { cpu: 45, memory: 62 },
562
+ },
563
+ {
564
+ id: "ec2-api-2",
565
+ name: "api-server-2",
566
+ type: "server",
567
+ status: "warning",
568
+ metrics: { cpu: 85, memory: 78 },
569
+ },
570
+ {
571
+ id: "ec2-api-3",
572
+ name: "api-server-3",
573
+ type: "server",
574
+ status: "healthy",
575
+ metrics: { cpu: 52, memory: 55 },
576
+ },
577
+ ],
578
+ },
579
+ {
580
+ id: "subnet-data-1",
581
+ name: "data-subnet-1a",
582
+ type: "subnet",
583
+ status: "healthy",
584
+ metadata: { az: "us-east-1a", cidr: "10.0.20.0/24" },
585
+ children: [
586
+ {
587
+ id: "rds-primary",
588
+ name: "postgres-primary",
589
+ type: "database",
590
+ status: "healthy",
591
+ metrics: { cpu: 35, memory: 68 },
592
+ },
593
+ {
594
+ id: "redis-1",
595
+ name: "redis-cluster",
596
+ type: "cache",
597
+ status: "healthy",
598
+ metrics: { memory: 45 },
599
+ },
600
+ ],
601
+ },
602
+ ],
603
+ },
604
+ ],
605
+ },
606
+ {
607
+ id: "region-eu-west-1",
608
+ name: "eu-west-1",
609
+ type: "region",
610
+ status: "critical",
611
+ provider: "aws",
612
+ region: "Ireland",
613
+ children: [
614
+ {
615
+ id: "vpc-prod-eu",
616
+ name: "production-vpc-eu",
617
+ type: "vpc",
618
+ status: "critical",
619
+ children: [
620
+ {
621
+ id: "ec2-eu-1",
622
+ name: "api-server-eu-1",
623
+ type: "server",
624
+ status: "critical",
625
+ metrics: { cpu: 98, memory: 95 },
626
+ },
627
+ {
628
+ id: "rds-eu",
629
+ name: "postgres-replica",
630
+ type: "database",
631
+ status: "warning",
632
+ metrics: { cpu: 75, memory: 82 },
633
+ },
634
+ ],
635
+ },
636
+ ],
637
+ },
638
+ ]