@aimeloic/monkey-tester 4.0.4 → 4.0.6
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 +283 -29
- package/package.json +1 -1
- package/temp.backup.js +279 -0
package/htmlTemplate.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Original Sandbox Runtime — Stringified safely to browser context
|
|
4
3
|
function runtimeClientSandbox() {
|
|
5
4
|
const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
|
|
6
5
|
let currentKey = null;
|
|
7
6
|
|
|
8
7
|
document.getElementById('base-url').value = window.location.origin;
|
|
9
8
|
|
|
10
|
-
// Auto-paste Token directly if it exists in local storage from the turnkey login page!
|
|
11
9
|
const savedToken = localStorage.getItem('__auth_token__');
|
|
12
10
|
if (savedToken) {
|
|
13
11
|
document.getElementById('jwt-input').value = savedToken;
|
|
@@ -151,9 +149,7 @@ function runtimeClientSandbox() {
|
|
|
151
149
|
buildSidebar();
|
|
152
150
|
}
|
|
153
151
|
|
|
154
|
-
// Global UI layouts object
|
|
155
152
|
const UI = {
|
|
156
|
-
// Original Tester UI Page
|
|
157
153
|
tester: (endpointsJsonB64) => `<!DOCTYPE html>
|
|
158
154
|
<html lang="en">
|
|
159
155
|
<head>
|
|
@@ -225,17 +221,16 @@ const UI = {
|
|
|
225
221
|
</body>
|
|
226
222
|
</html>`,
|
|
227
223
|
|
|
228
|
-
// Turnkey Customer Login Page
|
|
229
224
|
login: () => `<!DOCTYPE html><html><head><title>Sign In</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
230
225
|
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
231
226
|
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
232
227
|
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
233
228
|
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
234
|
-
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }
|
|
229
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; margin-top: 10px; }
|
|
235
230
|
.footer { text-align: center; margin-top: 20px; font-size: 13px; color: #9a8c78; } a { color: #e8a838; text-decoration: none; }</style></head>
|
|
236
231
|
<body><div class="card"><h2>Sign In</h2><div id="err" style="color:#d45c3c; font-size:13px; margin-bottom:15px; text-align:center;"></div>
|
|
237
|
-
<div class="field"><label>Email</label><input type="email" id="email" value="
|
|
238
|
-
<div class="field"><label>Password</label><input type="password" id="password" value="
|
|
232
|
+
<div class="field"><label>Email</label><input type="email" id="email" value="admin@bakery.com"></div>
|
|
233
|
+
<div class="field"><label>Password</label><input type="password" id="password" value="password123"></div>
|
|
239
234
|
<button onclick="handleLogin()">Log In</button><div class="footer">Need an account? <a href="/signup">Sign up</a></div></div>
|
|
240
235
|
<script>async function handleLogin() {
|
|
241
236
|
const email = document.getElementById('email').value;
|
|
@@ -248,32 +243,291 @@ const UI = {
|
|
|
248
243
|
} else { document.getElementById('err').textContent = data.error || 'Login failed'; }
|
|
249
244
|
}</script></body></html>`,
|
|
250
245
|
|
|
251
|
-
// Turnkey Customer Signup Page
|
|
252
246
|
signup: () => `<!DOCTYPE html><html><head><title>Create Account</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
253
247
|
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
254
248
|
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
255
249
|
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
256
250
|
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
257
|
-
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }
|
|
258
|
-
|
|
259
|
-
<div class="
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
251
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; margin-top: 10px; }
|
|
252
|
+
.footer { text-align: center; margin-top: 20px; font-size: 13px; color: #9a8c78; } a { color: #e8a838; text-decoration: none; }</style></head>
|
|
253
|
+
<body><div class="card"><h2>Sign Up</h2><div id="msg" style="font-size:13px; margin-bottom:15px; text-align:center;"></div>
|
|
254
|
+
<div class="field"><label>Username</label><input type="text" id="username" placeholder="Username"></div>
|
|
255
|
+
<div class="field"><label>Email Address</label><input type="email" id="email" placeholder="Email"></div>
|
|
256
|
+
<div class="field"><label>Password</label><input type="password" id="password"></div>
|
|
257
|
+
<button onclick="handleRegister()">Register Account</button><div class="footer">Have an account? <a href="/login">Sign In</a></div></div>
|
|
258
|
+
<script>async function handleRegister() {
|
|
259
|
+
const username = document.getElementById('username').value;
|
|
260
|
+
const email = document.getElementById('email').value;
|
|
261
|
+
const password = document.getElementById('password').value;
|
|
262
|
+
const msgDiv = document.getElementById('msg');
|
|
263
|
+
const res = await fetch('/api/v1/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, email, password }) });
|
|
264
|
+
const data = await res.json();
|
|
265
|
+
if(res.ok) {
|
|
266
|
+
msgDiv.style.color = '#6ba05a'; msgDiv.textContent = 'Registration complete! Redirecting...';
|
|
267
|
+
setTimeout(() => window.location.href = '/login', 1200);
|
|
268
|
+
} else { msgDiv.style.color = '#d45c3c'; msgDiv.textContent = data.error || 'Registration failed'; }
|
|
269
|
+
}</script></body></html>`,
|
|
270
|
+
|
|
271
|
+
dashboard: (endpointsJsonB64) => `<!DOCTYPE html><html><head><title>Dynamic Admin Dashboard</title>
|
|
272
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
273
|
+
<style>
|
|
274
|
+
:root { --bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020; --accent: #e8a838; --text: #f0e8d8; --text-dim: #9a8c78; --red: #d45c3c; --green: #6ba05a; }
|
|
275
|
+
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; padding: 40px; margin: 0; }
|
|
276
|
+
header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 20px; margin-bottom: 30px; }
|
|
277
|
+
h1 { color: var(--accent); margin: 0; font-size: 24px; }
|
|
278
|
+
.nav-links { display: flex; gap: 16px; align-items: center; }
|
|
279
|
+
.nav-links a { color: var(--text-dim); text-decoration: none; font-size: 14px; }
|
|
280
|
+
.nav-links a:hover { color: var(--accent); }
|
|
281
|
+
.logout-btn { background: #3a1a14; color: var(--red); border: 1px solid #5a2014; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight:600; }
|
|
282
|
+
.selector-banner { background: var(--surface); border: 1px solid var(--border); padding: 12px 20px; border-radius: 8px; margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
|
|
283
|
+
select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); padding: 8px 12px; border-radius: 6px; outline: none; font-weight: 500; }
|
|
284
|
+
.grid { display: grid; grid-template-columns: 360px 1fr; gap: 30px; }
|
|
285
|
+
.panel { background: var(--surface); border: 1px solid var(--border); padding: 24px; border-radius: 8px; height: fit-content; }
|
|
286
|
+
h3 { color: var(--accent); margin-top: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 8px; font-size:14px; text-transform:uppercase; letter-spacing:0.5px; }
|
|
287
|
+
.field { margin-bottom: 16px; }
|
|
288
|
+
label { display: block; font-size: 11px; color: var(--text-dim); text-transform: uppercase; margin-bottom: 6px; font-family: monospace; }
|
|
289
|
+
input { background: var(--surface2); border: 1px solid var(--border); color: var(--text); padding: 10px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
290
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 11px; width: 100%; border-radius: 6px; font-weight: 700; cursor: pointer; }
|
|
291
|
+
.btn-cancel { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); margin-top: 8px; }
|
|
292
|
+
.table-wrap { overflow-x: auto; background: var(--surface); border-radius: 8px; border: 1px solid var(--border); }
|
|
293
|
+
table { width: 100%; border-collapse: collapse; }
|
|
294
|
+
th { color: var(--text-dim); text-align: left; padding: 14px; border-bottom: 2px solid var(--border); font-size: 11px; text-transform: uppercase; background:#13100b; }
|
|
295
|
+
td { padding: 14px; border-bottom: 1px solid var(--surface2); font-size: 13px; font-family: monospace; }
|
|
296
|
+
.actions-cell { display: flex; gap: 8px; justify-content: flex-end; }
|
|
297
|
+
.btn-sm { padding: 4px 8px; border-radius: 4px; border: none; font-weight: 600; font-size: 11px; cursor: pointer; }
|
|
298
|
+
.btn-edit { background: #1a2e3a; color: #5a86c0; border: 1px solid #224054; }
|
|
299
|
+
.btn-del { background: #3a1a14; color: var(--red); border: 1px solid #5a2014; }
|
|
300
|
+
</style>
|
|
301
|
+
</head>
|
|
302
|
+
<body>
|
|
303
|
+
<div id="__monkey_data__" data-payload="${endpointsJsonB64}" style="display:none;"></div>
|
|
304
|
+
<header>
|
|
305
|
+
<h1>Universal Management Dashboard</h1>
|
|
306
|
+
<div class="nav-links">
|
|
307
|
+
<a href="/api/tester" target="_blank">🛠 Open Tester Sandbox</a>
|
|
308
|
+
<button class="logout-btn" onclick="localStorage.removeItem('__auth_token__'); window.location.href='/login'">Log Out</button>
|
|
309
|
+
</div>
|
|
310
|
+
</header>
|
|
311
|
+
|
|
312
|
+
<div class="selector-banner">
|
|
313
|
+
<label style="margin:0;">Target Data Resource Collection:</label>
|
|
314
|
+
<select id="route-selector" onchange="switchCollection()"></select>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<div class="grid">
|
|
318
|
+
<div class="panel">
|
|
319
|
+
<h3 id="form-title">Add Entry</h3>
|
|
320
|
+
<div id="dynamic-fields-container"></div>
|
|
321
|
+
<button class="btn" id="btn-submit" onclick="submitDataForm()">Execute Submission</button>
|
|
322
|
+
<button class="btn btn-sm btn-cancel" id="btn-cancel" style="display:none; margin-top:10px;" onclick="resetDataForm()">Cancel Action</button>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<div class="table-wrap">
|
|
326
|
+
<table id="dynamic-table">
|
|
327
|
+
<thead id="table-head"></thead>
|
|
328
|
+
<tbody id="table-body"></tbody>
|
|
329
|
+
</table>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<script>
|
|
334
|
+
const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
|
|
335
|
+
const token = localStorage.getItem('__auth_token__');
|
|
336
|
+
if (!token) window.location.href = '/login';
|
|
337
|
+
|
|
338
|
+
let dynamicCollections = {};
|
|
339
|
+
let activeCollectionPath = '';
|
|
340
|
+
let activeEditId = null;
|
|
341
|
+
|
|
342
|
+
function resolveRoutes() {
|
|
343
|
+
Object.values(ENDPOINTS).forEach(ep => {
|
|
344
|
+
if (!ep.path.includes(':')) {
|
|
345
|
+
if (!dynamicCollections[ep.path]) {
|
|
346
|
+
dynamicCollections[ep.path] = { get: null, post: null, put: null, del: null, modelFields: ep.fields || [] };
|
|
347
|
+
}
|
|
348
|
+
if (ep.method === 'GET') dynamicCollections[ep.path].get = ep.path;
|
|
349
|
+
if (ep.method === 'POST') {
|
|
350
|
+
dynamicCollections[ep.path].post = ep.path;
|
|
351
|
+
if (ep.fields && ep.fields.length) dynamicCollections[ep.path].modelFields = ep.fields;
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const basePath = ep.path.split('/:')[0];
|
|
355
|
+
if (!dynamicCollections[basePath]) {
|
|
356
|
+
dynamicCollections[basePath] = { get: null, post: null, put: null, del: null, modelFields: [] };
|
|
357
|
+
}
|
|
358
|
+
if (ep.method === 'PUT') dynamicCollections[basePath].put = ep.path;
|
|
359
|
+
if (ep.method === 'DELETE') dynamicCollections[basePath].del = ep.path;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const selector = document.getElementById('route-selector');
|
|
364
|
+
Object.keys(dynamicCollections).forEach(path => {
|
|
365
|
+
if (dynamicCollections[path].get) {
|
|
366
|
+
const opt = document.createElement('option');
|
|
367
|
+
opt.value = path;
|
|
368
|
+
opt.textContent = path + " (Dynamic Dataset)";
|
|
369
|
+
selector.appendChild(opt);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (selector.options.length > 0) {
|
|
374
|
+
switchCollection(selector.options[0].value);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function switchCollection(targetPath) {
|
|
379
|
+
activeCollectionPath = targetPath || document.getElementById('route-selector').value;
|
|
380
|
+
activeEditId = null;
|
|
381
|
+
resetDataForm();
|
|
382
|
+
renderFormFields();
|
|
383
|
+
fetchData();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderFormFields() {
|
|
387
|
+
const container = document.getElementById('dynamic-fields-container');
|
|
388
|
+
container.innerHTML = '';
|
|
389
|
+
const fields = dynamicCollections[activeCollectionPath].modelFields;
|
|
390
|
+
|
|
391
|
+
if (!fields || fields.length === 0) {
|
|
392
|
+
container.innerHTML = '<p style="font-size:12px; color:var(--text-dim);">No input properties found.</p>';
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fields.forEach(f => {
|
|
397
|
+
container.innerHTML += \`
|
|
398
|
+
<div class="field">
|
|
399
|
+
<label>\${f.label}</label>
|
|
400
|
+
<input type="\${f.type || 'text'}" id="input-\${f.name}" placeholder="\${f.placeholder || ''}">
|
|
401
|
+
</div>
|
|
402
|
+
\`;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function fetchData() {
|
|
407
|
+
const head = document.getElementById('table-head');
|
|
408
|
+
const body = document.getElementById('table-body');
|
|
409
|
+
head.innerHTML = '';
|
|
410
|
+
body.innerHTML = '<tr><td style="padding:20px; color:var(--text-dim)">Loading layout schemas...</td></tr>';
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const res = await fetch(activeCollectionPath, { headers: { 'Authorization': 'Bearer ' + token } });
|
|
414
|
+
const rawData = await res.json();
|
|
415
|
+
|
|
416
|
+
let list = [];
|
|
417
|
+
if (Array.isArray(rawData)) {
|
|
418
|
+
list = rawData;
|
|
419
|
+
} else if (rawData && typeof rawData === 'object') {
|
|
420
|
+
// UNIFIED RESPONSE FLATTENER: Extracts arrays even if nested inside object counts
|
|
421
|
+
const arrayKey = Object.keys(rawData).find(k => Array.isArray(rawData[k]));
|
|
422
|
+
list = arrayKey ? rawData[arrayKey] : [rawData];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!list || list.length === 0 || list[0] === null) {
|
|
426
|
+
body.innerHTML = '<tr><td style="padding:30px; color:var(--text-dim)">No records found inside this live API container.</td></tr>';
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let keys = Object.keys(list[0]).filter(k => typeof list[0][k] !== 'object');
|
|
431
|
+
|
|
432
|
+
let trHead = '<tr>';
|
|
433
|
+
keys.forEach(k => trHead += \`<th>\${k}</th>\`);
|
|
434
|
+
trHead += '<th style="text-align:right; padding-right:20px;">Actions</th></tr>';
|
|
435
|
+
head.innerHTML = trHead;
|
|
436
|
+
|
|
437
|
+
body.innerHTML = '';
|
|
438
|
+
list.forEach(row => {
|
|
439
|
+
let trBody = '<tr>';
|
|
440
|
+
keys.forEach(k => {
|
|
441
|
+
const val = row[k] !== undefined ? row[k] : '';
|
|
442
|
+
trBody += \`<td>\${val}</td>\`;
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const targetId = row.id || row._id || list.indexOf(row);
|
|
446
|
+
const rowJson = btoa(JSON.stringify(row));
|
|
447
|
+
|
|
448
|
+
trBody += \`
|
|
449
|
+
<td class="actions-cell" style="padding-right:20px;">
|
|
450
|
+
\${dynamicCollections[activeCollectionPath].put ? \`<button class="btn-sm btn-edit" onclick="startRowEdit('\${targetId}', '\${rowJson}')">Edit</button>\` : ''}
|
|
451
|
+
\${dynamicCollections[activeCollectionPath].del ? \`<button class="btn-sm btn-del" onclick="deleteRow('\${targetId}')">Delete</button>\` : ''}
|
|
452
|
+
</td>
|
|
453
|
+
</tr>\`;
|
|
454
|
+
body.innerHTML += trBody;
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
} catch (e) {
|
|
458
|
+
body.innerHTML = '<tr><td style="padding:20px; color:var(--red)">Failed processing remote endpoint structure data.</td></tr>';
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function submitDataForm() {
|
|
463
|
+
const fields = dynamicCollections[activeCollectionPath].modelFields;
|
|
464
|
+
const payload = {};
|
|
465
|
+
|
|
466
|
+
fields.forEach(f => {
|
|
467
|
+
const el = document.getElementById(\`input-\${f.name}\`);
|
|
468
|
+
if (el) {
|
|
469
|
+
payload[f.name] = f.type === 'number' ? Number(el.value) : el.value;
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
let url = activeCollectionPath;
|
|
474
|
+
let method = 'POST';
|
|
475
|
+
|
|
476
|
+
if (activeEditId !== null) {
|
|
477
|
+
const putTemplate = dynamicCollections[activeCollectionPath].put;
|
|
478
|
+
const paramName = putTemplate.split('/:')[1];
|
|
479
|
+
url = putTemplate.replace(\`:\${paramName}\`, activeEditId);
|
|
480
|
+
method = 'PUT';
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const res = await fetch(url, {
|
|
484
|
+
method: method,
|
|
485
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
486
|
+
body: JSON.stringify(payload)
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (res.ok) { resetDataForm(); fetchData(); } else { alert('Submission declined.'); }
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function deleteRow(id) {
|
|
493
|
+
if (!confirm('Execute atomic entry removal?')) return;
|
|
494
|
+
const delTemplate = dynamicCollections[activeCollectionPath].del;
|
|
495
|
+
const paramName = delTemplate.split('/:')[1];
|
|
496
|
+
const url = delTemplate.replace(\`:\${paramName}\`, id);
|
|
497
|
+
|
|
498
|
+
const res = await fetch(url, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
|
|
499
|
+
if (res.ok) fetchData(); else alert('Delete request blocked.');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function startRowEdit(id, encodedJson) {
|
|
503
|
+
activeEditId = id;
|
|
504
|
+
const data = JSON.parse(atob(encodedJson));
|
|
505
|
+
document.getElementById('form-title').textContent = \`Update Row #\${id}\`;
|
|
506
|
+
document.getElementById('btn-submit').textContent = 'Commit Changes';
|
|
507
|
+
document.getElementById('btn-cancel').style.display = 'block';
|
|
508
|
+
|
|
509
|
+
const fields = dynamicCollections[activeCollectionPath].modelFields;
|
|
510
|
+
fields.forEach(f => {
|
|
511
|
+
const el = document.getElementById(\`input-\${f.name}\`);
|
|
512
|
+
if (el && data[f.name] !== undefined) el.value = data[f.name];
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function resetDataForm() {
|
|
517
|
+
activeEditId = null;
|
|
518
|
+
document.getElementById('form-title').textContent = 'Add Entry';
|
|
519
|
+
document.getElementById('btn-submit').textContent = 'Execute Submission';
|
|
520
|
+
document.getElementById('btn-cancel').style.display = 'none';
|
|
521
|
+
const fields = dynamicCollections[activeCollectionPath]?.modelFields || [];
|
|
522
|
+
fields.forEach(f => {
|
|
523
|
+
const el = document.getElementById(\`input-\${f.name}\`);
|
|
524
|
+
if (el) el.value = '';
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
resolveRoutes();
|
|
529
|
+
</script>
|
|
530
|
+
</body></html>`
|
|
277
531
|
};
|
|
278
532
|
|
|
279
533
|
export { UI };
|
package/package.json
CHANGED
package/temp.backup.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Original Sandbox Runtime — Stringified safely to browser context
|
|
4
|
+
function runtimeClientSandbox() {
|
|
5
|
+
const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
|
|
6
|
+
let currentKey = null;
|
|
7
|
+
|
|
8
|
+
document.getElementById('base-url').value = window.location.origin;
|
|
9
|
+
|
|
10
|
+
// Auto-paste Token directly if it exists in local storage from the turnkey login page!
|
|
11
|
+
const savedToken = localStorage.getItem('__auth_token__');
|
|
12
|
+
if (savedToken) {
|
|
13
|
+
document.getElementById('jwt-input').value = savedToken;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildSidebar() {
|
|
17
|
+
const sidebar = document.getElementById('sidebar-nav');
|
|
18
|
+
const keys = Object.keys(ENDPOINTS);
|
|
19
|
+
if (keys.length === 0) {
|
|
20
|
+
sidebar.innerHTML += '<div style="padding:18px;color:var(--text-dim);font-size:12px">No endpoints discovered.</div>';
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
keys.forEach((key, i) => {
|
|
24
|
+
const ep = ENDPOINTS[key];
|
|
25
|
+
const item = document.createElement('div');
|
|
26
|
+
item.className = 'nav-item' + (i === 0 ? ' active' : '');
|
|
27
|
+
item.setAttribute('data-key', key);
|
|
28
|
+
item.innerHTML = '<span class="method-badge ' + ep.method + '">' + ep.method + '</span><span class="nav-label">' + ep.path + '</span>';
|
|
29
|
+
item.addEventListener('click', () => {
|
|
30
|
+
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
31
|
+
item.classList.add('active');
|
|
32
|
+
clearResponse();
|
|
33
|
+
renderPanel(key);
|
|
34
|
+
});
|
|
35
|
+
sidebar.appendChild(item);
|
|
36
|
+
});
|
|
37
|
+
renderPanel(keys[0]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderPanel(key) {
|
|
41
|
+
currentKey = key;
|
|
42
|
+
const ep = ENDPOINTS[key];
|
|
43
|
+
const main = document.getElementById('main-panel');
|
|
44
|
+
if (!ep) return;
|
|
45
|
+
|
|
46
|
+
let html = '<div class="endpoint-title">' + ep.title + '</div>' +
|
|
47
|
+
'<div class="endpoint-path"><span class="method-badge ' + ep.method + '">' + ep.method + '</span><span>' + ep.path + '</span></div>' +
|
|
48
|
+
'<div class="endpoint-desc">' + ep.desc + '</div>';
|
|
49
|
+
|
|
50
|
+
if (ep.params && ep.params.length) {
|
|
51
|
+
html += '<div class="form-section"><div class="form-section-title">Path Parameters</div>';
|
|
52
|
+
ep.params.forEach(p => {
|
|
53
|
+
html += '<div class="field-row"><label class="field-label">' + p.label + '</label><input type="text" id="param-' + p.name + '" placeholder="' + p.placeholder + '" /></div>';
|
|
54
|
+
});
|
|
55
|
+
html += '</div>';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (ep.fields && ep.fields.length) {
|
|
59
|
+
html += '<div class="form-section"><div class="form-section-title">HTTP JSON Payload Parameters</div>';
|
|
60
|
+
ep.fields.forEach(f => {
|
|
61
|
+
html += '<div class="field-row"><label class="field-label">' + f.label + '</label><input type="' + (f.type || 'text') + '" id="field-' + f.name + '" placeholder="' + (f.placeholder || '') + '" /></div>';
|
|
62
|
+
});
|
|
63
|
+
html += '</div>';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
html += '<div class="btn-row"><button class="btn" id="_exec_btn">Execute Route</button><button class="btn btn-secondary" id="_clear_btn">Clear Context</button></div>';
|
|
67
|
+
main.innerHTML = html;
|
|
68
|
+
document.getElementById('_exec_btn').addEventListener('click', sendRequest);
|
|
69
|
+
document.getElementById('_clear_btn').addEventListener('click', clearResponse);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function sendRequest() {
|
|
73
|
+
const ep = ENDPOINTS[currentKey];
|
|
74
|
+
let path = ep.path;
|
|
75
|
+
if (ep.params && ep.params.length) {
|
|
76
|
+
for (const p of ep.params) {
|
|
77
|
+
const val = (document.getElementById('param-' + p.name) || {}).value || '';
|
|
78
|
+
if (!val.trim()) { showToast('⚠ Path param "' + p.label + '" is required'); return; }
|
|
79
|
+
path = path.replace(':' + p.name, encodeURIComponent(val.trim()));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const baseUrl = document.getElementById('base-url').value.replace(/\/+$/, '');
|
|
83
|
+
const url = baseUrl + path;
|
|
84
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
85
|
+
const jwt = document.getElementById('jwt-input').value.trim();
|
|
86
|
+
if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
|
|
87
|
+
|
|
88
|
+
let body = undefined;
|
|
89
|
+
if (['POST', 'PUT', 'PATCH'].includes(ep.method) && ep.fields && ep.fields.length) {
|
|
90
|
+
const payload = {};
|
|
91
|
+
ep.fields.forEach(f => {
|
|
92
|
+
const el = document.getElementById('field-' + f.name);
|
|
93
|
+
if (!el) return;
|
|
94
|
+
let v = el.value.trim();
|
|
95
|
+
if (f.type === 'number' && v !== '') v = Number(v);
|
|
96
|
+
payload[f.name] = v;
|
|
97
|
+
});
|
|
98
|
+
body = JSON.stringify(payload);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setResponse(null, 'loading');
|
|
102
|
+
const t0 = Date.now();
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(url, { method: ep.method, headers, body });
|
|
105
|
+
const ms = Date.now() - t0;
|
|
106
|
+
const text = await res.text();
|
|
107
|
+
let data; try { data = JSON.parse(text); } catch { data = text; }
|
|
108
|
+
setResponse(data, res.ok ? 'ok' : 'err', res.status, ms);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
setResponse({ error: err.message }, 'err', 'FAIL', 0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setResponse(data, state, status, ms) {
|
|
115
|
+
const badge = document.getElementById('status-badge');
|
|
116
|
+
const body = document.getElementById('response-body');
|
|
117
|
+
if (state === 'loading') {
|
|
118
|
+
badge.className = 'status-badge status-idle'; badge.textContent = '…';
|
|
119
|
+
body.className = 'response-body empty'; body.textContent = 'Executing transmission…';
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
|
|
123
|
+
badge.textContent = status + ' · ' + ms + 'ms';
|
|
124
|
+
body.className = 'response-body';
|
|
125
|
+
const str = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
126
|
+
body.innerHTML = '<pre class="json-render-block">' + highlight(str) + '</pre>';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function clearResponse() {
|
|
130
|
+
document.getElementById('status-badge').className = 'status-badge status-idle';
|
|
131
|
+
document.getElementById('status-badge').textContent = '—';
|
|
132
|
+
const body = document.getElementById('response-body');
|
|
133
|
+
body.className = 'response-body empty'; body.textContent = 'Execute a request row to generate feedback data';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function highlight(str) {
|
|
137
|
+
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
138
|
+
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(m) {
|
|
139
|
+
if (/^"/.test(m)) return /:$/.test(m) ? '<span class="json-key">' + m + '</span>' : '<span class="json-str">' + m + '</span>';
|
|
140
|
+
if (/true|false/.test(m)) return '<span class="json-bool">' + m + '</span>';
|
|
141
|
+
if (/null/.test(m)) return '<span class="json-null">' + m + '</span>';
|
|
142
|
+
return '<span class="json-num">' + m + '</span>';
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function showToast(msg) {
|
|
147
|
+
const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show');
|
|
148
|
+
setTimeout(() => t.classList.remove('show'), 2500);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
buildSidebar();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Global UI layouts object
|
|
155
|
+
const UI = {
|
|
156
|
+
// Original Tester UI Page
|
|
157
|
+
tester: (endpointsJsonB64) => `<!DOCTYPE html>
|
|
158
|
+
<html lang="en">
|
|
159
|
+
<head>
|
|
160
|
+
<meta charset="UTF-8"><title>Endtester — Environment Hub</title>
|
|
161
|
+
<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">
|
|
162
|
+
<style>
|
|
163
|
+
:root { --bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020; --accent: #e8a838; --text: #f0e8d8; --text-dim: #9a8c78; --red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px; }
|
|
164
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
165
|
+
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%); }
|
|
166
|
+
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; }
|
|
167
|
+
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); }
|
|
168
|
+
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; margin-left: 8px; }
|
|
169
|
+
.header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
170
|
+
.jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
|
|
171
|
+
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; }
|
|
172
|
+
#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; }
|
|
173
|
+
.layout { display: grid; grid-template-columns: 280px 1fr 450px; height: calc(100vh - 65px); overflow: hidden; }
|
|
174
|
+
aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; background: #0b0907; }
|
|
175
|
+
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; padding: 12px 18px 6px; }
|
|
176
|
+
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); }
|
|
177
|
+
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
178
|
+
.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; }
|
|
179
|
+
.GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT { background: #3a2e10; color: #e8a838; } .DELETE { background: #3a1a14; color: #d45c3c; }
|
|
180
|
+
.nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
181
|
+
main { overflow-y: auto; padding: 32px; background: #0e0c09; }
|
|
182
|
+
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
|
|
183
|
+
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
|
|
184
|
+
.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; }
|
|
185
|
+
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
|
|
186
|
+
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
|
187
|
+
.field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
|
|
188
|
+
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; }
|
|
189
|
+
input[type=text], input[type=password], input[type=number] { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; }
|
|
190
|
+
.btn-row { margin-top: 24px; display: flex; gap: 12px; }
|
|
191
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; }
|
|
192
|
+
.btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
|
|
193
|
+
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; background: #110e0a; }
|
|
194
|
+
.response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
|
|
195
|
+
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; }
|
|
196
|
+
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; }
|
|
197
|
+
.status-ok { background: #1a3a22; color: #6ba05a; } .status-err { background: #3a1a14; color: #d45c3c; } .status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
198
|
+
.response-body { flex: 1; overflow-y: auto; padding: 0; background: #0d0b08; }
|
|
199
|
+
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; font-size: 13px; }
|
|
200
|
+
.json-render-block { display: block; padding: 20px; font-family: 'DM Mono', monospace; font-size: 12px; line-height: 1.5; white-space: pre; }
|
|
201
|
+
.json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; } .json-bool { color: #c47a1e; } .json-null { color: var(--text-dim); }
|
|
202
|
+
#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; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
|
|
203
|
+
#toast.show { opacity: 1; }
|
|
204
|
+
</style>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
<div id="__monkey_data__" data-payload="${endpointsJsonB64}" style="display:none;"></div>
|
|
208
|
+
<header>
|
|
209
|
+
<div class="logo">🐒 Endtester <span>Interactive API Hub</span></div>
|
|
210
|
+
<div class="header-right">
|
|
211
|
+
<div class="base-url-wrap"><label>HOST</label><input id="base-url" type="text" value=""></div>
|
|
212
|
+
<div class="jwt-wrap"><label>BEARER AUTH</label><input id="jwt-input" type="text" placeholder="Token value..."></div>
|
|
213
|
+
</div>
|
|
214
|
+
</header>
|
|
215
|
+
<div class="layout">
|
|
216
|
+
<aside id="sidebar-nav"><div class="section-label">Discovered Endpoints</div></aside>
|
|
217
|
+
<main id="main-panel"></main>
|
|
218
|
+
<div class="response-panel">
|
|
219
|
+
<div class="response-header"><span class="response-header-title">Response Output</span><span id="status-badge" class="status-badge status-idle">—</span></div>
|
|
220
|
+
<div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<div id="toast"></div>
|
|
224
|
+
<script>(${runtimeClientSandbox.toString()})();</script>
|
|
225
|
+
</body>
|
|
226
|
+
</html>`,
|
|
227
|
+
|
|
228
|
+
// Turnkey Customer Login Page
|
|
229
|
+
login: () => `<!DOCTYPE html><html><head><title>Sign In</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
230
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
231
|
+
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
232
|
+
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
233
|
+
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
234
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }
|
|
235
|
+
.footer { text-align: center; margin-top: 20px; font-size: 13px; color: #9a8c78; } a { color: #e8a838; text-decoration: none; }</style></head>
|
|
236
|
+
<body><div class="card"><h2>Sign In</h2><div id="err" style="color:#d45c3c; font-size:13px; margin-bottom:15px; text-align:center;"></div>
|
|
237
|
+
<div class="field"><label>Email</label><input type="email" id="email" value="customer@bakery.com"></div>
|
|
238
|
+
<div class="field"><label>Password</label><input type="password" id="password" value="secret123"></div>
|
|
239
|
+
<button onclick="handleLogin()">Log In</button><div class="footer">Need an account? <a href="/signup">Sign up</a></div></div>
|
|
240
|
+
<script>async function handleLogin() {
|
|
241
|
+
const email = document.getElementById('email').value;
|
|
242
|
+
const password = document.getElementById('password').value;
|
|
243
|
+
const res = await fetch('/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
|
|
244
|
+
const data = await res.json();
|
|
245
|
+
if (res.ok && data.token) {
|
|
246
|
+
localStorage.setItem('__auth_token__', data.token);
|
|
247
|
+
window.location.href = '/dashboard';
|
|
248
|
+
} else { document.getElementById('err').textContent = data.error || 'Login failed'; }
|
|
249
|
+
}</script></body></html>`,
|
|
250
|
+
|
|
251
|
+
// Turnkey Customer Signup Page
|
|
252
|
+
signup: () => `<!DOCTYPE html><html><head><title>Create Account</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
253
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
254
|
+
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
255
|
+
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
256
|
+
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
257
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }</style></head>
|
|
258
|
+
<body><div class="card"><h2>Sign Up</h2><div class="field"><label>Username</label><input type="text" id="username"></div>
|
|
259
|
+
<div class="field"><label>Email</label><input type="email" id="email"></div><div class="field"><label>Password</label><input type="password" id="password"></div>
|
|
260
|
+
<button onclick="window.location.href='/login'">Register Account</button></div></body></html>`,
|
|
261
|
+
|
|
262
|
+
// Turnkey Storefront Dashboard
|
|
263
|
+
dashboard: () => `<!DOCTYPE html><html><head><title>Storefront Catalog</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
264
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; padding: 40px; margin: 0; }
|
|
265
|
+
header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #3a3020; padding-bottom: 20px; margin-bottom: 30px; }
|
|
266
|
+
h1 { color: #e8a838; margin: 0; } .logout { background: #3a1a14; color: #d45c3c; border: 1px solid #5a2014; padding: 8px 16px; border-radius: 6px; cursor: pointer; }
|
|
267
|
+
.grid { display: grid; grid-template-columns: 1fr; gap: 30px; } .panel { background: #181510; border: 1px solid #3a3020; padding: 24px; border-radius: 8px; }
|
|
268
|
+
table { width: 100%; border-collapse: collapse; } th { color: #9a8c78; text-align: left; padding: 12px; border-bottom: 2px solid #3a3020; } td { padding: 12px; border-bottom: 1px solid #221d14; }</style></head>
|
|
269
|
+
<body><header><h1>Bakery Storefront</h1><button class="logout" onclick="localStorage.removeItem('__auth_token__'); window.location.href='/login'">Log Out</button></header>
|
|
270
|
+
<div class="grid"><div class="panel"><h3>Available Baked Goods</h3><table><thead><tr><th>Product Name</th><th>Price</th><th>Stock Status</th></tr></thead><tbody id="catalog"></tbody></table></div></div>
|
|
271
|
+
<script>const token = localStorage.getItem('__auth_token__'); if (!token) window.location.href = '/login';
|
|
272
|
+
async function load() {
|
|
273
|
+
const res = await fetch('/api/v1/products', { headers: { 'Authorization': 'Bearer ' + token } });
|
|
274
|
+
const data = await res.json(); const tbody = document.getElementById('catalog'); tbody.innerHTML = '';
|
|
275
|
+
if(data.products) { data.products.forEach(p => { tbody.innerHTML += '<tr><td><strong>'+p.name+'</strong></td><td>$'+p.price.toFixed(2)+'</td><td>'+p.stock+' left</td></tr>'; }); }
|
|
276
|
+
} load();</script></body></html>`
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export { UI };
|