@aimeloic/monkey-tester 2.0.1 → 2.0.3

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 (3) hide show
  1. package/htmlTemplate.js +270 -1038
  2. package/index.js +59 -73
  3. package/package.json +1 -1
package/htmlTemplate.js CHANGED
@@ -1,1172 +1,404 @@
1
1
  export function getHtmlTemplate(endpoints) {
2
- const safeJsonString = Buffer
3
- .from(JSON.stringify(endpoints))
4
- .toString('base64');
2
+ const safeJsonString = Buffer.from(JSON.stringify(endpoints)).toString('base64');
5
3
 
6
4
  return `
7
5
  <!DOCTYPE html>
8
6
  <html lang="en">
9
7
  <head>
10
- <meta charset="UTF-8" />
11
- <meta
12
- name="viewport"
13
- content="width=device-width, initial-scale=1.0"
14
- />
15
-
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
10
  <title>Endtester — API Environment</title>
17
-
18
- <link
19
- href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap"
20
- rel="stylesheet"
21
- />
22
-
11
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
23
12
  <style>
24
- :root{
25
- --bg:#0e0c09;
26
- --surface:#181510;
27
- --surface2:#221d14;
28
- --border:#3a3020;
29
- --accent:#e8a838;
30
- --text:#f0e8d8;
31
- --text-dim:#9a8c78;
32
- --red:#d45c3c;
33
- --green:#6ba05a;
34
- --blue:#5a86c0;
35
- --radius:8px;
36
- }
37
-
38
- *{
39
- box-sizing:border-box;
40
- margin:0;
41
- padding:0;
42
- min-width:0;
43
- }
44
-
45
- html,
46
- body{
47
- width:100%;
48
- overflow-x:hidden;
49
- }
50
-
51
- body{
52
- background:var(--bg);
53
- color:var(--text);
54
- font-family:'DM Sans',sans-serif;
55
- font-size:14px;
56
- min-height:100vh;
57
- overflow-x:hidden;
58
-
59
- background-image:
60
- radial-gradient(
61
- ellipse 80% 60% at 50% -20%,
62
- #3a2a0a22 0%,
63
- transparent 70%
64
- );
65
- }
66
-
67
- header{
68
- width:100%;
69
- border-bottom:1px solid var(--border);
70
-
71
- padding:20px 24px;
72
-
73
- display:flex;
74
- align-items:center;
75
- gap:20px;
76
-
77
- background:#0e0c09ee;
78
-
79
- backdrop-filter:blur(8px);
80
-
81
- position:sticky;
82
- top:0;
83
- z-index:100;
84
-
85
- overflow:hidden;
86
- }
87
-
88
- .logo{
89
- font-family:'Playfair Display',serif;
90
- font-size:22px;
91
- color:var(--accent);
92
- letter-spacing:.02em;
93
- flex-shrink:0;
94
- }
95
-
96
- .logo span{
97
- color:var(--text-dim);
98
- font-size:13px;
99
- font-family:'DM Mono',monospace;
100
- display:block;
101
- }
102
-
103
- .header-right{
104
- margin-left:auto;
105
-
106
- display:flex;
107
- align-items:center;
108
- gap:12px;
109
-
110
- flex-wrap:wrap;
111
- justify-content:flex-end;
112
-
113
- max-width:100%;
114
- }
115
-
116
- .jwt-wrap,
117
- .base-url-wrap{
118
- display:flex;
119
- align-items:center;
120
- gap:8px;
121
-
122
- min-width:0;
123
- }
124
-
125
- .jwt-wrap label,
126
- .base-url-wrap label{
127
- color:var(--text-dim);
128
- font-size:12px;
129
- font-family:'DM Mono',monospace;
130
- white-space:nowrap;
131
- }
132
-
133
- #jwt-input,
134
- #base-url{
135
- background:var(--surface2);
136
-
137
- border:1px solid var(--border);
138
-
139
- color:var(--text);
140
-
141
- font-family:'DM Mono',monospace;
142
-
143
- font-size:11px;
144
-
145
- padding:6px 10px;
146
-
147
- border-radius:var(--radius);
148
-
149
- width:220px;
150
- max-width:100%;
151
-
152
- outline:none;
153
- }
154
-
155
- .layout{
156
- width:100%;
157
- max-width:100vw;
158
-
159
- display:grid;
160
-
161
- grid-template-columns:
162
- minmax(220px,260px)
163
- minmax(0,1fr)
164
- minmax(300px,400px);
165
-
166
- height:calc(100vh - 78px);
167
-
168
- overflow:hidden;
169
- }
170
-
171
- aside{
172
- border-right:1px solid var(--border);
173
-
174
- overflow-y:auto;
175
- overflow-x:hidden;
176
-
177
- padding:16px 0;
178
- }
179
-
180
- .section-label{
181
- font-size:10px;
182
-
183
- font-family:'DM Mono',monospace;
184
-
185
- color:var(--text-dim);
186
-
187
- letter-spacing:.12em;
188
-
189
- text-transform:uppercase;
190
-
191
- padding:12px 18px 6px;
192
- }
193
-
194
- .nav-item{
195
- display:flex;
196
- align-items:center;
197
- gap:10px;
198
-
199
- padding:8px 18px;
200
-
201
- cursor:pointer;
202
-
203
- border-left:2px solid transparent;
204
-
205
- color:var(--text-dim);
206
-
207
- overflow:hidden;
208
- }
209
-
210
- .nav-item:hover{
211
- background:var(--surface);
212
- color:var(--text);
213
- }
214
-
215
- .nav-item.active{
216
- border-left-color:var(--accent);
217
-
218
- background:var(--surface);
219
-
220
- color:var(--accent);
221
- }
222
-
223
- .method-badge{
224
- font-family:'DM Mono',monospace;
225
-
226
- font-size:9px;
227
-
228
- font-weight:500;
229
-
230
- padding:2px 5px;
231
-
232
- border-radius:3px;
233
-
234
- min-width:45px;
235
-
236
- text-align:center;
237
-
238
- flex-shrink:0;
239
- }
240
-
241
- .GET{
242
- background:#1a3a22;
243
- color:#6ba05a;
244
- }
245
-
246
- .POST{
247
- background:#1a2e3a;
248
- color:#5a86c0;
249
- }
250
-
251
- .PUT,
252
- .PATCH{
253
- background:#3a2e10;
254
- color:#e8a838;
255
- }
256
-
257
- .DELETE{
258
- background:#3a1a14;
259
- color:#d45c3c;
260
- }
261
-
262
- .nav-label{
263
- font-size:11px;
264
-
265
- flex:1;
266
-
267
- overflow:hidden;
268
-
269
- text-overflow:ellipsis;
270
-
271
- white-space:nowrap;
272
- }
273
-
274
- main{
275
- overflow-y:auto;
276
- overflow-x:hidden;
277
-
278
- padding:24px;
279
- }
280
-
281
- .endpoint-title{
282
- font-family:'Playfair Display',serif;
283
-
284
- font-size:20px;
285
-
286
- color:var(--accent);
287
-
288
- margin-bottom:6px;
289
-
290
- overflow-wrap:anywhere;
291
- }
292
-
293
- .endpoint-path{
294
- font-family:'DM Mono',monospace;
295
-
296
- font-size:12px;
297
-
298
- color:var(--text-dim);
299
-
300
- margin-bottom:20px;
301
-
302
- display:flex;
303
- align-items:center;
304
- gap:8px;
305
-
306
- flex-wrap:wrap;
307
-
308
- overflow-wrap:anywhere;
309
- }
310
-
311
- .endpoint-desc{
312
- color:var(--text-dim);
313
-
314
- font-size:13px;
315
-
316
- line-height:1.6;
317
-
318
- margin-bottom:24px;
319
-
320
- border-left:2px solid var(--border);
321
-
322
- padding-left:12px;
323
- }
324
-
325
- .form-section{
326
- background:var(--surface);
327
-
328
- border:1px solid var(--border);
329
-
330
- border-radius:var(--radius);
331
-
332
- padding:18px;
333
-
334
- margin-bottom:16px;
335
-
336
- overflow:hidden;
337
- }
338
-
339
- .form-section-title{
340
- font-size:11px;
341
-
342
- font-family:'DM Mono',monospace;
343
-
344
- color:var(--text-dim);
345
-
346
- letter-spacing:.1em;
347
-
348
- text-transform:uppercase;
349
-
350
- margin-bottom:14px;
351
- }
352
-
353
- .field-row{
354
- display:grid;
355
-
356
- grid-template-columns:
357
- minmax(90px,140px)
358
- minmax(0,1fr);
359
-
360
- align-items:center;
361
-
362
- gap:10px;
363
-
364
- margin-bottom:10px;
365
- }
366
-
367
- .field-label{
368
- font-family:'DM Mono',monospace;
369
-
370
- font-size:11px;
371
-
372
- color:var(--text-dim);
373
-
374
- text-align:right;
375
-
376
- overflow-wrap:anywhere;
377
- }
378
-
379
- input,
380
- select,
381
- textarea{
382
- max-width:100%;
383
- }
384
-
385
- input[type=text],
386
- input[type=number],
387
- input[type=password],
388
- input[type=email],
389
- input[type=date],
390
- input[type=tel],
391
- input[type=url],
392
- select{
393
- background:var(--surface2);
394
-
395
- border:1px solid var(--border);
396
-
397
- color:var(--text);
398
-
399
- font-family:'DM Mono',monospace;
400
-
401
- font-size:12px;
402
-
403
- padding:7px 10px;
404
-
405
- border-radius:var(--radius);
406
-
407
- width:100%;
408
-
409
- outline:none;
410
- }
411
-
412
- .btn-row{
413
- margin-top:20px;
414
-
415
- display:flex;
416
- gap:10px;
417
-
418
- flex-wrap:wrap;
419
- }
420
-
421
- .btn{
422
- background:var(--accent);
423
-
424
- color:#0e0c09;
425
-
426
- border:none;
427
-
428
- padding:10px 22px;
429
-
430
- border-radius:var(--radius);
431
-
432
- font-size:13px;
433
-
434
- font-weight:500;
435
-
436
- cursor:pointer;
437
- }
438
-
439
- .btn-secondary{
440
- background:var(--surface2);
441
-
442
- color:var(--text-dim);
443
-
444
- border:1px solid var(--border);
445
- }
446
-
447
- .response-panel{
448
- border-left:1px solid var(--border);
449
-
450
- display:flex;
451
-
452
- flex-direction:column;
453
-
454
- overflow:hidden;
455
-
456
- min-width:0;
457
- }
458
-
459
- .response-header{
460
- padding:14px 18px;
461
-
462
- border-bottom:1px solid var(--border);
463
-
464
- display:flex;
465
- align-items:center;
466
-
467
- gap:10px;
468
-
469
- background:var(--surface);
470
-
471
- overflow:hidden;
472
- }
473
-
474
- .response-header-title{
475
- font-size:11px;
476
-
477
- font-family:'DM Mono',monospace;
478
-
479
- color:var(--text-dim);
480
-
481
- text-transform:uppercase;
482
- }
483
-
484
- .status-badge{
485
- font-family:'DM Mono',monospace;
486
-
487
- font-size:12px;
488
-
489
- margin-left:auto;
490
-
491
- padding:2px 8px;
492
-
493
- border-radius:4px;
494
-
495
- flex-shrink:0;
496
- }
497
-
498
- .status-ok{
499
- background:#1a3a22;
500
- color:#6ba05a;
501
- }
502
-
503
- .status-err{
504
- background:#3a1a14;
505
- color:#d45c3c;
506
- }
507
-
508
- .status-idle{
509
- background:var(--surface2);
510
- color:var(--text-dim);
511
- }
512
-
513
- .response-body{
514
- flex:1;
515
-
516
- overflow:auto;
517
-
518
- padding:16px;
519
-
520
- font-family:'DM Mono',monospace;
521
-
522
- font-size:12px;
523
-
524
- white-space:pre-wrap;
525
-
526
- overflow-wrap:anywhere;
527
-
528
- word-break:break-word;
529
- }
530
-
531
- .response-body.empty{
532
- color:var(--text-dim);
533
-
534
- display:flex;
535
-
536
- align-items:center;
537
-
538
- justify-content:center;
539
-
540
- text-align:center;
541
- }
542
-
543
- .json-key{color:#e8a838;}
544
- .json-str{color:#9ab878;}
545
- .json-num{color:#5a86c0;}
546
-
547
- #toast{
548
- position:fixed;
549
-
550
- bottom:24px;
551
- right:24px;
552
-
553
- background:var(--surface2);
554
-
555
- border:1px solid var(--border);
556
-
557
- padding:10px 18px;
558
-
559
- border-radius:var(--radius);
560
-
561
- opacity:0;
562
-
563
- transition:all .25s;
564
-
565
- max-width:90vw;
566
-
567
- overflow-wrap:anywhere;
568
- }
569
-
570
- #toast.show{
571
- opacity:1;
572
- }
573
-
574
- @media (max-width:1200px){
575
-
576
- .layout{
577
- grid-template-columns:
578
- 220px
579
- 1fr;
13
+ :root {
14
+ --bg: #0e0c09;
15
+ --surface: #181510;
16
+ --surface2: #221d14;
17
+ --border: #3a3020;
18
+ --accent: #e8a838;
19
+ --accent2: #c47a1e;
20
+ --text: #f0e8d8;
21
+ --text-dim: #9a8c78;
22
+ --red: #d45c3c;
23
+ --green: #6ba05a;
24
+ --blue: #5a86c0;
25
+ --radius: 8px;
580
26
  }
581
-
582
- .response-panel{
583
- display:none;
27
+
28
+ * { box-sizing: border-box; margin: 0; padding: 0; }
29
+
30
+ body {
31
+ background: var(--bg);
32
+ color: var(--text);
33
+ font-family: 'DM Sans', sans-serif;
34
+ font-size: 14px;
35
+ height: 100vh;
36
+ overflow: hidden; /* Prevents whole-page scrolling */
37
+ background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%);
584
38
  }
585
- }
586
-
587
- @media (max-width:768px){
588
-
589
- header{
590
- flex-direction:column;
591
- align-items:flex-start;
39
+
40
+ header {
41
+ border-bottom: 1px solid var(--border);
42
+ padding: 16px 32px;
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 20px;
46
+ background: #0e0c09ee;
47
+ backdrop-filter: blur(8px);
48
+ height: 65px;
592
49
  }
593
-
594
- .header-right{
595
- width:100%;
50
+
51
+ .logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
52
+ .logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
53
+ .header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
54
+ .jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
55
+ .jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; letter-spacing: 0.05em; }
56
+
57
+ #jwt-input, #base-url {
58
+ background: var(--surface2); border: 1px solid var(--border); color: var(--text);
59
+ font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none;
596
60
  }
597
-
598
- .layout{
599
- grid-template-columns:1fr;
61
+
62
+ /* Fixed view height viewport matrix layout grid rules */
63
+ .layout {
64
+ display: grid;
65
+ grid-template-columns: 280px 1fr 450px;
66
+ height: calc(100vh - 65px);
67
+ overflow: hidden;
600
68
  }
601
-
602
- aside{
603
- display:none;
69
+
70
+ aside {
71
+ border-right: 1px solid var(--border);
72
+ overflow-y: auto;
73
+ padding: 16px 0;
74
+ background: #0b0907;
604
75
  }
605
-
606
- .field-row{
607
- grid-template-columns:1fr;
76
+
77
+ .section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.12em; text-transform: uppercase; padding: 12px 18px 6px; }
78
+
79
+ .nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); transition: all 0.2s; }
80
+ .nav-item:hover { background: var(--surface); color: var(--text); }
81
+ .nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
82
+
83
+ .method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center; text-transform: uppercase; }
84
+ .GET { background: #1a3a22; color: #6ba05a; }
85
+ .POST { background: #1a2e3a; color: #5a86c0; }
86
+ .PUT, .PATCH { background: #3a2e10; color: #e8a838; }
87
+ .DELETE { background: #3a1a14; color: #d45c3c; }
88
+
89
+ .nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
90
+
91
+ main {
92
+ overflow-y: auto;
93
+ padding: 32px;
94
+ background: #0e0c09;
608
95
  }
609
-
610
- .field-label{
611
- text-align:left;
96
+
97
+ .endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
98
+ .endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
99
+ .endpoint-desc { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 24px; border-left: 2px solid var(--border); padding-left: 12px; }
100
+
101
+ .form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
102
+ .form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
103
+
104
+ .field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
105
+ .field-row:last-child { margin-bottom: 0; }
106
+ .field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
107
+
108
+ input[type=text], input[type=password], input[type=number], input[type=date], input[type=tel], input[type=url], select {
109
+ background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; transition: border-color 0.2s;
612
110
  }
613
- }
111
+ input:focus { border-color: var(--accent); }
112
+
113
+ .btn-row { margin-top: 24px; display: flex; gap: 12px; }
114
+ .btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
115
+ .btn:hover { background: #f0b850; }
116
+ .btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
117
+ .btn-secondary:hover { color: var(--text); background: var(--surface); }
118
+
119
+ /* FIXED: Response block scroll logic layout rules */
120
+ .response-panel {
121
+ border-left: 1px solid var(--border);
122
+ display: flex;
123
+ flex-direction: column;
124
+ overflow: hidden;
125
+ background: #110e0a;
126
+ }
127
+ .response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
128
+ .response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
129
+ .status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
130
+ .status-ok { background: #1a3a22; color: #6ba05a; }
131
+ .status-err { background: #3a1a14; color: #d45c3c; }
132
+ .status-idle { background: var(--surface2); color: var(--text-dim); }
133
+
134
+ /* FIXED: Body panel scrolls y natively, inner token element handles x scrolling */
135
+ .response-body {
136
+ flex: 1;
137
+ overflow-y: auto;
138
+ overflow-x: hidden;
139
+ padding: 0;
140
+ background: #0d0b08;
141
+ }
142
+ .response-body.empty {
143
+ color: var(--text-dim);
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ padding: 20px;
148
+ text-align: center;
149
+ font-size: 13px;
150
+ }
151
+
152
+ /* FIXED: Token code output container handles micro horizontal data flows elegantly */
153
+ .json-render-block {
154
+ display: block;
155
+ padding: 20px;
156
+ margin: 0;
157
+ font-family: 'DM Mono', monospace;
158
+ font-size: 12px;
159
+ line-height: 1.5;
160
+ white-space: pre;
161
+ overflow-x: auto;
162
+ word-break: normal;
163
+ word-wrap: normal;
164
+ }
165
+
166
+ .json-key { color: #e8a838; }
167
+ .json-str { color: #9ab878; }
168
+ .json-num { color: #5a86c0; }
169
+
170
+ #toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s; z-index: 1000; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
171
+ #toast.show { opacity: 1; }
614
172
  </style>
615
173
  </head>
616
-
617
174
  <body>
618
175
 
619
- <div
620
- id="endtester-data-vault"
621
- data-payload="${safeJsonString}"
622
- style="display:none;"
623
- ></div>
176
+ <div id="endtester-data-vault" data-payload="${safeJsonString}" style="display: none;"></div>
624
177
 
625
178
  <header>
626
- <div class="logo">
627
- Endtester
628
- <span>Application Runtime Sandbox</span>
629
- </div>
630
-
179
+ <div class="logo">Endtester <span>Application Runtime Sandbox</span></div>
631
180
  <div class="header-right">
632
-
633
181
  <div class="base-url-wrap">
634
182
  <label>TARGET HOST</label>
635
-
636
- <input
637
- id="base-url"
638
- type="text"
639
- value=""
640
- />
183
+ <input id="base-url" type="text" value="">
641
184
  </div>
642
-
643
185
  <div class="jwt-wrap">
644
186
  <label>BEARER AUTH</label>
645
-
646
- <input
647
- id="jwt-input"
648
- type="text"
649
- placeholder="Token value..."
650
- />
187
+ <input id="jwt-input" type="text" placeholder="Token value...">
651
188
  </div>
652
-
653
189
  </div>
654
190
  </header>
655
191
 
656
192
  <div class="layout">
657
-
658
193
  <aside id="sidebar-nav">
659
- <div class="section-label">
660
- Discovered Endpoints
661
- </div>
194
+ <div class="section-label">Discovered Endpoints</div>
662
195
  </aside>
663
-
664
196
  <main id="main-panel"></main>
665
-
666
197
  <div class="response-panel">
667
-
668
198
  <div class="response-header">
669
- <span class="response-header-title">
670
- Response Output
671
- </span>
672
-
673
- <span
674
- id="status-badge"
675
- class="status-badge status-idle"
676
- >
677
-
678
- </span>
679
- </div>
680
-
681
- <div
682
- class="response-body empty"
683
- id="response-body"
684
- >
685
- Execute a request row to generate feedback data
199
+ <span class="response-header-title">Response Output</span>
200
+ <span id="status-badge" class="status-badge status-idle">—</span>
686
201
  </div>
687
-
202
+ <div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
688
203
  </div>
689
-
690
204
  </div>
691
205
 
692
206
  <div id="toast"></div>
693
207
 
694
208
  <script>
695
- const rawPayload =
696
- document
697
- .getElementById('endtester-data-vault')
698
- .getAttribute('data-payload');
699
-
700
- const ENDPOINTS =
701
- JSON.parse(atob(rawPayload));
209
+ const rawPayload = document.getElementById('endtester-data-vault').getAttribute('data-payload');
210
+ const ENDPOINTS = JSON.parse(atob(rawPayload));
702
211
 
703
212
  let currentEp = '';
704
213
 
705
- document
706
- .getElementById('base-url')
707
- .value = window.location.origin;
708
-
709
- function buildSidebar(){
710
-
711
- const sidebar =
712
- document.getElementById('sidebar-nav');
214
+ document.getElementById('base-url').value = window.location.origin;
713
215
 
714
- const keys =
715
- Object.keys(ENDPOINTS);
716
-
717
- if(keys.length === 0){
718
-
719
- sidebar.innerHTML +=
720
- '<div style="padding:15px;color:var(--text-dim)">No endpoints discovered.</div>';
216
+ function buildSidebar() {
217
+ const sidebar = document.getElementById('sidebar-nav');
218
+ const keys = Object.keys(ENDPOINTS);
721
219
 
220
+ if (keys.length === 0) {
221
+ sidebar.innerHTML += '<div style="padding:15px; color:var(--text-dim)">No active application endpoints discovered.</div>';
722
222
  return;
723
223
  }
724
224
 
725
- keys.forEach((key,index)=>{
726
-
225
+ keys.forEach((key, index) => {
727
226
  const ep = ENDPOINTS[key];
728
-
729
- const div =
730
- document.createElement('div');
731
-
732
- div.className =
733
- index === 0
734
- ? 'nav-item active'
735
- : 'nav-item';
736
-
737
- div.setAttribute('data-ep',key);
738
-
739
- div.innerHTML =
740
- '<span class="method-badge ' + ep.method + '">' +
741
- ep.method +
742
- '</span>' +
743
- '<span class="nav-label">' +
744
- ep.path +
745
- '</span>';
746
-
747
- div.addEventListener('click',()=>{
748
-
749
- document
750
- .querySelectorAll('.nav-item')
751
- .forEach(n=>n.classList.remove('active'));
752
-
227
+ const div = document.createElement('div');
228
+ div.className = index === 0 ? 'nav-item active' : 'nav-item';
229
+ div.setAttribute('data-ep', key);
230
+ div.innerHTML = '<span class="method-badge ' + ep.method + '">' + ep.method + '</span><span class="nav-label">' + ep.path + '</span>';
231
+ div.addEventListener('click', () => {
232
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
753
233
  div.classList.add('active');
754
-
755
234
  clearResponse();
756
-
757
235
  renderPanel(key);
758
236
  });
759
-
760
237
  sidebar.appendChild(div);
761
238
  });
762
239
 
763
- if(keys.length > 0){
764
- renderPanel(keys[0]);
765
- }
240
+ if (keys.length > 0) renderPanel(keys[0]);
766
241
  }
767
242
 
768
- function makeInputString(
769
- type,
770
- id,
771
- placeholder,
772
- defaultValue
773
- ){
774
-
775
- const pAttr =
776
- placeholder
777
- ? ' placeholder="' + placeholder + '"'
778
- : '';
779
-
780
- const vAttr =
781
- defaultValue !== undefined
782
- ? ' value="' + defaultValue + '"'
783
- : '';
784
-
785
- return \`
786
- <input
787
- type="\${type}"
788
- id="\${id}"
789
- \${pAttr}
790
- \${vAttr}
791
- />
792
- \`;
243
+ function makeInputString(type, id, placeholder) {
244
+ const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
245
+ return '<input type="' + type + '" id="' + id + '"' + pAttr + ' />';
793
246
  }
794
247
 
795
- function renderPanel(epKey){
796
-
248
+ function renderPanel(epKey) {
797
249
  currentEp = epKey;
798
-
799
250
  const ep = ENDPOINTS[epKey];
800
-
801
- const main =
802
- document.getElementById('main-panel');
803
-
804
- if(!ep) return;
251
+ const main = document.getElementById('main-panel');
252
+ if (!ep) return;
805
253
 
806
254
  let html = \`
807
- <div class="endpoint-title">
808
- \${ep.title}
809
- </div>
810
-
255
+ <div class="endpoint-title">\${ep.title}</div>
811
256
  <div class="endpoint-path">
812
- <span class="method-badge \${ep.method}">
813
- \${ep.method}
814
- </span>
815
-
816
- <span>
817
- \${ep.path}
818
- </span>
819
- </div>
820
-
821
- <div class="endpoint-desc">
822
- \${ep.desc}
257
+ <span class="method-badge \${ep.method}">\${ep.method}</span>
258
+ <span>\${ep.path}</span>
823
259
  </div>
260
+ <div class="endpoint-desc">\${ep.desc}</div>
824
261
  \`;
825
262
 
826
- if(ep.params && ep.params.length){
827
-
828
- html += \`
829
- <div class="form-section">
830
-
831
- <div class="form-section-title">
832
- Path Parameters
833
- </div>
834
- \`;
835
-
836
- ep.params.forEach((p)=>{
837
-
263
+ if (ep.params && ep.params.length) {
264
+ html += \`<div class="form-section"><div class="form-section-title">Path Parameters</div>\`;
265
+ ep.params.forEach(function(p) {
266
+ const inputHtml = makeInputString('text', 'param-' + p.name, p.placeholder);
838
267
  html += \`
839
268
  <div class="field-row">
840
-
841
- <label class="field-label">
842
- \${p.label}
843
- </label>
844
-
845
- \${makeInputString(
846
- 'text',
847
- 'param-' + p.name,
848
- p.placeholder,
849
- ''
850
- )}
851
-
269
+ <label class="field-label">\${p.label}</label>
270
+ \${inputHtml}
852
271
  </div>
853
272
  \`;
854
273
  });
855
-
856
- html += '</div>';
274
+ html += \`</div>\`;
857
275
  }
858
276
 
859
- if(ep.fields && ep.fields.length){
860
-
861
- html += \`
862
- <div class="form-section">
863
-
864
- <div class="form-section-title">
865
- Request Body
866
- </div>
867
- \`;
868
-
869
- ep.fields.forEach((f)=>{
870
-
277
+ if (ep.fields && ep.fields.length) {
278
+ html += \`<div class="form-section"><div class="form-section-title">HTTP JSON Request Payload Parameters</div>\`;
279
+ ep.fields.forEach(function(f) {
280
+ const inputHtml = makeInputString(f.type || 'text', 'field-' + f.name, f.placeholder || '');
871
281
  html += \`
872
282
  <div class="field-row">
873
-
874
- <label class="field-label">
875
- \${f.label}
876
- </label>
877
-
878
- \${makeInputString(
879
- f.type || 'text',
880
- 'field-' + f.name,
881
- f.placeholder || '',
882
- ''
883
- )}
884
-
283
+ <label class="field-label">\${f.label}</label>
284
+ \${inputHtml}
885
285
  </div>
886
286
  \`;
887
287
  });
888
-
889
- html += '</div>';
288
+ html += \`</div>\`;
890
289
  }
891
290
 
892
291
  html += \`
893
292
  <div class="btn-row">
894
-
895
- <button
896
- class="btn"
897
- onclick="sendRequest()"
898
- >
899
- Execute Route
900
- </button>
901
-
902
- <button
903
- class="btn btn-secondary"
904
- onclick="clearResponse()"
905
- >
906
- Clear Context
907
- </button>
908
-
293
+ <button class="btn" onclick="sendRequest()">Execute Route</button>
294
+ <button class="btn btn-secondary" onclick="clearResponse()">Clear Context</button>
909
295
  </div>
910
296
  \`;
911
297
 
912
298
  main.innerHTML = html;
913
299
  }
914
300
 
915
- async function sendRequest(){
916
-
917
- const ep =
918
- ENDPOINTS[currentEp];
919
-
301
+ async function sendRequest() {
302
+ const ep = ENDPOINTS[currentEp];
920
303
  let path = ep.path;
921
304
 
922
- if(ep.params){
923
-
924
- for(const p of ep.params){
925
-
926
- const val =
927
- document
928
- .getElementById('param-' + p.name)
929
- ?.value
930
- .trim();
931
-
932
- if(!val){
933
-
934
- showToast(
935
- 'Parameter required: ' + p.label
936
- );
937
-
938
- return;
939
- }
940
-
941
- path =
942
- path.replace(
943
- ':' + p.name,
944
- encodeURIComponent(val)
945
- );
305
+ if (ep.params) {
306
+ for (const p of ep.params) {
307
+ const val = document.getElementById('param-' + p.name)?.value.trim();
308
+ if (!val) { showToast('Warning: Parameter ' + p.label + ' is required'); return; }
309
+ path = path.replace(':' + p.name, encodeURIComponent(val));
946
310
  }
947
311
  }
948
312
 
949
- const baseUrl =
950
- document
951
- .getElementById('base-url')
952
- .value
953
- .replace(/[/]+$/,'');
954
-
313
+ const baseUrl = document.getElementById('base-url').value.replace(/[/]+$/, '');
955
314
  const url = baseUrl + path;
956
-
957
- const headers = {
958
- 'Content-Type':'application/json'
959
- };
960
-
961
- const jwt =
962
- document
963
- .getElementById('jwt-input')
964
- .value
965
- .trim();
966
-
967
- if(jwt){
968
- headers['Authorization'] =
969
- 'Bearer ' + jwt;
970
- }
971
-
972
- let body;
973
-
974
- if(
975
- ep.fields &&
976
- ep.fields.length &&
977
- ['POST','PUT','PATCH']
978
- .includes(ep.method)
979
- ){
980
-
981
- const payloadObject = {};
982
-
983
- ep.fields.forEach((f)=>{
984
-
985
- const input =
986
- document.getElementById(
987
- 'field-' + f.name
988
- );
989
-
990
- if(!input) return;
991
-
992
- let value = input.value;
993
-
994
- if(f.type === 'number'){
995
- value =
996
- value === ''
997
- ? null
998
- : Number(value);
315
+ const headers = { 'Content-Type': 'application/json' };
316
+
317
+ const jwt = document.getElementById('jwt-input').value.trim();
318
+ if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
319
+
320
+ let body = undefined;
321
+ if (ep.fields && ep.fields.length && ['POST', 'PUT', 'PATCH'].includes(ep.method)) {
322
+ const jsonPayload = {};
323
+ for (const f of ep.fields) {
324
+ const targetEl = document.getElementById('field-' + f.name);
325
+ if (targetEl) {
326
+ let entryVal = targetEl.value.trim();
327
+ if (f.type === 'number' && entryVal !== '') {
328
+ entryVal = Number(entryVal);
329
+ }
330
+ jsonPayload[f.name] = entryVal;
999
331
  }
1000
-
1001
- payloadObject[f.name] = value;
1002
- });
1003
-
1004
- body = JSON.stringify(payloadObject);
332
+ }
333
+ body = JSON.stringify(jsonPayload);
1005
334
  }
1006
335
 
1007
- setResponse(null,'loading');
1008
-
336
+ setResponse(null, 'loading');
1009
337
  const start = Date.now();
1010
338
 
1011
- try{
1012
-
1013
- const res =
1014
- await fetch(url,{
1015
- method:ep.method,
1016
- headers,
1017
- body
1018
- });
1019
-
1020
- const ms =
1021
- Date.now() - start;
1022
-
1023
- const text =
1024
- await res.text();
1025
-
339
+ try {
340
+ const res = await fetch(url, { method: ep.method, headers, body });
341
+ const ms = Date.now() - start;
342
+ const text = await res.text();
1026
343
  let json;
1027
-
1028
- try{
1029
- json = JSON.parse(text);
1030
- }catch{
1031
- json = text;
1032
- }
1033
-
1034
- setResponse(
1035
- json,
1036
- res.ok ? 'ok' : 'err',
1037
- res.status,
1038
- ms
1039
- );
1040
-
1041
- }catch(err){
1042
-
1043
- setResponse(
1044
- { error: err.message },
1045
- 'err',
1046
- 'FAIL',
1047
- 0
1048
- );
344
+ try { json = JSON.parse(text); } catch { json = text; }
345
+ setResponse(json, res.ok ? 'ok' : 'err', res.status, ms);
346
+ } catch (err) {
347
+ setResponse({ error: err.message }, 'err', 'FAIL', 0);
1049
348
  }
1050
349
  }
1051
350
 
1052
- function setResponse(
1053
- data,
1054
- state,
1055
- status,
1056
- ms
1057
- ){
1058
-
1059
- const badge =
1060
- document.getElementById(
1061
- 'status-badge'
1062
- );
1063
-
1064
- const body =
1065
- document.getElementById(
1066
- 'response-body'
1067
- );
1068
-
1069
- if(state === 'loading'){
1070
-
1071
- badge.className =
1072
- 'status-badge status-idle';
351
+ function setResponse(data, state, status, ms) {
352
+ const badge = document.getElementById('status-badge');
353
+ const body = document.getElementById('response-body');
1073
354
 
355
+ if (state === 'loading') {
356
+ badge.className = 'status-badge status-idle';
1074
357
  badge.textContent = '...';
1075
-
1076
- body.innerHTML =
1077
- 'Executing transmission...';
1078
-
358
+ body.className = 'response-body empty';
359
+ body.innerHTML = 'Executing transmission...';
1079
360
  return;
1080
361
  }
1081
362
 
1082
- badge.className =
1083
- 'status-badge ' +
1084
- (
1085
- state === 'ok'
1086
- ? 'status-ok'
1087
- : 'status-err'
1088
- );
1089
-
1090
- badge.textContent =
1091
- status + ' · ' + ms + 'ms';
1092
-
363
+ badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
364
+ badge.textContent = status + ' · ' + ms + 'ms';
1093
365
  body.className = 'response-body';
1094
-
1095
- body.innerHTML =
1096
- highlightJson(
1097
- typeof data === 'string'
1098
- ? data
1099
- : JSON.stringify(data,null,2)
1100
- );
366
+
367
+ // FIXED: Nested rendering inside dedicated horizontal scrolling code tag block wrapper elements
368
+ const outputString = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
369
+ body.innerHTML = '<pre class="json-render-block">' + highlightJson(outputString) + '</pre>';
1101
370
  }
1102
371
 
1103
- function clearResponse(){
1104
-
1105
- const badge =
1106
- document.getElementById(
1107
- 'status-badge'
1108
- );
1109
-
1110
- const body =
1111
- document.getElementById(
1112
- 'response-body'
1113
- );
1114
-
1115
- badge.className =
1116
- 'status-badge status-idle';
1117
-
372
+ function clearResponse() {
373
+ const badge = document.getElementById('status-badge');
374
+ const body = document.getElementById('response-body');
375
+ badge.className = 'status-badge status-idle';
1118
376
  badge.textContent = '—';
1119
-
1120
- body.className =
1121
- 'response-body empty';
1122
-
1123
- body.textContent =
1124
- 'Execute a request row to generate feedback data';
377
+ body.className = 'response-body empty';
378
+ body.textContent = 'Execute a request row to generate feedback data';
1125
379
  }
1126
380
 
1127
- function highlightJson(str){
1128
-
381
+ function highlightJson(str) {
1129
382
  return str
1130
- .replace(/&/g,'&amp;')
1131
- .replace(/[<]/g,'&lt;')
1132
- .replace(/[>]/g,'&gt;')
1133
-
1134
- .replace(
1135
- /("(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\"])*"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g,
1136
-
1137
- function(match){
1138
-
1139
- if(/^"/.test(match)){
1140
-
1141
- if(/:$/.test(match)){
1142
- return '<span class="json-key">' + match + '</span>';
1143
- }
1144
-
1145
- return '<span class="json-str">' + match + '</span>';
1146
- }
1147
-
1148
- return '<span class="json-num">' + match + '</span>';
383
+ .replace(/&/g, '&amp;').replace(/[<]/g, '&lt;').replace(/[>]/g, '&gt;')
384
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(match) {
385
+ if (/^"/.test(match)) {
386
+ if (/:$/.test(match)) return '<span class="json-key">' + match + '</span>';
387
+ return '<span class="json-str">' + match + '</span>';
1149
388
  }
1150
- );
389
+ return '<span class="json-num">' + match + '</span>';
390
+ });
1151
391
  }
1152
392
 
1153
- function showToast(msg){
1154
-
1155
- const t =
1156
- document.getElementById('toast');
1157
-
393
+ function showToast(msg) {
394
+ const t = document.getElementById('toast');
1158
395
  t.textContent = msg;
1159
-
1160
396
  t.classList.add('show');
1161
-
1162
- setTimeout(()=>{
1163
- t.classList.remove('show');
1164
- },2500);
397
+ setTimeout(() => t.classList.remove('show'), 2500);
1165
398
  }
1166
399
 
1167
400
  buildSidebar();
1168
401
  </script>
1169
-
1170
402
  </body>
1171
403
  </html>
1172
404
  `;