@wakastellar/ui 2.1.2 → 2.3.0

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 (65) 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 +19 -0
  13. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  14. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  15. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  16. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  17. package/dist/components/waka-container-list/index.d.ts +51 -0
  18. package/dist/components/waka-database-card/index.d.ts +46 -0
  19. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  20. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  21. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  22. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  23. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  24. package/dist/components/waka-migration-list/index.d.ts +36 -0
  25. package/dist/components/waka-pod-card/index.d.ts +73 -0
  26. package/dist/components/waka-query-explain/index.d.ts +48 -0
  27. package/dist/components/waka-secret-card/index.d.ts +43 -0
  28. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  29. package/dist/components/waka-service-graph/index.d.ts +44 -0
  30. package/dist/components/waka-test-report/index.d.ts +60 -0
  31. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  32. package/dist/index.cjs.js +239 -194
  33. package/dist/index.es.js +45560 -35791
  34. package/package.json +1 -1
  35. package/src/blocks/apm-overview/index.tsx +672 -0
  36. package/src/blocks/cicd-builder/index.tsx +738 -0
  37. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  38. package/src/blocks/container-orchestrator/index.tsx +729 -0
  39. package/src/blocks/database-admin/index.tsx +679 -0
  40. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  41. package/src/blocks/incident-manager/index.tsx +586 -0
  42. package/src/blocks/index.ts +119 -0
  43. package/src/blocks/infrastructure-map/index.tsx +638 -0
  44. package/src/blocks/on-call-schedule/index.tsx +615 -0
  45. package/src/blocks/release-notes/index.tsx +643 -0
  46. package/src/components/index.ts +189 -0
  47. package/src/components/waka-alert-panel/index.tsx +493 -0
  48. package/src/components/waka-artifact-list/index.tsx +416 -0
  49. package/src/components/waka-build-matrix/index.tsx +396 -0
  50. package/src/components/waka-config-comparator/index.tsx +416 -0
  51. package/src/components/waka-container-list/index.tsx +475 -0
  52. package/src/components/waka-database-card/index.tsx +473 -0
  53. package/src/components/waka-dependency-tree/index.tsx +542 -0
  54. package/src/components/waka-env-var-editor/index.tsx +417 -0
  55. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  56. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  57. package/src/components/waka-log-viewer/index.tsx +386 -0
  58. package/src/components/waka-migration-list/index.tsx +487 -0
  59. package/src/components/waka-pod-card/index.tsx +528 -0
  60. package/src/components/waka-query-explain/index.tsx +657 -0
  61. package/src/components/waka-secret-card/index.tsx +371 -0
  62. package/src/components/waka-security-scan-result/index.tsx +473 -0
  63. package/src/components/waka-service-graph/index.tsx +445 -0
  64. package/src/components/waka-test-report/index.tsx +469 -0
  65. package/src/components/waka-trace-viewer/index.tsx +490 -0
@@ -366,3 +366,192 @@ export {
366
366
  type UseTooltipTourOptions,
367
367
  type UseTooltipTourReturn,
368
368
  } from './waka-tooltip-tour'
369
+
370
+ // DevOps - Monitoring & Logs Components
371
+ export {
372
+ WakaLogViewer,
373
+ defaultLogs,
374
+ type LogEntry,
375
+ type LogLevel as WakaLogLevel,
376
+ type WakaLogViewerProps,
377
+ } from './waka-log-viewer'
378
+
379
+ export {
380
+ WakaTraceViewer,
381
+ defaultTraceSpans,
382
+ type TraceSpan,
383
+ type SpanStatus,
384
+ type WakaTraceViewerProps,
385
+ } from './waka-trace-viewer'
386
+
387
+ export {
388
+ WakaAlertPanel,
389
+ defaultAlerts as defaultPanelAlerts,
390
+ type Alert as WakaPanelAlert,
391
+ type AlertSeverity as WakaPanelAlertSeverity,
392
+ type AlertStatus as WakaPanelAlertStatus,
393
+ type WakaAlertPanelProps,
394
+ } from './waka-alert-panel'
395
+
396
+ // DevOps - Containers & Kubernetes Components
397
+ export {
398
+ WakaContainerList,
399
+ defaultContainers,
400
+ type Container,
401
+ type ContainerStatus as WakaContainerStatus,
402
+ type ContainerResource,
403
+ type WakaContainerListProps,
404
+ } from './waka-container-list'
405
+
406
+ export {
407
+ WakaKubernetesOverview,
408
+ defaultK8sNodes,
409
+ defaultK8sPods,
410
+ defaultK8sDeployments,
411
+ defaultK8sServices,
412
+ defaultK8sNamespaces,
413
+ type K8sNode,
414
+ type K8sPod,
415
+ type K8sDeployment,
416
+ type K8sService,
417
+ type K8sNamespace,
418
+ type NodeStatus as K8sNodeStatus,
419
+ type PodPhase as K8sPodPhase,
420
+ type DeploymentStatus as K8sDeploymentStatus,
421
+ type WakaKubernetesOverviewProps,
422
+ } from './waka-kubernetes-overview'
423
+
424
+ export {
425
+ WakaPodCard,
426
+ defaultPodDetails,
427
+ type PodDetails,
428
+ type PodContainer,
429
+ type PodEvent,
430
+ type PodPhase,
431
+ type ContainerState,
432
+ type WakaPodCardProps,
433
+ } from './waka-pod-card'
434
+
435
+ export {
436
+ WakaServiceGraph,
437
+ defaultServices,
438
+ defaultConnections,
439
+ type ServiceNode,
440
+ type ServiceConnection,
441
+ type ServiceHealth,
442
+ type ServiceType,
443
+ type WakaServiceGraphProps,
444
+ } from './waka-service-graph'
445
+
446
+ // DevOps - CI/CD Components
447
+ export {
448
+ WakaBuildMatrix,
449
+ defaultBuildMatrixColumns,
450
+ defaultBuildMatrixRows,
451
+ type BuildMatrixRow,
452
+ type BuildCell,
453
+ type BuildStatus as WakaBuildStatus,
454
+ type WakaBuildMatrixProps,
455
+ } from './waka-build-matrix'
456
+
457
+ export {
458
+ WakaTestReport,
459
+ defaultTestSuites,
460
+ defaultCoverage,
461
+ type TestSuite,
462
+ type TestCase,
463
+ type CoverageReport,
464
+ type TestStatus,
465
+ type WakaTestReportProps,
466
+ } from './waka-test-report'
467
+
468
+ export {
469
+ WakaArtifactList,
470
+ defaultArtifacts,
471
+ type Artifact,
472
+ type WakaArtifactListProps,
473
+ } from './waka-artifact-list'
474
+
475
+ export {
476
+ WakaSecurityScanResult,
477
+ defaultVulnerabilities,
478
+ type Vulnerability,
479
+ type VulnerabilitySeverity,
480
+ type ScanType,
481
+ type ScanSummary,
482
+ type WakaSecurityScanResultProps,
483
+ } from './waka-security-scan-result'
484
+
485
+ export {
486
+ WakaDependencyTree,
487
+ defaultDependencies,
488
+ type Dependency,
489
+ type DependencyType,
490
+ type VulnSeverity as DependencyVulnSeverity,
491
+ type WakaDependencyTreeProps,
492
+ } from './waka-dependency-tree'
493
+
494
+ // DevOps - Configuration Components
495
+ export {
496
+ WakaEnvVarEditor,
497
+ defaultEnvVariables,
498
+ type EnvVariable,
499
+ type WakaEnvVarEditorProps,
500
+ } from './waka-env-var-editor'
501
+
502
+ export {
503
+ WakaSecretCard,
504
+ defaultSecret,
505
+ type Secret,
506
+ type SecretAccess,
507
+ type WakaSecretCardProps,
508
+ } from './waka-secret-card'
509
+
510
+ export {
511
+ WakaConfigComparator,
512
+ defaultConfigEnvironments,
513
+ type ConfigEnvironment,
514
+ type ConfigValue,
515
+ type ConfigDiff,
516
+ type DiffType,
517
+ type WakaConfigComparatorProps,
518
+ } from './waka-config-comparator'
519
+
520
+ export {
521
+ WakaFeatureFlagRow,
522
+ defaultFeatureFlags,
523
+ type FeatureFlag,
524
+ type FeatureFlagStatus,
525
+ type FeatureFlagSegment,
526
+ type WakaFeatureFlagRowProps,
527
+ } from './waka-feature-flag-row'
528
+
529
+ // DevOps - Database Components
530
+ export {
531
+ WakaDatabaseCard,
532
+ defaultDatabase,
533
+ defaultDatabases,
534
+ type DatabaseInfo,
535
+ type DatabaseMetrics,
536
+ type DatabaseType as WakaDatabaseType,
537
+ type DatabaseStatus as WakaDatabaseStatus,
538
+ type ReplicationRole,
539
+ type WakaDatabaseCardProps,
540
+ } from './waka-database-card'
541
+
542
+ export {
543
+ WakaMigrationList,
544
+ defaultMigrations,
545
+ type Migration,
546
+ type MigrationStatus,
547
+ type WakaMigrationListProps,
548
+ } from './waka-migration-list'
549
+
550
+ export {
551
+ WakaQueryExplain,
552
+ defaultQueryPlan,
553
+ type QueryPlan,
554
+ type ExplainNode,
555
+ type NodeType,
556
+ type WakaQueryExplainProps,
557
+ } from './waka-query-explain'
@@ -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
+ ]