@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,445 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../badge"
6
+ import { Button } from "../button"
7
+ import {
8
+ Tooltip,
9
+ TooltipContent,
10
+ TooltipProvider,
11
+ TooltipTrigger,
12
+ } from "../tooltip"
13
+ import {
14
+ Network,
15
+ Server,
16
+ Database,
17
+ Globe,
18
+ MessageSquare,
19
+ Zap,
20
+ RefreshCw,
21
+ ZoomIn,
22
+ ZoomOut,
23
+ Maximize2,
24
+ CheckCircle2,
25
+ XCircle,
26
+ AlertTriangle,
27
+ } from "lucide-react"
28
+
29
+ export type ServiceHealth = "healthy" | "degraded" | "unhealthy" | "unknown"
30
+ export type ServiceType = "api" | "database" | "cache" | "queue" | "gateway" | "external"
31
+
32
+ export interface ServiceNode {
33
+ id: string
34
+ name: string
35
+ type: ServiceType
36
+ health: ServiceHealth
37
+ namespace?: string
38
+ version?: string
39
+ replicas?: number
40
+ requestsPerSec?: number
41
+ errorRate?: number
42
+ latencyP50?: number
43
+ latencyP99?: number
44
+ }
45
+
46
+ export interface ServiceConnection {
47
+ source: string
48
+ target: string
49
+ protocol?: string
50
+ requestsPerSec?: number
51
+ errorRate?: number
52
+ latency?: number
53
+ }
54
+
55
+ export interface WakaServiceGraphProps {
56
+ /** List of services */
57
+ services: ServiceNode[]
58
+ /** List of connections between services */
59
+ connections: ServiceConnection[]
60
+ /** Selected service ID */
61
+ selectedService?: string
62
+ /** Callback when clicking on a service */
63
+ onServiceClick?: (service: ServiceNode) => void
64
+ /** Callback when clicking on a connection */
65
+ onConnectionClick?: (connection: ServiceConnection) => void
66
+ /** Callback when refreshing */
67
+ onRefresh?: () => void
68
+ /** Show metrics on nodes */
69
+ showMetrics?: boolean
70
+ /** Custom class name */
71
+ className?: string
72
+ }
73
+
74
+ const typeConfig: Record<ServiceType, { icon: React.ElementType; color: string }> = {
75
+ api: { icon: Server, color: "bg-blue-500" },
76
+ database: { icon: Database, color: "bg-purple-500" },
77
+ cache: { icon: Zap, color: "bg-yellow-500" },
78
+ queue: { icon: MessageSquare, color: "bg-orange-500" },
79
+ gateway: { icon: Globe, color: "bg-green-500" },
80
+ external: { icon: Network, color: "bg-gray-500" },
81
+ }
82
+
83
+ const healthConfig: Record<ServiceHealth, { color: string; borderColor: string; icon: React.ElementType }> = {
84
+ healthy: { color: "text-green-500", borderColor: "border-green-500", icon: CheckCircle2 },
85
+ degraded: { color: "text-yellow-500", borderColor: "border-yellow-500", icon: AlertTriangle },
86
+ unhealthy: { color: "text-red-500", borderColor: "border-red-500", icon: XCircle },
87
+ unknown: { color: "text-gray-500", borderColor: "border-gray-500", icon: AlertTriangle },
88
+ }
89
+
90
+ function ServiceNodeComponent({
91
+ service,
92
+ isSelected,
93
+ onClick,
94
+ showMetrics,
95
+ position,
96
+ }: {
97
+ service: ServiceNode
98
+ isSelected: boolean
99
+ onClick?: () => void
100
+ showMetrics: boolean
101
+ position: { x: number; y: number }
102
+ }) {
103
+ const typeConf = typeConfig[service.type]
104
+ const healthConf = healthConfig[service.health]
105
+ const TypeIcon = typeConf.icon
106
+ const HealthIcon = healthConf.icon
107
+
108
+ return (
109
+ <TooltipProvider>
110
+ <Tooltip>
111
+ <TooltipTrigger asChild>
112
+ <div
113
+ className={cn(
114
+ "absolute transform -translate-x-1/2 -translate-y-1/2 cursor-pointer",
115
+ "transition-all duration-200 hover:scale-110"
116
+ )}
117
+ style={{ left: position.x, top: position.y }}
118
+ onClick={onClick}
119
+ >
120
+ <div
121
+ className={cn(
122
+ "relative bg-background border-2 rounded-xl p-3 shadow-lg",
123
+ healthConf.borderColor,
124
+ isSelected && "ring-2 ring-primary ring-offset-2"
125
+ )}
126
+ >
127
+ {/* Health indicator */}
128
+ <div className={cn("absolute -top-2 -right-2", healthConf.color)}>
129
+ <HealthIcon className="h-4 w-4" />
130
+ </div>
131
+
132
+ {/* Icon */}
133
+ <div className={cn("w-10 h-10 rounded-lg flex items-center justify-center text-white", typeConf.color)}>
134
+ <TypeIcon className="h-5 w-5" />
135
+ </div>
136
+
137
+ {/* Name */}
138
+ <div className="mt-2 text-center">
139
+ <div className="font-medium text-sm truncate max-w-[100px]">{service.name}</div>
140
+ {service.namespace && (
141
+ <div className="text-xs text-muted-foreground">{service.namespace}</div>
142
+ )}
143
+ </div>
144
+
145
+ {/* Metrics */}
146
+ {showMetrics && (
147
+ <div className="mt-2 text-xs text-center space-y-0.5">
148
+ {service.requestsPerSec !== undefined && (
149
+ <div className="text-muted-foreground">{service.requestsPerSec} req/s</div>
150
+ )}
151
+ {service.errorRate !== undefined && service.errorRate > 0 && (
152
+ <div className="text-red-500">{(service.errorRate * 100).toFixed(1)}% errors</div>
153
+ )}
154
+ </div>
155
+ )}
156
+ </div>
157
+ </div>
158
+ </TooltipTrigger>
159
+ <TooltipContent side="right" className="max-w-xs">
160
+ <div className="space-y-2">
161
+ <div className="font-semibold">{service.name}</div>
162
+ <div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
163
+ <span className="text-muted-foreground">Type:</span>
164
+ <span className="capitalize">{service.type}</span>
165
+ <span className="text-muted-foreground">Health:</span>
166
+ <span className={healthConf.color}>{service.health}</span>
167
+ {service.version && (
168
+ <>
169
+ <span className="text-muted-foreground">Version:</span>
170
+ <span>{service.version}</span>
171
+ </>
172
+ )}
173
+ {service.replicas !== undefined && (
174
+ <>
175
+ <span className="text-muted-foreground">Replicas:</span>
176
+ <span>{service.replicas}</span>
177
+ </>
178
+ )}
179
+ {service.latencyP50 !== undefined && (
180
+ <>
181
+ <span className="text-muted-foreground">Latency P50:</span>
182
+ <span>{service.latencyP50}ms</span>
183
+ </>
184
+ )}
185
+ {service.latencyP99 !== undefined && (
186
+ <>
187
+ <span className="text-muted-foreground">Latency P99:</span>
188
+ <span>{service.latencyP99}ms</span>
189
+ </>
190
+ )}
191
+ </div>
192
+ </div>
193
+ </TooltipContent>
194
+ </Tooltip>
195
+ </TooltipProvider>
196
+ )
197
+ }
198
+
199
+ function ConnectionLine({
200
+ start,
201
+ end,
202
+ connection,
203
+ onClick,
204
+ }: {
205
+ start: { x: number; y: number }
206
+ end: { x: number; y: number }
207
+ connection: ServiceConnection
208
+ onClick?: () => void
209
+ }) {
210
+ // Calculate control points for curved line
211
+ const midX = (start.x + end.x) / 2
212
+ const midY = (start.y + end.y) / 2
213
+ const dx = end.x - start.x
214
+ const dy = end.y - start.y
215
+ const dist = Math.sqrt(dx * dx + dy * dy)
216
+ const curvature = 0.2
217
+
218
+ // Perpendicular offset for curve
219
+ const px = -dy / dist * curvature * dist
220
+ const py = dx / dist * curvature * dist
221
+
222
+ const controlX = midX + px
223
+ const controlY = midY + py
224
+
225
+ const path = `M ${start.x} ${start.y} Q ${controlX} ${controlY} ${end.x} ${end.y}`
226
+
227
+ // Determine line color based on error rate
228
+ const errorRate = connection.errorRate || 0
229
+ const lineColor = errorRate > 0.05 ? "#ef4444" : errorRate > 0.01 ? "#eab308" : "#6b7280"
230
+ const lineWidth = Math.min(Math.max((connection.requestsPerSec || 1) / 100, 1), 4)
231
+
232
+ return (
233
+ <g className="cursor-pointer hover:opacity-70" onClick={onClick}>
234
+ {/* Main line */}
235
+ <path
236
+ d={path}
237
+ fill="none"
238
+ stroke={lineColor}
239
+ strokeWidth={lineWidth}
240
+ strokeOpacity={0.6}
241
+ markerEnd="url(#arrowhead)"
242
+ />
243
+
244
+ {/* Label */}
245
+ {connection.requestsPerSec !== undefined && (
246
+ <text
247
+ x={controlX}
248
+ y={controlY}
249
+ textAnchor="middle"
250
+ dy={-8}
251
+ className="fill-muted-foreground text-xs"
252
+ >
253
+ {connection.requestsPerSec} req/s
254
+ </text>
255
+ )}
256
+ </g>
257
+ )
258
+ }
259
+
260
+ export function WakaServiceGraph({
261
+ services,
262
+ connections,
263
+ selectedService,
264
+ onServiceClick,
265
+ onConnectionClick,
266
+ onRefresh,
267
+ showMetrics = true,
268
+ className,
269
+ }: WakaServiceGraphProps) {
270
+ const [zoom, setZoom] = React.useState(1)
271
+ const containerRef = React.useRef<HTMLDivElement>(null)
272
+
273
+ // Calculate positions for services in a force-directed layout (simplified)
274
+ const positions = React.useMemo(() => {
275
+ const pos: Record<string, { x: number; y: number }> = {}
276
+ const width = 600
277
+ const height = 400
278
+ const centerX = width / 2
279
+ const centerY = height / 2
280
+
281
+ // Simple circular layout
282
+ const angleStep = (2 * Math.PI) / services.length
283
+ const radius = Math.min(width, height) * 0.35
284
+
285
+ services.forEach((service, i) => {
286
+ const angle = i * angleStep - Math.PI / 2
287
+ pos[service.id] = {
288
+ x: centerX + radius * Math.cos(angle),
289
+ y: centerY + radius * Math.sin(angle),
290
+ }
291
+ })
292
+
293
+ return pos
294
+ }, [services])
295
+
296
+ // Count services by health
297
+ const healthCounts = React.useMemo(() => {
298
+ return services.reduce((acc, s) => {
299
+ acc[s.health] = (acc[s.health] || 0) + 1
300
+ return acc
301
+ }, {} as Record<ServiceHealth, number>)
302
+ }, [services])
303
+
304
+ return (
305
+ <div className={cn("flex flex-col border rounded-lg bg-background", className)}>
306
+ {/* Header */}
307
+ <div className="flex items-center justify-between gap-4 p-3 border-b">
308
+ <div className="flex items-center gap-3">
309
+ <Network className="h-5 w-5" />
310
+ <h3 className="font-semibold">Service Mesh</h3>
311
+ <Badge variant="secondary">{services.length} services</Badge>
312
+
313
+ <div className="hidden md:flex items-center gap-1">
314
+ {healthCounts.healthy > 0 && (
315
+ <Badge className="bg-green-500 text-xs">{healthCounts.healthy} healthy</Badge>
316
+ )}
317
+ {healthCounts.degraded > 0 && (
318
+ <Badge className="bg-yellow-500 text-xs">{healthCounts.degraded} degraded</Badge>
319
+ )}
320
+ {healthCounts.unhealthy > 0 && (
321
+ <Badge className="bg-red-500 text-xs">{healthCounts.unhealthy} unhealthy</Badge>
322
+ )}
323
+ </div>
324
+ </div>
325
+
326
+ <div className="flex items-center gap-1">
327
+ <Button variant="ghost" size="sm" onClick={() => setZoom((z) => Math.max(0.5, z - 0.1))}>
328
+ <ZoomOut className="h-4 w-4" />
329
+ </Button>
330
+ <span className="text-xs text-muted-foreground w-12 text-center">{(zoom * 100).toFixed(0)}%</span>
331
+ <Button variant="ghost" size="sm" onClick={() => setZoom((z) => Math.min(2, z + 0.1))}>
332
+ <ZoomIn className="h-4 w-4" />
333
+ </Button>
334
+ <Button variant="ghost" size="sm" onClick={() => setZoom(1)}>
335
+ <Maximize2 className="h-4 w-4" />
336
+ </Button>
337
+ {onRefresh && (
338
+ <Button variant="ghost" size="sm" onClick={onRefresh}>
339
+ <RefreshCw className="h-4 w-4" />
340
+ </Button>
341
+ )}
342
+ </div>
343
+ </div>
344
+
345
+ {/* Legend */}
346
+ <div className="flex items-center gap-4 px-3 py-2 border-b bg-muted/30 text-xs flex-wrap">
347
+ {Object.entries(typeConfig).map(([type, config]) => {
348
+ const Icon = config.icon
349
+ return (
350
+ <div key={type} className="flex items-center gap-1.5">
351
+ <div className={cn("w-3 h-3 rounded", config.color)} />
352
+ <span className="capitalize">{type}</span>
353
+ </div>
354
+ )
355
+ })}
356
+ </div>
357
+
358
+ {/* Graph */}
359
+ <div
360
+ ref={containerRef}
361
+ className="flex-1 relative overflow-hidden min-h-[400px]"
362
+ style={{ cursor: "grab" }}
363
+ >
364
+ <div
365
+ className="absolute inset-0 transition-transform duration-200"
366
+ style={{
367
+ transform: `scale(${zoom})`,
368
+ transformOrigin: "center center",
369
+ }}
370
+ >
371
+ {/* SVG for connections */}
372
+ <svg className="absolute inset-0 w-full h-full pointer-events-none">
373
+ <defs>
374
+ <marker
375
+ id="arrowhead"
376
+ markerWidth="10"
377
+ markerHeight="7"
378
+ refX="9"
379
+ refY="3.5"
380
+ orient="auto"
381
+ >
382
+ <polygon
383
+ points="0 0, 10 3.5, 0 7"
384
+ fill="#6b7280"
385
+ />
386
+ </marker>
387
+ </defs>
388
+
389
+ {connections.map((connection, i) => {
390
+ const startPos = positions[connection.source]
391
+ const endPos = positions[connection.target]
392
+ if (!startPos || !endPos) return null
393
+
394
+ return (
395
+ <ConnectionLine
396
+ key={i}
397
+ start={startPos}
398
+ end={endPos}
399
+ connection={connection}
400
+ onClick={() => onConnectionClick?.(connection)}
401
+ />
402
+ )
403
+ })}
404
+ </svg>
405
+
406
+ {/* Service nodes */}
407
+ {services.map((service) => (
408
+ <ServiceNodeComponent
409
+ key={service.id}
410
+ service={service}
411
+ isSelected={selectedService === service.id}
412
+ onClick={() => onServiceClick?.(service)}
413
+ showMetrics={showMetrics}
414
+ position={positions[service.id]}
415
+ />
416
+ ))}
417
+ </div>
418
+ </div>
419
+ </div>
420
+ )
421
+ }
422
+
423
+ // Default sample data for demo
424
+ export const defaultServices: ServiceNode[] = [
425
+ { id: "gateway", name: "API Gateway", type: "gateway", health: "healthy", namespace: "ingress", version: "v2.1.0", replicas: 3, requestsPerSec: 1250, errorRate: 0.001 },
426
+ { id: "auth", name: "Auth Service", type: "api", health: "healthy", namespace: "auth", version: "v1.5.2", replicas: 2, requestsPerSec: 450, errorRate: 0.002 },
427
+ { id: "users", name: "User Service", type: "api", health: "healthy", namespace: "users", version: "v3.0.1", replicas: 3, requestsPerSec: 320, errorRate: 0.001 },
428
+ { id: "orders", name: "Order Service", type: "api", health: "degraded", namespace: "commerce", version: "v2.8.0", replicas: 4, requestsPerSec: 180, errorRate: 0.05 },
429
+ { id: "postgres", name: "PostgreSQL", type: "database", health: "healthy", version: "15.2", replicas: 1 },
430
+ { id: "redis", name: "Redis Cache", type: "cache", health: "healthy", version: "7.0", replicas: 3 },
431
+ { id: "rabbitmq", name: "RabbitMQ", type: "queue", health: "healthy", version: "3.12", replicas: 3 },
432
+ { id: "stripe", name: "Stripe API", type: "external", health: "healthy" },
433
+ ]
434
+
435
+ export const defaultConnections: ServiceConnection[] = [
436
+ { source: "gateway", target: "auth", protocol: "gRPC", requestsPerSec: 450, errorRate: 0.001 },
437
+ { source: "gateway", target: "users", protocol: "gRPC", requestsPerSec: 320, errorRate: 0.001 },
438
+ { source: "gateway", target: "orders", protocol: "gRPC", requestsPerSec: 180, errorRate: 0.03 },
439
+ { source: "auth", target: "postgres", protocol: "PostgreSQL", requestsPerSec: 200 },
440
+ { source: "auth", target: "redis", protocol: "Redis", requestsPerSec: 800 },
441
+ { source: "users", target: "postgres", protocol: "PostgreSQL", requestsPerSec: 150 },
442
+ { source: "orders", target: "postgres", protocol: "PostgreSQL", requestsPerSec: 100 },
443
+ { source: "orders", target: "rabbitmq", protocol: "AMQP", requestsPerSec: 50 },
444
+ { source: "orders", target: "stripe", protocol: "HTTPS", requestsPerSec: 30 },
445
+ ]
@@ -0,0 +1,97 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+
6
+ export type SponsoredBadgeVariant = "default" | "subtle" | "outline" | "dark"
7
+ export type SponsoredBadgeSize = "sm" | "md" | "lg"
8
+
9
+ export interface WakaSponsoredBadgeProps {
10
+ /** Sponsor name (optional) */
11
+ sponsor?: string
12
+ /** Badge variant */
13
+ variant?: SponsoredBadgeVariant
14
+ /** Badge size */
15
+ size?: SponsoredBadgeSize
16
+ /** Show info icon */
17
+ showIcon?: boolean
18
+ /** Custom label text */
19
+ label?: string
20
+ /** Custom class name */
21
+ className?: string
22
+ /** Callback when info is clicked */
23
+ onInfoClick?: () => void
24
+ }
25
+
26
+ export function WakaSponsoredBadge({
27
+ sponsor,
28
+ variant = "default",
29
+ size = "sm",
30
+ showIcon = true,
31
+ label = "Sponsored",
32
+ className,
33
+ onInfoClick,
34
+ }: WakaSponsoredBadgeProps) {
35
+ const variantClasses: Record<SponsoredBadgeVariant, string> = {
36
+ default: "bg-muted text-muted-foreground border-transparent",
37
+ subtle: "bg-black/50 text-white border-transparent backdrop-blur-sm",
38
+ outline: "bg-transparent text-muted-foreground border-muted-foreground/30",
39
+ dark: "bg-black/70 text-white border-transparent",
40
+ }
41
+
42
+ const sizeClasses: Record<SponsoredBadgeSize, string> = {
43
+ sm: "text-[10px] px-1.5 py-0.5 gap-1",
44
+ md: "text-xs px-2 py-1 gap-1.5",
45
+ lg: "text-sm px-2.5 py-1 gap-2",
46
+ }
47
+
48
+ const iconSizes: Record<SponsoredBadgeSize, string> = {
49
+ sm: "h-2.5 w-2.5",
50
+ md: "h-3 w-3",
51
+ lg: "h-3.5 w-3.5",
52
+ }
53
+
54
+ return (
55
+ <span
56
+ className={cn(
57
+ "inline-flex items-center rounded border font-medium",
58
+ variantClasses[variant],
59
+ sizeClasses[size],
60
+ className
61
+ )}
62
+ >
63
+ <span className="whitespace-nowrap">
64
+ {sponsor ? `${label} by ${sponsor}` : label}
65
+ </span>
66
+
67
+ {showIcon && (
68
+ <button
69
+ onClick={(e) => {
70
+ e.preventDefault()
71
+ e.stopPropagation()
72
+ onInfoClick?.()
73
+ }}
74
+ className={cn(
75
+ "rounded-full hover:opacity-70 transition-opacity focus:outline-none focus:ring-1 focus:ring-current",
76
+ iconSizes[size]
77
+ )}
78
+ aria-label="Why am I seeing this ad?"
79
+ >
80
+ <svg
81
+ viewBox="0 0 24 24"
82
+ fill="none"
83
+ stroke="currentColor"
84
+ strokeWidth={2}
85
+ className="w-full h-full"
86
+ >
87
+ <circle cx="12" cy="12" r="10" />
88
+ <path d="M12 16v-4" />
89
+ <path d="M12 8h.01" />
90
+ </svg>
91
+ </button>
92
+ )}
93
+ </span>
94
+ )
95
+ }
96
+
97
+ export default WakaSponsoredBadge