@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.
- package/htmlTemplate.js +272 -246
- package/package.json +1 -1
package/htmlTemplate.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
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
|
|
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>
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
86
|
-
.POST
|
|
87
|
-
.PUT
|
|
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
|
|
101
|
-
.endpoint-desc
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
129
|
-
.status-err
|
|
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
|
-
|
|
133
|
-
.response-body
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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="
|
|
176
|
+
<div id="endtester-data-vault" data-payload="${safeJsonString}" style="display: none;"></div>
|
|
154
177
|
|
|
155
178
|
<header>
|
|
156
|
-
<div class="logo"
|
|
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
|
|
164
|
-
<input id="jwt-input" type="text" placeholder="
|
|
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"
|
|
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
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
192
|
-
var sidebar = document.getElementById('sidebar-nav');
|
|
193
|
-
var keys = Object.keys(ENDPOINTS);
|
|
212
|
+
let currentEp = '';
|
|
194
213
|
|
|
195
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
'
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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.
|
|
359
|
+
body.innerHTML = 'Executing transmission...';
|
|
360
|
+
return;
|
|
351
361
|
}
|
|
352
362
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
+
}
|
|
372
380
|
|
|
373
|
-
|
|
381
|
+
function highlightJson(str) {
|
|
382
|
+
return str
|
|
383
|
+
.replace(/&/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>')
|
|
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 };
|