api-playground/templates/playground.html
2026-06-08 17:11:32 +07:00

298 lines
10 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Enroll Playground</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', sans-serif; background: #1e1e2f; color: #e0e0e0; padding: 20px; }
h1 { text-align: center; color: #7c83fd; margin-bottom: 10px; }
h2 { color: #a5abff; margin-bottom: 10px; font-size: 1.1em; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-width: 1200px; margin: 0 auto; }
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } }
.card { background: #2a2a3d; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.4); }
.card.full { grid-column: 1 / -1; }
label { display: block; margin: 8px 0 3px; font-size: 0.85em; color: #999; }
input, select { width: 100%; padding: 8px 12px; border: 1px solid #444; border-radius: 6px; background: #1e1e2f; color: #e0e0e0; font-size: 0.9em; }
input:focus { outline: none; border-color: #7c83fd; }
.btn-row { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
button { padding: 8px 18px; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85em; transition: opacity .15s; }
button:hover { opacity: .85; }
.btn-get { background: #4caf50; color: #fff; }
.btn-post { background: #7c83fd; color: #fff; }
.btn-del { background: #f44336; color: #fff; }
.result { margin-top: 12px; background: #111; border-radius: 6px; padding: 12px; font-family: 'Fira Code', monospace; font-size: 0.82em; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow-y: auto; color: #a8ff60; }
.result.err { color: #ff6b6b; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #333; font-size: 0.85em; }
th { color: #7c83fd; }
tr:hover { background: #33334d; }
.badge { padding: 2px 8px; border-radius: 10px; font-size: 0.75em; font-weight: 600; }
.badge.type { background: #ff980033; color: #ff9800; }
.badge.service { background: #2196f333; color: #64b5f6; }
.badge.user { background: #4caf5033; color: #81c784; }
/* ── auth bar ── */
.auth-bar {
max-width: 1200px;
margin: 0 auto 25px;
display: flex;
align-items: center;
gap: 10px;
background: #2a2a3d;
border-radius: 10px;
padding: 14px 20px;
box-shadow: 0 2px 8px rgba(0,0,0,.4);
flex-wrap: wrap;
}
.auth-bar label { margin: 0; color: #999; font-size: 0.85em; }
.auth-bar input { width: 220px; }
.auth-bar .status {
font-size: 0.82em;
font-weight: 600;
padding: 4px 12px;
border-radius: 20px;
}
.status.ok { background: #4caf5033; color: #4caf50; }
.status.bad { background: #f4433633; color: #f44336; }
.status.none { background: #55555533; color: #888; }
.hint { font-size: 0.75em; color: #666; margin-left: auto; }
</style>
</head>
<body>
<h1>🚀 API Enroll Playground</h1>
<!-- ── AUTH BAR ── -->
<div class="auth-bar">
<label>🔑 Bearer Token</label>
<input id="token-input" type="password" placeholder="enter token…" oninput="updateStatus()">
<span id="auth-status" class="status none">no token</span>
<span class="hint">default: secret123</span>
</div>
<div class="grid">
<!-- ── ADD ── -->
<div class="card">
<h2> Add (POST)</h2>
<label>type</label>
<select id="add-type">
<option value="">-- pilih --</option>
<option value="Annually">Annually</option>
<option value="Monthly">Monthly</option>
</select>
<label>service</label>
<select id="add-service">
<option value="">-- pilih --</option>
<option value="Lite">Lite</option>
<option value="Value">Value</option>
<option value="Pro">Pro</option>
</select>
<label>user</label> <input id="add-user" placeholder="e.g. alice">
<div class="btn-row">
<button class="btn-post" onclick="doAdd()">POST /api/enroll</button>
</div>
<pre class="result" id="add-result"></pre>
</div>
<!-- ── LIST ── -->
<div class="card">
<h2>📋 List (GET)</h2>
<div class="btn-row">
<button class="btn-get" onclick="doList()">GET /api/enroll</button>
</div>
<pre class="result" id="list-result"></pre>
</div>
<!-- ── DETAIL ── -->
<div class="card">
<h2>🔍 Detail (GET)</h2>
<label>id</label>
<input id="detail-id" type="number" placeholder="e.g. 1">
<div class="btn-row">
<button class="btn-get" onclick="doDetail()">GET /api/enroll?id=</button>
</div>
<pre class="result" id="detail-result"></pre>
</div>
<!-- ── EDIT ── -->
<div class="card">
<h2>✏️ Edit (POST)</h2>
<label>id</label> <input id="edit-id" type="number" placeholder="e.g. 1">
<label>type</label>
<select id="edit-type">
<option value="">-- pilih --</option>
<option value="Annually">Annually</option>
<option value="Monthly">Monthly</option>
</select>
<label>service</label>
<select id="edit-service">
<option value="">-- pilih --</option>
<option value="Lite">Lite</option>
<option value="Value">Value</option>
<option value="Pro">Pro</option>
</select>
<label>user</label> <input id="edit-user" placeholder="e.g. bob">
<div class="btn-row">
<button class="btn-post" onclick="doEdit()">POST /api/enroll (edit)</button>
</div>
<pre class="result" id="edit-result"></pre>
</div>
<!-- ── REMOVE ── -->
<div class="card">
<h2>🗑️ Remove (POST)</h2>
<label>id</label>
<input id="del-id" type="number" placeholder="e.g. 1">
<div class="btn-row">
<button class="btn-del" onclick="doRemove()">POST /api/enroll (remove)</button>
</div>
<pre class="result" id="del-result"></pre>
</div>
<!-- ── TABLE PREVIEW ── -->
<div class="card full">
<h2>📊 Table Preview</h2>
<div class="btn-row">
<button class="btn-get" onclick="refreshTable()">Refresh Table</button>
</div>
<table id="preview-table">
<thead><tr><th>ID</th><th>Type</th><th>Service</th><th>User</th></tr></thead>
<tbody id="preview-body"><tr><td colspan="4" style="text-align:center;color:#555">Click Refresh</td></tr></tbody>
</table>
</div>
</div>
<script>
const BASE = "/api/enroll";
// ── auth helpers ──
function getToken() {
return document.getElementById('token-input').value.trim();
}
function authHeaders() {
const h = {};
const t = getToken();
if (t) h['Authorization'] = 'Bearer ' + t;
return h;
}
function updateStatus() {
const el = document.getElementById('auth-status');
const t = getToken();
if (!t) {
el.textContent = 'no token';
el.className = 'status none';
} else if (t === 'secret123') {
el.textContent = '✅ authenticated';
el.className = 'status ok';
} else {
el.textContent = '❌ wrong token';
el.className = 'status bad';
}
}
// ── ui helpers ──
function show(elId, data, isErr) {
const el = document.getElementById(elId);
el.textContent = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
el.classList.toggle('err', !!isErr);
}
// ── API calls (semua pakai authHeaders) ──
async function doAdd() {
const body = new URLSearchParams();
body.append('type', document.getElementById('add-type').value);
body.append('service', document.getElementById('add-service').value);
body.append('user', document.getElementById('add-user').value);
try {
const r = await fetch(BASE, { method: 'POST', headers: authHeaders(), body });
const j = await r.json();
show('add-result', j, j.error);
if (!j.error) doList();
} catch (e) { show('add-result', e.message, true); }
}
async function doList() {
try {
const r = await fetch(BASE, { headers: authHeaders() });
const j = await r.json();
show('list-result', j, j.error);
refreshTable();
} catch (e) { show('list-result', e.message, true); }
}
async function doDetail() {
const id = document.getElementById('detail-id').value;
if (!id) { show('detail-result', 'Please enter an id', true); return; }
try {
const r = await fetch(`${BASE}?id=${id}`, { headers: authHeaders() });
const j = await r.json();
show('detail-result', j, j.error);
} catch (e) { show('detail-result', e.message, true); }
}
async function doEdit() {
const body = new URLSearchParams();
body.append('id', document.getElementById('edit-id').value);
body.append('type', document.getElementById('edit-type').value);
body.append('service',document.getElementById('edit-service').value);
body.append('user', document.getElementById('edit-user').value);
try {
const r = await fetch(BASE, { method: 'POST', headers: authHeaders(), body });
const j = await r.json();
show('edit-result', j, j.error);
if (!j.error) doList();
} catch (e) { show('edit-result', e.message, true); }
}
async function doRemove() {
const id = document.getElementById('del-id').value;
if (!id) { show('del-result', 'Please enter an id', true); return; }
const body = new URLSearchParams();
body.append('id', id);
body.append('action', 'remove');
try {
const r = await fetch(BASE, { method: 'POST', headers: authHeaders(), body });
const j = await r.json();
show('del-result', j, j.error);
if (!j.error) doList();
} catch (e) { show('del-result', e.message, true); }
}
async function refreshTable() {
try {
const r = await fetch(BASE, { headers: authHeaders() });
const j = await r.json();
const tbody = document.getElementById('preview-body');
if (!j.data || j.data.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;color:#555">No data</td></tr>';
return;
}
tbody.innerHTML = j.data.map(row => `
<tr>
<td>${row.id}</td>
<td><span class="badge type">${row.type}</span></td>
<td><span class="badge service">${row.service}</span></td>
<td><span class="badge user">${row.user}</span></td>
</tr>
`).join('');
} catch (e) { /* ignore */ }
}
// auto-load on start
doList();
</script>
</body>
</html>