nest-devtools 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 (46) hide show
  1. package/dist/constants.d.ts +4 -0
  2. package/dist/constants.js +8 -0
  3. package/dist/constants.js.map +1 -0
  4. package/dist/dashboard/index.html.d.ts +1 -0
  5. package/dist/dashboard/index.html.js +1052 -0
  6. package/dist/dashboard/index.html.js.map +1 -0
  7. package/dist/devtools.controller.d.ts +2 -0
  8. package/dist/devtools.controller.js +87 -0
  9. package/dist/devtools.controller.js.map +1 -0
  10. package/dist/devtools.module.d.ts +6 -0
  11. package/dist/devtools.module.js +55 -0
  12. package/dist/devtools.module.js.map +1 -0
  13. package/dist/devtools.realtime.d.ts +18 -0
  14. package/dist/devtools.realtime.js +111 -0
  15. package/dist/devtools.realtime.js.map +1 -0
  16. package/dist/devtools.store.d.ts +12 -0
  17. package/dist/devtools.store.js +51 -0
  18. package/dist/devtools.store.js.map +1 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +6 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger/logger.capture.d.ts +23 -0
  23. package/dist/logger/logger.capture.js +246 -0
  24. package/dist/logger/logger.capture.js.map +1 -0
  25. package/dist/realtime/websocket.utils.d.ts +2 -0
  26. package/dist/realtime/websocket.utils.js +28 -0
  27. package/dist/realtime/websocket.utils.js.map +1 -0
  28. package/dist/request/request.interceptor.d.ts +13 -0
  29. package/dist/request/request.interceptor.js +74 -0
  30. package/dist/request/request.interceptor.js.map +1 -0
  31. package/dist/types.d.ts +41 -0
  32. package/dist/types.js +3 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/utils/headers.d.ts +2 -0
  35. package/dist/utils/headers.js +25 -0
  36. package/dist/utils/headers.js.map +1 -0
  37. package/dist/utils/id.d.ts +1 -0
  38. package/dist/utils/id.js +9 -0
  39. package/dist/utils/id.js.map +1 -0
  40. package/dist/utils/path.d.ts +4 -0
  41. package/dist/utils/path.js +37 -0
  42. package/dist/utils/path.js.map +1 -0
  43. package/dist/utils/ring-buffer.d.ts +10 -0
  44. package/dist/utils/ring-buffer.js +41 -0
  45. package/dist/utils/ring-buffer.js.map +1 -0
  46. package/package.json +38 -0
@@ -0,0 +1,1052 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderDashboardHtml = renderDashboardHtml;
4
+ const DASHBOARD_TEMPLATE = `<!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>nest-devtools</title>
10
+ <style>
11
+ :root {
12
+ --bg: #081018;
13
+ --bg-elevated: rgba(14, 24, 36, 0.84);
14
+ --bg-panel: rgba(17, 28, 41, 0.92);
15
+ --border: rgba(166, 188, 212, 0.14);
16
+ --border-strong: rgba(166, 188, 212, 0.24);
17
+ --text: #edf4fb;
18
+ --text-muted: #9db0c4;
19
+ --accent: #67d0ff;
20
+ --accent-warm: #f8b94a;
21
+ --success: #4fd39a;
22
+ --warning: #f7c85f;
23
+ --danger: #ff7d7d;
24
+ --debug: #7fa8ff;
25
+ --shadow: 0 18px 48px rgba(1, 7, 14, 0.42);
26
+ --radius-lg: 24px;
27
+ --radius-md: 16px;
28
+ --radius-sm: 12px;
29
+ }
30
+
31
+ * {
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ html,
36
+ body {
37
+ margin: 0;
38
+ min-height: 100%;
39
+ background:
40
+ radial-gradient(circle at top left, rgba(103, 208, 255, 0.14), transparent 32%),
41
+ radial-gradient(circle at top right, rgba(248, 185, 74, 0.12), transparent 28%),
42
+ linear-gradient(180deg, #0b1622 0%, #081018 100%);
43
+ color: var(--text);
44
+ font-family: Avenir Next, Trebuchet MS, Segoe UI, Helvetica Neue, Arial, sans-serif;
45
+ }
46
+
47
+ body {
48
+ padding: 24px;
49
+ }
50
+
51
+ .shell {
52
+ max-width: 1440px;
53
+ min-height: calc(100vh - 48px);
54
+ margin: 0 auto;
55
+ padding: 18px;
56
+ border: 1px solid var(--border);
57
+ border-radius: 28px;
58
+ background:
59
+ linear-gradient(180deg, rgba(19, 31, 45, 0.94), rgba(10, 17, 28, 0.96)),
60
+ rgba(6, 12, 18, 0.82);
61
+ box-shadow: var(--shadow);
62
+ backdrop-filter: blur(14px);
63
+ }
64
+
65
+ .topbar {
66
+ display: flex;
67
+ flex-wrap: wrap;
68
+ justify-content: space-between;
69
+ gap: 16px;
70
+ align-items: center;
71
+ padding: 12px 12px 20px;
72
+ }
73
+
74
+ .brand {
75
+ display: flex;
76
+ gap: 14px;
77
+ align-items: center;
78
+ }
79
+
80
+ .brand-mark {
81
+ width: 44px;
82
+ height: 44px;
83
+ border-radius: 14px;
84
+ background:
85
+ linear-gradient(135deg, rgba(248, 185, 74, 0.9), rgba(103, 208, 255, 0.92));
86
+ position: relative;
87
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
88
+ }
89
+
90
+ .brand-mark::before,
91
+ .brand-mark::after {
92
+ content: '';
93
+ position: absolute;
94
+ inset: 9px;
95
+ border: 2px solid rgba(8, 16, 24, 0.72);
96
+ border-radius: 10px;
97
+ }
98
+
99
+ .brand-mark::after {
100
+ inset: 16px 10px 10px 16px;
101
+ border-color: rgba(8, 16, 24, 0.48);
102
+ }
103
+
104
+ .brand-copy {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 4px;
108
+ }
109
+
110
+ .eyebrow {
111
+ color: var(--accent-warm);
112
+ font-size: 12px;
113
+ text-transform: uppercase;
114
+ letter-spacing: 0.16em;
115
+ }
116
+
117
+ .title {
118
+ font-size: 28px;
119
+ font-weight: 700;
120
+ letter-spacing: -0.04em;
121
+ }
122
+
123
+ .subtitle {
124
+ color: var(--text-muted);
125
+ font-size: 14px;
126
+ }
127
+
128
+ .tab-group,
129
+ .toolbar-group {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ gap: 8px;
133
+ padding: 6px;
134
+ border: 1px solid var(--border);
135
+ border-radius: 999px;
136
+ background: rgba(255, 255, 255, 0.03);
137
+ }
138
+
139
+ .toolbar {
140
+ display: flex;
141
+ flex-wrap: wrap;
142
+ justify-content: space-between;
143
+ gap: 16px;
144
+ align-items: center;
145
+ margin-bottom: 18px;
146
+ padding: 12px;
147
+ border: 1px solid var(--border);
148
+ border-radius: 20px;
149
+ background: var(--bg-elevated);
150
+ }
151
+
152
+ .tab-button,
153
+ .filter-button,
154
+ .toolbar-button {
155
+ border: 0;
156
+ border-radius: 999px;
157
+ background: transparent;
158
+ color: var(--text-muted);
159
+ cursor: pointer;
160
+ font: inherit;
161
+ transition:
162
+ background 160ms ease,
163
+ color 160ms ease,
164
+ transform 160ms ease;
165
+ }
166
+
167
+ .tab-button,
168
+ .toolbar-button {
169
+ padding: 10px 16px;
170
+ }
171
+
172
+ .filter-button {
173
+ padding: 8px 14px;
174
+ }
175
+
176
+ .tab-button:hover,
177
+ .filter-button:hover,
178
+ .toolbar-button:hover {
179
+ color: var(--text);
180
+ background: rgba(255, 255, 255, 0.06);
181
+ transform: translateY(-1px);
182
+ }
183
+
184
+ .is-active {
185
+ background: rgba(103, 208, 255, 0.14);
186
+ color: var(--text);
187
+ box-shadow: inset 0 0 0 1px rgba(103, 208, 255, 0.14);
188
+ }
189
+
190
+ .filter-button[data-level='log'].is-active {
191
+ background: rgba(79, 211, 154, 0.16);
192
+ }
193
+
194
+ .filter-button[data-level='warn'].is-active {
195
+ background: rgba(247, 200, 95, 0.18);
196
+ }
197
+
198
+ .filter-button[data-level='error'].is-active {
199
+ background: rgba(255, 125, 125, 0.18);
200
+ }
201
+
202
+ .filter-button[data-level='debug'].is-active {
203
+ background: rgba(127, 168, 255, 0.18);
204
+ }
205
+
206
+ .filter-button[data-level='verbose'].is-active {
207
+ background: rgba(103, 208, 255, 0.18);
208
+ }
209
+
210
+ .search {
211
+ width: min(360px, 100%);
212
+ padding: 11px 14px;
213
+ border: 1px solid var(--border);
214
+ border-radius: 999px;
215
+ background: rgba(5, 12, 20, 0.64);
216
+ color: var(--text);
217
+ font: inherit;
218
+ }
219
+
220
+ .search::placeholder {
221
+ color: #72869c;
222
+ }
223
+
224
+ .status {
225
+ display: inline-flex;
226
+ align-items: center;
227
+ gap: 10px;
228
+ padding: 10px 14px;
229
+ border: 1px solid var(--border);
230
+ border-radius: 999px;
231
+ background: rgba(5, 12, 20, 0.5);
232
+ color: var(--text-muted);
233
+ }
234
+
235
+ .status-dot {
236
+ width: 10px;
237
+ height: 10px;
238
+ border-radius: 999px;
239
+ background: var(--warning);
240
+ box-shadow: 0 0 16px rgba(247, 200, 95, 0.5);
241
+ }
242
+
243
+ .status-dot.is-live {
244
+ background: var(--success);
245
+ box-shadow: 0 0 18px rgba(79, 211, 154, 0.5);
246
+ }
247
+
248
+ .panel {
249
+ border: 1px solid var(--border);
250
+ border-radius: var(--radius-lg);
251
+ background: var(--bg-panel);
252
+ overflow: hidden;
253
+ }
254
+
255
+ .panel-header {
256
+ display: flex;
257
+ flex-wrap: wrap;
258
+ justify-content: space-between;
259
+ gap: 16px;
260
+ align-items: center;
261
+ padding: 16px 18px;
262
+ border-bottom: 1px solid var(--border);
263
+ }
264
+
265
+ .panel-title {
266
+ font-size: 14px;
267
+ font-weight: 700;
268
+ letter-spacing: 0.08em;
269
+ text-transform: uppercase;
270
+ color: var(--text-muted);
271
+ }
272
+
273
+ .panel-meta {
274
+ color: var(--text-muted);
275
+ font-size: 13px;
276
+ }
277
+
278
+ .log-feed {
279
+ height: calc(100vh - 270px);
280
+ min-height: 420px;
281
+ overflow: auto;
282
+ padding: 14px 0;
283
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace;
284
+ font-size: 13px;
285
+ }
286
+
287
+ .log-row {
288
+ display: grid;
289
+ grid-template-columns: 112px 82px 160px minmax(0, 1fr);
290
+ gap: 14px;
291
+ align-items: start;
292
+ padding: 10px 18px;
293
+ border-left: 3px solid transparent;
294
+ transition: background 120ms ease;
295
+ }
296
+
297
+ .log-row:hover {
298
+ background: rgba(255, 255, 255, 0.03);
299
+ }
300
+
301
+ .log-row[data-level='log'] {
302
+ border-left-color: var(--success);
303
+ }
304
+
305
+ .log-row[data-level='warn'] {
306
+ border-left-color: var(--warning);
307
+ }
308
+
309
+ .log-row[data-level='error'] {
310
+ border-left-color: var(--danger);
311
+ }
312
+
313
+ .log-row[data-level='debug'] {
314
+ border-left-color: var(--debug);
315
+ }
316
+
317
+ .log-row[data-level='verbose'] {
318
+ border-left-color: var(--accent);
319
+ }
320
+
321
+ .log-badge,
322
+ .request-badge {
323
+ display: inline-flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ min-width: 68px;
327
+ padding: 6px 10px;
328
+ border-radius: 999px;
329
+ font-size: 11px;
330
+ font-weight: 700;
331
+ letter-spacing: 0.08em;
332
+ text-transform: uppercase;
333
+ color: #081018;
334
+ }
335
+
336
+ .log-badge[data-level='log'] {
337
+ background: var(--success);
338
+ }
339
+
340
+ .log-badge[data-level='warn'] {
341
+ background: var(--warning);
342
+ }
343
+
344
+ .log-badge[data-level='error'] {
345
+ background: var(--danger);
346
+ }
347
+
348
+ .log-badge[data-level='debug'] {
349
+ background: var(--debug);
350
+ }
351
+
352
+ .log-badge[data-level='verbose'] {
353
+ background: var(--accent);
354
+ }
355
+
356
+ .log-time,
357
+ .request-time {
358
+ color: var(--text-muted);
359
+ }
360
+
361
+ .log-context {
362
+ color: #bdd2e7;
363
+ }
364
+
365
+ .log-message {
366
+ white-space: pre-wrap;
367
+ word-break: break-word;
368
+ color: var(--text);
369
+ }
370
+
371
+ .requests {
372
+ min-height: 420px;
373
+ overflow: auto;
374
+ }
375
+
376
+ .request-table {
377
+ width: 100%;
378
+ border-collapse: collapse;
379
+ }
380
+
381
+ .request-table th,
382
+ .request-table td {
383
+ padding: 14px 18px;
384
+ text-align: left;
385
+ border-bottom: 1px solid var(--border);
386
+ }
387
+
388
+ .request-table th {
389
+ position: sticky;
390
+ top: 0;
391
+ background: rgba(15, 24, 36, 0.96);
392
+ color: var(--text-muted);
393
+ font-size: 12px;
394
+ letter-spacing: 0.08em;
395
+ text-transform: uppercase;
396
+ z-index: 1;
397
+ }
398
+
399
+ .request-row {
400
+ cursor: pointer;
401
+ transition: background 140ms ease;
402
+ }
403
+
404
+ .request-row:hover {
405
+ background: rgba(255, 255, 255, 0.03);
406
+ }
407
+
408
+ .request-row.is-open {
409
+ background: rgba(103, 208, 255, 0.06);
410
+ }
411
+
412
+ .request-badge[data-method='GET'] {
413
+ background: rgba(103, 208, 255, 0.92);
414
+ }
415
+
416
+ .request-badge[data-method='POST'] {
417
+ background: rgba(79, 211, 154, 0.92);
418
+ }
419
+
420
+ .request-badge[data-method='PUT'] {
421
+ background: rgba(127, 168, 255, 0.92);
422
+ }
423
+
424
+ .request-badge[data-method='DELETE'] {
425
+ background: rgba(255, 125, 125, 0.92);
426
+ }
427
+
428
+ .request-badge[data-method='PATCH'] {
429
+ background: rgba(248, 185, 74, 0.92);
430
+ }
431
+
432
+ .request-badge[data-method='OTHER'] {
433
+ background: rgba(157, 176, 196, 0.92);
434
+ }
435
+
436
+ .status-badge {
437
+ display: inline-flex;
438
+ align-items: center;
439
+ justify-content: center;
440
+ min-width: 62px;
441
+ padding: 6px 10px;
442
+ border-radius: 999px;
443
+ font-size: 11px;
444
+ font-weight: 700;
445
+ letter-spacing: 0.08em;
446
+ color: #081018;
447
+ }
448
+
449
+ .status-badge[data-range='2xx'] {
450
+ background: var(--success);
451
+ }
452
+
453
+ .status-badge[data-range='4xx'] {
454
+ background: var(--warning);
455
+ }
456
+
457
+ .status-badge[data-range='5xx'] {
458
+ background: var(--danger);
459
+ }
460
+
461
+ .status-badge[data-range='other'] {
462
+ background: #c9d6e2;
463
+ }
464
+
465
+ .duration-wrap {
466
+ display: flex;
467
+ align-items: center;
468
+ gap: 12px;
469
+ }
470
+
471
+ .duration-bar {
472
+ width: 120px;
473
+ height: 8px;
474
+ overflow: hidden;
475
+ border-radius: 999px;
476
+ background: rgba(255, 255, 255, 0.06);
477
+ }
478
+
479
+ .duration-bar > span {
480
+ display: block;
481
+ height: 100%;
482
+ border-radius: inherit;
483
+ background: linear-gradient(90deg, var(--accent), var(--accent-warm));
484
+ }
485
+
486
+ .expanded-row td {
487
+ padding-top: 0;
488
+ background: rgba(7, 13, 20, 0.55);
489
+ }
490
+
491
+ .expanded-card {
492
+ margin: 0 0 16px;
493
+ padding: 16px;
494
+ border: 1px solid var(--border);
495
+ border-radius: var(--radius-md);
496
+ background: rgba(4, 10, 16, 0.62);
497
+ }
498
+
499
+ .expanded-label {
500
+ margin-bottom: 8px;
501
+ color: var(--text-muted);
502
+ font-size: 12px;
503
+ letter-spacing: 0.08em;
504
+ text-transform: uppercase;
505
+ }
506
+
507
+ .expanded-card pre {
508
+ margin: 0;
509
+ color: var(--text);
510
+ white-space: pre-wrap;
511
+ word-break: break-word;
512
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace;
513
+ }
514
+
515
+ .empty-state {
516
+ display: grid;
517
+ place-items: center;
518
+ min-height: 320px;
519
+ padding: 32px;
520
+ color: var(--text-muted);
521
+ text-align: center;
522
+ }
523
+
524
+ .hidden {
525
+ display: none !important;
526
+ }
527
+
528
+ select {
529
+ padding: 10px 14px;
530
+ border: 1px solid var(--border);
531
+ border-radius: 999px;
532
+ background: rgba(5, 12, 20, 0.64);
533
+ color: var(--text);
534
+ font: inherit;
535
+ }
536
+
537
+ @media (max-width: 980px) {
538
+ body {
539
+ padding: 16px;
540
+ }
541
+
542
+ .shell {
543
+ min-height: calc(100vh - 32px);
544
+ padding: 14px;
545
+ }
546
+
547
+ .toolbar {
548
+ padding: 10px;
549
+ }
550
+
551
+ .log-feed {
552
+ height: auto;
553
+ min-height: 320px;
554
+ }
555
+
556
+ .log-row {
557
+ grid-template-columns: 1fr;
558
+ gap: 8px;
559
+ }
560
+
561
+ .request-table th:nth-child(3),
562
+ .request-table td:nth-child(3) {
563
+ min-width: 180px;
564
+ }
565
+
566
+ .duration-bar {
567
+ width: 80px;
568
+ }
569
+ }
570
+ </style>
571
+ </head>
572
+ <body>
573
+ <div class="shell">
574
+ <div class="topbar">
575
+ <div class="brand">
576
+ <div class="brand-mark" aria-hidden="true"></div>
577
+ <div class="brand-copy">
578
+ <div class="eyebrow">NestJS Developer Dashboard</div>
579
+ <div class="title">nest-devtools</div>
580
+ <div class="subtitle">Live logs and request traces with zero extra setup.</div>
581
+ </div>
582
+ </div>
583
+ <div class="tab-group" id="tab-group">
584
+ <button class="tab-button is-active" data-tab="logs" type="button">Logs</button>
585
+ <button class="tab-button" data-tab="requests" type="button">Requests</button>
586
+ </div>
587
+ </div>
588
+
589
+ <div class="toolbar">
590
+ <div style="display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
591
+ <div class="toolbar-group" id="logs-toolbar">
592
+ <button class="filter-button is-active" data-level="log" type="button">log</button>
593
+ <button class="filter-button is-active" data-level="warn" type="button">warn</button>
594
+ <button class="filter-button is-active" data-level="error" type="button">error</button>
595
+ <button class="filter-button is-active" data-level="debug" type="button">debug</button>
596
+ <button class="filter-button is-active" data-level="verbose" type="button">verbose</button>
597
+ </div>
598
+
599
+ <div class="toolbar-group hidden" id="requests-toolbar">
600
+ <button class="filter-button is-active" data-method="GET" type="button">GET</button>
601
+ <button class="filter-button is-active" data-method="POST" type="button">POST</button>
602
+ <button class="filter-button is-active" data-method="PUT" type="button">PUT</button>
603
+ <button class="filter-button is-active" data-method="PATCH" type="button">PATCH</button>
604
+ <button class="filter-button is-active" data-method="DELETE" type="button">DELETE</button>
605
+ <select id="status-filter" aria-label="Filter requests by status">
606
+ <option value="all">All statuses</option>
607
+ <option value="2xx">2xx</option>
608
+ <option value="4xx">4xx</option>
609
+ <option value="5xx">5xx</option>
610
+ </select>
611
+ </div>
612
+
613
+ <input id="search-input" class="search" type="search" placeholder="Search logs by context or message" />
614
+ </div>
615
+
616
+ <div style="display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
617
+ <button class="toolbar-button" id="autoscroll-button" type="button">Follow tail</button>
618
+ <button class="toolbar-button" id="clear-button" type="button">Clear logs</button>
619
+ <div class="status" aria-live="polite">
620
+ <span id="status-dot" class="status-dot"></span>
621
+ <span id="status-text">Connecting...</span>
622
+ </div>
623
+ </div>
624
+ </div>
625
+
626
+ <section class="panel">
627
+ <div class="panel-header">
628
+ <div class="panel-title" id="panel-title">Live Log Feed</div>
629
+ <div class="panel-meta" id="panel-meta">0 entries</div>
630
+ </div>
631
+
632
+ <div id="logs-view" class="log-feed"></div>
633
+ <div id="requests-view" class="requests hidden"></div>
634
+ </section>
635
+ </div>
636
+
637
+ <script>
638
+ window.__NEST_DEVTOOLS_CONFIG__ = __NEST_DEVTOOLS_CONFIG__;
639
+
640
+ (function () {
641
+ var config = window.__NEST_DEVTOOLS_CONFIG__;
642
+ var state = {
643
+ activeTab: 'logs',
644
+ logs: [],
645
+ requests: [],
646
+ expandedRequests: {},
647
+ activeLogLevels: { log: true, warn: true, error: true, debug: true, verbose: true },
648
+ activeMethods: { GET: true, POST: true, PUT: true, PATCH: true, DELETE: true },
649
+ statusFilter: 'all',
650
+ logSearch: '',
651
+ autoScroll: true,
652
+ reconnectTimer: null,
653
+ socket: null,
654
+ };
655
+
656
+ var logsView = document.getElementById('logs-view');
657
+ var requestsView = document.getElementById('requests-view');
658
+ var tabGroup = document.getElementById('tab-group');
659
+ var logsToolbar = document.getElementById('logs-toolbar');
660
+ var requestsToolbar = document.getElementById('requests-toolbar');
661
+ var searchInput = document.getElementById('search-input');
662
+ var clearButton = document.getElementById('clear-button');
663
+ var autoscrollButton = document.getElementById('autoscroll-button');
664
+ var panelTitle = document.getElementById('panel-title');
665
+ var panelMeta = document.getElementById('panel-meta');
666
+ var statusFilter = document.getElementById('status-filter');
667
+ var statusDot = document.getElementById('status-dot');
668
+ var statusText = document.getElementById('status-text');
669
+
670
+ function escapeHtml(value) {
671
+ return String(value)
672
+ .replace(/&/g, '&amp;')
673
+ .replace(/</g, '&lt;')
674
+ .replace(/>/g, '&gt;')
675
+ .replace(/"/g, '&quot;')
676
+ .replace(/'/g, '&#39;');
677
+ }
678
+
679
+ function formatTime(timestamp) {
680
+ try {
681
+ return new Date(timestamp).toLocaleTimeString([], {
682
+ hour12: false,
683
+ hour: '2-digit',
684
+ minute: '2-digit',
685
+ second: '2-digit',
686
+ });
687
+ } catch (error) {
688
+ return timestamp;
689
+ }
690
+ }
691
+
692
+ function formatDuration(duration) {
693
+ return Number(duration).toFixed(2).replace(/\\.00$/, '') + ' ms';
694
+ }
695
+
696
+ function statusRange(code) {
697
+ if (code >= 200 && code < 300) {
698
+ return '2xx';
699
+ }
700
+
701
+ if (code >= 400 && code < 500) {
702
+ return '4xx';
703
+ }
704
+
705
+ if (code >= 500 && code < 600) {
706
+ return '5xx';
707
+ }
708
+
709
+ return 'other';
710
+ }
711
+
712
+ function methodLabel(method) {
713
+ if (state.activeMethods[method] !== undefined) {
714
+ return method;
715
+ }
716
+
717
+ return 'OTHER';
718
+ }
719
+
720
+ function buildApiUrl(path) {
721
+ return config.basePath + path;
722
+ }
723
+
724
+ function setConnectionState(isLive, label) {
725
+ statusDot.classList.toggle('is-live', isLive);
726
+ statusText.textContent = label;
727
+ }
728
+
729
+ function filteredLogs() {
730
+ return state.logs.filter(function (entry) {
731
+ if (!state.activeLogLevels[entry.level]) {
732
+ return false;
733
+ }
734
+
735
+ if (!state.logSearch) {
736
+ return true;
737
+ }
738
+
739
+ var haystack = (entry.context + ' ' + entry.message).toLowerCase();
740
+ return haystack.includes(state.logSearch.toLowerCase());
741
+ });
742
+ }
743
+
744
+ function filteredRequests() {
745
+ return state.requests.filter(function (entry) {
746
+ if (state.activeMethods[entry.method] === false) {
747
+ return false;
748
+ }
749
+
750
+ if (state.statusFilter !== 'all' && statusRange(entry.statusCode) !== state.statusFilter) {
751
+ return false;
752
+ }
753
+
754
+ return true;
755
+ });
756
+ }
757
+
758
+ function renderLogs() {
759
+ var visibleLogs = filteredLogs();
760
+ panelTitle.textContent = 'Live Log Feed';
761
+ panelMeta.textContent = visibleLogs.length + ' entries';
762
+
763
+ if (visibleLogs.length === 0) {
764
+ logsView.innerHTML =
765
+ '<div class="empty-state">No logs match the active filters yet.</div>';
766
+ } else {
767
+ logsView.innerHTML = visibleLogs
768
+ .map(function (entry) {
769
+ return (
770
+ '<div class="log-row" data-level="' + escapeHtml(entry.level) + '">' +
771
+ '<div class="log-time">' + escapeHtml(formatTime(entry.timestamp)) + '</div>' +
772
+ '<div><span class="log-badge" data-level="' + escapeHtml(entry.level) + '">' + escapeHtml(entry.level) + '</span></div>' +
773
+ '<div class="log-context">' + escapeHtml(entry.context || 'app') + '</div>' +
774
+ '<div class="log-message">' + escapeHtml(entry.message) + '</div>' +
775
+ '</div>'
776
+ );
777
+ })
778
+ .join('');
779
+ }
780
+
781
+ if (state.autoScroll) {
782
+ requestAnimationFrame(function () {
783
+ logsView.scrollTop = logsView.scrollHeight;
784
+ });
785
+ }
786
+ }
787
+
788
+ function renderRequests() {
789
+ var visibleRequests = filteredRequests().slice().reverse();
790
+ var maxDuration = visibleRequests.reduce(function (current, entry) {
791
+ return Math.max(current, entry.duration || 0);
792
+ }, 1);
793
+
794
+ panelTitle.textContent = 'HTTP Request Trace';
795
+ panelMeta.textContent = visibleRequests.length + ' entries';
796
+
797
+ if (visibleRequests.length === 0) {
798
+ requestsView.innerHTML =
799
+ '<div class="empty-state">No requests match the active filters yet.</div>';
800
+ return;
801
+ }
802
+
803
+ requestsView.innerHTML =
804
+ '<table class="request-table">' +
805
+ '<thead><tr>' +
806
+ '<th>Timestamp</th>' +
807
+ '<th>Method</th>' +
808
+ '<th>Path</th>' +
809
+ '<th>Status</th>' +
810
+ '<th>Duration</th>' +
811
+ '</tr></thead>' +
812
+ '<tbody>' +
813
+ visibleRequests
814
+ .map(function (entry) {
815
+ var isExpanded = Boolean(state.expandedRequests[entry.id]);
816
+ var width = Math.max(6, Math.round((entry.duration / maxDuration) * 100));
817
+ var range = statusRange(entry.statusCode);
818
+ var expandedMarkup = '';
819
+
820
+ if (isExpanded) {
821
+ expandedMarkup =
822
+ '<tr class="expanded-row">' +
823
+ '<td colspan="5">' +
824
+ '<div class="expanded-card">' +
825
+ '<div class="expanded-label">Full path</div>' +
826
+ '<pre>' + escapeHtml(entry.path) + '</pre>' +
827
+ '</div>' +
828
+ '<div class="expanded-card">' +
829
+ '<div class="expanded-label">Headers</div>' +
830
+ '<pre>' + escapeHtml(JSON.stringify(entry.headers || {}, null, 2)) + '</pre>' +
831
+ '</div>' +
832
+ '</td>' +
833
+ '</tr>';
834
+ }
835
+
836
+ return (
837
+ '<tr class="request-row' + (isExpanded ? ' is-open' : '') + '" data-request-id="' + escapeHtml(entry.id) + '">' +
838
+ '<td class="request-time">' + escapeHtml(formatTime(entry.timestamp)) + '</td>' +
839
+ '<td><span class="request-badge" data-method="' + escapeHtml(methodLabel(entry.method)) + '">' + escapeHtml(entry.method) + '</span></td>' +
840
+ '<td>' + escapeHtml(entry.path) + '</td>' +
841
+ '<td><span class="status-badge" data-range="' + escapeHtml(range) + '">' + escapeHtml(String(entry.statusCode)) + '</span></td>' +
842
+ '<td>' +
843
+ '<div class="duration-wrap">' +
844
+ '<div class="duration-bar"><span style="width:' + width + '%"></span></div>' +
845
+ '<span>' + escapeHtml(formatDuration(entry.duration)) + '</span>' +
846
+ '</div>' +
847
+ '</td>' +
848
+ '</tr>' +
849
+ expandedMarkup
850
+ );
851
+ })
852
+ .join('') +
853
+ '</tbody>' +
854
+ '</table>';
855
+ }
856
+
857
+ function renderToolbar() {
858
+ var isLogs = state.activeTab === 'logs';
859
+ logsToolbar.classList.toggle('hidden', !isLogs);
860
+ requestsToolbar.classList.toggle('hidden', isLogs);
861
+ searchInput.classList.toggle('hidden', !isLogs);
862
+ autoscrollButton.classList.toggle('hidden', !isLogs);
863
+ clearButton.textContent = isLogs ? 'Clear logs' : 'Clear requests';
864
+ }
865
+
866
+ function render() {
867
+ var isLogs = state.activeTab === 'logs';
868
+
869
+ logsView.classList.toggle('hidden', !isLogs);
870
+ requestsView.classList.toggle('hidden', isLogs);
871
+ renderToolbar();
872
+
873
+ if (isLogs) {
874
+ renderLogs();
875
+ } else {
876
+ renderRequests();
877
+ }
878
+ }
879
+
880
+ function handleRealtimeMessage(message) {
881
+ if (!message || !message.type) {
882
+ return;
883
+ }
884
+
885
+ if (message.type === 'log' && message.payload) {
886
+ state.logs.push(message.payload);
887
+ }
888
+
889
+ if (message.type === 'request' && message.payload) {
890
+ state.requests.push(message.payload);
891
+ }
892
+
893
+ if (message.type === 'clear:logs') {
894
+ state.logs = [];
895
+ }
896
+
897
+ if (message.type === 'clear:requests') {
898
+ state.requests = [];
899
+ state.expandedRequests = {};
900
+ }
901
+
902
+ render();
903
+ }
904
+
905
+ function connectWebSocket() {
906
+ if (state.socket && state.socket.readyState <= 1) {
907
+ return;
908
+ }
909
+
910
+ setConnectionState(false, 'Connecting...');
911
+
912
+ var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
913
+ var socket = new WebSocket(protocol + '//' + window.location.host + config.basePath + '/ws');
914
+ state.socket = socket;
915
+
916
+ socket.addEventListener('open', function () {
917
+ setConnectionState(true, 'Live stream connected');
918
+ });
919
+
920
+ socket.addEventListener('message', function (event) {
921
+ try {
922
+ handleRealtimeMessage(JSON.parse(event.data));
923
+ } catch (error) {
924
+ console.error('nest-devtools failed to parse message', error);
925
+ }
926
+ });
927
+
928
+ socket.addEventListener('close', function () {
929
+ setConnectionState(false, 'Reconnecting...');
930
+
931
+ if (state.reconnectTimer) {
932
+ window.clearTimeout(state.reconnectTimer);
933
+ }
934
+
935
+ state.reconnectTimer = window.setTimeout(function () {
936
+ state.reconnectTimer = null;
937
+ connectWebSocket();
938
+ }, 1500);
939
+ });
940
+
941
+ socket.addEventListener('error', function () {
942
+ socket.close();
943
+ });
944
+ }
945
+
946
+ function setActiveTab(tab) {
947
+ state.activeTab = tab;
948
+ Array.prototype.forEach.call(tabGroup.querySelectorAll('[data-tab]'), function (button) {
949
+ button.classList.toggle('is-active', button.getAttribute('data-tab') === tab);
950
+ });
951
+ render();
952
+ }
953
+
954
+ async function clearActiveTab() {
955
+ var target = state.activeTab === 'logs' ? '/api/logs' : '/api/requests';
956
+ await fetch(buildApiUrl(target), { method: 'DELETE' });
957
+ }
958
+
959
+ async function bootstrap() {
960
+ setConnectionState(false, 'Loading snapshots...');
961
+
962
+ var snapshots = await Promise.all([
963
+ fetch(buildApiUrl('/api/logs')).then(function (response) { return response.json(); }),
964
+ fetch(buildApiUrl('/api/requests')).then(function (response) { return response.json(); }),
965
+ ]);
966
+
967
+ state.logs = snapshots[0];
968
+ state.requests = snapshots[1];
969
+ render();
970
+ connectWebSocket();
971
+ }
972
+
973
+ tabGroup.addEventListener('click', function (event) {
974
+ var target = event.target;
975
+ if (!(target instanceof HTMLElement) || !target.dataset.tab) {
976
+ return;
977
+ }
978
+
979
+ setActiveTab(target.dataset.tab);
980
+ });
981
+
982
+ logsToolbar.addEventListener('click', function (event) {
983
+ var target = event.target;
984
+ if (!(target instanceof HTMLElement) || !target.dataset.level) {
985
+ return;
986
+ }
987
+
988
+ var level = target.dataset.level;
989
+ state.activeLogLevels[level] = !state.activeLogLevels[level];
990
+ target.classList.toggle('is-active', state.activeLogLevels[level]);
991
+ render();
992
+ });
993
+
994
+ requestsToolbar.addEventListener('click', function (event) {
995
+ var target = event.target;
996
+ if (!(target instanceof HTMLElement) || !target.dataset.method) {
997
+ return;
998
+ }
999
+
1000
+ var method = target.dataset.method;
1001
+ state.activeMethods[method] = !state.activeMethods[method];
1002
+ target.classList.toggle('is-active', state.activeMethods[method]);
1003
+ render();
1004
+ });
1005
+
1006
+ statusFilter.addEventListener('change', function () {
1007
+ state.statusFilter = statusFilter.value;
1008
+ render();
1009
+ });
1010
+
1011
+ searchInput.addEventListener('input', function () {
1012
+ state.logSearch = searchInput.value;
1013
+ render();
1014
+ });
1015
+
1016
+ clearButton.addEventListener('click', function () {
1017
+ clearActiveTab().catch(function (error) {
1018
+ console.error('nest-devtools failed to clear data', error);
1019
+ });
1020
+ });
1021
+
1022
+ autoscrollButton.addEventListener('click', function () {
1023
+ state.autoScroll = !state.autoScroll;
1024
+ autoscrollButton.textContent = state.autoScroll ? 'Follow tail' : 'Pause tail';
1025
+ render();
1026
+ });
1027
+
1028
+ requestsView.addEventListener('click', function (event) {
1029
+ var row = event.target instanceof HTMLElement ? event.target.closest('[data-request-id]') : null;
1030
+ if (!row) {
1031
+ return;
1032
+ }
1033
+
1034
+ var requestId = row.getAttribute('data-request-id');
1035
+ state.expandedRequests[requestId] = !state.expandedRequests[requestId];
1036
+ render();
1037
+ });
1038
+
1039
+ bootstrap().catch(function (error) {
1040
+ setConnectionState(false, 'Dashboard failed to load');
1041
+ console.error('nest-devtools failed to bootstrap', error);
1042
+ });
1043
+ })();
1044
+ </script>
1045
+ </body>
1046
+ </html>`;
1047
+ function renderDashboardHtml(basePath) {
1048
+ return DASHBOARD_TEMPLATE.replace('__NEST_DEVTOOLS_CONFIG__', JSON.stringify({
1049
+ basePath,
1050
+ }));
1051
+ }
1052
+ //# sourceMappingURL=index.html.js.map