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
package/dist/ui/app.js ADDED
@@ -0,0 +1,382 @@
1
+ const MONITORING_BASE_PATH = globalThis.window?.__MONITORING_BASE_PATH || "/monitoring";
2
+ const THEME_STORAGE_KEY = "monitoring_theme_mode";
3
+ const AUTO_REFRESH_INTERVAL_SECONDS = 10;
4
+ const TOAST_TIMEOUT_MS = 2500;
5
+
6
+ // Alpine.js component — referenced from index.html via x-data="monitoringApp()"
7
+ globalThis.monitoringApp = function monitoringApp() {
8
+ return {
9
+ events: [],
10
+ total: null,
11
+ loading: false,
12
+ error: null,
13
+ lastRefreshed: null,
14
+ selectedEvent: null,
15
+ detail: null,
16
+ detailLoading: false,
17
+ displayPreference: {
18
+ mode: "dark",
19
+ updatedAt: null,
20
+ },
21
+ refreshState: {
22
+ enabled: false,
23
+ intervalSeconds: AUTO_REFRESH_INTERVAL_SECONDS,
24
+ inFlight: false,
25
+ timerId: null,
26
+ },
27
+ feedback: {
28
+ visible: false,
29
+ message: "",
30
+ type: "info",
31
+ timeoutId: null,
32
+ },
33
+ deleteState: {
34
+ confirmOpen: false,
35
+ inProgress: false,
36
+ },
37
+
38
+ filters: {
39
+ q: "",
40
+ type: "",
41
+ method: "",
42
+ status: "",
43
+ from: "",
44
+ to: "",
45
+ page: 1,
46
+ pageSize: 50,
47
+ },
48
+
49
+ get totalPages() {
50
+ if (!this.total || !this.filters.pageSize) return 1;
51
+ return Math.max(1, Math.ceil(this.total / this.filters.pageSize));
52
+ },
53
+
54
+ get isDarkMode() {
55
+ return this.displayPreference.mode === "dark";
56
+ },
57
+
58
+ get hasNoEvents() {
59
+ return !this.loading && !this.error && this.events.length === 0;
60
+ },
61
+
62
+ async init() {
63
+ this.initThemeMode();
64
+ this.applyThemeMode();
65
+ await this.loadEvents();
66
+ },
67
+
68
+ initThemeMode() {
69
+ try {
70
+ const persisted = globalThis.localStorage?.getItem(THEME_STORAGE_KEY);
71
+ if (persisted === "dark" || persisted === "light") {
72
+ this.displayPreference.mode = persisted;
73
+ this.displayPreference.updatedAt = new Date().toISOString();
74
+ return;
75
+ }
76
+ } catch (err) {
77
+ this.queueToast(`Could not read theme preference: ${err.message}`, "warning");
78
+ }
79
+ const prefersDark =
80
+ typeof globalThis.window !== "undefined" &&
81
+ typeof globalThis.window.matchMedia === "function" &&
82
+ globalThis.window.matchMedia("(prefers-color-scheme: dark)").matches;
83
+ this.displayPreference.mode = prefersDark ? "dark" : "light";
84
+ },
85
+
86
+ applyThemeMode() {
87
+ const root = globalThis.document.documentElement;
88
+ if (this.displayPreference.mode === "dark") {
89
+ root.classList.add("dark");
90
+ } else {
91
+ root.classList.remove("dark");
92
+ }
93
+ },
94
+
95
+ persistThemeMode() {
96
+ try {
97
+ globalThis.localStorage?.setItem(THEME_STORAGE_KEY, this.displayPreference.mode);
98
+ } catch (err) {
99
+ this.queueToast(`Could not save theme preference: ${err.message}`, "warning");
100
+ }
101
+ },
102
+
103
+ toggleThemeMode() {
104
+ this.displayPreference.mode = this.isDarkMode ? "light" : "dark";
105
+ this.displayPreference.updatedAt = new Date().toISOString();
106
+ this.applyThemeMode();
107
+ this.persistThemeMode();
108
+ this.queueToast(`Switched to ${this.displayPreference.mode} mode`, "success");
109
+ },
110
+
111
+ queueToast(message, type = "info") {
112
+ this.clearToast();
113
+ this.feedback.message = message;
114
+ this.feedback.type = type;
115
+ this.feedback.visible = true;
116
+ this.feedback.timeoutId = setTimeout(() => {
117
+ this.feedback.visible = false;
118
+ this.feedback.timeoutId = null;
119
+ }, TOAST_TIMEOUT_MS);
120
+ },
121
+
122
+ clearToast() {
123
+ if (this.feedback.timeoutId) {
124
+ clearTimeout(this.feedback.timeoutId);
125
+ this.feedback.timeoutId = null;
126
+ }
127
+ },
128
+
129
+ async loadEvents({ background = false } = {}) {
130
+ if (background) {
131
+ if (this.refreshState.inFlight) return;
132
+ this.refreshState.inFlight = true;
133
+ } else {
134
+ this.loading = true;
135
+ }
136
+ this.error = null;
137
+ try {
138
+ const params = new URLSearchParams();
139
+ if (this.filters.q) params.set("q", this.filters.q);
140
+ if (this.filters.type) params.set("type", this.filters.type);
141
+ if (this.filters.method) params.set("method", this.filters.method);
142
+ if (this.filters.status) params.set("status", this.filters.status);
143
+ if (this.filters.from) params.set("from", new Date(this.filters.from).toISOString());
144
+ if (this.filters.to) params.set("to", new Date(this.filters.to).toISOString());
145
+ params.set("page", this.filters.page);
146
+ params.set("pageSize", this.filters.pageSize);
147
+
148
+ const res = await fetch(`${MONITORING_BASE_PATH}/api/events?` + params.toString(), {
149
+ credentials: "include",
150
+ });
151
+ if (!res.ok) throw new Error("HTTP " + res.status + ": " + (await res.text()));
152
+ const data = await res.json();
153
+ this.events = data.items || [];
154
+ this.total = data.total ?? null;
155
+ this.lastRefreshed = new Date().toLocaleTimeString();
156
+ if (
157
+ this.selectedEvent &&
158
+ !this.events.find((event) => event.id === this.selectedEvent.id)
159
+ ) {
160
+ this.selectedEvent = null;
161
+ this.detail = null;
162
+ }
163
+ } catch (err) {
164
+ this.error = err.message;
165
+ if (!background) this.events = [];
166
+ } finally {
167
+ if (background) {
168
+ this.refreshState.inFlight = false;
169
+ } else {
170
+ this.loading = false;
171
+ }
172
+ }
173
+ },
174
+
175
+ async selectEvent(ev) {
176
+ this.selectedEvent = ev;
177
+ this.detail = null;
178
+ this.detailLoading = true;
179
+ try {
180
+ const res = await fetch(`${MONITORING_BASE_PATH}/api/events/` + ev.id, {
181
+ credentials: "include",
182
+ });
183
+ if (!res.ok) throw new Error("HTTP " + res.status);
184
+ this.detail = await res.json();
185
+ } catch (err) {
186
+ this.detail = { event: ev, _error: err.message };
187
+ } finally {
188
+ this.detailLoading = false;
189
+ }
190
+ },
191
+
192
+ clearFilters() {
193
+ this.filters = {
194
+ q: "",
195
+ type: "",
196
+ method: "",
197
+ status: "",
198
+ from: "",
199
+ to: "",
200
+ page: 1,
201
+ pageSize: this.filters.pageSize,
202
+ };
203
+ this.loadEvents();
204
+ },
205
+
206
+ setAutoRefresh(enabled) {
207
+ this.refreshState.enabled = enabled;
208
+ if (enabled) {
209
+ this.startAutoRefresh();
210
+ this.queueToast("Auto-refresh enabled", "success");
211
+ this.loadEvents({ background: true });
212
+ } else {
213
+ this.stopAutoRefresh();
214
+ this.queueToast("Auto-refresh disabled", "info");
215
+ }
216
+ },
217
+
218
+ toggleAutoRefresh() {
219
+ this.setAutoRefresh(!this.refreshState.enabled);
220
+ },
221
+
222
+ startAutoRefresh() {
223
+ if (this.refreshState.timerId) clearInterval(this.refreshState.timerId);
224
+ this.refreshState.timerId = setInterval(() => {
225
+ this.loadEvents({ background: true });
226
+ }, this.refreshState.intervalSeconds * 1000);
227
+ },
228
+
229
+ stopAutoRefresh() {
230
+ if (this.refreshState.timerId) {
231
+ clearInterval(this.refreshState.timerId);
232
+ this.refreshState.timerId = null;
233
+ }
234
+ },
235
+
236
+ openDeleteConfirmation() {
237
+ this.deleteState.confirmOpen = true;
238
+ },
239
+
240
+ closeDeleteConfirmation() {
241
+ if (this.deleteState.inProgress) return;
242
+ this.deleteState.confirmOpen = false;
243
+ },
244
+
245
+ async deleteRecordedData() {
246
+ this.deleteState.inProgress = true;
247
+ try {
248
+ const res = await fetch(`${MONITORING_BASE_PATH}/api/events`, {
249
+ method: "DELETE",
250
+ credentials: "include",
251
+ headers: { "Content-Type": "application/json" },
252
+ body: JSON.stringify({ scope: "all" }),
253
+ });
254
+ if (!res.ok) throw new Error("HTTP " + res.status + ": " + (await res.text()));
255
+ const payload = await res.json();
256
+ this.events = [];
257
+ this.total = payload.remaining ?? 0;
258
+ this.selectedEvent = null;
259
+ this.detail = null;
260
+ this.deleteState.confirmOpen = false;
261
+ this.lastRefreshed = new Date().toLocaleTimeString();
262
+ this.queueToast(payload.message || "Monitoring data deleted", "success");
263
+ await this.loadEvents({ background: true });
264
+ } catch (err) {
265
+ this.queueToast("Delete failed: " + err.message, "error");
266
+ } finally {
267
+ this.deleteState.inProgress = false;
268
+ }
269
+ },
270
+
271
+ async copyJsonValue(value, label = "JSON block") {
272
+ const text = this.prettyJson(value);
273
+ if (!text || !text.trim()) {
274
+ this.queueToast(`${label} is empty, nothing to copy`, "warning");
275
+ return;
276
+ }
277
+ try {
278
+ if (!globalThis.navigator?.clipboard || !globalThis.navigator.clipboard.writeText) {
279
+ throw new Error("Clipboard API unavailable");
280
+ }
281
+ await globalThis.navigator.clipboard.writeText(text);
282
+ this.queueToast(`${label} copied`, "success");
283
+ } catch {
284
+ if (this.fallbackCopy(text)) {
285
+ this.queueToast(`${label} copied with fallback`, "success");
286
+ return;
287
+ }
288
+ this.queueToast(
289
+ `${label} could not be copied automatically. Please copy manually from the block.`,
290
+ "error"
291
+ );
292
+ }
293
+ },
294
+
295
+ fallbackCopy(text) {
296
+ try {
297
+ const textarea = globalThis.document.createElement("textarea");
298
+ textarea.value = text;
299
+ textarea.setAttribute("readonly", "");
300
+ textarea.style.position = "absolute";
301
+ textarea.style.left = "-9999px";
302
+ globalThis.document.body.appendChild(textarea);
303
+ textarea.select();
304
+ const copied = globalThis.document.execCommand("copy");
305
+ globalThis.document.body.removeChild(textarea);
306
+ return copied;
307
+ } catch {
308
+ return false;
309
+ }
310
+ },
311
+
312
+ formatTime(ts) {
313
+ if (!ts) return "-";
314
+ try {
315
+ return new Date(ts).toLocaleString(undefined, {
316
+ month: "2-digit",
317
+ day: "2-digit",
318
+ hour: "2-digit",
319
+ minute: "2-digit",
320
+ second: "2-digit",
321
+ });
322
+ } catch {
323
+ return ts;
324
+ }
325
+ },
326
+
327
+ prettyJson(val) {
328
+ if (val === null || val === undefined) return "";
329
+ if (typeof val === "string") {
330
+ try {
331
+ return JSON.stringify(JSON.parse(val), null, 2);
332
+ } catch {
333
+ return val;
334
+ }
335
+ }
336
+ try {
337
+ return JSON.stringify(val, null, 2);
338
+ } catch {
339
+ return String(val);
340
+ }
341
+ },
342
+
343
+ typeColor(type) {
344
+ const map = {
345
+ REQUEST: "bg-blue-900 text-blue-300",
346
+ EXCEPTION: "bg-red-900 text-red-300",
347
+ EXTERNAL_HTTP: "bg-yellow-900 text-yellow-300",
348
+ LOG: "bg-gray-700 text-gray-300",
349
+ SCHEDULED_TASK: "bg-purple-900 text-purple-300",
350
+ DB_QUERY: "bg-green-900 text-green-300",
351
+ JOB: "bg-indigo-900 text-indigo-300",
352
+ NOTIFICATION: "bg-pink-900 text-pink-300",
353
+ MAIL: "bg-cyan-900 text-cyan-300",
354
+ CACHE_OP: "bg-orange-900 text-orange-300",
355
+ };
356
+ return map[type] || "bg-gray-800 text-gray-400";
357
+ },
358
+
359
+ statusColor(code) {
360
+ if (!code) return "text-gray-400";
361
+ if (code < 300) return "text-green-400";
362
+ if (code < 400) return "text-yellow-400";
363
+ if (code < 500) return "text-orange-400";
364
+ return "text-red-400";
365
+ },
366
+
367
+ severityColor(sev) {
368
+ const map = { ERROR: "text-red-400", WARN: "text-yellow-400", INFO: "text-green-400" };
369
+ return map[sev] || "text-gray-400";
370
+ },
371
+
372
+ toastClass() {
373
+ const map = {
374
+ success: "bg-green-600 text-white",
375
+ error: "bg-red-600 text-white",
376
+ warning: "bg-yellow-600 text-white",
377
+ info: "bg-gray-700 text-white",
378
+ };
379
+ return map[this.feedback.type] || map.info;
380
+ },
381
+ };
382
+ };