const accountSelect = document.getElementById("account-select"); const txForm = document.getElementById("tx-form"); const resultJson = document.getElementById("result-json"); const resultRtt = document.getElementById("result-rtt"); const resultRequestId = document.getElementById("result-request-id"); const burstButton = document.getElementById("burst-button"); const burstN = document.getElementById("burst-n"); const burstTableBody = document.querySelector("#burst-table tbody"); const chaosFlagsDiv = document.getElementById("chaos-flags"); const healthStrip = document.getElementById("health-strip"); async function loadAccounts() { const resp = await fetch("/api/accounts"); const accounts = await resp.json(); accountSelect.innerHTML = accounts .slice(0, 200) .map((a) => ``) .join(""); } txForm.addEventListener("submit", async (e) => { e.preventDefault(); const payload = { account_id: accountSelect.value, kind: document.querySelector('input[name="kind"]:checked').value, amount_minor: Number(document.getElementById("amount-input").value), currency: "EUR", merchant_name: document.getElementById("merchant-input").value, }; const start = performance.now(); const resp = await fetch("/api/transactions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const rtt = performance.now() - start; const body = await resp.json(); resultRtt.textContent = rtt.toFixed(1); resultRequestId.textContent = body.request_id || "--"; resultJson.textContent = JSON.stringify(body, null, 2); }); function percentile(sorted, p) { if (sorted.length === 0) return 0; const rank = Math.max(1, Math.min(sorted.length, Math.ceil(p * sorted.length))); return sorted[rank - 1]; } burstButton.addEventListener("click", async () => { const n = Number(burstN.value); const accounts = Array.from(accountSelect.options).map((o) => o.value); burstTableBody.innerHTML = `running ${n} requests...`; const latencies = []; let success = 0; let errors = 0; await Promise.all( Array.from({ length: n }, async () => { const accountId = accounts[Math.floor(Math.random() * accounts.length)]; const start = performance.now(); try { const resp = await fetch("/api/transactions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account_id: accountId, kind: "purchase", amount_minor: 500, currency: "EUR", merchant_name: "Load Burst", }), }); latencies.push(performance.now() - start); if (resp.ok) success += 1; else errors += 1; } catch (e) { latencies.push(performance.now() - start); errors += 1; } }) ); latencies.sort((a, b) => a - b); const mean = latencies.reduce((a, b) => a + b, 0) / latencies.length; const row = [ latencies.length, success, errors, latencies[0].toFixed(1), mean.toFixed(1), percentile(latencies, 0.5).toFixed(1), percentile(latencies, 0.95).toFixed(1), latencies[latencies.length - 1].toFixed(1), ]; burstTableBody.innerHTML = `${row.map((v) => `${v}`).join("")}`; }); async function loadChaos() { const resp = await fetch("/api/admin/chaos"); const { flags } = await resp.json(); chaosFlagsDiv.innerHTML = Object.entries(flags) .map( ([flag, enabled]) => ` ` ) .join(""); chaosFlagsDiv.querySelectorAll("input[type=checkbox]").forEach((el) => { el.addEventListener("change", async () => { await fetch("/api/admin/chaos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ flag: el.dataset.flag, enabled: el.checked }), }); }); }); } document.querySelectorAll("#chaos-profiles button").forEach((btn) => { btn.addEventListener("click", async () => { await fetch("/api/admin/chaos/profile", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ profile: btn.dataset.profile }), }); await loadChaos(); }); }); async function pollHealth() { try { const resp = await fetch("/api/health/all"); const { services } = await resp.json(); healthStrip.innerHTML = Object.entries(services) .map(([name, status]) => `${name}`) .join(""); } catch (e) { // ignore transient polling failures } } loadAccounts(); loadChaos(); pollHealth(); setInterval(pollHealth, 5000);