@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.
- package/dist/blocks/apm-overview/index.d.ts +58 -0
- package/dist/blocks/cicd-builder/index.d.ts +47 -0
- package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
- package/dist/blocks/container-orchestrator/index.d.ts +63 -0
- package/dist/blocks/database-admin/index.d.ts +84 -0
- package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
- package/dist/blocks/incident-manager/index.d.ts +44 -0
- package/dist/blocks/index.d.ts +10 -0
- package/dist/blocks/infrastructure-map/index.d.ts +32 -0
- package/dist/blocks/on-call-schedule/index.d.ts +43 -0
- package/dist/blocks/release-notes/index.d.ts +49 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-alert-panel/index.d.ts +45 -0
- package/dist/components/waka-artifact-list/index.d.ts +32 -0
- package/dist/components/waka-build-matrix/index.d.ts +36 -0
- package/dist/components/waka-config-comparator/index.d.ts +37 -0
- package/dist/components/waka-container-list/index.d.ts +51 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-database-card/index.d.ts +46 -0
- package/dist/components/waka-dependency-tree/index.d.ts +38 -0
- package/dist/components/waka-env-var-editor/index.d.ts +30 -0
- package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
- package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
- package/dist/components/waka-log-viewer/index.d.ts +38 -0
- package/dist/components/waka-migration-list/index.d.ts +36 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-pod-card/index.d.ts +73 -0
- package/dist/components/waka-query-explain/index.d.ts +48 -0
- package/dist/components/waka-secret-card/index.d.ts +43 -0
- package/dist/components/waka-security-scan-result/index.d.ts +45 -0
- package/dist/components/waka-service-graph/index.d.ts +44 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +251 -200
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +47315 -35823
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/apm-overview/index.tsx +672 -0
- package/src/blocks/cicd-builder/index.tsx +738 -0
- package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
- package/src/blocks/container-orchestrator/index.tsx +729 -0
- package/src/blocks/database-admin/index.tsx +679 -0
- package/src/blocks/gitops-sync-status/index.tsx +557 -0
- package/src/blocks/incident-manager/index.tsx +586 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/infrastructure-map/index.tsx +638 -0
- package/src/blocks/on-call-schedule/index.tsx +615 -0
- package/src/blocks/release-notes/index.tsx +643 -0
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +283 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-alert-panel/index.tsx +493 -0
- package/src/components/waka-artifact-list/index.tsx +416 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-build-matrix/index.tsx +396 -0
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-database-card/index.tsx +473 -0
- package/src/components/waka-dependency-tree/index.tsx +542 -0
- package/src/components/waka-env-var-editor/index.tsx +417 -0
- package/src/components/waka-feature-flag-row/index.tsx +386 -0
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-secret-card/index.tsx +371 -0
- package/src/components/waka-security-scan-result/index.tsx +473 -0
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-service-graph/index.tsx +445 -0
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- package/src/styles/themes/twilight.css +16 -0
|
@@ -0,0 +1,490 @@
|
|
|
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 { ScrollArea } from "../scroll-area"
|
|
8
|
+
import {
|
|
9
|
+
Tooltip,
|
|
10
|
+
TooltipContent,
|
|
11
|
+
TooltipProvider,
|
|
12
|
+
TooltipTrigger,
|
|
13
|
+
} from "../tooltip"
|
|
14
|
+
import {
|
|
15
|
+
ChevronDown,
|
|
16
|
+
ChevronRight,
|
|
17
|
+
Clock,
|
|
18
|
+
AlertCircle,
|
|
19
|
+
CheckCircle2,
|
|
20
|
+
Loader2,
|
|
21
|
+
ExternalLink,
|
|
22
|
+
Copy,
|
|
23
|
+
} from "lucide-react"
|
|
24
|
+
|
|
25
|
+
export type SpanStatus = "ok" | "error" | "unset"
|
|
26
|
+
|
|
27
|
+
export interface TraceSpan {
|
|
28
|
+
id: string
|
|
29
|
+
traceId: string
|
|
30
|
+
parentId?: string
|
|
31
|
+
operationName: string
|
|
32
|
+
serviceName: string
|
|
33
|
+
startTime: number // timestamp in ms
|
|
34
|
+
duration: number // duration in ms
|
|
35
|
+
status: SpanStatus
|
|
36
|
+
tags?: Record<string, string | number | boolean>
|
|
37
|
+
logs?: Array<{
|
|
38
|
+
timestamp: number
|
|
39
|
+
message: string
|
|
40
|
+
level?: "info" | "warn" | "error"
|
|
41
|
+
}>
|
|
42
|
+
children?: TraceSpan[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WakaTraceViewerProps {
|
|
46
|
+
/** Root spans of the trace */
|
|
47
|
+
spans: TraceSpan[]
|
|
48
|
+
/** Trace ID */
|
|
49
|
+
traceId?: string
|
|
50
|
+
/** Total trace duration */
|
|
51
|
+
totalDuration?: number
|
|
52
|
+
/** Callback when clicking on a span */
|
|
53
|
+
onSpanClick?: (span: TraceSpan) => void
|
|
54
|
+
/** Callback when copying trace ID */
|
|
55
|
+
onCopyTraceId?: (traceId: string) => void
|
|
56
|
+
/** Show service colors */
|
|
57
|
+
showServiceColors?: boolean
|
|
58
|
+
/** Custom class name */
|
|
59
|
+
className?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const serviceColors = [
|
|
63
|
+
"bg-blue-500",
|
|
64
|
+
"bg-green-500",
|
|
65
|
+
"bg-purple-500",
|
|
66
|
+
"bg-orange-500",
|
|
67
|
+
"bg-pink-500",
|
|
68
|
+
"bg-cyan-500",
|
|
69
|
+
"bg-yellow-500",
|
|
70
|
+
"bg-red-500",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
function getServiceColor(serviceName: string, serviceMap: Map<string, string>): string {
|
|
74
|
+
if (!serviceMap.has(serviceName)) {
|
|
75
|
+
serviceMap.set(serviceName, serviceColors[serviceMap.size % serviceColors.length])
|
|
76
|
+
}
|
|
77
|
+
return serviceMap.get(serviceName)!
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function SpanRow({
|
|
81
|
+
span,
|
|
82
|
+
depth,
|
|
83
|
+
totalDuration,
|
|
84
|
+
minTime,
|
|
85
|
+
serviceMap,
|
|
86
|
+
isExpanded,
|
|
87
|
+
onToggle,
|
|
88
|
+
onSpanClick,
|
|
89
|
+
}: {
|
|
90
|
+
span: TraceSpan
|
|
91
|
+
depth: number
|
|
92
|
+
totalDuration: number
|
|
93
|
+
minTime: number
|
|
94
|
+
serviceMap: Map<string, string>
|
|
95
|
+
isExpanded: boolean
|
|
96
|
+
onToggle: () => void
|
|
97
|
+
onSpanClick?: (span: TraceSpan) => void
|
|
98
|
+
}) {
|
|
99
|
+
const hasChildren = span.children && span.children.length > 0
|
|
100
|
+
const serviceColor = getServiceColor(span.serviceName, serviceMap)
|
|
101
|
+
|
|
102
|
+
// Calculate position and width on timeline
|
|
103
|
+
const startPercent = ((span.startTime - minTime) / totalDuration) * 100
|
|
104
|
+
const widthPercent = Math.max((span.duration / totalDuration) * 100, 0.5)
|
|
105
|
+
|
|
106
|
+
const statusIcon = {
|
|
107
|
+
ok: <CheckCircle2 className="h-4 w-4 text-green-500" />,
|
|
108
|
+
error: <AlertCircle className="h-4 w-4 text-red-500" />,
|
|
109
|
+
unset: <Clock className="h-4 w-4 text-muted-foreground" />,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="group">
|
|
114
|
+
<div
|
|
115
|
+
className={cn(
|
|
116
|
+
"flex items-center gap-2 px-2 py-1.5 hover:bg-muted/50 cursor-pointer border-b border-border/30",
|
|
117
|
+
span.status === "error" && "bg-red-500/5"
|
|
118
|
+
)}
|
|
119
|
+
onClick={() => onSpanClick?.(span)}
|
|
120
|
+
>
|
|
121
|
+
{/* Expand/Collapse */}
|
|
122
|
+
<div style={{ width: depth * 20 }} />
|
|
123
|
+
<button
|
|
124
|
+
onClick={(e) => {
|
|
125
|
+
e.stopPropagation()
|
|
126
|
+
onToggle()
|
|
127
|
+
}}
|
|
128
|
+
className="w-5 h-5 flex items-center justify-center"
|
|
129
|
+
disabled={!hasChildren}
|
|
130
|
+
>
|
|
131
|
+
{hasChildren ? (
|
|
132
|
+
isExpanded ? (
|
|
133
|
+
<ChevronDown className="h-4 w-4" />
|
|
134
|
+
) : (
|
|
135
|
+
<ChevronRight className="h-4 w-4" />
|
|
136
|
+
)
|
|
137
|
+
) : (
|
|
138
|
+
<span className="w-4" />
|
|
139
|
+
)}
|
|
140
|
+
</button>
|
|
141
|
+
|
|
142
|
+
{/* Service indicator */}
|
|
143
|
+
<div className={cn("w-3 h-3 rounded-full shrink-0", serviceColor)} />
|
|
144
|
+
|
|
145
|
+
{/* Service name */}
|
|
146
|
+
<span className="text-xs text-muted-foreground w-24 truncate shrink-0">
|
|
147
|
+
{span.serviceName}
|
|
148
|
+
</span>
|
|
149
|
+
|
|
150
|
+
{/* Operation name */}
|
|
151
|
+
<span className="text-sm font-medium truncate w-48 shrink-0">
|
|
152
|
+
{span.operationName}
|
|
153
|
+
</span>
|
|
154
|
+
|
|
155
|
+
{/* Status */}
|
|
156
|
+
<div className="shrink-0">{statusIcon[span.status]}</div>
|
|
157
|
+
|
|
158
|
+
{/* Timeline bar */}
|
|
159
|
+
<div className="flex-1 h-6 bg-muted/30 rounded relative min-w-[200px]">
|
|
160
|
+
<TooltipProvider>
|
|
161
|
+
<Tooltip>
|
|
162
|
+
<TooltipTrigger asChild>
|
|
163
|
+
<div
|
|
164
|
+
className={cn(
|
|
165
|
+
"absolute h-full rounded transition-all",
|
|
166
|
+
span.status === "error" ? "bg-red-500" : serviceColor,
|
|
167
|
+
"opacity-80 hover:opacity-100"
|
|
168
|
+
)}
|
|
169
|
+
style={{
|
|
170
|
+
left: `${startPercent}%`,
|
|
171
|
+
width: `${widthPercent}%`,
|
|
172
|
+
minWidth: "4px",
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
</TooltipTrigger>
|
|
176
|
+
<TooltipContent>
|
|
177
|
+
<div className="text-xs space-y-1">
|
|
178
|
+
<div className="font-semibold">{span.operationName}</div>
|
|
179
|
+
<div>Service: {span.serviceName}</div>
|
|
180
|
+
<div>Duration: {span.duration.toFixed(2)}ms</div>
|
|
181
|
+
<div>Start: +{(span.startTime - minTime).toFixed(2)}ms</div>
|
|
182
|
+
</div>
|
|
183
|
+
</TooltipContent>
|
|
184
|
+
</Tooltip>
|
|
185
|
+
</TooltipProvider>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Duration */}
|
|
189
|
+
<span className="text-xs text-muted-foreground w-20 text-right shrink-0">
|
|
190
|
+
{span.duration.toFixed(2)}ms
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function SpanTree({
|
|
198
|
+
spans,
|
|
199
|
+
depth,
|
|
200
|
+
totalDuration,
|
|
201
|
+
minTime,
|
|
202
|
+
serviceMap,
|
|
203
|
+
expandedIds,
|
|
204
|
+
onToggle,
|
|
205
|
+
onSpanClick,
|
|
206
|
+
}: {
|
|
207
|
+
spans: TraceSpan[]
|
|
208
|
+
depth: number
|
|
209
|
+
totalDuration: number
|
|
210
|
+
minTime: number
|
|
211
|
+
serviceMap: Map<string, string>
|
|
212
|
+
expandedIds: Set<string>
|
|
213
|
+
onToggle: (id: string) => void
|
|
214
|
+
onSpanClick?: (span: TraceSpan) => void
|
|
215
|
+
}) {
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
{spans.map((span) => {
|
|
219
|
+
const isExpanded = expandedIds.has(span.id)
|
|
220
|
+
return (
|
|
221
|
+
<React.Fragment key={span.id}>
|
|
222
|
+
<SpanRow
|
|
223
|
+
span={span}
|
|
224
|
+
depth={depth}
|
|
225
|
+
totalDuration={totalDuration}
|
|
226
|
+
minTime={minTime}
|
|
227
|
+
serviceMap={serviceMap}
|
|
228
|
+
isExpanded={isExpanded}
|
|
229
|
+
onToggle={() => onToggle(span.id)}
|
|
230
|
+
onSpanClick={onSpanClick}
|
|
231
|
+
/>
|
|
232
|
+
{isExpanded && span.children && (
|
|
233
|
+
<SpanTree
|
|
234
|
+
spans={span.children}
|
|
235
|
+
depth={depth + 1}
|
|
236
|
+
totalDuration={totalDuration}
|
|
237
|
+
minTime={minTime}
|
|
238
|
+
serviceMap={serviceMap}
|
|
239
|
+
expandedIds={expandedIds}
|
|
240
|
+
onToggle={onToggle}
|
|
241
|
+
onSpanClick={onSpanClick}
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
244
|
+
</React.Fragment>
|
|
245
|
+
)
|
|
246
|
+
})}
|
|
247
|
+
</>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function WakaTraceViewer({
|
|
252
|
+
spans,
|
|
253
|
+
traceId,
|
|
254
|
+
totalDuration: propTotalDuration,
|
|
255
|
+
onSpanClick,
|
|
256
|
+
onCopyTraceId,
|
|
257
|
+
showServiceColors = true,
|
|
258
|
+
className,
|
|
259
|
+
}: WakaTraceViewerProps) {
|
|
260
|
+
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set())
|
|
261
|
+
const serviceMap = React.useMemo(() => new Map<string, string>(), [])
|
|
262
|
+
|
|
263
|
+
// Calculate min time and total duration
|
|
264
|
+
const { minTime, totalDuration } = React.useMemo(() => {
|
|
265
|
+
const allSpans: TraceSpan[] = []
|
|
266
|
+
const collectSpans = (spans: TraceSpan[]) => {
|
|
267
|
+
spans.forEach((span) => {
|
|
268
|
+
allSpans.push(span)
|
|
269
|
+
if (span.children) collectSpans(span.children)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
collectSpans(spans)
|
|
273
|
+
|
|
274
|
+
if (allSpans.length === 0) return { minTime: 0, totalDuration: 1 }
|
|
275
|
+
|
|
276
|
+
const minTime = Math.min(...allSpans.map((s) => s.startTime))
|
|
277
|
+
const maxTime = Math.max(...allSpans.map((s) => s.startTime + s.duration))
|
|
278
|
+
return {
|
|
279
|
+
minTime,
|
|
280
|
+
totalDuration: propTotalDuration || maxTime - minTime || 1,
|
|
281
|
+
}
|
|
282
|
+
}, [spans, propTotalDuration])
|
|
283
|
+
|
|
284
|
+
const toggleExpand = (id: string) => {
|
|
285
|
+
setExpandedIds((prev) => {
|
|
286
|
+
const next = new Set(prev)
|
|
287
|
+
if (next.has(id)) {
|
|
288
|
+
next.delete(id)
|
|
289
|
+
} else {
|
|
290
|
+
next.add(id)
|
|
291
|
+
}
|
|
292
|
+
return next
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const expandAll = () => {
|
|
297
|
+
const allIds = new Set<string>()
|
|
298
|
+
const collectIds = (spans: TraceSpan[]) => {
|
|
299
|
+
spans.forEach((span) => {
|
|
300
|
+
allIds.add(span.id)
|
|
301
|
+
if (span.children) collectIds(span.children)
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
collectIds(spans)
|
|
305
|
+
setExpandedIds(allIds)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const collapseAll = () => {
|
|
309
|
+
setExpandedIds(new Set())
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Get unique services
|
|
313
|
+
const services = React.useMemo(() => {
|
|
314
|
+
const serviceSet = new Set<string>()
|
|
315
|
+
const collectServices = (spans: TraceSpan[]) => {
|
|
316
|
+
spans.forEach((span) => {
|
|
317
|
+
serviceSet.add(span.serviceName)
|
|
318
|
+
if (span.children) collectServices(span.children)
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
collectServices(spans)
|
|
322
|
+
return Array.from(serviceSet)
|
|
323
|
+
}, [spans])
|
|
324
|
+
|
|
325
|
+
// Count spans
|
|
326
|
+
const spanCount = React.useMemo(() => {
|
|
327
|
+
let count = 0
|
|
328
|
+
const countSpans = (spans: TraceSpan[]) => {
|
|
329
|
+
spans.forEach((span) => {
|
|
330
|
+
count++
|
|
331
|
+
if (span.children) countSpans(span.children)
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
countSpans(spans)
|
|
335
|
+
return count
|
|
336
|
+
}, [spans])
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
340
|
+
{/* Header */}
|
|
341
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
342
|
+
<div className="flex items-center gap-3">
|
|
343
|
+
<h3 className="font-semibold">Trace</h3>
|
|
344
|
+
{traceId && (
|
|
345
|
+
<div className="flex items-center gap-1">
|
|
346
|
+
<code className="text-xs bg-muted px-2 py-0.5 rounded font-mono">
|
|
347
|
+
{traceId.slice(0, 16)}...
|
|
348
|
+
</code>
|
|
349
|
+
<Button
|
|
350
|
+
variant="ghost"
|
|
351
|
+
size="sm"
|
|
352
|
+
className="h-6 w-6 p-0"
|
|
353
|
+
onClick={() => onCopyTraceId?.(traceId)}
|
|
354
|
+
>
|
|
355
|
+
<Copy className="h-3 w-3" />
|
|
356
|
+
</Button>
|
|
357
|
+
</div>
|
|
358
|
+
)}
|
|
359
|
+
<Badge variant="secondary">{spanCount} spans</Badge>
|
|
360
|
+
<Badge variant="outline">{totalDuration.toFixed(2)}ms</Badge>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div className="flex items-center gap-2">
|
|
364
|
+
<Button variant="ghost" size="sm" onClick={expandAll}>
|
|
365
|
+
Expand All
|
|
366
|
+
</Button>
|
|
367
|
+
<Button variant="ghost" size="sm" onClick={collapseAll}>
|
|
368
|
+
Collapse All
|
|
369
|
+
</Button>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
{/* Service legend */}
|
|
374
|
+
{showServiceColors && services.length > 0 && (
|
|
375
|
+
<div className="flex items-center gap-3 px-3 py-2 border-b bg-muted/30 flex-wrap">
|
|
376
|
+
<span className="text-xs text-muted-foreground">Services:</span>
|
|
377
|
+
{services.map((service) => (
|
|
378
|
+
<div key={service} className="flex items-center gap-1.5">
|
|
379
|
+
<div className={cn("w-2.5 h-2.5 rounded-full", getServiceColor(service, serviceMap))} />
|
|
380
|
+
<span className="text-xs">{service}</span>
|
|
381
|
+
</div>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
385
|
+
|
|
386
|
+
{/* Timeline header */}
|
|
387
|
+
<div className="flex items-center gap-2 px-2 py-1 border-b bg-muted/20 text-xs text-muted-foreground">
|
|
388
|
+
<div className="w-5" />
|
|
389
|
+
<div className="w-5" />
|
|
390
|
+
<div className="w-3" />
|
|
391
|
+
<div className="w-24">Service</div>
|
|
392
|
+
<div className="w-48">Operation</div>
|
|
393
|
+
<div className="w-4" />
|
|
394
|
+
<div className="flex-1 flex justify-between min-w-[200px]">
|
|
395
|
+
<span>0ms</span>
|
|
396
|
+
<span>{(totalDuration / 2).toFixed(0)}ms</span>
|
|
397
|
+
<span>{totalDuration.toFixed(0)}ms</span>
|
|
398
|
+
</div>
|
|
399
|
+
<div className="w-20 text-right">Duration</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
{/* Spans */}
|
|
403
|
+
<ScrollArea className="flex-1">
|
|
404
|
+
{spans.length === 0 ? (
|
|
405
|
+
<div className="flex items-center justify-center h-32 text-muted-foreground">
|
|
406
|
+
No spans to display
|
|
407
|
+
</div>
|
|
408
|
+
) : (
|
|
409
|
+
<SpanTree
|
|
410
|
+
spans={spans}
|
|
411
|
+
depth={0}
|
|
412
|
+
totalDuration={totalDuration}
|
|
413
|
+
minTime={minTime}
|
|
414
|
+
serviceMap={serviceMap}
|
|
415
|
+
expandedIds={expandedIds}
|
|
416
|
+
onToggle={toggleExpand}
|
|
417
|
+
onSpanClick={onSpanClick}
|
|
418
|
+
/>
|
|
419
|
+
)}
|
|
420
|
+
</ScrollArea>
|
|
421
|
+
</div>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Default sample trace for demo
|
|
426
|
+
export const defaultTraceSpans: TraceSpan[] = [
|
|
427
|
+
{
|
|
428
|
+
id: "span-1",
|
|
429
|
+
traceId: "abc123def456",
|
|
430
|
+
operationName: "HTTP GET /api/users",
|
|
431
|
+
serviceName: "api-gateway",
|
|
432
|
+
startTime: 0,
|
|
433
|
+
duration: 245,
|
|
434
|
+
status: "ok",
|
|
435
|
+
children: [
|
|
436
|
+
{
|
|
437
|
+
id: "span-2",
|
|
438
|
+
traceId: "abc123def456",
|
|
439
|
+
parentId: "span-1",
|
|
440
|
+
operationName: "authenticate",
|
|
441
|
+
serviceName: "auth-service",
|
|
442
|
+
startTime: 5,
|
|
443
|
+
duration: 35,
|
|
444
|
+
status: "ok",
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: "span-3",
|
|
448
|
+
traceId: "abc123def456",
|
|
449
|
+
parentId: "span-1",
|
|
450
|
+
operationName: "SELECT * FROM users",
|
|
451
|
+
serviceName: "user-service",
|
|
452
|
+
startTime: 45,
|
|
453
|
+
duration: 120,
|
|
454
|
+
status: "ok",
|
|
455
|
+
children: [
|
|
456
|
+
{
|
|
457
|
+
id: "span-4",
|
|
458
|
+
traceId: "abc123def456",
|
|
459
|
+
parentId: "span-3",
|
|
460
|
+
operationName: "PostgreSQL Query",
|
|
461
|
+
serviceName: "postgres",
|
|
462
|
+
startTime: 50,
|
|
463
|
+
duration: 95,
|
|
464
|
+
status: "ok",
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
id: "span-5",
|
|
470
|
+
traceId: "abc123def456",
|
|
471
|
+
parentId: "span-1",
|
|
472
|
+
operationName: "cache.get",
|
|
473
|
+
serviceName: "redis",
|
|
474
|
+
startTime: 170,
|
|
475
|
+
duration: 8,
|
|
476
|
+
status: "ok",
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "span-6",
|
|
480
|
+
traceId: "abc123def456",
|
|
481
|
+
parentId: "span-1",
|
|
482
|
+
operationName: "serialize response",
|
|
483
|
+
serviceName: "api-gateway",
|
|
484
|
+
startTime: 185,
|
|
485
|
+
duration: 55,
|
|
486
|
+
status: "ok",
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
]
|