@aimeloic/monkey-tester 3.0.10 → 3.0.11

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 +176 -302
  2. package/monkey.js +1 -1
  3. package/package.json +1 -1
package/htmlTemplate.js CHANGED
@@ -1,8 +1,9 @@
1
- export function getHtmlTemplate(endpoints) {
1
+ 'use strict';
2
+
3
+ function getHtmlTemplate(endpoints) {
2
4
  const safeJsonString = Buffer.from(JSON.stringify(endpoints)).toString('base64');
3
5
 
4
- return `
5
- <!DOCTYPE html>
6
+ return `<!DOCTYPE html>
6
7
  <html lang="en">
7
8
  <head>
8
9
  <meta charset="UTF-8">
@@ -11,172 +12,63 @@ export function getHtmlTemplate(endpoints) {
11
12
  <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">
12
13
  <style>
13
14
  :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;
15
+ --bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020;
16
+ --accent: #e8a838; --accent2: #c47a1e; --text: #f0e8d8; --text-dim: #9a8c78;
17
+ --red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px;
26
18
  }
27
-
28
19
  * { 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%);
38
- }
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;
49
- }
50
-
20
+ body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 14px; height: 100vh; overflow: hidden; background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%); }
21
+ header { border-bottom: 1px solid var(--border); padding: 16px 32px; display: flex; align-items: center; gap: 20px; background: #0e0c09ee; backdrop-filter: blur(8px); height: 65px; }
51
22
  .logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
52
23
  .logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
53
24
  .header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
54
25
  .jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
55
26
  .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;
60
- }
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;
68
- }
69
-
70
- aside {
71
- border-right: 1px solid var(--border);
72
- overflow-y: auto;
73
- padding: 16px 0;
74
- background: #0b0907;
75
- }
76
-
27
+ #jwt-input, #base-url { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none; }
28
+ .layout { display: grid; grid-template-columns: 280px 1fr 450px; height: calc(100vh - 65px); overflow: hidden; }
29
+ aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; background: #0b0907; }
77
30
  .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
31
  .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
32
  .nav-item:hover { background: var(--surface); color: var(--text); }
81
33
  .nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
82
-
83
34
  .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
-
35
+ .GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT, .PATCH { background: #3a2e10; color: #e8a838; } .DELETE { background: #3a1a14; color: #d45c3c; }
89
36
  .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;
95
- }
96
-
37
+ main { overflow-y: auto; padding: 32px; background: #0e0c09; }
97
38
  .endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
98
39
  .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
40
  .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
41
  .form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
102
42
  .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
43
  .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
44
  .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;
110
- }
45
+ input[type=text], input[type=password], input[type=number], input[type=date], input[type=tel], input[type=url], input[type=email], select { 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; }
111
46
  input:focus { border-color: var(--accent); }
112
-
113
47
  .btn-row { margin-top: 24px; display: flex; gap: 12px; }
114
48
  .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
- }
49
+ .btn:hover { background: #f0b850; } .btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); } .btn-secondary:hover { color: var(--text); background: var(--surface); }
50
+ .response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; background: #110e0a; }
127
51
  .response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
128
52
  .response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
129
53
  .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
-
54
+ .status-ok { background: #1a3a22; color: #6ba05a; } .status-err { background: #3a1a14; color: #d45c3c; } .status-idle { background: var(--surface2); color: var(--text-dim); }
55
+ .response-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0; background: #0d0b08; }
56
+ .response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; font-size: 13px; }
57
+ .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; }
58
+ .json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; } .json-bool { color: #c47a1e; } .json-null { color: var(--text-dim); }
170
59
  #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
60
  #toast.show { opacity: 1; }
61
+ .empty-state { text-align: center; padding: 60px 20px; color: var(--text-dim); }
62
+ .empty-state .monkey { font-size: 48px; margin-bottom: 16px; }
63
+ .empty-state h2 { color: var(--text); font-family: 'Playfair Display', serif; margin-bottom: 8px; }
172
64
  </style>
173
65
  </head>
174
66
  <body>
175
67
 
176
- <div id="endtester-data-vault" data-payload="${safeJsonString}" style="display: none;"></div>
68
+ <div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
177
69
 
178
70
  <header>
179
- <div class="logo">Endtester <span>Application Runtime Sandbox</span></div>
71
+ <div class="logo">🐒 Endtester <span>Application Runtime Sandbox</span></div>
180
72
  <div class="header-right">
181
73
  <div class="base-url-wrap">
182
74
  <label>TARGET HOST</label>
@@ -206,200 +98,182 @@ export function getHtmlTemplate(endpoints) {
206
98
  <div id="toast"></div>
207
99
 
208
100
  <script>
209
- const rawPayload = document.getElementById('endtester-data-vault').getAttribute('data-payload');
210
- const ENDPOINTS = JSON.parse(atob(rawPayload));
101
+ const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
102
+ let currentKey = null;
211
103
 
212
- let currentEp = '';
104
+ document.getElementById('base-url').value = window.location.origin;
213
105
 
214
- document.getElementById('base-url').value = window.location.origin;
106
+ function buildSidebar() {
107
+ const sidebar = document.getElementById('sidebar-nav');
108
+ const keys = Object.keys(ENDPOINTS);
215
109
 
216
- function buildSidebar() {
217
- const sidebar = document.getElementById('sidebar-nav');
218
- const keys = Object.keys(ENDPOINTS);
110
+ if (keys.length === 0) {
111
+ sidebar.innerHTML += '<div style="padding:18px;color:var(--text-dim);font-size:12px">No endpoints discovered.</div>';
112
+ return;
113
+ }
114
+
115
+ keys.forEach((key, i) => {
116
+ const ep = ENDPOINTS[key];
117
+ const item = document.createElement('div');
118
+ item.className = 'nav-item' + (i === 0 ? ' active' : '');
119
+ item.setAttribute('data-key', key);
120
+ item.innerHTML =
121
+ '<span class="method-badge ' + ep.method + '">' + ep.method + '</span>' +
122
+ '<span class="nav-label">' + ep.path + '</span>';
123
+ item.addEventListener('click', () => {
124
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
125
+ item.classList.add('active');
126
+ clearResponse();
127
+ renderPanel(key);
128
+ });
129
+ sidebar.appendChild(item);
130
+ });
219
131
 
220
- if (keys.length === 0) {
221
- sidebar.innerHTML += '<div style="padding:15px; color:var(--text-dim)">No active application endpoints discovered.</div>';
222
- return;
132
+ renderPanel(keys[0]);
223
133
  }
224
134
 
225
- keys.forEach((key, index) => {
135
+ function renderPanel(key) {
136
+ currentKey = key;
226
137
  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
- });
239
-
240
- if (keys.length > 0) renderPanel(keys[0]);
241
- }
138
+ const main = document.getElementById('main-panel');
139
+ if (!ep) return;
140
+
141
+ let html =
142
+ '<div class="endpoint-title">' + ep.title + '</div>' +
143
+ '<div class="endpoint-path"><span class="method-badge ' + ep.method + '">' + ep.method + '</span>' +
144
+ '<span>' + ep.path + '</span></div>' +
145
+ '<div class="endpoint-desc">' + ep.desc + '</div>';
146
+
147
+ if (ep.params && ep.params.length) {
148
+ html += '<div class="form-section"><div class="form-section-title">Path Parameters</div>';
149
+ ep.params.forEach(function(p) {
150
+ html +=
151
+ '<div class="field-row">' +
152
+ '<label class="field-label">' + p.label + '</label>' +
153
+ '<input type="text" id="param-' + p.name + '" placeholder="' + p.placeholder + '" />' +
154
+ '</div>';
155
+ });
156
+ html += '</div>';
157
+ }
242
158
 
243
- function makeInputString(type, id, placeholder) {
244
- const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
245
- return '<input type="' + type + '" id="' + id + '"' + pAttr + ' />';
246
- }
159
+ if (ep.fields && ep.fields.length) {
160
+ html += '<div class="form-section"><div class="form-section-title">HTTP JSON Request Payload Parameters</div>';
161
+ ep.fields.forEach(function(f) {
162
+ html +=
163
+ '<div class="field-row">' +
164
+ '<label class="field-label">' + f.label + '</label>' +
165
+ '<input type="' + (f.type || 'text') + '" id="field-' + f.name + '" placeholder="' + (f.placeholder || '') + '" />' +
166
+ '</div>';
167
+ });
168
+ html += '</div>';
169
+ }
247
170
 
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>\`;
275
- }
171
+ html +=
172
+ '<div class="btn-row">' +
173
+ '<button class="btn" onclick="sendRequest()">Execute Route</button>' +
174
+ '<button class="btn btn-secondary" onclick="clearResponse()">Clear Context</button>' +
175
+ '</div>';
276
176
 
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>\`;
177
+ main.innerHTML = html;
289
178
  }
290
179
 
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
- \`;
180
+ async function sendRequest() {
181
+ const ep = ENDPOINTS[currentKey];
182
+ let path = ep.path;
297
183
 
298
- main.innerHTML = html;
299
- }
184
+ if (ep.params && ep.params.length) {
185
+ for (const p of ep.params) {
186
+ const val = (document.getElementById('param-' + p.name) || {}).value || '';
187
+ if (!val.trim()) { showToast('⚠ Path param "' + p.label + '" is required'); return; }
188
+ path = path.replace(':' + p.name, encodeURIComponent(val.trim()));
189
+ }
190
+ }
300
191
 
301
- async function sendRequest() {
302
- const ep = ENDPOINTS[currentEp];
303
- let path = ep.path;
192
+ const baseUrl = document.getElementById('base-url').value.replace(/\/+$/, '');
193
+ const url = baseUrl + path;
194
+ const headers = { 'Content-Type': 'application/json' };
195
+ const jwt = document.getElementById('jwt-input').value.trim();
196
+ if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
197
+
198
+ let body = undefined;
199
+ if (['POST', 'PUT', 'PATCH'].includes(ep.method) && ep.fields && ep.fields.length) {
200
+ const payload = {};
201
+ ep.fields.forEach(function(f) {
202
+ const el = document.getElementById('field-' + f.name);
203
+ if (!el) return;
204
+ let v = el.value.trim();
205
+ if (f.type === 'number' && v !== '') v = Number(v);
206
+ payload[f.name] = v;
207
+ });
208
+ body = JSON.stringify(payload);
209
+ }
304
210
 
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));
211
+ setResponse(null, 'loading');
212
+ const t0 = Date.now();
213
+
214
+ try {
215
+ const res = await fetch(url, { method: ep.method, headers, body });
216
+ const ms = Date.now() - t0;
217
+ const text = await res.text();
218
+ let data;
219
+ try { data = JSON.parse(text); } catch { data = text; }
220
+ setResponse(data, res.ok ? 'ok' : 'err', res.status, ms);
221
+ } catch (err) {
222
+ setResponse({ error: err.message }, 'err', 'FAIL', 0);
310
223
  }
311
224
  }
312
225
 
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
- }
226
+ function setResponse(data, state, status, ms) {
227
+ const badge = document.getElementById('status-badge');
228
+ const body = document.getElementById('response-body');
229
+
230
+ if (state === 'loading') {
231
+ badge.className = 'status-badge status-idle';
232
+ badge.textContent = '…';
233
+ body.className = 'response-body empty';
234
+ body.textContent = 'Executing transmission…';
235
+ return;
332
236
  }
333
- body = JSON.stringify(jsonPayload);
334
- }
335
237
 
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);
238
+ badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
239
+ badge.textContent = status + ' · ' + ms + 'ms';
240
+ body.className = 'response-body';
241
+ const str = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
242
+ body.innerHTML = '<pre class="json-render-block">' + highlight(str) + '</pre>';
348
243
  }
349
- }
350
-
351
- function setResponse(data, state, status, ms) {
352
- const badge = document.getElementById('status-badge');
353
- const body = document.getElementById('response-body');
354
244
 
355
- if (state === 'loading') {
356
- badge.className = 'status-badge status-idle';
357
- badge.textContent = '...';
245
+ function clearResponse() {
246
+ document.getElementById('status-badge').className = 'status-badge status-idle';
247
+ document.getElementById('status-badge').textContent = '';
248
+ const body = document.getElementById('response-body');
358
249
  body.className = 'response-body empty';
359
- body.innerHTML = 'Executing transmission...';
360
- return;
250
+ body.textContent = 'Execute a request row to generate feedback data';
361
251
  }
362
252
 
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
- }
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
- }
380
-
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
- }
253
+ function highlight(str) {
254
+ return str
255
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
256
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(m) {
257
+ if (/^"/.test(m)) return /:$/.test(m)
258
+ ? '<span class="json-key">' + m + '</span>'
259
+ : '<span class="json-str">' + m + '</span>';
260
+ if (/true|false/.test(m)) return '<span class="json-bool">' + m + '</span>';
261
+ if (/null/.test(m)) return '<span class="json-null">' + m + '</span>';
262
+ return '<span class="json-num">' + m + '</span>';
263
+ });
264
+ }
392
265
 
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
- }
266
+ function showToast(msg) {
267
+ const t = document.getElementById('toast');
268
+ t.textContent = msg;
269
+ t.classList.add('show');
270
+ setTimeout(() => t.classList.remove('show'), 2500);
271
+ }
399
272
 
400
- buildSidebar();
273
+ buildSidebar();
401
274
  </script>
402
275
  </body>
403
- </html>
404
- `;
276
+ </html>`;
405
277
  }
278
+
279
+ exports = { getHtmlTemplate };
package/monkey.js CHANGED
@@ -151,4 +151,4 @@ function endtesterExpress() {
151
151
  };
152
152
  }
153
153
 
154
- module.exports = { endtesterExpress };
154
+ exports = { endtesterExpress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "3.0.10",
3
+ "version": "3.0.11",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",