@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,597 @@
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 { Progress } from "../../components/progress"
9
+ import { ScrollArea } from "../../components/scroll-area"
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../components/select"
17
+ import {
18
+ DollarSign,
19
+ TrendingUp,
20
+ TrendingDown,
21
+ Cloud,
22
+ Server,
23
+ Database,
24
+ HardDrive,
25
+ Network,
26
+ Cpu,
27
+ AlertTriangle,
28
+ ArrowUpRight,
29
+ ArrowDownRight,
30
+ BarChart3,
31
+ PieChart,
32
+ Calendar,
33
+ Download,
34
+ Filter,
35
+ RefreshCw,
36
+ Zap,
37
+ Globe,
38
+ Container,
39
+ Shield,
40
+ } from "lucide-react"
41
+
42
+ export type CloudProvider = "aws" | "gcp" | "azure" | "all"
43
+ export type CostCategory = "compute" | "storage" | "database" | "network" | "security" | "other"
44
+ export type TimeRange = "day" | "week" | "month" | "quarter" | "year"
45
+
46
+ export interface CostItem {
47
+ id: string
48
+ name: string
49
+ category: CostCategory
50
+ provider: CloudProvider
51
+ cost: number
52
+ previousCost?: number
53
+ budget?: number
54
+ usage?: number
55
+ unit?: string
56
+ trend?: number
57
+ tags?: string[]
58
+ }
59
+
60
+ export interface CostSummary {
61
+ totalCost: number
62
+ previousTotalCost?: number
63
+ budget?: number
64
+ forecast?: number
65
+ byProvider: Record<CloudProvider, number>
66
+ byCategory: Record<CostCategory, number>
67
+ topServices: CostItem[]
68
+ }
69
+
70
+ export interface CostAnomaly {
71
+ id: string
72
+ service: string
73
+ expectedCost: number
74
+ actualCost: number
75
+ deviation: number
76
+ timestamp: Date
77
+ status: "new" | "acknowledged" | "resolved"
78
+ }
79
+
80
+ export interface CloudCostDashboardProps {
81
+ summary: CostSummary
82
+ items: CostItem[]
83
+ anomalies?: CostAnomaly[]
84
+ timeRange?: TimeRange
85
+ onTimeRangeChange?: (range: TimeRange) => void
86
+ onProviderFilter?: (provider: CloudProvider) => void
87
+ onExport?: () => void
88
+ onRefresh?: () => void
89
+ className?: string
90
+ }
91
+
92
+ const categoryConfig: Record<CostCategory, { icon: React.ElementType; color: string; label: string }> = {
93
+ compute: { icon: Cpu, color: "text-blue-500", label: "Compute" },
94
+ storage: { icon: HardDrive, color: "text-green-500", label: "Storage" },
95
+ database: { icon: Database, color: "text-purple-500", label: "Database" },
96
+ network: { icon: Network, color: "text-cyan-500", label: "Network" },
97
+ security: { icon: Shield, color: "text-red-500", label: "Security" },
98
+ other: { icon: Cloud, color: "text-gray-500", label: "Other" },
99
+ }
100
+
101
+ const providerConfig: Record<CloudProvider, { label: string; color: string }> = {
102
+ aws: { label: "AWS", color: "text-orange-500" },
103
+ gcp: { label: "GCP", color: "text-blue-500" },
104
+ azure: { label: "Azure", color: "text-cyan-500" },
105
+ all: { label: "All Providers", color: "text-gray-500" },
106
+ }
107
+
108
+ function formatCurrency(amount: number): string {
109
+ return new Intl.NumberFormat("en-US", {
110
+ style: "currency",
111
+ currency: "USD",
112
+ minimumFractionDigits: 0,
113
+ maximumFractionDigits: 0,
114
+ }).format(amount)
115
+ }
116
+
117
+ function formatPercentage(value: number): string {
118
+ return `${value >= 0 ? "+" : ""}${value.toFixed(1)}%`
119
+ }
120
+
121
+ function CostMetricCard({
122
+ title,
123
+ value,
124
+ previousValue,
125
+ icon: Icon,
126
+ trend,
127
+ budget,
128
+ forecast,
129
+ }: {
130
+ title: string
131
+ value: number
132
+ previousValue?: number
133
+ icon: React.ElementType
134
+ trend?: number
135
+ budget?: number
136
+ forecast?: number
137
+ }) {
138
+ const trendValue = previousValue !== undefined
139
+ ? ((value - previousValue) / previousValue) * 100
140
+ : trend
141
+
142
+ const isOverBudget = budget !== undefined && value > budget
143
+ const budgetPercentage = budget !== undefined ? (value / budget) * 100 : undefined
144
+
145
+ return (
146
+ <Card className={cn(isOverBudget && "border-red-500/50")}>
147
+ <CardContent className="p-4">
148
+ <div className="flex items-start justify-between">
149
+ <div className="p-2 rounded-lg bg-muted">
150
+ <Icon className="h-5 w-5 text-muted-foreground" />
151
+ </div>
152
+ {trendValue !== undefined && (
153
+ <Badge
154
+ variant="outline"
155
+ className={cn(
156
+ trendValue > 0 ? "text-red-500" : "text-green-500"
157
+ )}
158
+ >
159
+ {trendValue > 0 ? (
160
+ <ArrowUpRight className="h-3 w-3 mr-1" />
161
+ ) : (
162
+ <ArrowDownRight className="h-3 w-3 mr-1" />
163
+ )}
164
+ {formatPercentage(Math.abs(trendValue))}
165
+ </Badge>
166
+ )}
167
+ </div>
168
+
169
+ <div className="mt-3">
170
+ <div className="text-2xl font-bold">{formatCurrency(value)}</div>
171
+ <div className="text-sm text-muted-foreground">{title}</div>
172
+ </div>
173
+
174
+ {budgetPercentage !== undefined && (
175
+ <div className="mt-3">
176
+ <div className="flex items-center justify-between text-xs mb-1">
177
+ <span className="text-muted-foreground">Budget</span>
178
+ <span className={cn(isOverBudget && "text-red-500")}>
179
+ {formatCurrency(value)} / {formatCurrency(budget!)}
180
+ </span>
181
+ </div>
182
+ <Progress
183
+ value={Math.min(budgetPercentage, 100)}
184
+ className={cn(
185
+ "h-2",
186
+ budgetPercentage > 100 && "[&>div]:bg-red-500",
187
+ budgetPercentage > 80 && budgetPercentage <= 100 && "[&>div]:bg-yellow-500"
188
+ )}
189
+ />
190
+ </div>
191
+ )}
192
+
193
+ {forecast !== undefined && (
194
+ <div className="mt-2 text-xs text-muted-foreground">
195
+ Forecast: {formatCurrency(forecast)}
196
+ </div>
197
+ )}
198
+ </CardContent>
199
+ </Card>
200
+ )
201
+ }
202
+
203
+ function CategoryBreakdown({ data }: { data: Record<CostCategory, number> }) {
204
+ const total = Object.values(data).reduce((a, b) => a + b, 0)
205
+ const sorted = Object.entries(data)
206
+ .sort(([, a], [, b]) => b - a)
207
+ .filter(([, value]) => value > 0) as [CostCategory, number][]
208
+
209
+ return (
210
+ <Card>
211
+ <CardHeader className="pb-2">
212
+ <CardTitle className="text-base flex items-center gap-2">
213
+ <PieChart className="h-4 w-4" />
214
+ Cost by Category
215
+ </CardTitle>
216
+ </CardHeader>
217
+ <CardContent className="space-y-3">
218
+ {sorted.map(([category, value]) => {
219
+ const config = categoryConfig[category]
220
+ const Icon = config.icon
221
+ const percentage = (value / total) * 100
222
+
223
+ return (
224
+ <div key={category}>
225
+ <div className="flex items-center justify-between mb-1">
226
+ <div className="flex items-center gap-2">
227
+ <Icon className={cn("h-4 w-4", config.color)} />
228
+ <span className="text-sm">{config.label}</span>
229
+ </div>
230
+ <div className="text-sm font-medium">
231
+ {formatCurrency(value)}
232
+ <span className="text-muted-foreground ml-1">
233
+ ({percentage.toFixed(0)}%)
234
+ </span>
235
+ </div>
236
+ </div>
237
+ <Progress value={percentage} className="h-2" />
238
+ </div>
239
+ )
240
+ })}
241
+ </CardContent>
242
+ </Card>
243
+ )
244
+ }
245
+
246
+ function ProviderBreakdown({ data }: { data: Record<CloudProvider, number> }) {
247
+ const total = Object.values(data).reduce((a, b) => a + b, 0)
248
+ const sorted = Object.entries(data)
249
+ .filter(([key]) => key !== "all")
250
+ .sort(([, a], [, b]) => b - a)
251
+ .filter(([, value]) => value > 0) as [CloudProvider, number][]
252
+
253
+ return (
254
+ <Card>
255
+ <CardHeader className="pb-2">
256
+ <CardTitle className="text-base flex items-center gap-2">
257
+ <Cloud className="h-4 w-4" />
258
+ Cost by Provider
259
+ </CardTitle>
260
+ </CardHeader>
261
+ <CardContent className="space-y-3">
262
+ {sorted.map(([provider, value]) => {
263
+ const config = providerConfig[provider]
264
+ const percentage = (value / total) * 100
265
+
266
+ return (
267
+ <div key={provider}>
268
+ <div className="flex items-center justify-between mb-1">
269
+ <span className={cn("text-sm font-medium", config.color)}>
270
+ {config.label}
271
+ </span>
272
+ <div className="text-sm font-medium">
273
+ {formatCurrency(value)}
274
+ <span className="text-muted-foreground ml-1">
275
+ ({percentage.toFixed(0)}%)
276
+ </span>
277
+ </div>
278
+ </div>
279
+ <Progress value={percentage} className="h-2" />
280
+ </div>
281
+ )
282
+ })}
283
+ </CardContent>
284
+ </Card>
285
+ )
286
+ }
287
+
288
+ function TopServicesTable({ items }: { items: CostItem[] }) {
289
+ return (
290
+ <Card>
291
+ <CardHeader className="pb-2">
292
+ <CardTitle className="text-base flex items-center gap-2">
293
+ <BarChart3 className="h-4 w-4" />
294
+ Top Services by Cost
295
+ </CardTitle>
296
+ </CardHeader>
297
+ <CardContent>
298
+ <ScrollArea className="h-[300px]">
299
+ <div className="space-y-2">
300
+ {items.map((item, index) => {
301
+ const categoryConf = categoryConfig[item.category]
302
+ const CategoryIcon = categoryConf.icon
303
+ const providerConf = providerConfig[item.provider]
304
+ const trendValue = item.previousCost !== undefined
305
+ ? ((item.cost - item.previousCost) / item.previousCost) * 100
306
+ : item.trend
307
+
308
+ return (
309
+ <div
310
+ key={item.id}
311
+ className="flex items-center gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors"
312
+ >
313
+ <div className="text-lg font-bold text-muted-foreground w-6 text-center">
314
+ {index + 1}
315
+ </div>
316
+
317
+ <div className={cn("p-1.5 rounded", `${categoryConf.color.replace("text-", "bg-")}/10`)}>
318
+ <CategoryIcon className={cn("h-4 w-4", categoryConf.color)} />
319
+ </div>
320
+
321
+ <div className="flex-1 min-w-0">
322
+ <div className="font-medium truncate">{item.name}</div>
323
+ <div className="text-xs text-muted-foreground flex items-center gap-2">
324
+ <span className={providerConf.color}>{providerConf.label}</span>
325
+ <span>•</span>
326
+ <span>{categoryConf.label}</span>
327
+ {item.usage !== undefined && item.unit && (
328
+ <>
329
+ <span>•</span>
330
+ <span>{item.usage} {item.unit}</span>
331
+ </>
332
+ )}
333
+ </div>
334
+ </div>
335
+
336
+ <div className="text-right shrink-0">
337
+ <div className="font-semibold">{formatCurrency(item.cost)}</div>
338
+ {trendValue !== undefined && (
339
+ <div className={cn(
340
+ "text-xs flex items-center justify-end gap-0.5",
341
+ trendValue > 0 ? "text-red-500" : "text-green-500"
342
+ )}>
343
+ {trendValue > 0 ? (
344
+ <TrendingUp className="h-3 w-3" />
345
+ ) : (
346
+ <TrendingDown className="h-3 w-3" />
347
+ )}
348
+ {formatPercentage(Math.abs(trendValue))}
349
+ </div>
350
+ )}
351
+ </div>
352
+ </div>
353
+ )
354
+ })}
355
+ </div>
356
+ </ScrollArea>
357
+ </CardContent>
358
+ </Card>
359
+ )
360
+ }
361
+
362
+ function AnomaliesCard({
363
+ anomalies,
364
+ onAcknowledge,
365
+ }: {
366
+ anomalies: CostAnomaly[]
367
+ onAcknowledge?: (id: string) => void
368
+ }) {
369
+ const newAnomalies = anomalies.filter((a) => a.status === "new")
370
+
371
+ return (
372
+ <Card className={cn(newAnomalies.length > 0 && "border-yellow-500/50")}>
373
+ <CardHeader className="pb-2">
374
+ <div className="flex items-center justify-between">
375
+ <CardTitle className="text-base flex items-center gap-2">
376
+ <AlertTriangle className="h-4 w-4 text-yellow-500" />
377
+ Cost Anomalies
378
+ </CardTitle>
379
+ {newAnomalies.length > 0 && (
380
+ <Badge className="bg-yellow-500">{newAnomalies.length} new</Badge>
381
+ )}
382
+ </div>
383
+ </CardHeader>
384
+ <CardContent>
385
+ {anomalies.length === 0 ? (
386
+ <div className="text-center text-muted-foreground py-4">
387
+ <Zap className="h-8 w-8 mx-auto mb-2 text-green-500" />
388
+ <p>No anomalies detected</p>
389
+ </div>
390
+ ) : (
391
+ <div className="space-y-2">
392
+ {anomalies.slice(0, 5).map((anomaly) => (
393
+ <div
394
+ key={anomaly.id}
395
+ className={cn(
396
+ "flex items-center gap-3 p-2 rounded-lg",
397
+ anomaly.status === "new" && "bg-yellow-500/10"
398
+ )}
399
+ >
400
+ <AlertTriangle className={cn(
401
+ "h-4 w-4 shrink-0",
402
+ anomaly.deviation > 100 ? "text-red-500" : "text-yellow-500"
403
+ )} />
404
+
405
+ <div className="flex-1 min-w-0">
406
+ <div className="font-medium truncate">{anomaly.service}</div>
407
+ <div className="text-xs text-muted-foreground">
408
+ Expected {formatCurrency(anomaly.expectedCost)} • Actual {formatCurrency(anomaly.actualCost)}
409
+ </div>
410
+ </div>
411
+
412
+ <Badge variant="outline" className="text-red-500 shrink-0">
413
+ +{anomaly.deviation.toFixed(0)}%
414
+ </Badge>
415
+ </div>
416
+ ))}
417
+ </div>
418
+ )}
419
+ </CardContent>
420
+ </Card>
421
+ )
422
+ }
423
+
424
+ export function CloudCostDashboard({
425
+ summary,
426
+ items,
427
+ anomalies = [],
428
+ timeRange = "month",
429
+ onTimeRangeChange,
430
+ onProviderFilter,
431
+ onExport,
432
+ onRefresh,
433
+ className,
434
+ }: CloudCostDashboardProps) {
435
+ const [selectedProvider, setSelectedProvider] = React.useState<CloudProvider>("all")
436
+
437
+ const handleProviderChange = (provider: CloudProvider) => {
438
+ setSelectedProvider(provider)
439
+ onProviderFilter?.(provider)
440
+ }
441
+
442
+ const filteredItems = React.useMemo(() => {
443
+ if (selectedProvider === "all") return items
444
+ return items.filter((item) => item.provider === selectedProvider)
445
+ }, [items, selectedProvider])
446
+
447
+ const trendValue = summary.previousTotalCost !== undefined
448
+ ? ((summary.totalCost - summary.previousTotalCost) / summary.previousTotalCost) * 100
449
+ : undefined
450
+
451
+ return (
452
+ <div className={cn("space-y-6", className)}>
453
+ {/* Header */}
454
+ <Card>
455
+ <CardHeader>
456
+ <div className="flex items-center justify-between">
457
+ <div className="flex items-center gap-3">
458
+ <DollarSign className="h-6 w-6" />
459
+ <div>
460
+ <CardTitle>Cloud Cost Dashboard</CardTitle>
461
+ <p className="text-sm text-muted-foreground">
462
+ Monitor and optimize your cloud spending
463
+ </p>
464
+ </div>
465
+ </div>
466
+
467
+ <div className="flex items-center gap-2">
468
+ <Select value={timeRange} onValueChange={(v) => onTimeRangeChange?.(v as TimeRange)}>
469
+ <SelectTrigger className="w-32">
470
+ <Calendar className="h-4 w-4 mr-2" />
471
+ <SelectValue />
472
+ </SelectTrigger>
473
+ <SelectContent>
474
+ <SelectItem value="day">Today</SelectItem>
475
+ <SelectItem value="week">This Week</SelectItem>
476
+ <SelectItem value="month">This Month</SelectItem>
477
+ <SelectItem value="quarter">This Quarter</SelectItem>
478
+ <SelectItem value="year">This Year</SelectItem>
479
+ </SelectContent>
480
+ </Select>
481
+
482
+ <Select value={selectedProvider} onValueChange={(v) => handleProviderChange(v as CloudProvider)}>
483
+ <SelectTrigger className="w-32">
484
+ <Cloud className="h-4 w-4 mr-2" />
485
+ <SelectValue />
486
+ </SelectTrigger>
487
+ <SelectContent>
488
+ <SelectItem value="all">All Providers</SelectItem>
489
+ <SelectItem value="aws">AWS</SelectItem>
490
+ <SelectItem value="gcp">GCP</SelectItem>
491
+ <SelectItem value="azure">Azure</SelectItem>
492
+ </SelectContent>
493
+ </Select>
494
+
495
+ {onRefresh && (
496
+ <Button variant="outline" size="icon" onClick={onRefresh}>
497
+ <RefreshCw className="h-4 w-4" />
498
+ </Button>
499
+ )}
500
+
501
+ {onExport && (
502
+ <Button variant="outline" onClick={onExport}>
503
+ <Download className="h-4 w-4 mr-1" />
504
+ Export
505
+ </Button>
506
+ )}
507
+ </div>
508
+ </div>
509
+ </CardHeader>
510
+ </Card>
511
+
512
+ {/* Key metrics */}
513
+ <div className="grid grid-cols-4 gap-4">
514
+ <CostMetricCard
515
+ title="Total Cost"
516
+ value={summary.totalCost}
517
+ previousValue={summary.previousTotalCost}
518
+ icon={DollarSign}
519
+ budget={summary.budget}
520
+ />
521
+ <CostMetricCard
522
+ title="Compute"
523
+ value={summary.byCategory.compute}
524
+ icon={Cpu}
525
+ trend={-5.2}
526
+ />
527
+ <CostMetricCard
528
+ title="Storage"
529
+ value={summary.byCategory.storage}
530
+ icon={HardDrive}
531
+ trend={12.3}
532
+ />
533
+ <CostMetricCard
534
+ title="Forecast (EOM)"
535
+ value={summary.forecast || summary.totalCost * 1.1}
536
+ icon={TrendingUp}
537
+ budget={summary.budget}
538
+ />
539
+ </div>
540
+
541
+ {/* Main content */}
542
+ <div className="grid grid-cols-3 gap-6">
543
+ <div className="col-span-2 space-y-6">
544
+ <TopServicesTable items={filteredItems.slice(0, 10)} />
545
+ </div>
546
+
547
+ <div className="space-y-6">
548
+ <CategoryBreakdown data={summary.byCategory} />
549
+ <ProviderBreakdown data={summary.byProvider} />
550
+ <AnomaliesCard anomalies={anomalies} />
551
+ </div>
552
+ </div>
553
+ </div>
554
+ )
555
+ }
556
+
557
+ // Default sample data
558
+ export const defaultCloudCostSummary: CostSummary = {
559
+ totalCost: 45230,
560
+ previousTotalCost: 42100,
561
+ budget: 50000,
562
+ forecast: 48500,
563
+ byProvider: {
564
+ aws: 28500,
565
+ gcp: 12300,
566
+ azure: 4430,
567
+ all: 45230,
568
+ },
569
+ byCategory: {
570
+ compute: 22000,
571
+ storage: 8500,
572
+ database: 7200,
573
+ network: 4100,
574
+ security: 1800,
575
+ other: 1630,
576
+ },
577
+ topServices: [],
578
+ }
579
+
580
+ export const defaultCloudCostItems: CostItem[] = [
581
+ { id: "1", name: "EC2 Instances", category: "compute", provider: "aws", cost: 12500, previousCost: 11800, usage: 450, unit: "hours" },
582
+ { id: "2", name: "RDS PostgreSQL", category: "database", provider: "aws", cost: 4200, previousCost: 4000 },
583
+ { id: "3", name: "S3 Storage", category: "storage", provider: "aws", cost: 3800, previousCost: 3200, usage: 12, unit: "TB" },
584
+ { id: "4", name: "GKE Clusters", category: "compute", provider: "gcp", cost: 6500, previousCost: 6800 },
585
+ { id: "5", name: "Cloud SQL", category: "database", provider: "gcp", cost: 2800, previousCost: 2600 },
586
+ { id: "6", name: "BigQuery", category: "database", provider: "gcp", cost: 1800, previousCost: 1500, usage: 45, unit: "TB scanned" },
587
+ { id: "7", name: "Azure VMs", category: "compute", provider: "azure", cost: 2400, previousCost: 2200 },
588
+ { id: "8", name: "Blob Storage", category: "storage", provider: "azure", cost: 1200, previousCost: 1100 },
589
+ { id: "9", name: "CloudFront CDN", category: "network", provider: "aws", cost: 2100, previousCost: 1900, usage: 85, unit: "TB" },
590
+ { id: "10", name: "Lambda Functions", category: "compute", provider: "aws", cost: 850, previousCost: 720, usage: 12000000, unit: "invocations" },
591
+ ]
592
+
593
+ export const defaultCostAnomalies: CostAnomaly[] = [
594
+ { id: "1", service: "EC2 Spot Instances", expectedCost: 500, actualCost: 1250, deviation: 150, timestamp: new Date(), status: "new" },
595
+ { id: "2", service: "Data Transfer", expectedCost: 800, actualCost: 1400, deviation: 75, timestamp: new Date(Date.now() - 3600000), status: "new" },
596
+ { id: "3", service: "Lambda", expectedCost: 200, actualCost: 320, deviation: 60, timestamp: new Date(Date.now() - 7200000), status: "acknowledged" },
597
+ ]