@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,493 @@
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 {
9
+ Select,
10
+ SelectContent,
11
+ SelectItem,
12
+ SelectTrigger,
13
+ SelectValue,
14
+ } from "../select"
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuSeparator,
20
+ DropdownMenuTrigger,
21
+ } from "../dropdown-menu"
22
+ import {
23
+ AlertCircle,
24
+ AlertTriangle,
25
+ Info,
26
+ Bell,
27
+ BellOff,
28
+ Check,
29
+ Clock,
30
+ MoreVertical,
31
+ ExternalLink,
32
+ Eye,
33
+ EyeOff,
34
+ Filter,
35
+ RefreshCw,
36
+ Volume2,
37
+ VolumeX,
38
+ } from "lucide-react"
39
+
40
+ export type AlertSeverity = "critical" | "warning" | "info"
41
+ export type AlertStatus = "firing" | "acknowledged" | "resolved" | "silenced"
42
+
43
+ export interface Alert {
44
+ id: string
45
+ title: string
46
+ description?: string
47
+ severity: AlertSeverity
48
+ status: AlertStatus
49
+ source: string
50
+ startedAt: Date
51
+ endedAt?: Date
52
+ acknowledgedAt?: Date
53
+ acknowledgedBy?: string
54
+ silencedUntil?: Date
55
+ labels?: Record<string, string>
56
+ annotations?: Record<string, string>
57
+ fingerprint?: string
58
+ runbookUrl?: string
59
+ }
60
+
61
+ export interface WakaAlertPanelProps {
62
+ /** List of alerts */
63
+ alerts: Alert[]
64
+ /** Callback when acknowledging an alert */
65
+ onAcknowledge?: (alertId: string) => void
66
+ /** Callback when silencing an alert */
67
+ onSilence?: (alertId: string, duration: number) => void
68
+ /** Callback when resolving an alert */
69
+ onResolve?: (alertId: string) => void
70
+ /** Callback when viewing alert details */
71
+ onViewDetails?: (alert: Alert) => void
72
+ /** Callback when clicking runbook link */
73
+ onRunbook?: (alert: Alert) => void
74
+ /** Callback when refreshing alerts */
75
+ onRefresh?: () => void
76
+ /** Whether alerts are loading */
77
+ isLoading?: boolean
78
+ /** Show resolved alerts */
79
+ showResolved?: boolean
80
+ /** Custom class name */
81
+ className?: string
82
+ /** Title */
83
+ title?: string
84
+ }
85
+
86
+ const severityConfig: Record<AlertSeverity, { icon: React.ElementType; color: string; bgColor: string }> = {
87
+ critical: { icon: AlertCircle, color: "text-red-500", bgColor: "bg-red-500/10 border-red-500/30" },
88
+ warning: { icon: AlertTriangle, color: "text-yellow-500", bgColor: "bg-yellow-500/10 border-yellow-500/30" },
89
+ info: { icon: Info, color: "text-blue-500", bgColor: "bg-blue-500/10 border-blue-500/30" },
90
+ }
91
+
92
+ const statusConfig: Record<AlertStatus, { label: string; variant: "default" | "secondary" | "outline" | "destructive" }> = {
93
+ firing: { label: "Firing", variant: "destructive" },
94
+ acknowledged: { label: "Acknowledged", variant: "secondary" },
95
+ resolved: { label: "Resolved", variant: "outline" },
96
+ silenced: { label: "Silenced", variant: "outline" },
97
+ }
98
+
99
+ function formatDuration(date: Date): string {
100
+ const now = new Date()
101
+ const diff = now.getTime() - date.getTime()
102
+ const minutes = Math.floor(diff / 60000)
103
+ const hours = Math.floor(minutes / 60)
104
+ const days = Math.floor(hours / 24)
105
+
106
+ if (days > 0) return `${days}d ${hours % 24}h`
107
+ if (hours > 0) return `${hours}h ${minutes % 60}m`
108
+ return `${minutes}m`
109
+ }
110
+
111
+ function AlertCard({
112
+ alert,
113
+ onAcknowledge,
114
+ onSilence,
115
+ onResolve,
116
+ onViewDetails,
117
+ onRunbook,
118
+ }: {
119
+ alert: Alert
120
+ onAcknowledge?: (alertId: string) => void
121
+ onSilence?: (alertId: string, duration: number) => void
122
+ onResolve?: (alertId: string) => void
123
+ onViewDetails?: (alert: Alert) => void
124
+ onRunbook?: (alert: Alert) => void
125
+ }) {
126
+ const config = severityConfig[alert.severity]
127
+ const statusCfg = statusConfig[alert.status]
128
+ const Icon = config.icon
129
+
130
+ return (
131
+ <div
132
+ className={cn(
133
+ "border rounded-lg p-4 transition-all",
134
+ config.bgColor,
135
+ alert.status === "firing" && alert.severity === "critical" && "animate-pulse"
136
+ )}
137
+ >
138
+ <div className="flex items-start gap-3">
139
+ <Icon className={cn("h-5 w-5 mt-0.5 shrink-0", config.color)} />
140
+
141
+ <div className="flex-1 min-w-0">
142
+ <div className="flex items-center gap-2 flex-wrap">
143
+ <h4 className="font-semibold truncate">{alert.title}</h4>
144
+ <Badge variant={statusCfg.variant}>{statusCfg.label}</Badge>
145
+ <Badge variant="outline" className="text-xs">
146
+ {alert.source}
147
+ </Badge>
148
+ </div>
149
+
150
+ {alert.description && (
151
+ <p className="text-sm text-muted-foreground mt-1 line-clamp-2">
152
+ {alert.description}
153
+ </p>
154
+ )}
155
+
156
+ <div className="flex items-center gap-4 mt-2 text-xs text-muted-foreground">
157
+ <span className="flex items-center gap-1">
158
+ <Clock className="h-3 w-3" />
159
+ {formatDuration(alert.startedAt)}
160
+ </span>
161
+ {alert.acknowledgedBy && (
162
+ <span>
163
+ Ack by {alert.acknowledgedBy}
164
+ </span>
165
+ )}
166
+ {alert.silencedUntil && alert.silencedUntil > new Date() && (
167
+ <span className="flex items-center gap-1">
168
+ <VolumeX className="h-3 w-3" />
169
+ Silenced until {alert.silencedUntil.toLocaleTimeString()}
170
+ </span>
171
+ )}
172
+ </div>
173
+
174
+ {alert.labels && Object.keys(alert.labels).length > 0 && (
175
+ <div className="flex items-center gap-1 mt-2 flex-wrap">
176
+ {Object.entries(alert.labels).slice(0, 3).map(([key, value]) => (
177
+ <Badge key={key} variant="outline" className="text-xs">
178
+ {key}={value}
179
+ </Badge>
180
+ ))}
181
+ {Object.keys(alert.labels).length > 3 && (
182
+ <Badge variant="outline" className="text-xs">
183
+ +{Object.keys(alert.labels).length - 3}
184
+ </Badge>
185
+ )}
186
+ </div>
187
+ )}
188
+ </div>
189
+
190
+ <DropdownMenu>
191
+ <DropdownMenuTrigger asChild>
192
+ <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
193
+ <MoreVertical className="h-4 w-4" />
194
+ </Button>
195
+ </DropdownMenuTrigger>
196
+ <DropdownMenuContent align="end">
197
+ <DropdownMenuItem onClick={() => onViewDetails?.(alert)}>
198
+ <Eye className="h-4 w-4 mr-2" />
199
+ View Details
200
+ </DropdownMenuItem>
201
+ {alert.runbookUrl && (
202
+ <DropdownMenuItem onClick={() => onRunbook?.(alert)}>
203
+ <ExternalLink className="h-4 w-4 mr-2" />
204
+ View Runbook
205
+ </DropdownMenuItem>
206
+ )}
207
+ <DropdownMenuSeparator />
208
+ {alert.status === "firing" && (
209
+ <>
210
+ <DropdownMenuItem onClick={() => onAcknowledge?.(alert.id)}>
211
+ <Check className="h-4 w-4 mr-2" />
212
+ Acknowledge
213
+ </DropdownMenuItem>
214
+ <DropdownMenuItem onClick={() => onSilence?.(alert.id, 30)}>
215
+ <VolumeX className="h-4 w-4 mr-2" />
216
+ Silence 30m
217
+ </DropdownMenuItem>
218
+ <DropdownMenuItem onClick={() => onSilence?.(alert.id, 60)}>
219
+ <VolumeX className="h-4 w-4 mr-2" />
220
+ Silence 1h
221
+ </DropdownMenuItem>
222
+ <DropdownMenuItem onClick={() => onSilence?.(alert.id, 240)}>
223
+ <VolumeX className="h-4 w-4 mr-2" />
224
+ Silence 4h
225
+ </DropdownMenuItem>
226
+ </>
227
+ )}
228
+ {alert.status !== "resolved" && (
229
+ <DropdownMenuItem onClick={() => onResolve?.(alert.id)}>
230
+ <CheckCircle className="h-4 w-4 mr-2" />
231
+ Mark Resolved
232
+ </DropdownMenuItem>
233
+ )}
234
+ </DropdownMenuContent>
235
+ </DropdownMenu>
236
+ </div>
237
+ </div>
238
+ )
239
+ }
240
+
241
+ // Add missing import
242
+ import { CheckCircle } from "lucide-react"
243
+
244
+ export function WakaAlertPanel({
245
+ alerts,
246
+ onAcknowledge,
247
+ onSilence,
248
+ onResolve,
249
+ onViewDetails,
250
+ onRunbook,
251
+ onRefresh,
252
+ isLoading = false,
253
+ showResolved = false,
254
+ className,
255
+ title = "Alerts",
256
+ }: WakaAlertPanelProps) {
257
+ const [severityFilter, setSeverityFilter] = React.useState<AlertSeverity | "all">("all")
258
+ const [statusFilter, setStatusFilter] = React.useState<AlertStatus | "all">("all")
259
+
260
+ // Filter alerts
261
+ const filteredAlerts = React.useMemo(() => {
262
+ return alerts.filter((alert) => {
263
+ if (!showResolved && alert.status === "resolved") return false
264
+ if (severityFilter !== "all" && alert.severity !== severityFilter) return false
265
+ if (statusFilter !== "all" && alert.status !== statusFilter) return false
266
+ return true
267
+ })
268
+ }, [alerts, showResolved, severityFilter, statusFilter])
269
+
270
+ // Count by severity
271
+ const severityCounts = React.useMemo(() => {
272
+ const active = alerts.filter((a) => a.status !== "resolved")
273
+ return {
274
+ critical: active.filter((a) => a.severity === "critical").length,
275
+ warning: active.filter((a) => a.severity === "warning").length,
276
+ info: active.filter((a) => a.severity === "info").length,
277
+ }
278
+ }, [alerts])
279
+
280
+ // Group by status
281
+ const groupedAlerts = React.useMemo(() => {
282
+ const firing = filteredAlerts.filter((a) => a.status === "firing")
283
+ const acknowledged = filteredAlerts.filter((a) => a.status === "acknowledged")
284
+ const silenced = filteredAlerts.filter((a) => a.status === "silenced")
285
+ const resolved = filteredAlerts.filter((a) => a.status === "resolved")
286
+ return { firing, acknowledged, silenced, resolved }
287
+ }, [filteredAlerts])
288
+
289
+ return (
290
+ <div className={cn("flex flex-col border rounded-lg bg-background", className)}>
291
+ {/* Header */}
292
+ <div className="flex items-center justify-between gap-4 p-3 border-b">
293
+ <div className="flex items-center gap-3">
294
+ <Bell className="h-5 w-5" />
295
+ <h3 className="font-semibold">{title}</h3>
296
+
297
+ {/* Severity counts */}
298
+ <div className="flex items-center gap-1">
299
+ {severityCounts.critical > 0 && (
300
+ <Badge variant="destructive" className="text-xs">
301
+ {severityCounts.critical} critical
302
+ </Badge>
303
+ )}
304
+ {severityCounts.warning > 0 && (
305
+ <Badge className="bg-yellow-500 text-xs">
306
+ {severityCounts.warning} warning
307
+ </Badge>
308
+ )}
309
+ {severityCounts.info > 0 && (
310
+ <Badge variant="secondary" className="text-xs">
311
+ {severityCounts.info} info
312
+ </Badge>
313
+ )}
314
+ </div>
315
+ </div>
316
+
317
+ <div className="flex items-center gap-2">
318
+ {onRefresh && (
319
+ <Button
320
+ variant="ghost"
321
+ size="sm"
322
+ onClick={onRefresh}
323
+ disabled={isLoading}
324
+ >
325
+ <RefreshCw className={cn("h-4 w-4", isLoading && "animate-spin")} />
326
+ </Button>
327
+ )}
328
+ </div>
329
+ </div>
330
+
331
+ {/* Filters */}
332
+ <div className="flex items-center gap-2 p-2 border-b bg-muted/30">
333
+ <Select value={severityFilter} onValueChange={(v) => setSeverityFilter(v as AlertSeverity | "all")}>
334
+ <SelectTrigger className="w-[130px] h-8">
335
+ <Filter className="h-4 w-4 mr-2" />
336
+ <SelectValue placeholder="Severity" />
337
+ </SelectTrigger>
338
+ <SelectContent>
339
+ <SelectItem value="all">All Severity</SelectItem>
340
+ <SelectItem value="critical">Critical</SelectItem>
341
+ <SelectItem value="warning">Warning</SelectItem>
342
+ <SelectItem value="info">Info</SelectItem>
343
+ </SelectContent>
344
+ </Select>
345
+
346
+ <Select value={statusFilter} onValueChange={(v) => setStatusFilter(v as AlertStatus | "all")}>
347
+ <SelectTrigger className="w-[140px] h-8">
348
+ <SelectValue placeholder="Status" />
349
+ </SelectTrigger>
350
+ <SelectContent>
351
+ <SelectItem value="all">All Status</SelectItem>
352
+ <SelectItem value="firing">Firing</SelectItem>
353
+ <SelectItem value="acknowledged">Acknowledged</SelectItem>
354
+ <SelectItem value="silenced">Silenced</SelectItem>
355
+ {showResolved && <SelectItem value="resolved">Resolved</SelectItem>}
356
+ </SelectContent>
357
+ </Select>
358
+ </div>
359
+
360
+ {/* Alerts list */}
361
+ <ScrollArea className="flex-1">
362
+ <div className="p-3 space-y-4">
363
+ {filteredAlerts.length === 0 ? (
364
+ <div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
365
+ <BellOff className="h-8 w-8 mb-2" />
366
+ <span>No alerts to display</span>
367
+ </div>
368
+ ) : (
369
+ <>
370
+ {groupedAlerts.firing.length > 0 && (
371
+ <div className="space-y-2">
372
+ <h4 className="text-sm font-medium text-red-500 flex items-center gap-2">
373
+ <Volume2 className="h-4 w-4" />
374
+ Firing ({groupedAlerts.firing.length})
375
+ </h4>
376
+ {groupedAlerts.firing.map((alert) => (
377
+ <AlertCard
378
+ key={alert.id}
379
+ alert={alert}
380
+ onAcknowledge={onAcknowledge}
381
+ onSilence={onSilence}
382
+ onResolve={onResolve}
383
+ onViewDetails={onViewDetails}
384
+ onRunbook={onRunbook}
385
+ />
386
+ ))}
387
+ </div>
388
+ )}
389
+
390
+ {groupedAlerts.acknowledged.length > 0 && (
391
+ <div className="space-y-2">
392
+ <h4 className="text-sm font-medium text-muted-foreground flex items-center gap-2">
393
+ <Check className="h-4 w-4" />
394
+ Acknowledged ({groupedAlerts.acknowledged.length})
395
+ </h4>
396
+ {groupedAlerts.acknowledged.map((alert) => (
397
+ <AlertCard
398
+ key={alert.id}
399
+ alert={alert}
400
+ onAcknowledge={onAcknowledge}
401
+ onSilence={onSilence}
402
+ onResolve={onResolve}
403
+ onViewDetails={onViewDetails}
404
+ onRunbook={onRunbook}
405
+ />
406
+ ))}
407
+ </div>
408
+ )}
409
+
410
+ {groupedAlerts.silenced.length > 0 && (
411
+ <div className="space-y-2">
412
+ <h4 className="text-sm font-medium text-muted-foreground flex items-center gap-2">
413
+ <VolumeX className="h-4 w-4" />
414
+ Silenced ({groupedAlerts.silenced.length})
415
+ </h4>
416
+ {groupedAlerts.silenced.map((alert) => (
417
+ <AlertCard
418
+ key={alert.id}
419
+ alert={alert}
420
+ onAcknowledge={onAcknowledge}
421
+ onSilence={onSilence}
422
+ onResolve={onResolve}
423
+ onViewDetails={onViewDetails}
424
+ onRunbook={onRunbook}
425
+ />
426
+ ))}
427
+ </div>
428
+ )}
429
+
430
+ {showResolved && groupedAlerts.resolved.length > 0 && (
431
+ <div className="space-y-2">
432
+ <h4 className="text-sm font-medium text-muted-foreground flex items-center gap-2">
433
+ <CheckCircle className="h-4 w-4" />
434
+ Resolved ({groupedAlerts.resolved.length})
435
+ </h4>
436
+ {groupedAlerts.resolved.map((alert) => (
437
+ <AlertCard
438
+ key={alert.id}
439
+ alert={alert}
440
+ onAcknowledge={onAcknowledge}
441
+ onSilence={onSilence}
442
+ onResolve={onResolve}
443
+ onViewDetails={onViewDetails}
444
+ onRunbook={onRunbook}
445
+ />
446
+ ))}
447
+ </div>
448
+ )}
449
+ </>
450
+ )}
451
+ </div>
452
+ </ScrollArea>
453
+ </div>
454
+ )
455
+ }
456
+
457
+ // Default sample alerts for demo
458
+ export const defaultAlerts: Alert[] = [
459
+ {
460
+ id: "1",
461
+ title: "High CPU Usage on prod-api-1",
462
+ description: "CPU usage has exceeded 90% for more than 5 minutes",
463
+ severity: "critical",
464
+ status: "firing",
465
+ source: "prometheus",
466
+ startedAt: new Date(Date.now() - 15 * 60000),
467
+ labels: { instance: "prod-api-1", job: "api-server" },
468
+ runbookUrl: "https://runbooks.example.com/high-cpu",
469
+ },
470
+ {
471
+ id: "2",
472
+ title: "Database Connection Pool Exhausted",
473
+ description: "PostgreSQL connection pool is at 95% capacity",
474
+ severity: "warning",
475
+ status: "acknowledged",
476
+ source: "prometheus",
477
+ startedAt: new Date(Date.now() - 45 * 60000),
478
+ acknowledgedAt: new Date(Date.now() - 30 * 60000),
479
+ acknowledgedBy: "john.doe",
480
+ labels: { db: "primary", pool: "main" },
481
+ },
482
+ {
483
+ id: "3",
484
+ title: "SSL Certificate Expiring Soon",
485
+ description: "Certificate for api.example.com expires in 7 days",
486
+ severity: "info",
487
+ status: "silenced",
488
+ source: "cert-manager",
489
+ startedAt: new Date(Date.now() - 2 * 3600000),
490
+ silencedUntil: new Date(Date.now() + 24 * 3600000),
491
+ labels: { domain: "api.example.com" },
492
+ },
493
+ ]