nuxt-devtools-observatory 0.1.30 → 0.1.32

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 (51) hide show
  1. package/README.md +117 -30
  2. package/client/.env.example +2 -1
  3. package/client/dist/assets/index-5Wl1XYRH.js +17 -0
  4. package/client/dist/assets/index-DT_QUiIh.css +1 -0
  5. package/client/dist/index.html +2 -2
  6. package/client/src/App.vue +4 -0
  7. package/client/src/components/Flamegraph.vue +442 -0
  8. package/client/src/components/SpanInspector.vue +446 -0
  9. package/client/src/components/TraceFilter.vue +342 -0
  10. package/client/src/components/WaterfallView.vue +443 -0
  11. package/client/src/composables/composable-search.ts +124 -0
  12. package/client/src/composables/trace-render-aggregation.ts +254 -0
  13. package/client/src/composables/useExportImport.ts +63 -0
  14. package/client/src/composables/useTraceFilter.ts +160 -0
  15. package/client/src/stores/observatory.ts +13 -1
  16. package/client/src/views/ComposableTracker.vue +65 -30
  17. package/client/src/views/RenderHeatmap.vue +63 -1
  18. package/client/src/views/TraceViewer.vue +1212 -0
  19. package/client/src/views/TransitionTimeline.vue +1 -6
  20. package/dist/module.d.mts +5 -0
  21. package/dist/module.json +1 -1
  22. package/dist/module.mjs +31 -45
  23. package/dist/runtime/composables/composable-registry.d.ts +19 -0
  24. package/dist/runtime/composables/composable-registry.js +63 -5
  25. package/dist/runtime/composables/render-registry.js +74 -110
  26. package/dist/runtime/composables/transition-registry.js +103 -28
  27. package/dist/runtime/instrumentation/asyncData.d.ts +9 -0
  28. package/dist/runtime/instrumentation/asyncData.js +49 -0
  29. package/dist/runtime/instrumentation/component.d.ts +2 -0
  30. package/dist/runtime/instrumentation/component.js +126 -0
  31. package/dist/runtime/instrumentation/fetch.d.ts +2 -0
  32. package/dist/runtime/instrumentation/fetch.js +89 -0
  33. package/dist/runtime/instrumentation/route.d.ts +6 -0
  34. package/dist/runtime/instrumentation/route.js +41 -0
  35. package/dist/runtime/nitro/fetch-capture.d.ts +1 -2
  36. package/dist/runtime/nitro/fetch-capture.js +85 -7
  37. package/dist/runtime/nitro/ssr-trace-store.d.ts +85 -0
  38. package/dist/runtime/nitro/ssr-trace-store.js +84 -0
  39. package/dist/runtime/plugin.js +82 -2
  40. package/dist/runtime/tracing/context.d.ts +9 -0
  41. package/dist/runtime/tracing/context.js +15 -0
  42. package/dist/runtime/tracing/trace.d.ts +25 -0
  43. package/dist/runtime/tracing/trace.js +0 -0
  44. package/dist/runtime/tracing/traceStore.d.ts +39 -0
  45. package/dist/runtime/tracing/traceStore.js +101 -0
  46. package/dist/runtime/tracing/tracing.d.ts +27 -0
  47. package/dist/runtime/tracing/tracing.js +48 -0
  48. package/package.json +5 -1
  49. package/client/.env +0 -16
  50. package/client/dist/assets/index-BCaKoHBH.js +0 -17
  51. package/client/dist/assets/index-BmGW_M3W.css +0 -1
@@ -0,0 +1,446 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { TraceEntry, TraceSpan } from '@observatory/types/snapshot'
4
+
5
+ interface Props {
6
+ trace: TraceEntry
7
+ span?: TraceSpan
8
+ }
9
+
10
+ const props = defineProps<Props>()
11
+
12
+ const emit = defineEmits<{
13
+ 'select-span': [span: TraceSpan]
14
+ }>()
15
+
16
+ const parentSpan = computed(() => {
17
+ if (!props.span || !props.span.parentSpanId) {
18
+ return null
19
+ }
20
+
21
+ return props.trace.spans.find((s) => s.id === props.span!.parentSpanId)
22
+ })
23
+
24
+ const childSpans = computed(() => {
25
+ if (!props.span) {
26
+ return []
27
+ }
28
+
29
+ return props.trace.spans.filter((s) => s.parentSpanId === props.span!.id).sort((a, b) => a.startTime - b.startTime)
30
+ })
31
+
32
+ function formatDuration(durationMs?: number) {
33
+ if (!durationMs) {
34
+ return '0ms'
35
+ }
36
+
37
+ if (durationMs < 1) {
38
+ return `${(durationMs * 1000).toFixed(1)}μs`
39
+ }
40
+
41
+ if (durationMs < 1000) {
42
+ return `${durationMs.toFixed(1)}ms`
43
+ }
44
+
45
+ return `${(durationMs / 1000).toFixed(2)}s`
46
+ }
47
+
48
+ function formatRelativeTime(timestamp: number) {
49
+ const relative = timestamp - props.trace.startTime
50
+
51
+ if (Math.abs(relative) < 1) {
52
+ return `+${(relative * 1000).toFixed(1)}μs`
53
+ }
54
+
55
+ if (Math.abs(relative) < 1000) {
56
+ return `+${relative.toFixed(3)}ms`
57
+ }
58
+
59
+ return `+${(relative / 1000).toFixed(3)}s`
60
+ }
61
+
62
+ function getDisplayEndTime(span: TraceSpan) {
63
+ if (span.endTime !== undefined) {
64
+ return span.endTime
65
+ }
66
+
67
+ if (span.durationMs !== undefined) {
68
+ return span.startTime + span.durationMs
69
+ }
70
+
71
+ return span.startTime
72
+ }
73
+
74
+ function getStatusColor(status: string): string {
75
+ const colors: Record<string, string> = {
76
+ ok: '#22c55e',
77
+ error: '#ef4444',
78
+ cancelled: '#6b7280',
79
+ active: '#eab308',
80
+ }
81
+
82
+ return colors[status] || '#6b7280'
83
+ }
84
+
85
+ function getSpanColorClass(type: string) {
86
+ const colors: Record<string, string> = {
87
+ fetch: 'bg-blue-500',
88
+ composable: 'bg-purple-500',
89
+ component: 'bg-green-500',
90
+ navigation: 'bg-yellow-500',
91
+ render: 'bg-orange-500',
92
+ transition: 'bg-pink-500',
93
+ }
94
+
95
+ return colors[type] || 'bg-gray-500'
96
+ }
97
+ </script>
98
+
99
+ <template>
100
+ <div class="span-inspector">
101
+ <div v-if="!span" class="span-inspector__empty">
102
+ <div class="span-inspector__empty-icon">📊</div>
103
+ <p>Select a span to view details</p>
104
+ </div>
105
+
106
+ <div v-else class="span-inspector__content">
107
+ <div class="span-inspector__header">
108
+ <div class="span-inspector__title">
109
+ <span :class="getSpanColorClass(span.type)" class="span-inspector__type-badge"></span>
110
+ {{ span.name }}
111
+ </div>
112
+ <div class="span-inspector__status" :style="{ color: getStatusColor(span.status) }">
113
+ {{ span.status }}
114
+ </div>
115
+ </div>
116
+
117
+ <div class="span-inspector__section">
118
+ <div class="span-inspector__section-title">Timing</div>
119
+ <div class="span-inspector__property-grid">
120
+ <div class="span-inspector__property">
121
+ <span class="span-inspector__label">Duration</span>
122
+ <span class="span-inspector__value mono">{{ formatDuration(span.durationMs) }}</span>
123
+ </div>
124
+ <div class="span-inspector__property">
125
+ <span class="span-inspector__label">Start Time</span>
126
+ <span class="span-inspector__value mono">{{ formatRelativeTime(span.startTime) }}</span>
127
+ </div>
128
+ <div class="span-inspector__property">
129
+ <span class="span-inspector__label">End Time</span>
130
+ <span class="span-inspector__value mono">{{ formatRelativeTime(getDisplayEndTime(span)) }}</span>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="span-inspector__section">
136
+ <div class="span-inspector__section-title">Properties</div>
137
+ <div class="span-inspector__property-grid">
138
+ <div class="span-inspector__property">
139
+ <span class="span-inspector__label">ID</span>
140
+ <span class="span-inspector__value mono text-xs">{{ span.id }}</span>
141
+ </div>
142
+ <div class="span-inspector__property">
143
+ <span class="span-inspector__label">Type</span>
144
+ <span class="span-inspector__value mono">{{ span.type }}</span>
145
+ </div>
146
+ <div class="span-inspector__property">
147
+ <span class="span-inspector__label">Trace ID</span>
148
+ <span class="span-inspector__value mono text-xs">{{ span.traceId }}</span>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div v-if="parentSpan" class="span-inspector__section">
154
+ <div class="span-inspector__section-title">Parent Span</div>
155
+ <div class="span-inspector__link" @click="emit('select-span', parentSpan)">
156
+ <span :class="getSpanColorClass(parentSpan.type)" class="span-inspector__link-badge"></span>
157
+ {{ parentSpan.name }}
158
+ </div>
159
+ </div>
160
+
161
+ <div v-if="childSpans.length > 0" class="span-inspector__section">
162
+ <div class="span-inspector__section-title">Child Spans ({{ childSpans.length }})</div>
163
+ <div class="span-inspector__child-list">
164
+ <div v-for="child in childSpans" :key="child.id" class="span-inspector__child-item" @click="emit('select-span', child)">
165
+ <span :class="getSpanColorClass(child.type)" class="span-inspector__child-badge"></span>
166
+ <span class="span-inspector__child-name">{{ child.name }}</span>
167
+ <span class="span-inspector__child-duration">{{ formatDuration(child.durationMs) }}</span>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <div v-if="span.metadata && Object.keys(span.metadata).length > 0" class="span-inspector__section">
173
+ <div class="span-inspector__section-title">Metadata</div>
174
+ <div class="span-inspector__metadata">
175
+ <div v-for="(value, key) in span.metadata" :key="key" class="span-inspector__metadata-item">
176
+ <span class="span-inspector__metadata-key">{{ key }}:</span>
177
+ <span class="span-inspector__metadata-value">
178
+ <code v-if="typeof value === 'object'" class="span-inspector__code">
179
+ {{ JSON.stringify(value, null, 2) }}
180
+ </code>
181
+ <span v-else class="span-inspector__code">{{ value }}</span>
182
+ </span>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </template>
189
+
190
+ <style scoped>
191
+ .span-inspector {
192
+ display: flex;
193
+ flex-direction: column;
194
+ height: 100%;
195
+ overflow: auto;
196
+ padding: 16px;
197
+ background: var(--bg);
198
+ }
199
+
200
+ .span-inspector__empty {
201
+ display: flex;
202
+ flex-direction: column;
203
+ align-items: center;
204
+ justify-content: center;
205
+ height: 100%;
206
+ color: var(--text-secondary);
207
+ font-size: 13px;
208
+ }
209
+
210
+ .span-inspector__empty-icon {
211
+ font-size: 48px;
212
+ margin-bottom: 12px;
213
+ opacity: 0.5;
214
+ }
215
+
216
+ .span-inspector__content {
217
+ display: flex;
218
+ flex-direction: column;
219
+ gap: 16px;
220
+ }
221
+
222
+ .span-inspector__header {
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: space-between;
226
+ padding-bottom: 12px;
227
+ border-bottom: 1px solid var(--border);
228
+ }
229
+
230
+ .span-inspector__title {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 8px;
234
+ font-size: 14px;
235
+ font-weight: 600;
236
+ color: var(--text);
237
+ flex: 1;
238
+ min-width: 0;
239
+ overflow: hidden;
240
+ text-overflow: ellipsis;
241
+ white-space: nowrap;
242
+ }
243
+
244
+ .span-inspector__type-badge {
245
+ display: inline-block;
246
+ width: 10px;
247
+ height: 10px;
248
+ border-radius: 2px;
249
+ flex-shrink: 0;
250
+ }
251
+
252
+ .span-inspector__status {
253
+ font-size: 11px;
254
+ font-weight: 600;
255
+ text-transform: uppercase;
256
+ letter-spacing: 0.5px;
257
+ flex-shrink: 0;
258
+ }
259
+
260
+ .span-inspector__section {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 8px;
264
+ }
265
+
266
+ .span-inspector__section-title {
267
+ font-size: 12px;
268
+ font-weight: 600;
269
+ color: var(--text-secondary);
270
+ text-transform: uppercase;
271
+ letter-spacing: 0.5px;
272
+ }
273
+
274
+ .span-inspector__property-grid {
275
+ display: flex;
276
+ flex-direction: column;
277
+ gap: 6px;
278
+ }
279
+
280
+ .span-inspector__property {
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: space-between;
284
+ gap: 12px;
285
+ font-size: 12px;
286
+ padding: 6px;
287
+ background: var(--bg-secondary);
288
+ border-radius: 3px;
289
+ }
290
+
291
+ .span-inspector__label {
292
+ color: var(--text-secondary);
293
+ font-weight: 500;
294
+ }
295
+
296
+ .span-inspector__value {
297
+ color: var(--text);
298
+ font-family: var(--mono);
299
+ overflow: hidden;
300
+ text-overflow: ellipsis;
301
+ white-space: nowrap;
302
+ flex-shrink: 0;
303
+ }
304
+
305
+ .span-inspector__link {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 6px;
309
+ padding: 8px;
310
+ background: var(--bg-secondary);
311
+ border: 1px solid var(--border);
312
+ border-radius: 3px;
313
+ cursor: pointer;
314
+ transition: background 0.2s;
315
+ }
316
+
317
+ .span-inspector__link:hover {
318
+ background: var(--bg-tertiary);
319
+ }
320
+
321
+ .span-inspector__link-badge {
322
+ display: inline-block;
323
+ width: 8px;
324
+ height: 8px;
325
+ border-radius: 2px;
326
+ flex-shrink: 0;
327
+ }
328
+
329
+ .span-inspector__child-list {
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 6px;
333
+ }
334
+
335
+ .span-inspector__child-item {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 6px;
339
+ padding: 6px 8px;
340
+ background: var(--bg-secondary);
341
+ border-radius: 3px;
342
+ cursor: pointer;
343
+ font-size: 12px;
344
+ transition: background 0.2s;
345
+ }
346
+
347
+ .span-inspector__child-item:hover {
348
+ background: var(--bg-tertiary);
349
+ }
350
+
351
+ .span-inspector__child-badge {
352
+ display: inline-block;
353
+ width: 8px;
354
+ height: 8px;
355
+ border-radius: 2px;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .span-inspector__child-name {
360
+ flex: 1;
361
+ min-width: 0;
362
+ overflow: hidden;
363
+ text-overflow: ellipsis;
364
+ white-space: nowrap;
365
+ color: var(--text);
366
+ }
367
+
368
+ .span-inspector__child-duration {
369
+ color: var(--text-secondary);
370
+ font-family: var(--mono);
371
+ font-size: 11px;
372
+ flex-shrink: 0;
373
+ }
374
+
375
+ .span-inspector__metadata {
376
+ display: flex;
377
+ flex-direction: column;
378
+ gap: 8px;
379
+ }
380
+
381
+ .span-inspector__metadata-item {
382
+ display: flex;
383
+ flex-direction: column;
384
+ gap: 4px;
385
+ font-size: 11px;
386
+ }
387
+
388
+ .span-inspector__metadata-key {
389
+ color: var(--text-secondary);
390
+ font-weight: 600;
391
+ }
392
+
393
+ .span-inspector__metadata-value {
394
+ color: var(--text);
395
+ }
396
+
397
+ .span-inspector__code {
398
+ display: block;
399
+ padding: 6px 8px;
400
+ background: var(--bg-secondary);
401
+ border-radius: 3px;
402
+ font-family: var(--mono);
403
+ font-size: 10px;
404
+ overflow: auto;
405
+ max-height: 200px;
406
+ white-space: pre-wrap;
407
+ word-break: break-word;
408
+ }
409
+
410
+ /* Color utilities */
411
+ .bg-blue-500 {
412
+ background-color: #3b82f6;
413
+ }
414
+
415
+ .bg-purple-500 {
416
+ background-color: #a855f7;
417
+ }
418
+
419
+ .bg-green-500 {
420
+ background-color: #22c55e;
421
+ }
422
+
423
+ .bg-yellow-500 {
424
+ background-color: #eab308;
425
+ }
426
+
427
+ .bg-orange-500 {
428
+ background-color: #f97316;
429
+ }
430
+
431
+ .bg-pink-500 {
432
+ background-color: #ec4899;
433
+ }
434
+
435
+ .bg-gray-500 {
436
+ background-color: #6b7280;
437
+ }
438
+
439
+ .mono {
440
+ font-family: var(--mono);
441
+ }
442
+
443
+ .text-xs {
444
+ font-size: 10px;
445
+ }
446
+ </style>