nuxt-devtools-observatory 0.1.28 → 0.1.31

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 (48) hide show
  1. package/README.md +93 -11
  2. package/client/.env +2 -1
  3. package/client/.env.example +2 -1
  4. package/client/dist/assets/index-BuMXDBO9.js +17 -0
  5. package/client/dist/assets/index-CwcspZ6w.css +1 -0
  6. package/client/dist/index.html +2 -2
  7. package/client/src/App.vue +4 -0
  8. package/client/src/components/Flamegraph.vue +443 -0
  9. package/client/src/components/SpanInspector.vue +446 -0
  10. package/client/src/components/TraceFilter.vue +344 -0
  11. package/client/src/components/WaterfallView.vue +443 -0
  12. package/client/src/composables/useResizablePane.ts +65 -0
  13. package/client/src/composables/useTraceFilter.ts +164 -0
  14. package/client/src/stores/observatory.ts +16 -2
  15. package/client/src/style.css +203 -28
  16. package/client/src/views/ComposableTracker.vue +324 -259
  17. package/client/src/views/FetchDashboard.vue +104 -133
  18. package/client/src/views/ProvideInjectGraph.vue +99 -109
  19. package/client/src/views/RenderHeatmap.vue +191 -147
  20. package/client/src/views/TraceViewer.vue +599 -0
  21. package/client/src/views/TransitionTimeline.vue +167 -137
  22. package/client/tsconfig.json +3 -1
  23. package/client/vite.config.ts +8 -0
  24. package/dist/module.d.mts +5 -0
  25. package/dist/module.json +1 -1
  26. package/dist/module.mjs +186 -200
  27. package/dist/runtime/composables/render-registry.js +66 -110
  28. package/dist/runtime/composables/transition-registry.js +103 -28
  29. package/dist/runtime/instrumentation/asyncData.d.ts +9 -0
  30. package/dist/runtime/instrumentation/asyncData.js +49 -0
  31. package/dist/runtime/instrumentation/component.d.ts +2 -0
  32. package/dist/runtime/instrumentation/component.js +126 -0
  33. package/dist/runtime/instrumentation/fetch.d.ts +2 -0
  34. package/dist/runtime/instrumentation/fetch.js +89 -0
  35. package/dist/runtime/instrumentation/route.d.ts +6 -0
  36. package/dist/runtime/instrumentation/route.js +41 -0
  37. package/dist/runtime/plugin.js +39 -3
  38. package/dist/runtime/tracing/context.d.ts +9 -0
  39. package/dist/runtime/tracing/context.js +15 -0
  40. package/dist/runtime/tracing/trace.d.ts +25 -0
  41. package/dist/runtime/tracing/trace.js +0 -0
  42. package/dist/runtime/tracing/traceStore.d.ts +39 -0
  43. package/dist/runtime/tracing/traceStore.js +101 -0
  44. package/dist/runtime/tracing/tracing.d.ts +27 -0
  45. package/dist/runtime/tracing/tracing.js +48 -0
  46. package/package.json +9 -6
  47. package/client/dist/assets/index-DXCGQOSF.js +0 -17
  48. package/client/dist/assets/index-htI4WwBU.css +0 -1
@@ -0,0 +1,344 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { TraceEntry } from '@observatory/types/snapshot'
4
+ import { getSpanTypesFromTraces } from '@observatory-client/composables/useTraceFilter'
5
+
6
+ interface Props {
7
+ traces: TraceEntry[]
8
+ searchQuery: string
9
+ selectedSpanTypes: Set<string>
10
+ minDuration: number
11
+ maxDuration: number
12
+ routeFilter: string
13
+ hasActiveFilters: boolean
14
+ }
15
+
16
+ const props = defineProps<Props>()
17
+
18
+ const emit = defineEmits<{
19
+ 'update:search': [value: string]
20
+ 'update:types': [value: Set<string>]
21
+ 'update:min-duration': [value: number]
22
+ 'update:max-duration': [value: number]
23
+ 'update:route': [value: string]
24
+ 'clear-filters': []
25
+ }>()
26
+
27
+ const availableSpanTypes = computed(() => getSpanTypesFromTraces(props.traces))
28
+
29
+ const maxTraceDuration = computed(() => {
30
+ let max = 0
31
+
32
+ for (const trace of props.traces) {
33
+ if (trace.durationMs && trace.durationMs > max) {
34
+ max = trace.durationMs
35
+ }
36
+ }
37
+
38
+ return Math.ceil(max / 10) * 10 || 1000
39
+ })
40
+
41
+ function handleSearchChange(value: string) {
42
+ emit('update:search', value)
43
+ }
44
+
45
+ function getInputValue(event: Event): string {
46
+ return (event.target as HTMLInputElement).value
47
+ }
48
+
49
+ function handleTypeToggle(type: string) {
50
+ const nextTypes = new Set(props.selectedSpanTypes)
51
+
52
+ if (nextTypes.has(type)) {
53
+ nextTypes.delete(type)
54
+ } else {
55
+ nextTypes.add(type)
56
+ }
57
+
58
+ emit('update:types', nextTypes)
59
+ }
60
+
61
+ function handleMinDurationChange(value: string) {
62
+ const num = Math.max(0, parseInt(value) || 0)
63
+ emit('update:min-duration', num)
64
+ }
65
+
66
+ function handleMaxDurationChange(value: string) {
67
+ const num = Math.max(0, parseInt(value) || Infinity)
68
+ emit('update:max-duration', num)
69
+ }
70
+
71
+ function handleRouteChange(value: string) {
72
+ emit('update:route', value)
73
+ }
74
+
75
+ function handleClearFilters() {
76
+ emit('clear-filters')
77
+ }
78
+
79
+ function getSpanTypeColor(type: string): string {
80
+ const colors: Record<string, string> = {
81
+ fetch: '#3b82f6',
82
+ composable: '#a855f7',
83
+ component: '#22c55e',
84
+ navigation: '#eab308',
85
+ render: '#f97316',
86
+ transition: '#ec4899',
87
+ }
88
+
89
+ return colors[type] || '#6b7280'
90
+ }
91
+ </script>
92
+
93
+ <template>
94
+ <div class="trace-filter">
95
+ <!-- Search bar -->
96
+ <div class="trace-filter__section">
97
+ <label class="trace-filter__label">Search</label>
98
+ <input
99
+ :value="props.searchQuery"
100
+ type="text"
101
+ class="trace-filter__search-input"
102
+ placeholder="Search traces, spans, endpoints…"
103
+ @input="handleSearchChange(getInputValue($event))"
104
+ />
105
+ </div>
106
+
107
+ <!-- Span type filter -->
108
+ <div v-if="availableSpanTypes.length > 0" class="trace-filter__section">
109
+ <label class="trace-filter__label">Span Type</label>
110
+ <div class="trace-filter__type-filters">
111
+ <button
112
+ v-for="type in availableSpanTypes"
113
+ :key="type"
114
+ :class="{ 'trace-filter__type-badge--selected': props.selectedSpanTypes.has(type) }"
115
+ class="trace-filter__type-badge"
116
+ :title="`Filter by ${type} spans`"
117
+ @click="handleTypeToggle(type)"
118
+ >
119
+ <span class="trace-filter__type-dot" :style="{ backgroundColor: getSpanTypeColor(type) }"></span>
120
+ {{ type }}
121
+ </button>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Duration filter -->
126
+ <div class="trace-filter__section">
127
+ <label class="trace-filter__label">Duration (ms)</label>
128
+ <div class="trace-filter__duration-inputs">
129
+ <input
130
+ :value="props.minDuration"
131
+ type="number"
132
+ min="0"
133
+ class="trace-filter__duration-input"
134
+ placeholder="Min"
135
+ @input="handleMinDurationChange(getInputValue($event))"
136
+ />
137
+ <span class="trace-filter__duration-separator">–</span>
138
+ <input
139
+ :value="props.maxDuration === Infinity ? '' : props.maxDuration"
140
+ type="number"
141
+ :min="props.minDuration"
142
+ :max="maxTraceDuration"
143
+ class="trace-filter__duration-input"
144
+ placeholder="Max"
145
+ @input="handleMaxDurationChange(getInputValue($event))"
146
+ />
147
+ </div>
148
+ <input
149
+ v-if="maxTraceDuration"
150
+ :value="props.maxDuration === Infinity ? maxTraceDuration : props.maxDuration"
151
+ type="range"
152
+ :min="0"
153
+ :max="maxTraceDuration"
154
+ class="trace-filter__duration-slider"
155
+ @input="handleMaxDurationChange(getInputValue($event))"
156
+ />
157
+ </div>
158
+
159
+ <!-- Route filter -->
160
+ <div class="trace-filter__section">
161
+ <label class="trace-filter__label">Route</label>
162
+ <input
163
+ :value="props.routeFilter"
164
+ type="text"
165
+ class="trace-filter__search-input"
166
+ placeholder="Filter by route…"
167
+ @input="handleRouteChange(getInputValue($event))"
168
+ />
169
+ </div>
170
+
171
+ <!-- Clear filters button -->
172
+ <button v-if="props.hasActiveFilters" class="trace-filter__clear-btn" @click="handleClearFilters">Clear Filters</button>
173
+ </div>
174
+ </template>
175
+
176
+ <style scoped>
177
+ .trace-filter {
178
+ display: flex;
179
+ flex-direction: column;
180
+ gap: 16px;
181
+ padding: 12px;
182
+ background: var(--bg-secondary);
183
+ border-bottom: 1px solid var(--border);
184
+ }
185
+
186
+ .trace-filter__section {
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: 6px;
190
+ }
191
+
192
+ .trace-filter__label {
193
+ font-size: 11px;
194
+ font-weight: 600;
195
+ color: var(--text-secondary);
196
+ text-transform: uppercase;
197
+ letter-spacing: 0.5px;
198
+ }
199
+
200
+ .trace-filter__search-input {
201
+ padding: 6px 8px;
202
+ border: 1px solid var(--border);
203
+ border-radius: 4px;
204
+ background: var(--bg);
205
+ color: var(--text);
206
+ font-family: var(--mono);
207
+ font-size: 11px;
208
+ transition: border-color 0.2s;
209
+ }
210
+
211
+ .trace-filter__search-input:focus {
212
+ outline: none;
213
+ border-color: var(--accent);
214
+ }
215
+
216
+ .trace-filter__type-filters {
217
+ display: flex;
218
+ flex-wrap: wrap;
219
+ gap: 6px;
220
+ }
221
+
222
+ .trace-filter__type-badge {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 4px;
226
+ padding: 4px 8px;
227
+ border: 1px solid var(--border);
228
+ border-radius: 3px;
229
+ background: var(--bg);
230
+ color: var(--text-secondary);
231
+ font-size: 10px;
232
+ font-weight: 500;
233
+ cursor: pointer;
234
+ transition: all 0.2s;
235
+ }
236
+
237
+ .trace-filter__type-badge:hover {
238
+ background: var(--bg-tertiary);
239
+ border-color: var(--accent);
240
+ }
241
+
242
+ .trace-filter__type-badge--selected {
243
+ background: var(--accent-bg);
244
+ border-color: var(--accent);
245
+ color: var(--accent);
246
+ }
247
+
248
+ .trace-filter__type-dot {
249
+ display: inline-block;
250
+ width: 6px;
251
+ height: 6px;
252
+ border-radius: 1px;
253
+ }
254
+
255
+ .trace-filter__duration-inputs {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 6px;
259
+ }
260
+
261
+ .trace-filter__duration-input {
262
+ flex: 1;
263
+ min-width: 0;
264
+ padding: 6px 8px;
265
+ border: 1px solid var(--border);
266
+ border-radius: 4px;
267
+ background: var(--bg);
268
+ color: var(--text);
269
+ font-family: var(--mono);
270
+ font-size: 11px;
271
+ transition: border-color 0.2s;
272
+ }
273
+
274
+ .trace-filter__duration-input:focus {
275
+ outline: none;
276
+ border-color: var(--accent);
277
+ }
278
+
279
+ .trace-filter__duration-input::placeholder {
280
+ color: var(--text-secondary);
281
+ }
282
+
283
+ .trace-filter__duration-separator {
284
+ color: var(--text-secondary);
285
+ font-size: 10px;
286
+ }
287
+
288
+ .trace-filter__duration-slider {
289
+ width: 100%;
290
+ height: 4px;
291
+ border-radius: 2px;
292
+ background: var(--bg);
293
+ outline: none;
294
+ -webkit-appearance: none;
295
+ appearance: none;
296
+ }
297
+
298
+ .trace-filter__duration-slider::-webkit-slider-thumb {
299
+ -webkit-appearance: none;
300
+ appearance: none;
301
+ width: 12px;
302
+ height: 12px;
303
+ border-radius: 50%;
304
+ background: var(--accent);
305
+ cursor: pointer;
306
+ transition: background 0.2s;
307
+ }
308
+
309
+ .trace-filter__duration-slider::-webkit-slider-thumb:hover {
310
+ background: var(--accent-bright);
311
+ }
312
+
313
+ .trace-filter__duration-slider::-moz-range-thumb {
314
+ width: 12px;
315
+ height: 12px;
316
+ border-radius: 50%;
317
+ background: var(--accent);
318
+ border: none;
319
+ cursor: pointer;
320
+ transition: background 0.2s;
321
+ }
322
+
323
+ .trace-filter__duration-slider::-moz-range-thumb:hover {
324
+ background: var(--accent-bright);
325
+ }
326
+
327
+ .trace-filter__clear-btn {
328
+ padding: 6px 12px;
329
+ border: 1px solid var(--border);
330
+ border-radius: 4px;
331
+ background: var(--bg-tertiary);
332
+ color: var(--text-secondary);
333
+ font-size: 11px;
334
+ font-weight: 600;
335
+ cursor: pointer;
336
+ transition: all 0.2s;
337
+ }
338
+
339
+ .trace-filter__clear-btn:hover {
340
+ background: var(--bg);
341
+ border-color: var(--accent);
342
+ color: var(--accent);
343
+ }
344
+ </style>