@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,672 @@
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
+ Activity,
19
+ TrendingUp,
20
+ TrendingDown,
21
+ Clock,
22
+ AlertTriangle,
23
+ CheckCircle2,
24
+ XCircle,
25
+ Zap,
26
+ BarChart3,
27
+ ArrowRight,
28
+ ExternalLink,
29
+ RefreshCw,
30
+ Search,
31
+ Filter,
32
+ Server,
33
+ Globe,
34
+ Database,
35
+ Cpu,
36
+ MemoryStick,
37
+ HardDrive,
38
+ } from "lucide-react"
39
+
40
+ export type ServiceStatus = "healthy" | "warning" | "critical" | "unknown"
41
+ export type TimeRange = "1h" | "6h" | "24h" | "7d" | "30d"
42
+
43
+ export interface ServiceMetrics {
44
+ requestsPerSecond: number
45
+ avgResponseTime: number // in ms
46
+ p50ResponseTime: number
47
+ p95ResponseTime: number
48
+ p99ResponseTime: number
49
+ errorRate: number // percentage
50
+ successRate: number // percentage
51
+ activeConnections: number
52
+ cpuUsage?: number
53
+ memoryUsage?: number
54
+ throughput?: number // requests per minute
55
+ }
56
+
57
+ export interface ServiceEndpoint {
58
+ path: string
59
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
60
+ requestsPerMinute: number
61
+ avgResponseTime: number
62
+ errorRate: number
63
+ status: ServiceStatus
64
+ }
65
+
66
+ export interface APMService {
67
+ id: string
68
+ name: string
69
+ type: "api" | "web" | "worker" | "database" | "cache" | "queue"
70
+ status: ServiceStatus
71
+ metrics: ServiceMetrics
72
+ previousMetrics?: ServiceMetrics
73
+ endpoints?: ServiceEndpoint[]
74
+ instances?: number
75
+ version?: string
76
+ dependencies?: string[]
77
+ tags?: string[]
78
+ }
79
+
80
+ export interface APMTransaction {
81
+ id: string
82
+ name: string
83
+ service: string
84
+ duration: number
85
+ timestamp: Date
86
+ status: "success" | "error"
87
+ traceId?: string
88
+ }
89
+
90
+ export interface APMOverviewProps {
91
+ services: APMService[]
92
+ recentTransactions?: APMTransaction[]
93
+ timeRange?: TimeRange
94
+ onTimeRangeChange?: (range: TimeRange) => void
95
+ onServiceClick?: (service: APMService) => void
96
+ onTransactionClick?: (transaction: APMTransaction) => void
97
+ onRefresh?: () => void
98
+ className?: string
99
+ }
100
+
101
+ const statusConfig: Record<ServiceStatus, { color: string; bgColor: string; label: string }> = {
102
+ healthy: { color: "text-green-500", bgColor: "bg-green-500", label: "Healthy" },
103
+ warning: { color: "text-yellow-500", bgColor: "bg-yellow-500", label: "Warning" },
104
+ critical: { color: "text-red-500", bgColor: "bg-red-500", label: "Critical" },
105
+ unknown: { color: "text-gray-500", bgColor: "bg-gray-500", label: "Unknown" },
106
+ }
107
+
108
+ const serviceTypeConfig: Record<APMService["type"], { icon: React.ElementType; color: string }> = {
109
+ api: { icon: Globe, color: "text-blue-500" },
110
+ web: { icon: Globe, color: "text-purple-500" },
111
+ worker: { icon: Cpu, color: "text-orange-500" },
112
+ database: { icon: Database, color: "text-green-500" },
113
+ cache: { icon: HardDrive, color: "text-red-500" },
114
+ queue: { icon: Activity, color: "text-cyan-500" },
115
+ }
116
+
117
+ function formatMs(ms: number): string {
118
+ if (ms < 1) return `${(ms * 1000).toFixed(0)}µs`
119
+ if (ms < 1000) return `${ms.toFixed(0)}ms`
120
+ return `${(ms / 1000).toFixed(2)}s`
121
+ }
122
+
123
+ function formatNumber(n: number): string {
124
+ if (n < 1000) return n.toFixed(0)
125
+ if (n < 1000000) return `${(n / 1000).toFixed(1)}k`
126
+ return `${(n / 1000000).toFixed(1)}M`
127
+ }
128
+
129
+ function TrendIndicator({ current, previous }: { current: number; previous?: number }) {
130
+ if (previous === undefined) return null
131
+ const change = ((current - previous) / previous) * 100
132
+ const isPositive = change > 0
133
+
134
+ return (
135
+ <span className={cn(
136
+ "text-xs flex items-center gap-0.5",
137
+ isPositive ? "text-red-500" : "text-green-500"
138
+ )}>
139
+ {isPositive ? <TrendingUp className="h-3 w-3" /> : <TrendingDown className="h-3 w-3" />}
140
+ {Math.abs(change).toFixed(1)}%
141
+ </span>
142
+ )
143
+ }
144
+
145
+ function MetricCard({
146
+ title,
147
+ value,
148
+ unit,
149
+ previousValue,
150
+ status,
151
+ icon: Icon,
152
+ }: {
153
+ title: string
154
+ value: number
155
+ unit?: string
156
+ previousValue?: number
157
+ status?: ServiceStatus
158
+ icon: React.ElementType
159
+ }) {
160
+ const statusConf = status ? statusConfig[status] : null
161
+
162
+ return (
163
+ <Card>
164
+ <CardContent className="p-4">
165
+ <div className="flex items-start justify-between">
166
+ <div className="p-2 rounded-lg bg-muted">
167
+ <Icon className="h-4 w-4 text-muted-foreground" />
168
+ </div>
169
+ {statusConf && (
170
+ <div className={cn("w-2 h-2 rounded-full", statusConf.bgColor)} />
171
+ )}
172
+ </div>
173
+ <div className="mt-3">
174
+ <div className="flex items-baseline gap-1">
175
+ <span className="text-2xl font-bold">{formatNumber(value)}</span>
176
+ {unit && <span className="text-sm text-muted-foreground">{unit}</span>}
177
+ </div>
178
+ <div className="flex items-center justify-between mt-1">
179
+ <span className="text-sm text-muted-foreground">{title}</span>
180
+ <TrendIndicator current={value} previous={previousValue} />
181
+ </div>
182
+ </div>
183
+ </CardContent>
184
+ </Card>
185
+ )
186
+ }
187
+
188
+ function ServiceCard({
189
+ service,
190
+ onClick,
191
+ }: {
192
+ service: APMService
193
+ onClick?: () => void
194
+ }) {
195
+ const statusConf = statusConfig[service.status]
196
+ const typeConf = serviceTypeConfig[service.type]
197
+ const TypeIcon = typeConf.icon
198
+ const { metrics, previousMetrics } = service
199
+
200
+ const apdexScore = (metrics.successRate / 100) * (1 - (metrics.p95ResponseTime / 3000))
201
+
202
+ return (
203
+ <Card
204
+ className={cn(
205
+ "cursor-pointer transition-all hover:shadow-md",
206
+ service.status === "critical" && "border-red-500/50",
207
+ service.status === "warning" && "border-yellow-500/30"
208
+ )}
209
+ onClick={onClick}
210
+ >
211
+ <CardContent className="p-4">
212
+ <div className="flex items-start gap-4">
213
+ {/* Service icon */}
214
+ <div className={cn("p-2 rounded-lg", `${typeConf.color.replace("text-", "bg-")}/10`)}>
215
+ <TypeIcon className={cn("h-5 w-5", typeConf.color)} />
216
+ </div>
217
+
218
+ {/* Main info */}
219
+ <div className="flex-1 min-w-0">
220
+ <div className="flex items-center gap-2">
221
+ <h4 className="font-semibold truncate">{service.name}</h4>
222
+ <div className={cn("w-2 h-2 rounded-full shrink-0", statusConf.bgColor)} />
223
+ {service.version && (
224
+ <Badge variant="outline" className="text-xs">
225
+ v{service.version}
226
+ </Badge>
227
+ )}
228
+ </div>
229
+
230
+ <div className="flex items-center gap-4 mt-2 text-sm">
231
+ <span className="flex items-center gap-1">
232
+ <Zap className="h-3 w-3 text-muted-foreground" />
233
+ {metrics.requestsPerSecond}/s
234
+ </span>
235
+ <span className="flex items-center gap-1">
236
+ <Clock className="h-3 w-3 text-muted-foreground" />
237
+ {formatMs(metrics.avgResponseTime)}
238
+ </span>
239
+ <span className={cn(
240
+ "flex items-center gap-1",
241
+ metrics.errorRate > 5 && "text-red-500",
242
+ metrics.errorRate > 1 && metrics.errorRate <= 5 && "text-yellow-500"
243
+ )}>
244
+ <AlertTriangle className="h-3 w-3" />
245
+ {metrics.errorRate.toFixed(2)}%
246
+ </span>
247
+ </div>
248
+
249
+ {/* Response time breakdown */}
250
+ <div className="flex items-center gap-4 mt-3 text-xs text-muted-foreground">
251
+ <span>p50: {formatMs(metrics.p50ResponseTime)}</span>
252
+ <span>p95: {formatMs(metrics.p95ResponseTime)}</span>
253
+ <span>p99: {formatMs(metrics.p99ResponseTime)}</span>
254
+ </div>
255
+
256
+ {/* Tags */}
257
+ {service.tags && service.tags.length > 0 && (
258
+ <div className="flex items-center gap-1 mt-2">
259
+ {service.tags.slice(0, 3).map((tag) => (
260
+ <Badge key={tag} variant="secondary" className="text-xs">
261
+ {tag}
262
+ </Badge>
263
+ ))}
264
+ {service.tags.length > 3 && (
265
+ <span className="text-xs text-muted-foreground">
266
+ +{service.tags.length - 3}
267
+ </span>
268
+ )}
269
+ </div>
270
+ )}
271
+ </div>
272
+
273
+ {/* Right side metrics */}
274
+ <div className="text-right shrink-0 space-y-1">
275
+ <div className="text-lg font-bold text-green-500">
276
+ {metrics.successRate.toFixed(1)}%
277
+ </div>
278
+ <div className="text-xs text-muted-foreground">Success</div>
279
+ {service.instances && (
280
+ <div className="text-xs text-muted-foreground">
281
+ {service.instances} instances
282
+ </div>
283
+ )}
284
+ </div>
285
+ </div>
286
+
287
+ {/* Resource usage */}
288
+ {(metrics.cpuUsage !== undefined || metrics.memoryUsage !== undefined) && (
289
+ <div className="grid grid-cols-2 gap-3 mt-4 pt-3 border-t">
290
+ {metrics.cpuUsage !== undefined && (
291
+ <div>
292
+ <div className="flex items-center justify-between text-xs mb-1">
293
+ <span className="text-muted-foreground flex items-center gap-1">
294
+ <Cpu className="h-3 w-3" /> CPU
295
+ </span>
296
+ <span className={cn(metrics.cpuUsage > 80 && "text-red-500")}>
297
+ {metrics.cpuUsage}%
298
+ </span>
299
+ </div>
300
+ <Progress
301
+ value={metrics.cpuUsage}
302
+ className={cn(
303
+ "h-1.5",
304
+ metrics.cpuUsage > 80 && "[&>div]:bg-red-500"
305
+ )}
306
+ />
307
+ </div>
308
+ )}
309
+ {metrics.memoryUsage !== undefined && (
310
+ <div>
311
+ <div className="flex items-center justify-between text-xs mb-1">
312
+ <span className="text-muted-foreground flex items-center gap-1">
313
+ <MemoryStick className="h-3 w-3" /> Memory
314
+ </span>
315
+ <span className={cn(metrics.memoryUsage > 80 && "text-red-500")}>
316
+ {metrics.memoryUsage}%
317
+ </span>
318
+ </div>
319
+ <Progress
320
+ value={metrics.memoryUsage}
321
+ className={cn(
322
+ "h-1.5",
323
+ metrics.memoryUsage > 80 && "[&>div]:bg-red-500"
324
+ )}
325
+ />
326
+ </div>
327
+ )}
328
+ </div>
329
+ )}
330
+ </CardContent>
331
+ </Card>
332
+ )
333
+ }
334
+
335
+ function RecentTransactions({
336
+ transactions,
337
+ onClick,
338
+ }: {
339
+ transactions: APMTransaction[]
340
+ onClick?: (t: APMTransaction) => void
341
+ }) {
342
+ return (
343
+ <Card>
344
+ <CardHeader className="pb-2">
345
+ <CardTitle className="text-base flex items-center gap-2">
346
+ <Activity className="h-4 w-4" />
347
+ Recent Transactions
348
+ </CardTitle>
349
+ </CardHeader>
350
+ <CardContent>
351
+ <ScrollArea className="h-[300px]">
352
+ <div className="space-y-2">
353
+ {transactions.map((tx) => (
354
+ <div
355
+ key={tx.id}
356
+ className="flex items-center gap-3 p-2 rounded-lg hover:bg-muted/50 cursor-pointer transition-colors"
357
+ onClick={() => onClick?.(tx)}
358
+ >
359
+ {tx.status === "success" ? (
360
+ <CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
361
+ ) : (
362
+ <XCircle className="h-4 w-4 text-red-500 shrink-0" />
363
+ )}
364
+
365
+ <div className="flex-1 min-w-0">
366
+ <div className="font-medium text-sm truncate">{tx.name}</div>
367
+ <div className="text-xs text-muted-foreground">{tx.service}</div>
368
+ </div>
369
+
370
+ <div className="text-right shrink-0">
371
+ <div className={cn(
372
+ "text-sm font-medium",
373
+ tx.duration > 1000 && "text-yellow-500",
374
+ tx.duration > 3000 && "text-red-500"
375
+ )}>
376
+ {formatMs(tx.duration)}
377
+ </div>
378
+ <div className="text-xs text-muted-foreground">
379
+ {tx.timestamp.toLocaleTimeString()}
380
+ </div>
381
+ </div>
382
+ </div>
383
+ ))}
384
+ </div>
385
+ </ScrollArea>
386
+ </CardContent>
387
+ </Card>
388
+ )
389
+ }
390
+
391
+ export function APMOverview({
392
+ services,
393
+ recentTransactions = [],
394
+ timeRange = "1h",
395
+ onTimeRangeChange,
396
+ onServiceClick,
397
+ onTransactionClick,
398
+ onRefresh,
399
+ className,
400
+ }: APMOverviewProps) {
401
+ const [statusFilter, setStatusFilter] = React.useState<ServiceStatus | "all">("all")
402
+
403
+ // Aggregate metrics
404
+ const aggregateMetrics = React.useMemo(() => {
405
+ const totalRequests = services.reduce((acc, s) => acc + s.metrics.requestsPerSecond, 0)
406
+ const avgResponseTime = services.reduce((acc, s) => acc + s.metrics.avgResponseTime, 0) / services.length
407
+ const avgErrorRate = services.reduce((acc, s) => acc + s.metrics.errorRate, 0) / services.length
408
+ const healthyCount = services.filter((s) => s.status === "healthy").length
409
+
410
+ return {
411
+ totalRequests,
412
+ avgResponseTime,
413
+ avgErrorRate,
414
+ healthyCount,
415
+ totalServices: services.length,
416
+ }
417
+ }, [services])
418
+
419
+ const filteredServices = React.useMemo(() => {
420
+ if (statusFilter === "all") return services
421
+ return services.filter((s) => s.status === statusFilter)
422
+ }, [services, statusFilter])
423
+
424
+ return (
425
+ <div className={cn("space-y-6", className)}>
426
+ {/* Header */}
427
+ <Card>
428
+ <CardHeader>
429
+ <div className="flex items-center justify-between">
430
+ <div className="flex items-center gap-3">
431
+ <Activity className="h-6 w-6" />
432
+ <div>
433
+ <CardTitle>Application Performance</CardTitle>
434
+ <p className="text-sm text-muted-foreground">
435
+ Monitor service health and performance metrics
436
+ </p>
437
+ </div>
438
+ </div>
439
+
440
+ <div className="flex items-center gap-2">
441
+ <Select value={timeRange} onValueChange={(v) => onTimeRangeChange?.(v as TimeRange)}>
442
+ <SelectTrigger className="w-28">
443
+ <Clock className="h-4 w-4 mr-2" />
444
+ <SelectValue />
445
+ </SelectTrigger>
446
+ <SelectContent>
447
+ <SelectItem value="1h">Last 1h</SelectItem>
448
+ <SelectItem value="6h">Last 6h</SelectItem>
449
+ <SelectItem value="24h">Last 24h</SelectItem>
450
+ <SelectItem value="7d">Last 7d</SelectItem>
451
+ <SelectItem value="30d">Last 30d</SelectItem>
452
+ </SelectContent>
453
+ </Select>
454
+
455
+ {onRefresh && (
456
+ <Button variant="outline" onClick={onRefresh}>
457
+ <RefreshCw className="h-4 w-4" />
458
+ </Button>
459
+ )}
460
+ </div>
461
+ </div>
462
+ </CardHeader>
463
+ </Card>
464
+
465
+ {/* Key metrics */}
466
+ <div className="grid grid-cols-4 gap-4">
467
+ <MetricCard
468
+ title="Total Requests"
469
+ value={aggregateMetrics.totalRequests}
470
+ unit="/s"
471
+ icon={Zap}
472
+ />
473
+ <MetricCard
474
+ title="Avg Response Time"
475
+ value={aggregateMetrics.avgResponseTime}
476
+ unit="ms"
477
+ icon={Clock}
478
+ status={aggregateMetrics.avgResponseTime > 500 ? "warning" : "healthy"}
479
+ />
480
+ <MetricCard
481
+ title="Error Rate"
482
+ value={aggregateMetrics.avgErrorRate}
483
+ unit="%"
484
+ icon={AlertTriangle}
485
+ status={aggregateMetrics.avgErrorRate > 5 ? "critical" : aggregateMetrics.avgErrorRate > 1 ? "warning" : "healthy"}
486
+ />
487
+ <MetricCard
488
+ title="Services Healthy"
489
+ value={aggregateMetrics.healthyCount}
490
+ unit={`/ ${aggregateMetrics.totalServices}`}
491
+ icon={CheckCircle2}
492
+ status={aggregateMetrics.healthyCount === aggregateMetrics.totalServices ? "healthy" : "warning"}
493
+ />
494
+ </div>
495
+
496
+ <div className="grid grid-cols-3 gap-6">
497
+ {/* Services */}
498
+ <div className="col-span-2 space-y-4">
499
+ <div className="flex items-center justify-between">
500
+ <h3 className="font-semibold">Services</h3>
501
+ <div className="flex items-center gap-2">
502
+ {(["all", "healthy", "warning", "critical"] as const).map((status) => (
503
+ <Button
504
+ key={status}
505
+ variant={statusFilter === status ? "default" : "ghost"}
506
+ size="sm"
507
+ onClick={() => setStatusFilter(status)}
508
+ >
509
+ {status === "all" ? "All" : statusConfig[status].label}
510
+ </Button>
511
+ ))}
512
+ </div>
513
+ </div>
514
+
515
+ <ScrollArea className="h-[500px]">
516
+ <div className="space-y-4 pr-4">
517
+ {filteredServices.map((service) => (
518
+ <ServiceCard
519
+ key={service.id}
520
+ service={service}
521
+ onClick={() => onServiceClick?.(service)}
522
+ />
523
+ ))}
524
+ </div>
525
+ </ScrollArea>
526
+ </div>
527
+
528
+ {/* Recent transactions */}
529
+ <div>
530
+ <RecentTransactions
531
+ transactions={recentTransactions}
532
+ onClick={onTransactionClick}
533
+ />
534
+ </div>
535
+ </div>
536
+ </div>
537
+ )
538
+ }
539
+
540
+ // Default sample data
541
+ export const defaultAPMServices: APMService[] = [
542
+ {
543
+ id: "1",
544
+ name: "api-gateway",
545
+ type: "api",
546
+ status: "healthy",
547
+ version: "2.4.1",
548
+ instances: 3,
549
+ metrics: {
550
+ requestsPerSecond: 1250,
551
+ avgResponseTime: 45,
552
+ p50ResponseTime: 32,
553
+ p95ResponseTime: 120,
554
+ p99ResponseTime: 250,
555
+ errorRate: 0.12,
556
+ successRate: 99.88,
557
+ activeConnections: 450,
558
+ cpuUsage: 45,
559
+ memoryUsage: 62,
560
+ },
561
+ tags: ["production", "critical"],
562
+ },
563
+ {
564
+ id: "2",
565
+ name: "user-service",
566
+ type: "api",
567
+ status: "healthy",
568
+ version: "1.8.0",
569
+ instances: 2,
570
+ metrics: {
571
+ requestsPerSecond: 350,
572
+ avgResponseTime: 85,
573
+ p50ResponseTime: 65,
574
+ p95ResponseTime: 180,
575
+ p99ResponseTime: 450,
576
+ errorRate: 0.5,
577
+ successRate: 99.5,
578
+ activeConnections: 120,
579
+ cpuUsage: 35,
580
+ memoryUsage: 55,
581
+ },
582
+ tags: ["production"],
583
+ },
584
+ {
585
+ id: "3",
586
+ name: "payment-service",
587
+ type: "api",
588
+ status: "warning",
589
+ version: "3.1.0",
590
+ instances: 2,
591
+ metrics: {
592
+ requestsPerSecond: 150,
593
+ avgResponseTime: 520,
594
+ p50ResponseTime: 280,
595
+ p95ResponseTime: 1200,
596
+ p99ResponseTime: 2500,
597
+ errorRate: 2.5,
598
+ successRate: 97.5,
599
+ activeConnections: 85,
600
+ cpuUsage: 78,
601
+ memoryUsage: 82,
602
+ },
603
+ tags: ["production", "payment"],
604
+ },
605
+ {
606
+ id: "4",
607
+ name: "notification-worker",
608
+ type: "worker",
609
+ status: "healthy",
610
+ instances: 5,
611
+ metrics: {
612
+ requestsPerSecond: 500,
613
+ avgResponseTime: 25,
614
+ p50ResponseTime: 18,
615
+ p95ResponseTime: 55,
616
+ p99ResponseTime: 120,
617
+ errorRate: 0.05,
618
+ successRate: 99.95,
619
+ activeConnections: 0,
620
+ cpuUsage: 25,
621
+ memoryUsage: 40,
622
+ },
623
+ },
624
+ {
625
+ id: "5",
626
+ name: "postgres-primary",
627
+ type: "database",
628
+ status: "healthy",
629
+ instances: 1,
630
+ metrics: {
631
+ requestsPerSecond: 2500,
632
+ avgResponseTime: 8,
633
+ p50ResponseTime: 5,
634
+ p95ResponseTime: 25,
635
+ p99ResponseTime: 80,
636
+ errorRate: 0.01,
637
+ successRate: 99.99,
638
+ activeConnections: 120,
639
+ cpuUsage: 55,
640
+ memoryUsage: 72,
641
+ },
642
+ },
643
+ {
644
+ id: "6",
645
+ name: "redis-cache",
646
+ type: "cache",
647
+ status: "healthy",
648
+ instances: 3,
649
+ metrics: {
650
+ requestsPerSecond: 15000,
651
+ avgResponseTime: 0.5,
652
+ p50ResponseTime: 0.3,
653
+ p95ResponseTime: 1.2,
654
+ p99ResponseTime: 3,
655
+ errorRate: 0.001,
656
+ successRate: 99.999,
657
+ activeConnections: 500,
658
+ memoryUsage: 45,
659
+ },
660
+ },
661
+ ]
662
+
663
+ export const defaultAPMTransactions: APMTransaction[] = [
664
+ { id: "1", name: "POST /api/users", service: "user-service", duration: 85, timestamp: new Date(Date.now() - 5000), status: "success" },
665
+ { id: "2", name: "GET /api/products", service: "api-gateway", duration: 42, timestamp: new Date(Date.now() - 12000), status: "success" },
666
+ { id: "3", name: "POST /api/payments", service: "payment-service", duration: 1250, timestamp: new Date(Date.now() - 25000), status: "error" },
667
+ { id: "4", name: "GET /api/orders/:id", service: "api-gateway", duration: 65, timestamp: new Date(Date.now() - 45000), status: "success" },
668
+ { id: "5", name: "POST /api/notifications", service: "notification-worker", duration: 22, timestamp: new Date(Date.now() - 60000), status: "success" },
669
+ { id: "6", name: "GET /api/users/:id", service: "user-service", duration: 95, timestamp: new Date(Date.now() - 90000), status: "success" },
670
+ { id: "7", name: "PUT /api/cart", service: "api-gateway", duration: 180, timestamp: new Date(Date.now() - 120000), status: "success" },
671
+ { id: "8", name: "POST /api/checkout", service: "payment-service", duration: 3200, timestamp: new Date(Date.now() - 180000), status: "error" },
672
+ ]