@aimeloic/monkey-tester 3.0.6 → 3.0.7

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 (2) hide show
  1. package/htmlTemplate.js +272 -246
  2. package/package.json +1 -1
package/htmlTemplate.js CHANGED
@@ -1,14 +1,13 @@
1
- 'use strict';
2
-
3
- function getHtmlTemplate(endpoints) {
1
+ export function getHtmlTemplate(endpoints) {
4
2
  const safeJsonString = Buffer.from(JSON.stringify(endpoints)).toString('base64');
5
3
 
6
- return `<!DOCTYPE html>
4
+ return `
5
+ <!DOCTYPE html>
7
6
  <html lang="en">
8
7
  <head>
9
8
  <meta charset="UTF-8">
10
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
- <title>Monkey Tester &mdash; API Sandbox</title>
10
+ <title>Endtester API Environment</title>
12
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">
13
12
  <style>
14
13
  :root {
@@ -25,143 +24,167 @@ function getHtmlTemplate(endpoints) {
25
24
  --blue: #5a86c0;
26
25
  --radius: 8px;
27
26
  }
28
-
27
+
29
28
  * { box-sizing: border-box; margin: 0; padding: 0; }
30
-
29
+
31
30
  body {
32
- background: var(--bg);
33
- color: var(--text);
34
- font-family: 'DM Sans', sans-serif;
35
- font-size: 14px;
31
+ background: var(--bg);
32
+ color: var(--text);
33
+ font-family: 'DM Sans', sans-serif;
34
+ font-size: 14px;
36
35
  height: 100vh;
37
- overflow: hidden;
36
+ overflow: hidden; /* Prevents whole-page scrolling */
38
37
  background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%);
39
38
  }
40
-
39
+
41
40
  header {
42
- border-bottom: 1px solid var(--border);
43
- padding: 16px 32px;
44
- display: flex;
45
- align-items: center;
41
+ border-bottom: 1px solid var(--border);
42
+ padding: 16px 32px;
43
+ display: flex;
44
+ align-items: center;
46
45
  gap: 20px;
47
- background: #0e0c09ee;
48
- backdrop-filter: blur(8px);
46
+ background: #0e0c09ee;
47
+ backdrop-filter: blur(8px);
49
48
  height: 65px;
50
49
  }
51
-
50
+
52
51
  .logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
53
52
  .logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
54
53
  .header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
55
54
  .jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
56
55
  .jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; letter-spacing: 0.05em; }
57
-
56
+
58
57
  #jwt-input, #base-url {
59
58
  background: var(--surface2); border: 1px solid var(--border); color: var(--text);
60
- font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px;
61
- border-radius: var(--radius); width: 220px; outline: none;
59
+ font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none;
62
60
  }
63
-
64
- .layout {
65
- display: grid;
66
- grid-template-columns: 280px 1fr 450px;
67
- height: calc(100vh - 65px);
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);
68
67
  overflow: hidden;
69
68
  }
70
-
71
- aside {
72
- border-right: 1px solid var(--border);
73
- overflow-y: auto;
74
- padding: 16px 0;
69
+
70
+ aside {
71
+ border-right: 1px solid var(--border);
72
+ overflow-y: auto;
73
+ padding: 16px 0;
75
74
  background: #0b0907;
76
75
  }
77
-
76
+
78
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; }
79
-
78
+
80
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; }
81
80
  .nav-item:hover { background: var(--surface); color: var(--text); }
82
81
  .nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
83
-
82
+
84
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; }
85
- .GET { background: #1a3a22; color: #6ba05a; }
86
- .POST { background: #1a2e3a; color: #5a86c0; }
87
- .PUT { background: #3a2e10; color: #e8a838; }
88
- .PATCH { background: #3a2e10; color: #e8a838; }
84
+ .GET { background: #1a3a22; color: #6ba05a; }
85
+ .POST { background: #1a2e3a; color: #5a86c0; }
86
+ .PUT, .PATCH { background: #3a2e10; color: #e8a838; }
89
87
  .DELETE { background: #3a1a14; color: #d45c3c; }
90
-
88
+
91
89
  .nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
92
-
93
- main {
94
- overflow-y: auto;
95
- padding: 32px;
90
+
91
+ main {
92
+ overflow-y: auto;
93
+ padding: 32px;
96
94
  background: #0e0c09;
97
95
  }
98
-
96
+
99
97
  .endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
100
- .endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
101
- .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; }
102
-
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
+
103
101
  .form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
104
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; }
105
-
103
+
106
104
  .field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
107
105
  .field-row:last-child { margin-bottom: 0; }
108
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; }
109
-
110
- input[type=text], input[type=password], input[type=number], input[type=date],
111
- input[type=tel], input[type=url], input[type=email], select {
112
- background: var(--surface2); border: 1px solid var(--border); color: var(--text);
113
- font-family: 'DM Sans', sans-serif; font-size: 13px; padding: 8px 12px;
114
- border-radius: var(--radius); width: 100%; outline: none; transition: border-color 0.2s;
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;
115
110
  }
116
111
  input:focus { border-color: var(--accent); }
117
-
112
+
118
113
  .btn-row { margin-top: 24px; display: flex; gap: 12px; }
119
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; }
120
115
  .btn:hover { background: #f0b850; }
121
116
  .btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
122
117
  .btn-secondary:hover { color: var(--text); background: var(--surface); }
123
-
124
- .response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; background: #110e0a; }
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
+ }
125
127
  .response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
126
128
  .response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
127
129
  .status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
128
- .status-ok { background: #1a3a22; color: #6ba05a; }
129
- .status-err { background: #3a1a14; color: #d45c3c; }
130
+ .status-ok { background: #1a3a22; color: #6ba05a; }
131
+ .status-err { background: #3a1a14; color: #d45c3c; }
130
132
  .status-idle { background: var(--surface2); color: var(--text-dim); }
131
-
132
- .response-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0; background: #0d0b08; }
133
- .response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; font-size: 13px; }
134
-
135
- .json-render-block { display: block; padding: 20px; margin: 0; font-family: 'DM Mono', monospace; font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto; word-break: normal; word-wrap: normal; }
136
-
137
- .json-key { color: #e8a838; }
138
- .json-str { color: #9ab878; }
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; }
139
168
  .json-num { color: #5a86c0; }
140
- .json-bool { color: #c47a1e; }
141
- .json-null { color: var(--text-dim); }
142
-
169
+
143
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); }
144
171
  #toast.show { opacity: 1; }
145
-
146
- .empty-state { text-align: center; padding: 60px 20px; color: var(--text-dim); }
147
- .empty-state .monkey { font-size: 48px; margin-bottom: 16px; }
148
- .empty-state h2 { color: var(--text); font-family: 'Playfair Display', serif; margin-bottom: 8px; }
149
172
  </style>
150
173
  </head>
151
174
  <body>
152
175
 
153
- <div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
176
+ <div id="endtester-data-vault" data-payload="${safeJsonString}" style="display: none;"></div>
154
177
 
155
178
  <header>
156
- <div class="logo">&#x1F412; Monkey Tester <span>API Sandbox</span></div>
179
+ <div class="logo">Endtester <span>Application Runtime Sandbox</span></div>
157
180
  <div class="header-right">
158
181
  <div class="base-url-wrap">
159
182
  <label>TARGET HOST</label>
160
183
  <input id="base-url" type="text" value="">
161
184
  </div>
162
185
  <div class="jwt-wrap">
163
- <label>BEARER TOKEN</label>
164
- <input id="jwt-input" type="text" placeholder="Paste token here...">
186
+ <label>BEARER AUTH</label>
187
+ <input id="jwt-input" type="text" placeholder="Token value...">
165
188
  </div>
166
189
  </div>
167
190
  </header>
@@ -173,207 +196,210 @@ function getHtmlTemplate(endpoints) {
173
196
  <main id="main-panel"></main>
174
197
  <div class="response-panel">
175
198
  <div class="response-header">
176
- <span class="response-header-title">Response</span>
177
- <span id="status-badge" class="status-badge status-idle">&mdash;</span>
199
+ <span class="response-header-title">Response Output</span>
200
+ <span id="status-badge" class="status-badge status-idle">—</span>
178
201
  </div>
179
- <div class="response-body empty" id="response-body">Execute a request to see the response</div>
202
+ <div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
180
203
  </div>
181
204
  </div>
182
205
 
183
206
  <div id="toast"></div>
184
207
 
185
208
  <script>
186
- var ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
187
- var currentKey = null;
188
-
189
- document.getElementById('base-url').value = window.location.origin;
209
+ const rawPayload = document.getElementById('endtester-data-vault').getAttribute('data-payload');
210
+ const ENDPOINTS = JSON.parse(atob(rawPayload));
190
211
 
191
- function buildSidebar() {
192
- var sidebar = document.getElementById('sidebar-nav');
193
- var keys = Object.keys(ENDPOINTS);
212
+ let currentEp = '';
194
213
 
195
- if (keys.length === 0) {
196
- sidebar.innerHTML += '<div style="padding:18px;color:var(--text-dim);font-size:12px">No endpoints discovered.</div>';
197
- showEmptyState();
198
- return;
199
- }
214
+ document.getElementById('base-url').value = window.location.origin;
200
215
 
201
- keys.forEach(function(key, i) {
202
- var ep = ENDPOINTS[key];
203
- var item = document.createElement('div');
204
- item.className = 'nav-item' + (i === 0 ? ' active' : '');
205
- item.setAttribute('data-key', key);
206
- item.innerHTML =
207
- '<span class="method-badge ' + ep.method + '">' + ep.method + '</span>' +
208
- '<span class="nav-label">' + ep.path + '</span>';
209
- item.addEventListener('click', function() {
210
- document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); });
211
- item.classList.add('active');
212
- clearResponse();
213
- renderPanel(key);
214
- });
215
- sidebar.appendChild(item);
216
- });
216
+ function buildSidebar() {
217
+ const sidebar = document.getElementById('sidebar-nav');
218
+ const keys = Object.keys(ENDPOINTS);
217
219
 
218
- renderPanel(keys[0]);
220
+ if (keys.length === 0) {
221
+ sidebar.innerHTML += '<div style="padding:15px; color:var(--text-dim)">No active application endpoints discovered.</div>';
222
+ return;
219
223
  }
220
224
 
221
- function renderPanel(key) {
222
- currentKey = key;
223
- var ep = ENDPOINTS[key];
224
- var main = document.getElementById('main-panel');
225
- if (!ep) return;
226
-
227
- var html =
228
- '<div class="endpoint-title">' + ep.title + '</div>' +
229
- '<div class="endpoint-path"><span class="method-badge ' + ep.method + '">' + ep.method + '</span>' +
230
- '<span>' + ep.path + '</span></div>' +
231
- '<div class="endpoint-desc">' + ep.desc + '</div>';
232
-
233
- if (ep.params && ep.params.length) {
234
- html += '<div class="form-section"><div class="form-section-title">Path Parameters</div>';
235
- ep.params.forEach(function(p) {
236
- html +=
237
- '<div class="field-row">' +
238
- '<label class="field-label">' + p.label + '</label>' +
239
- '<input type="text" id="param-' + p.name + '" placeholder="' + p.placeholder + '" />' +
240
- '</div>';
241
- });
242
- html += '</div>';
243
- }
225
+ keys.forEach((key, index) => {
226
+ const ep = ENDPOINTS[key];
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'));
233
+ div.classList.add('active');
234
+ clearResponse();
235
+ renderPanel(key);
236
+ });
237
+ sidebar.appendChild(div);
238
+ });
244
239
 
245
- if (ep.fields && ep.fields.length) {
246
- html += '<div class="form-section"><div class="form-section-title">Request Body</div>';
247
- ep.fields.forEach(function(f) {
248
- html +=
249
- '<div class="field-row">' +
250
- '<label class="field-label">' + f.label + '</label>' +
251
- '<input type="' + (f.type || 'text') + '" id="field-' + f.name + '" placeholder="' + (f.placeholder || '') + '" />' +
252
- '</div>';
253
- });
254
- html += '</div>';
255
- }
240
+ if (keys.length > 0) renderPanel(keys[0]);
241
+ }
256
242
 
257
- html +=
258
- '<div class="btn-row">' +
259
- '<button class="btn" onclick="sendRequest()">&#9658; Send</button>' +
260
- '<button class="btn btn-secondary" onclick="clearResponse()">Clear</button>' +
261
- '</div>';
243
+ function makeInputString(type, id, placeholder) {
244
+ const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
245
+ return '<input type="' + type + '" id="' + id + '"' + pAttr + ' />';
246
+ }
262
247
 
263
- main.innerHTML = html;
248
+ function renderPanel(epKey) {
249
+ currentEp = epKey;
250
+ const ep = ENDPOINTS[epKey];
251
+ const main = document.getElementById('main-panel');
252
+ if (!ep) return;
253
+
254
+ let html = \`
255
+ <div class="endpoint-title">\${ep.title}</div>
256
+ <div class="endpoint-path">
257
+ <span class="method-badge \${ep.method}">\${ep.method}</span>
258
+ <span>\${ep.path}</span>
259
+ </div>
260
+ <div class="endpoint-desc">\${ep.desc}</div>
261
+ \`;
262
+
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);
267
+ html += \`
268
+ <div class="field-row">
269
+ <label class="field-label">\${p.label}</label>
270
+ \${inputHtml}
271
+ </div>
272
+ \`;
273
+ });
274
+ html += \`</div>\`;
264
275
  }
265
276
 
266
- function showEmptyState() {
267
- document.getElementById('main-panel').innerHTML =
268
- '<div class="empty-state"><div class="monkey">&#x1F412;</div>' +
269
- '<h2>No endpoints found</h2>' +
270
- '<p>Make sure <code>app.use(endtesterExpress())</code> is added after your routes.</p></div>';
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 || '');
281
+ html += \`
282
+ <div class="field-row">
283
+ <label class="field-label">\${f.label}</label>
284
+ \${inputHtml}
285
+ </div>
286
+ \`;
287
+ });
288
+ html += \`</div>\`;
271
289
  }
272
290
 
273
- async function sendRequest() {
274
- var ep = ENDPOINTS[currentKey];
275
- var path = ep.path;
291
+ html += \`
292
+ <div class="btn-row">
293
+ <button class="btn" onclick="sendRequest()">Execute Route</button>
294
+ <button class="btn btn-secondary" onclick="clearResponse()">Clear Context</button>
295
+ </div>
296
+ \`;
276
297
 
277
- if (ep.params && ep.params.length) {
278
- for (var i = 0; i < ep.params.length; i++) {
279
- var p = ep.params[i];
280
- var val = (document.getElementById('param-' + p.name) || {}).value || '';
281
- if (!val.trim()) { showToast('[!] Path param "' + p.label + '" is required'); return; }
282
- path = path.replace(':' + p.name, encodeURIComponent(val.trim()));
283
- }
284
- }
298
+ main.innerHTML = html;
299
+ }
285
300
 
286
- var baseUrl = document.getElementById('base-url').value.replace(/\/+$/, '');
287
- var url = baseUrl + path;
288
- var headers = { 'Content-Type': 'application/json' };
289
- var jwt = document.getElementById('jwt-input').value.trim();
290
- if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
291
-
292
- var body = undefined;
293
- if (['POST', 'PUT', 'PATCH'].includes(ep.method) && ep.fields && ep.fields.length) {
294
- var payload = {};
295
- var hasData = false;
296
-
297
- ep.fields.forEach(function(f) {
298
- var el = document.getElementById('field-' + f.name);
299
- if (!el) return;
300
- var v = el.value.trim();
301
- if (v === '') return;
302
-
303
- if (f.type === 'number') v = Number(v);
304
- payload[f.name] = v;
305
- hasData = true;
306
- });
307
-
308
- if (hasData) body = JSON.stringify(payload);
309
- }
301
+ async function sendRequest() {
302
+ const ep = ENDPOINTS[currentEp];
303
+ let path = ep.path;
310
304
 
311
- setResponse(null, 'loading');
312
- var t0 = Date.now();
313
-
314
- try {
315
- var res = await fetch(url, { method: ep.method, headers: headers, body: body });
316
- var ms = Date.now() - t0;
317
- var text = await res.text();
318
- var data;
319
- try { data = JSON.parse(text); } catch(e) { data = text; }
320
- setResponse(data, res.ok ? 'ok' : 'err', res.status, ms);
321
- } catch (err) {
322
- setResponse({ error: err.message }, 'err', 'FAIL', 0);
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));
323
310
  }
324
311
  }
325
312
 
326
- function setResponse(data, state, status, ms) {
327
- var badge = document.getElementById('status-badge');
328
- var body = document.getElementById('response-body');
329
-
330
- if (state === 'loading') {
331
- badge.className = 'status-badge status-idle';
332
- badge.textContent = '...';
333
- body.className = 'response-body empty';
334
- body.textContent = 'Sending...';
335
- return;
313
+ const baseUrl = document.getElementById('base-url').value.replace(/[/]+$/, '');
314
+ const url = baseUrl + path;
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;
331
+ }
336
332
  }
333
+ body = JSON.stringify(jsonPayload);
334
+ }
337
335
 
338
- badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
339
- badge.textContent = status + ' \u00b7 ' + ms + 'ms';
340
- body.className = 'response-body';
341
- var str = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
342
- body.innerHTML = '<pre class="json-render-block">' + highlight(str) + '</pre>';
336
+ setResponse(null, 'loading');
337
+ const start = Date.now();
338
+
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();
343
+ let json;
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);
343
348
  }
349
+ }
350
+
351
+ function setResponse(data, state, status, ms) {
352
+ const badge = document.getElementById('status-badge');
353
+ const body = document.getElementById('response-body');
344
354
 
345
- function clearResponse() {
346
- document.getElementById('status-badge').className = 'status-badge status-idle';
347
- document.getElementById('status-badge').textContent = '\u2014';
348
- var body = document.getElementById('response-body');
355
+ if (state === 'loading') {
356
+ badge.className = 'status-badge status-idle';
357
+ badge.textContent = '...';
349
358
  body.className = 'response-body empty';
350
- body.textContent = 'Execute a request to see the response';
359
+ body.innerHTML = 'Executing transmission...';
360
+ return;
351
361
  }
352
362
 
353
- function highlight(str) {
354
- return str
355
- .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
356
- .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(m) {
357
- if (/^"/.test(m)) return /:$/.test(m)
358
- ? '<span class="json-key">' + m + '</span>'
359
- : '<span class="json-str">' + m + '</span>';
360
- if (/true|false/.test(m)) return '<span class="json-bool">' + m + '</span>';
361
- if (/null/.test(m)) return '<span class="json-null">' + m + '</span>';
362
- return '<span class="json-num">' + m + '</span>';
363
- });
364
- }
363
+ badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
364
+ badge.textContent = status + ' · ' + ms + 'ms';
365
+ body.className = 'response-body';
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>';
370
+ }
365
371
 
366
- function showToast(msg) {
367
- var t = document.getElementById('toast');
368
- t.textContent = msg;
369
- t.classList.add('show');
370
- setTimeout(function() { t.classList.remove('show'); }, 2500);
371
- }
372
+ function clearResponse() {
373
+ const badge = document.getElementById('status-badge');
374
+ const body = document.getElementById('response-body');
375
+ badge.className = 'status-badge status-idle';
376
+ badge.textContent = '';
377
+ body.className = 'response-body empty';
378
+ body.textContent = 'Execute a request row to generate feedback data';
379
+ }
372
380
 
373
- buildSidebar();
381
+ function highlightJson(str) {
382
+ return str
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>';
388
+ }
389
+ return '<span class="json-num">' + match + '</span>';
390
+ });
391
+ }
392
+
393
+ function showToast(msg) {
394
+ const t = document.getElementById('toast');
395
+ t.textContent = msg;
396
+ t.classList.add('show');
397
+ setTimeout(() => t.classList.remove('show'), 2500);
398
+ }
399
+
400
+ buildSidebar();
374
401
  </script>
375
402
  </body>
376
- </html>`;
403
+ </html>
404
+ `;
377
405
  }
378
-
379
- export { getHtmlTemplate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "3.0.6",
3
+ "version": "3.0.7",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",