spider-watch 0.1.0

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 (67) hide show
  1. package/README.md +274 -0
  2. package/dist/config/defaults.d.ts +5 -0
  3. package/dist/config/defaults.d.ts.map +1 -0
  4. package/dist/config/defaults.js +70 -0
  5. package/dist/config/env-loader.d.ts +3 -0
  6. package/dist/config/env-loader.d.ts.map +1 -0
  7. package/dist/config/env-loader.js +29 -0
  8. package/dist/config/validate.d.ts +3 -0
  9. package/dist/config/validate.d.ts.map +1 -0
  10. package/dist/config/validate.js +19 -0
  11. package/dist/create-monitoring.d.ts +3 -0
  12. package/dist/create-monitoring.d.ts.map +1 -0
  13. package/dist/create-monitoring.js +73 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +2 -0
  17. package/dist/middleware/auth-basic.d.ts +4 -0
  18. package/dist/middleware/auth-basic.d.ts.map +1 -0
  19. package/dist/middleware/auth-basic.js +34 -0
  20. package/dist/middleware/capture.d.ts +7 -0
  21. package/dist/middleware/capture.d.ts.map +1 -0
  22. package/dist/middleware/capture.js +68 -0
  23. package/dist/middleware/error.d.ts +4 -0
  24. package/dist/middleware/error.d.ts.map +1 -0
  25. package/dist/middleware/error.js +27 -0
  26. package/dist/repository/monitoring-repository.d.ts +4 -0
  27. package/dist/repository/monitoring-repository.d.ts.map +1 -0
  28. package/dist/repository/monitoring-repository.js +239 -0
  29. package/dist/repository/sqlite-db.d.ts +7 -0
  30. package/dist/repository/sqlite-db.d.ts.map +1 -0
  31. package/dist/repository/sqlite-db.js +91 -0
  32. package/dist/router/async-handler.d.ts +3 -0
  33. package/dist/router/async-handler.d.ts.map +1 -0
  34. package/dist/router/async-handler.js +5 -0
  35. package/dist/router/monitoring-router.d.ts +4 -0
  36. package/dist/router/monitoring-router.d.ts.map +1 -0
  37. package/dist/router/monitoring-router.js +109 -0
  38. package/dist/services/console-hook.d.ts +12 -0
  39. package/dist/services/console-hook.d.ts.map +1 -0
  40. package/dist/services/console-hook.js +61 -0
  41. package/dist/services/context.d.ts +7 -0
  42. package/dist/services/context.d.ts.map +1 -0
  43. package/dist/services/context.js +22 -0
  44. package/dist/services/http-client.d.ts +7 -0
  45. package/dist/services/http-client.d.ts.map +1 -0
  46. package/dist/services/http-client.js +56 -0
  47. package/dist/services/instrumentation.d.ts +8 -0
  48. package/dist/services/instrumentation.d.ts.map +1 -0
  49. package/dist/services/instrumentation.js +9 -0
  50. package/dist/services/recorder.d.ts +5 -0
  51. package/dist/services/recorder.d.ts.map +1 -0
  52. package/dist/services/recorder.js +23 -0
  53. package/dist/services/retention.d.ts +12 -0
  54. package/dist/services/retention.d.ts.map +1 -0
  55. package/dist/services/retention.js +36 -0
  56. package/dist/services/runtime.d.ts +3 -0
  57. package/dist/services/runtime.d.ts.map +1 -0
  58. package/dist/services/runtime.js +9 -0
  59. package/dist/types.d.ts +133 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +1 -0
  62. package/dist/ui/app.js +382 -0
  63. package/dist/ui/index.html +610 -0
  64. package/dist/utils/masking.d.ts +7 -0
  65. package/dist/utils/masking.d.ts.map +1 -0
  66. package/dist/utils/masking.js +79 -0
  67. package/package.json +71 -0
@@ -0,0 +1,610 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Monitoring Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ darkMode: "class",
11
+ theme: {
12
+ extend: {
13
+ fontFamily: { mono: ["ui-monospace", "SFMono-Regular", "monospace"] },
14
+ },
15
+ },
16
+ };
17
+ </script>
18
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
19
+ <style>
20
+ [x-cloak] {
21
+ display: none !important;
22
+ }
23
+ .scroll-x {
24
+ overflow-x: auto;
25
+ }
26
+ pre {
27
+ white-space: pre-wrap;
28
+ word-break: break-all;
29
+ }
30
+
31
+ html:not(.dark) body {
32
+ background-color: #f3f4f6;
33
+ color: #111827;
34
+ }
35
+
36
+ html:not(.dark) .bg-gray-950,
37
+ html:not(.dark) .bg-gray-900 {
38
+ background-color: #ffffff !important;
39
+ }
40
+
41
+ html:not(.dark) .bg-gray-800 {
42
+ background-color: #f3f4f6 !important;
43
+ }
44
+
45
+ html:not(.dark) .border-gray-800,
46
+ html:not(.dark) .border-gray-700 {
47
+ border-color: #e5e7eb !important;
48
+ }
49
+
50
+ html:not(.dark) .text-gray-100,
51
+ html:not(.dark) .text-white,
52
+ html:not(.dark) .text-gray-200,
53
+ html:not(.dark) .text-gray-300,
54
+ html:not(.dark) .text-gray-400,
55
+ html:not(.dark) .text-gray-500 {
56
+ color: #1f2937 !important;
57
+ }
58
+ </style>
59
+ </head>
60
+ <body
61
+ class="bg-gray-950 text-gray-100 min-h-screen font-sans"
62
+ x-data="monitoringApp()"
63
+ x-init="init()"
64
+ x-cloak
65
+ >
66
+ <div
67
+ x-show="feedback.visible"
68
+ class="fixed right-4 top-4 z-50 px-4 py-2 rounded-md shadow-lg text-sm"
69
+ :class="toastClass()"
70
+ x-text="feedback.message"
71
+ ></div>
72
+
73
+ <!-- Header -->
74
+ <header
75
+ class="bg-gray-900 border-b border-gray-800 px-6 py-4 flex items-center justify-between"
76
+ >
77
+ <div class="flex items-center gap-3">
78
+ <span class="text-2xl">📡</span>
79
+ <h1 class="text-lg font-semibold text-white">Spider Watch</h1>
80
+ </div>
81
+ <div class="flex items-center gap-3">
82
+ <span
83
+ class="text-xs text-gray-500"
84
+ x-text="lastRefreshed ? 'Last refresh: ' + lastRefreshed : ''"
85
+ ></span>
86
+ <button
87
+ @click="toggleThemeMode()"
88
+ class="text-xs bg-gray-700 hover:bg-gray-600 px-3 py-1.5 rounded-md transition"
89
+ x-text="isDarkMode ? '☀' : '🌙'"
90
+ ></button>
91
+ <button
92
+ @click="toggleAutoRefresh()"
93
+ class="text-xs px-3 py-1.5 rounded-md transition"
94
+ :class="refreshState.enabled ? 'bg-green-700 hover:bg-green-600 text-white' : 'bg-gray-700 hover:bg-gray-600 text-white'"
95
+ x-text="refreshState.enabled ? 'Auto-refresh' : 'Auto-refresh'"
96
+ ></button>
97
+ <button
98
+ @click="loadEvents()"
99
+ class="text-xs bg-gray-700 hover:bg-gray-600 px-3 py-1.5 rounded-md transition"
100
+ >
101
+ ⟳ Refresh
102
+ </button>
103
+ <button
104
+ @click="openDeleteConfirmation()"
105
+ class="text-xs bg-red-700 hover:bg-red-600 px-3 py-1.5 rounded-md transition text-white"
106
+ >
107
+ 🗑 Delete data
108
+ </button>
109
+ </div>
110
+ </header>
111
+
112
+ <div class="flex h-[calc(100vh-65px)]">
113
+ <!-- Left pane: list -->
114
+ <div class="flex flex-col w-full" :class="selectedEvent ? 'lg:w-1/2' : 'w-full'">
115
+ <!-- Filters bar -->
116
+ <div
117
+ class="bg-gray-900 border-b border-gray-800 px-4 py-3 flex flex-wrap gap-2 items-center"
118
+ >
119
+ <input
120
+ type="text"
121
+ placeholder="Search (path, IP, correlationId…)"
122
+ x-model="filters.q"
123
+ @input.debounce.400ms="filters.page = 1; loadEvents()"
124
+ class="bg-gray-800 border border-gray-700 rounded px-3 py-1.5 text-sm flex-1 min-w-48 placeholder-gray-500 focus:outline-none focus:border-blue-500"
125
+ />
126
+ <select
127
+ x-model="filters.type"
128
+ @change="filters.page = 1; loadEvents()"
129
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-blue-500"
130
+ >
131
+ <option value="">All types</option>
132
+ <option value="REQUEST">REQUEST</option>
133
+ <option value="EXCEPTION">EXCEPTION</option>
134
+ <option value="EXTERNAL_HTTP">EXTERNAL_HTTP</option>
135
+ <option value="LOG">LOG</option>
136
+ <option value="SCHEDULED_TASK">SCHEDULED_TASK</option>
137
+ <option value="DB_QUERY">DB_QUERY</option>
138
+ <option value="JOB">JOB</option>
139
+ <option value="NOTIFICATION">NOTIFICATION</option>
140
+ <option value="MAIL">MAIL</option>
141
+ <option value="CACHE_OP">CACHE_OP</option>
142
+ </select>
143
+ <select
144
+ x-model="filters.method"
145
+ @change="filters.page = 1; loadEvents()"
146
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-blue-500"
147
+ >
148
+ <option value="">All methods</option>
149
+ <option>GET</option>
150
+ <option>POST</option>
151
+ <option>PUT</option>
152
+ <option>PATCH</option>
153
+ <option>DELETE</option>
154
+ </select>
155
+ <select
156
+ x-model="filters.status"
157
+ @change="filters.page = 1; loadEvents()"
158
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-blue-500"
159
+ >
160
+ <option value="">All statuses</option>
161
+ <option value="success">Success</option>
162
+ <option value="error">Error</option>
163
+ </select>
164
+ <input
165
+ type="datetime-local"
166
+ x-model="filters.from"
167
+ @change="filters.page = 1; loadEvents()"
168
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-blue-500"
169
+ />
170
+ <input
171
+ type="datetime-local"
172
+ x-model="filters.to"
173
+ @change="filters.page = 1; loadEvents()"
174
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-blue-500"
175
+ />
176
+ <button
177
+ @click="clearFilters()"
178
+ class="text-xs text-gray-400 hover:text-white px-2 py-1.5"
179
+ >
180
+ ✕ Clear
181
+ </button>
182
+ </div>
183
+
184
+ <!-- Stats bar -->
185
+ <div
186
+ class="bg-gray-900 border-b border-gray-800 px-4 py-2 flex items-center gap-4 text-xs text-gray-400"
187
+ >
188
+ <span x-show="!loading && total !== null" x-text="total + ' event(s)'"></span>
189
+ <span x-show="loading" class="animate-pulse">Loading…</span>
190
+ <span x-show="error" class="text-red-400" x-text="error"></span>
191
+ </div>
192
+
193
+ <!-- Table -->
194
+ <div class="flex-1 overflow-y-auto scroll-x">
195
+ <!-- Loading state -->
196
+ <div x-show="loading" class="flex items-center justify-center h-32 text-gray-500">
197
+ <span class="animate-spin mr-2">⟳</span> Loading events…
198
+ </div>
199
+
200
+ <!-- Error state -->
201
+ <div
202
+ x-show="!loading && error"
203
+ class="flex items-center justify-center h-32 text-red-400 flex-col gap-2"
204
+ >
205
+ <span class="text-2xl">⚠️</span>
206
+ <span x-text="error"></span>
207
+ <button
208
+ @click="loadEvents()"
209
+ class="text-xs bg-gray-700 hover:bg-gray-600 px-3 py-1.5 rounded"
210
+ >
211
+ Retry
212
+ </button>
213
+ </div>
214
+
215
+ <!-- Empty state -->
216
+ <div
217
+ x-show="!loading && !error && events.length === 0"
218
+ class="flex items-center justify-center h-32 text-gray-500 flex-col gap-2"
219
+ >
220
+ <span class="text-2xl">📭</span>
221
+ <span>No events found. Try adjusting the filters.</span>
222
+ </div>
223
+
224
+ <!-- Event table -->
225
+ <table x-show="!loading && !error && events.length > 0" class="w-full text-xs">
226
+ <thead class="bg-gray-900 sticky top-0">
227
+ <tr class="text-left text-gray-400 border-b border-gray-800">
228
+ <th class="px-3 py-2 font-medium">Timestamp</th>
229
+ <th class="px-3 py-2 font-medium">Type</th>
230
+ <th
231
+ class="px-3 py-2 font-medium"
232
+ x-show="!filters.type || filters.type === 'REQUEST' || filters.type === 'EXTERNAL_HTTP'"
233
+ >
234
+ Method
235
+ </th>
236
+ <th
237
+ class="px-3 py-2 font-medium"
238
+ x-show="!filters.type || filters.type === 'REQUEST' || filters.type === 'EXTERNAL_HTTP'"
239
+ >
240
+ Status
241
+ </th>
242
+ <th class="px-3 py-2 font-medium">Summary / Path</th>
243
+ <th class="px-3 py-2 font-medium">Duration</th>
244
+ <th
245
+ class="px-3 py-2 font-medium"
246
+ x-show="!filters.type || filters.type === 'REQUEST'"
247
+ >
248
+ IP
249
+ </th>
250
+ </tr>
251
+ </thead>
252
+ <tbody>
253
+ <template x-for="ev in events" :key="ev.id">
254
+ <tr
255
+ @click="selectEvent(ev)"
256
+ class="border-b border-gray-800 cursor-pointer hover:bg-gray-800/60 transition"
257
+ :class="selectedEvent && selectedEvent.id === ev.id ? 'bg-gray-800' : ''"
258
+ >
259
+ <td
260
+ class="px-3 py-2 font-mono text-gray-400 whitespace-nowrap"
261
+ x-text="formatTime(ev.timestamp)"
262
+ ></td>
263
+ <td class="px-3 py-2">
264
+ <span
265
+ class="px-1.5 py-0.5 rounded text-[10px] font-semibold"
266
+ :class="typeColor(ev.eventType)"
267
+ x-text="ev.eventType"
268
+ ></span>
269
+ </td>
270
+ <td
271
+ class="px-3 py-2 font-mono text-gray-300"
272
+ x-show="!filters.type || filters.type === 'REQUEST' || filters.type === 'EXTERNAL_HTTP'"
273
+ x-text="ev.method || '-'"
274
+ ></td>
275
+ <td
276
+ class="px-3 py-2"
277
+ x-show="!filters.type || filters.type === 'REQUEST' || filters.type === 'EXTERNAL_HTTP'"
278
+ >
279
+ <span
280
+ x-show="ev.statusCode"
281
+ class="font-mono"
282
+ :class="statusColor(ev.statusCode)"
283
+ x-text="ev.statusCode"
284
+ ></span>
285
+ <span
286
+ x-show="!ev.statusCode && ev.severity"
287
+ :class="severityColor(ev.severity)"
288
+ x-text="ev.severity || '-'"
289
+ ></span>
290
+ </td>
291
+ <td
292
+ class="px-3 py-2 text-gray-300 max-w-xs truncate"
293
+ x-text="ev.path || ev.summary || '-'"
294
+ ></td>
295
+ <td
296
+ class="px-3 py-2 font-mono text-gray-400"
297
+ x-text="ev.durationMs != null ? ev.durationMs + 'ms' : '-'"
298
+ ></td>
299
+ <td
300
+ class="px-3 py-2 font-mono text-gray-500"
301
+ x-show="!filters.type || filters.type === 'REQUEST'"
302
+ x-text="ev.ipAddress || '-'"
303
+ ></td>
304
+ </tr>
305
+ </template>
306
+ </tbody>
307
+ </table>
308
+ </div>
309
+
310
+ <!-- Pagination -->
311
+ <div
312
+ x-show="total > 0"
313
+ class="bg-gray-900 border-t border-gray-800 px-4 py-2 flex items-center justify-between text-xs text-gray-400"
314
+ >
315
+ <div class="flex items-center gap-2">
316
+ <span>Page</span>
317
+ <input
318
+ type="number"
319
+ x-model.number="filters.page"
320
+ @change="loadEvents()"
321
+ min="1"
322
+ :max="totalPages"
323
+ class="w-14 bg-gray-800 border border-gray-700 rounded px-2 py-1 text-center focus:outline-none"
324
+ />
325
+ <span x-text="'of ' + totalPages"></span>
326
+ </div>
327
+ <div class="flex gap-2">
328
+ <button
329
+ @click="filters.page = Math.max(1, filters.page - 1); loadEvents()"
330
+ :disabled="filters.page <= 1"
331
+ class="px-3 py-1 rounded bg-gray-800 disabled:opacity-40 hover:bg-gray-700"
332
+ >
333
+ ← Prev
334
+ </button>
335
+ <button
336
+ @click="filters.page = Math.min(totalPages, filters.page + 1); loadEvents()"
337
+ :disabled="filters.page >= totalPages"
338
+ class="px-3 py-1 rounded bg-gray-800 disabled:opacity-40 hover:bg-gray-700"
339
+ >
340
+ Next →
341
+ </button>
342
+ </div>
343
+ <div>
344
+ <select
345
+ x-model.number="filters.pageSize"
346
+ @change="filters.page = 1; loadEvents()"
347
+ class="bg-gray-800 border border-gray-700 rounded px-2 py-1 focus:outline-none"
348
+ >
349
+ <option :value="25">25/page</option>
350
+ <option :value="50">50/page</option>
351
+ <option :value="100">100/page</option>
352
+ </select>
353
+ </div>
354
+ </div>
355
+ </div>
356
+
357
+ <!-- Right pane: detail -->
358
+ <div
359
+ x-show="selectedEvent"
360
+ class="hidden lg:flex flex-col w-1/2 border-l border-gray-800 overflow-y-auto bg-gray-950"
361
+ >
362
+ <div
363
+ class="bg-gray-900 border-b border-gray-800 px-4 py-3 flex items-center justify-between"
364
+ >
365
+ <h2 class="text-sm font-semibold">Event Detail</h2>
366
+ <button
367
+ @click="selectedEvent = null; detail = null"
368
+ class="text-gray-400 hover:text-white text-lg leading-none"
369
+ >
370
+
371
+ </button>
372
+ </div>
373
+
374
+ <!-- Loading detail -->
375
+ <div x-show="detailLoading" class="flex items-center justify-center h-32 text-gray-500">
376
+ <span class="animate-spin mr-2">⟳</span> Loading detail…
377
+ </div>
378
+
379
+ <div x-show="!detailLoading && detail" class="p-4 space-y-4 text-xs">
380
+ <!-- Event metadata -->
381
+ <div class="bg-gray-900 rounded-lg p-3 space-y-2">
382
+ <h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Event</h3>
383
+ <div class="grid grid-cols-2 gap-x-4 gap-y-1">
384
+ <template x-for="[k,v] in Object.entries(detail.event || {})" :key="k">
385
+ <template x-if="v !== null && v !== undefined && k !== 'data'">
386
+ <div class="contents">
387
+ <span class="text-gray-500" x-text="k"></span>
388
+ <span
389
+ class="font-mono text-gray-200 truncate"
390
+ x-text="typeof v === 'object' ? JSON.stringify(v) : v"
391
+ ></span>
392
+ </div>
393
+ </template>
394
+ </template>
395
+ </div>
396
+ </div>
397
+
398
+ <!-- Request record -->
399
+ <div x-show="detail.request" class="bg-gray-900 rounded-lg p-3 space-y-2">
400
+ <h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wider">
401
+ Request Record
402
+ </h3>
403
+ <div class="grid grid-cols-2 gap-x-4 gap-y-1">
404
+ <template x-for="[k,v] in Object.entries(detail.request || {})" :key="k">
405
+ <template
406
+ x-if="v !== null && v !== undefined && !['requestHeaders','responseHeaders','requestBody','responseBody'].includes(k)"
407
+ >
408
+ <div class="contents">
409
+ <span class="text-gray-500" x-text="k"></span>
410
+ <span
411
+ class="font-mono text-gray-200"
412
+ x-text="typeof v === 'object' ? JSON.stringify(v) : v"
413
+ ></span>
414
+ </div>
415
+ </template>
416
+ </template>
417
+ </div>
418
+ <!-- Bodies -->
419
+ <template x-if="detail.request?.requestBody">
420
+ <div>
421
+ <div class="flex items-center justify-between mt-2 mb-1">
422
+ <p class="text-gray-500">Request Body</p>
423
+ <button
424
+ @click="copyJsonValue(detail.request.requestBody, 'Request body')"
425
+ class="text-[10px] bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded"
426
+ >
427
+ Copy
428
+ </button>
429
+ </div>
430
+ <pre
431
+ class="bg-gray-800 rounded p-2 text-green-300 text-[11px] max-h-40 overflow-y-auto"
432
+ x-text="prettyJson(detail.request.requestBody)"
433
+ ></pre>
434
+ </div>
435
+ </template>
436
+ <template x-if="detail.request?.responseBody">
437
+ <div>
438
+ <div class="flex items-center justify-between mt-2 mb-1">
439
+ <p class="text-gray-500">Response Body</p>
440
+ <button
441
+ @click="copyJsonValue(detail.request.responseBody, 'Response body')"
442
+ class="text-[10px] bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded"
443
+ >
444
+ Copy
445
+ </button>
446
+ </div>
447
+ <pre
448
+ class="bg-gray-800 rounded p-2 text-blue-300 text-[11px] max-h-40 overflow-y-auto"
449
+ x-text="prettyJson(detail.request.responseBody)"
450
+ ></pre>
451
+ </div>
452
+ </template>
453
+ </div>
454
+
455
+ <!-- Outbound HTTP calls -->
456
+ <div
457
+ x-show="detail.externalHttpCalls && detail.externalHttpCalls.length > 0"
458
+ class="bg-gray-900 rounded-lg p-3 space-y-3"
459
+ >
460
+ <h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wider">
461
+ Outbound HTTP Calls (<span x-text="detail.externalHttpCalls?.length"></span>)
462
+ </h3>
463
+ <template x-for="(call, idx) in detail.externalHttpCalls || []" :key="call.eventId">
464
+ <div class="border border-gray-700 rounded p-2 space-y-1">
465
+ <div class="flex items-center gap-2">
466
+ <span class="font-mono font-semibold text-yellow-300" x-text="call.method"></span>
467
+ <span class="font-mono text-gray-300 truncate flex-1" x-text="call.url"></span>
468
+ <span
469
+ :class="statusColor(call.statusCode)"
470
+ class="font-mono font-semibold"
471
+ x-text="call.statusCode || 'ERR'"
472
+ ></span>
473
+ <span
474
+ class="text-gray-500"
475
+ x-text="call.durationMs != null ? call.durationMs + 'ms' : ''"
476
+ ></span>
477
+ </div>
478
+ <template x-if="call.requestBody">
479
+ <div>
480
+ <div class="flex items-center justify-between mt-1 mb-0.5">
481
+ <p class="text-gray-500">→ Request</p>
482
+ <button
483
+ @click="copyJsonValue(call.requestBody, 'Outbound request body')"
484
+ class="text-[10px] bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded"
485
+ >
486
+ Copy
487
+ </button>
488
+ </div>
489
+ <pre
490
+ class="bg-gray-800 rounded p-1.5 text-green-300 text-[10px] max-h-24 overflow-y-auto"
491
+ x-text="prettyJson(call.requestBody)"
492
+ ></pre>
493
+ </div>
494
+ </template>
495
+ <template x-if="call.responseBody">
496
+ <div>
497
+ <div class="flex items-center justify-between mt-1 mb-0.5">
498
+ <p class="text-gray-500">← Response</p>
499
+ <button
500
+ @click="copyJsonValue(call.responseBody, 'Outbound response body')"
501
+ class="text-[10px] bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded"
502
+ >
503
+ Copy
504
+ </button>
505
+ </div>
506
+ <pre
507
+ class="bg-gray-800 rounded p-1.5 text-blue-300 text-[10px] max-h-24 overflow-y-auto"
508
+ x-text="prettyJson(call.responseBody)"
509
+ ></pre>
510
+ </div>
511
+ </template>
512
+ </div>
513
+ </template>
514
+ </div>
515
+
516
+ <!-- Exceptions -->
517
+ <div
518
+ x-show="detail.exceptions && detail.exceptions.length > 0"
519
+ class="bg-gray-900 rounded-lg p-3 space-y-2"
520
+ >
521
+ <h3 class="text-xs font-semibold text-red-400 uppercase tracking-wider">
522
+ Exceptions (<span x-text="detail.exceptions?.length"></span>)
523
+ </h3>
524
+ <template x-for="exc in detail.exceptions || []" :key="exc.eventId">
525
+ <div class="border border-red-900 rounded p-2 space-y-1">
526
+ <p class="font-semibold text-red-300" x-text="exc.name + ': ' + exc.message"></p>
527
+ <pre
528
+ x-show="exc.stackTrace"
529
+ class="text-[10px] text-gray-400 max-h-32 overflow-y-auto"
530
+ x-text="exc.stackTrace"
531
+ ></pre>
532
+ </div>
533
+ </template>
534
+ </div>
535
+
536
+ <!-- Exception detail (single) -->
537
+ <div
538
+ x-show="detail.exception && !detail.exceptions?.length"
539
+ class="bg-gray-900 rounded-lg p-3 space-y-2"
540
+ >
541
+ <h3 class="text-xs font-semibold text-red-400 uppercase tracking-wider">Exception</h3>
542
+ <p
543
+ class="font-semibold text-red-300"
544
+ x-text="(detail.exception?.name || '') + ': ' + (detail.exception?.message || '')"
545
+ ></p>
546
+ <pre
547
+ x-show="detail.exception?.stackTrace"
548
+ class="text-[10px] text-gray-400 max-h-32 overflow-y-auto"
549
+ x-text="detail.exception?.stackTrace"
550
+ ></pre>
551
+ </div>
552
+
553
+ <!-- Generic event data -->
554
+ <template x-if="detail.event?.data">
555
+ <div class="bg-gray-900 rounded-lg p-3 space-y-2">
556
+ <div class="flex items-center justify-between">
557
+ <h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wider">
558
+ Event Data
559
+ </h3>
560
+ <button
561
+ @click="copyJsonValue(detail.event.data, 'Event data')"
562
+ class="text-[10px] bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded"
563
+ >
564
+ Copy
565
+ </button>
566
+ </div>
567
+ <pre
568
+ class="bg-gray-800 rounded p-2 text-purple-300 text-[11px] max-h-48 overflow-y-auto"
569
+ x-text="prettyJson(detail.event.data)"
570
+ ></pre>
571
+ </div>
572
+ </template>
573
+ </div>
574
+ </div>
575
+ </div>
576
+
577
+ <div
578
+ x-show="deleteState.confirmOpen"
579
+ class="fixed inset-0 bg-black/60 z-40 flex items-center justify-center p-4"
580
+ x-cloak
581
+ >
582
+ <div class="bg-gray-900 border border-gray-700 rounded-lg p-5 w-full max-w-md space-y-4">
583
+ <h3 class="text-base font-semibold text-white">Delete monitoring data?</h3>
584
+ <p class="text-sm text-gray-300">
585
+ This action will permanently remove recorded monitoring events. It cannot be undone.
586
+ </p>
587
+ <div class="flex justify-end gap-2">
588
+ <button
589
+ @click="closeDeleteConfirmation()"
590
+ :disabled="deleteState.inProgress"
591
+ class="px-3 py-1.5 rounded bg-gray-700 hover:bg-gray-600 text-sm disabled:opacity-60"
592
+ >
593
+ Cancel
594
+ </button>
595
+ <button
596
+ @click="deleteRecordedData()"
597
+ :disabled="deleteState.inProgress"
598
+ class="px-3 py-1.5 rounded bg-red-700 hover:bg-red-600 text-sm text-white disabled:opacity-60"
599
+ x-text="deleteState.inProgress ? 'Deleting…' : 'Delete now'"
600
+ ></button>
601
+ </div>
602
+ </div>
603
+ </div>
604
+
605
+ <script>
606
+ window.__MONITORING_BASE_PATH = "__MONITORING_BASE_PATH__";
607
+ </script>
608
+ <script src="__MONITORING_BASE_PATH__/app.js"></script>
609
+ </body>
610
+ </html>
@@ -0,0 +1,7 @@
1
+ import type { MonitoringMaskingOptions } from "../types.js";
2
+ export declare function createMaskingUtils(masking: MonitoringMaskingOptions): {
3
+ maskObject: (value: unknown, depth?: number) => unknown;
4
+ maskBody: (body: unknown) => string | null;
5
+ maskHeaders: (headers: unknown) => Record<string, unknown>;
6
+ };
7
+ //# sourceMappingURL=masking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masking.d.ts","sourceRoot":"","sources":["../../src/utils/masking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAQ5D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,wBAAwB;wBAQvC,OAAO,qBAAc,OAAO;qBAqB/B,OAAO,KAAG,MAAM,GAAG,IAAI;2BAiCjB,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;EAUhE"}