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.
- package/README.md +274 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +70 -0
- package/dist/config/env-loader.d.ts +3 -0
- package/dist/config/env-loader.d.ts.map +1 -0
- package/dist/config/env-loader.js +29 -0
- package/dist/config/validate.d.ts +3 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +19 -0
- package/dist/create-monitoring.d.ts +3 -0
- package/dist/create-monitoring.d.ts.map +1 -0
- package/dist/create-monitoring.js +73 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/middleware/auth-basic.d.ts +4 -0
- package/dist/middleware/auth-basic.d.ts.map +1 -0
- package/dist/middleware/auth-basic.js +34 -0
- package/dist/middleware/capture.d.ts +7 -0
- package/dist/middleware/capture.d.ts.map +1 -0
- package/dist/middleware/capture.js +68 -0
- package/dist/middleware/error.d.ts +4 -0
- package/dist/middleware/error.d.ts.map +1 -0
- package/dist/middleware/error.js +27 -0
- package/dist/repository/monitoring-repository.d.ts +4 -0
- package/dist/repository/monitoring-repository.d.ts.map +1 -0
- package/dist/repository/monitoring-repository.js +239 -0
- package/dist/repository/sqlite-db.d.ts +7 -0
- package/dist/repository/sqlite-db.d.ts.map +1 -0
- package/dist/repository/sqlite-db.js +91 -0
- package/dist/router/async-handler.d.ts +3 -0
- package/dist/router/async-handler.d.ts.map +1 -0
- package/dist/router/async-handler.js +5 -0
- package/dist/router/monitoring-router.d.ts +4 -0
- package/dist/router/monitoring-router.d.ts.map +1 -0
- package/dist/router/monitoring-router.js +109 -0
- package/dist/services/console-hook.d.ts +12 -0
- package/dist/services/console-hook.d.ts.map +1 -0
- package/dist/services/console-hook.js +61 -0
- package/dist/services/context.d.ts +7 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +22 -0
- package/dist/services/http-client.d.ts +7 -0
- package/dist/services/http-client.d.ts.map +1 -0
- package/dist/services/http-client.js +56 -0
- package/dist/services/instrumentation.d.ts +8 -0
- package/dist/services/instrumentation.d.ts.map +1 -0
- package/dist/services/instrumentation.js +9 -0
- package/dist/services/recorder.d.ts +5 -0
- package/dist/services/recorder.d.ts.map +1 -0
- package/dist/services/recorder.js +23 -0
- package/dist/services/retention.d.ts +12 -0
- package/dist/services/retention.d.ts.map +1 -0
- package/dist/services/retention.js +36 -0
- package/dist/services/runtime.d.ts +3 -0
- package/dist/services/runtime.d.ts.map +1 -0
- package/dist/services/runtime.js +9 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/ui/app.js +382 -0
- package/dist/ui/index.html +610 -0
- package/dist/utils/masking.d.ts +7 -0
- package/dist/utils/masking.d.ts.map +1 -0
- package/dist/utils/masking.js +79 -0
- 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"}
|