<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HeliosProxy admin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
color-scheme: light dark;
--ok: #2ea043;
--warn: #bf8700;
--err: #cf222e;
--bg: #ffffff;
--fg: #1f2328;
--muted: #656d76;
--line: #d0d7de;
--chip: #f6f8fa;
--accent: #0969da;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--fg: #e6edf3;
--muted: #848d97;
--line: #30363d;
--chip: #161b22;
--accent:#2f81f7;
}
}
html, body { background: var(--bg); color: var(--fg); margin: 0; padding: 0; }
body {
font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
header {
padding: 18px 24px;
border-bottom: 1px solid var(--line);
display: flex; align-items: baseline; gap: 16px;
}
header h1 { font-size: 18px; margin: 0; font-weight: 600; }
header .version { color: var(--muted); font-size: 13px; }
header .right { margin-left: auto; color: var(--muted); font-size: 12px; }
main { padding: 24px; display: grid; gap: 24px; grid-template-columns: 1fr; max-width: 1200px; margin: 0 auto; }
@media (min-width: 900px) {
main { grid-template-columns: 1fr 1fr; }
main .full { grid-column: 1 / -1; }
}
section {
border: 1px solid var(--line);
border-radius: 6px;
background: var(--bg);
overflow: hidden;
}
section > h2 {
margin: 0;
padding: 10px 14px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--muted);
border-bottom: 1px solid var(--line);
background: var(--chip);
}
section > .body { padding: 14px; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 6px 8px; font-variant-numeric: tabular-nums; }
th { font-weight: 500; color: var(--muted); border-bottom: 1px solid var(--line); }
tr + tr td { border-top: 1px solid var(--line); }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.dot-ok { background: var(--ok); }
.dot-warn { background: var(--warn); }
.dot-err { background: var(--err); }
.metric {
display: flex; justify-content: space-between; align-items: baseline;
padding: 6px 0;
}
.metric + .metric { border-top: 1px solid var(--line); }
.metric .k { color: var(--muted); font-size: 12px; }
.metric .v { font-variant-numeric: tabular-nums; font-size: 18px; font-weight: 500; }
textarea {
width: 100%; min-height: 80px; box-sizing: border-box;
font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
background: var(--chip); color: var(--fg);
border: 1px solid var(--line); border-radius: 6px;
padding: 8px; resize: vertical;
}
button {
font: inherit;
background: var(--accent); color: white; border: 0; border-radius: 6px;
padding: 6px 12px; cursor: pointer;
}
button:hover { filter: brightness(1.08); }
pre.result {
background: var(--chip); border: 1px solid var(--line); border-radius: 6px;
padding: 10px; overflow: auto; max-height: 340px;
font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
white-space: pre-wrap; word-break: break-word;
margin: 10px 0 0;
}
.row { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
.row.wrap { flex-wrap: wrap; }
input[type="text"], input[type="number"], input[type="datetime-local"] {
font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
background: var(--chip); color: var(--fg);
border: 1px solid var(--line); border-radius: 6px;
padding: 6px 8px;
color-scheme: light dark;
}
label { font-size: 12px; color: var(--muted); display: flex; flex-direction: column; gap: 2px; }
footer {
padding: 18px 24px; color: var(--muted); border-top: 1px solid var(--line);
font-size: 12px;
}
</style>
</head>
<body>
<header>
<h1>HeliosProxy</h1>
<span class="version" id="version">—</span>
<span class="right" id="last-refresh">—</span>
</header>
<main>
<section class="full">
<h2>Nodes</h2>
<div class="body">
<table id="nodes">
<thead>
<tr>
<th style="width:16px"></th>
<th>Address</th>
<th>Healthy</th>
<th>Failures</th>
<th>Latency (ms)</th>
<th>Last check</th>
</tr>
</thead>
<tbody><tr><td colspan="6">loading…</td></tr></tbody>
</table>
</div>
</section>
<section>
<h2>Traffic</h2>
<div class="body" id="metrics-traffic">loading…</div>
</section>
<section>
<h2>Cluster</h2>
<div class="body" id="metrics-cluster">loading…</div>
</section>
<section>
<h2>Topology</h2>
<div class="body" id="topology">loading…</div>
</section>
<section class="full">
<h2>Edge Mode</h2>
<div class="body">
<p style="color: var(--muted); margin: 0 0 8px 0;">
Cache stats + registered edges. Manual invalidation drops
cached results matching the supplied tables and broadcasts
to every edge.
</p>
<div id="edge-stats" style="margin-bottom: 12px;">loading…</div>
<div class="row wrap">
<label>Tables (comma-separated)
<input type="text" id="edge-inv-tables" placeholder="users, orders">
</label>
<button id="edge-invalidate">Invalidate</button>
<span id="edge-status" style="color: var(--muted);"></span>
</div>
<pre class="result" id="edge-result">No invalidations broadcast yet.</pre>
</div>
</section>
<section class="full">
<h2>Anomalies</h2>
<div class="body">
<table id="anomalies">
<thead>
<tr>
<th>When</th>
<th>Severity</th>
<th>Kind</th>
<th>Details</th>
</tr>
</thead>
<tbody><tr><td colspan="4">loading…</td></tr></tbody>
</table>
</div>
</section>
<section class="full">
<h2>Plugins</h2>
<div class="body">
<table id="plugins">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Hooks</th>
<th>State</th>
<th>Invocations</th>
<th>Errors</th>
</tr>
</thead>
<tbody><tr><td colspan="6">loading…</td></tr></tbody>
</table>
</div>
</section>
<section class="full">
<h2>SQL</h2>
<div class="body">
<textarea id="sql" placeholder="SELECT 1" spellcheck="false">SELECT 1</textarea>
<div class="row">
<button id="run">Run</button>
<span id="sql-status" style="color: var(--muted);"></span>
</div>
<pre class="result" id="sql-result">—</pre>
</div>
</section>
<section class="full">
<h2>Chaos Mode</h2>
<div class="body">
<p style="color: var(--muted); margin: 0 0 8px 0;">
Inject controlled faults to exercise the failover machinery.
Force a node unhealthy, then restore (or hit Reset to clear
everything in one call). Real health checks will reassert if
the underlying probe still fails.
</p>
<div class="row wrap">
<label>Target node
<input type="text" id="chaos-node" placeholder="primary.svc:5432">
</label>
<button id="chaos-force">Force unhealthy</button>
<button id="chaos-restore">Restore</button>
<button id="chaos-reset">Reset all</button>
<span id="chaos-status" style="color: var(--muted);"></span>
</div>
<pre class="result" id="chaos-result">No active chaos overrides.</pre>
</div>
</section>
<section class="full">
<h2>Shadow Execution</h2>
<div class="body">
<p style="color: var(--muted); margin: 0 0 8px 0;">
Run a query against two backends and diff the results. Use
for major-version upgrade validation, schema-migration
canaries, and replica-drift detection. The "is_clean" line
in the result tells you whether both sides matched.
</p>
<textarea id="shadow-sql" placeholder="SELECT id, name FROM users LIMIT 10" spellcheck="false">SELECT 1</textarea>
<div class="row wrap">
<label>Source host
<input type="text" id="shadow-src-host" placeholder="primary.svc">
</label>
<label>Source port
<input type="number" id="shadow-src-port" value="5432" min="1" max="65535">
</label>
<label>Shadow host
<input type="text" id="shadow-tgt-host" placeholder="upgrade-candidate.svc">
</label>
<label>Shadow port
<input type="number" id="shadow-tgt-port" value="5432" min="1" max="65535">
</label>
</div>
<div class="row">
<button id="shadow-run">Diff query</button>
<span id="shadow-status" style="color: var(--muted);"></span>
</div>
<pre class="result" id="shadow-result">Pick source + shadow, type a SELECT, hit Diff query.</pre>
</div>
</section>
<section class="full">
<h2>Time-Travel Replay</h2>
<div class="body">
<div class="row wrap">
<label>From (UTC)
<input type="datetime-local" id="replay-from" step="1">
</label>
<label>To (UTC)
<input type="datetime-local" id="replay-to" step="1">
</label>
<label>Target host
<input type="text" id="replay-host" placeholder="staging-db.svc">
</label>
<label>Target port
<input type="number" id="replay-port" value="5432" min="1" max="65535">
</label>
</div>
<div class="row wrap">
<label>Target user (optional)
<input type="text" id="replay-user" placeholder="postgres">
</label>
<label>Target password (optional)
<input type="password" id="replay-password" placeholder="••••••••">
</label>
<label>Target database (optional)
<input type="text" id="replay-database" placeholder="appdb">
</label>
</div>
<div class="row">
<button id="replay-run">Replay window</button>
<span id="replay-status" style="color: var(--muted);"></span>
</div>
<pre class="result" id="replay-result">Pick a window + target, then click Replay window. Replays every journaled statement in the inclusive window onto the target backend in chronological order. Returns a summary of statements applied / failed / elapsed.</pre>
</div>
</section>
</main>
<footer>
Data refreshes every 5 s. SQL requests go to <code>POST /api/sql</code> —
write statements route to the primary, reads to a healthy replica
per the proxy's load-balancing policy.
</footer>
<script>
const REFRESH_MS = 5000;
async function j(url, opts) {
const r = await fetch(url, opts);
if (!r.ok) throw new Error(r.status + " " + r.statusText);
return r.json();
}
function dotClass(healthy) {
return "dot " + (healthy ? "dot-ok" : "dot-err");
}
function fmtTs(iso) {
if (!iso) return "—";
try { return new Date(iso).toLocaleTimeString(); } catch (e) { return iso; }
}
function renderNodes(nodes) {
const rows = nodes.map(n => `
<tr>
<td><span class="${dotClass(n.healthy)}"></span></td>
<td><code>${n.address}</code></td>
<td>${n.healthy ? "yes" : "<strong style='color:var(--err)'>no</strong>"}</td>
<td>${n.failure_count ?? 0}</td>
<td>${(n.latency_ms ?? 0).toFixed(1)}</td>
<td>${fmtTs(n.last_check)}</td>
</tr>
`).join("");
document.querySelector("#nodes tbody").innerHTML = rows || "<tr><td colspan='6'>no nodes</td></tr>";
}
function metricRow(k, v) {
return `<div class="metric"><span class="k">${k}</span><span class="v">${v}</span></div>`;
}
function renderTraffic(m) {
document.querySelector("#metrics-traffic").innerHTML =
metricRow("Queries processed", (m.queries_processed ?? 0).toLocaleString()) +
metricRow("Connections accepted", (m.connections_accepted ?? 0).toLocaleString()) +
metricRow("Connections closed", (m.connections_closed ?? 0).toLocaleString()) +
metricRow("Bytes received", (m.bytes_received ?? 0).toLocaleString()) +
metricRow("Bytes sent", (m.bytes_sent ?? 0).toLocaleString());
}
function renderCluster(m, sessions) {
document.querySelector("#metrics-cluster").innerHTML =
metricRow("Active sessions", (sessions ?? 0).toLocaleString()) +
metricRow("Failovers", (m.failovers ?? 0).toLocaleString());
}
function renderPlugins(plugins) {
if (!Array.isArray(plugins)) {
document.querySelector("#plugins tbody").innerHTML =
`<tr><td colspan="6" style="color: var(--muted);">${plugins.error ?? "plugins unavailable"}</td></tr>`;
return;
}
if (plugins.length === 0) {
document.querySelector("#plugins tbody").innerHTML =
`<tr><td colspan="6" style="color: var(--muted);">no plugins loaded</td></tr>`;
return;
}
const rows = plugins.map(p => `
<tr>
<td><code>${p.name}</code></td>
<td>${p.version}</td>
<td><code>${(p.hooks ?? []).join(", ")}</code></td>
<td>${p.state}</td>
<td>${(p.invocations ?? 0).toLocaleString()}</td>
<td>${(p.errors ?? 0) > 0
? `<strong style='color:var(--err)'>${p.errors}</strong>`
: "0"}</td>
</tr>
`).join("");
document.querySelector("#plugins tbody").innerHTML = rows;
}
function severityBadge(sev) {
const colour = sev === "critical" ? "var(--err)"
: sev === "warning" ? "var(--warn)"
: "var(--muted)";
return `<strong style='color:${colour}'>${sev}</strong>`;
}
function anomalyDetails(ev) {
switch (ev.kind) {
case "rate_spike":
return `tenant <code>${ev.tenant}</code>: ${ev.rate_per_sec.toFixed(1)}/s (baseline ${ev.baseline.toFixed(1)}, z=${ev.z_score.toFixed(1)})`;
case "auth_burst":
return `user <code>${ev.user}</code> from <code>${ev.client_ip}</code>: ${ev.failures} failures in ${ev.window_secs}s`;
case "sql_injection":
return `patterns: ${(ev.patterns_matched ?? []).join(", ")} — <code>${(ev.sql_excerpt ?? "").replace(/</g, "<")}</code>`;
case "novel_query":
return `fingerprint <code>${ev.fingerprint}</code> — ${(ev.sql_excerpt ?? "").replace(/</g, "<")}`;
default:
return JSON.stringify(ev);
}
}
function renderEdge(payload) {
const el = document.querySelector("#edge-stats");
if (!payload || payload.error) {
el.innerHTML = `<span style="color: var(--muted);">${payload?.error ?? "edge-proxy unavailable"}</span>`;
return;
}
const c = payload.cache ?? {};
const edges = payload.registered ?? [];
el.innerHTML =
metricRow("Edges registered", payload.edge_count ?? 0) +
metricRow("Cache entries", c.current_entries ?? 0) +
metricRow("Cache hits / misses", `${c.hits ?? 0} / ${c.misses ?? 0}`) +
metricRow("Inserts / evictions", `${c.inserts ?? 0} / ${c.entries_evicted ?? 0}`) +
metricRow("Invalidations received", c.invalidations_received ?? 0) +
(edges.length === 0 ? "" :
`<div style="margin-top:10px;font-size:12px;color:var(--muted);">Registered: ${edges.map(e => `<code>${e.edge_id}@${e.region}</code>`).join(", ")}</div>`);
}
function renderAnomalies(payload) {
const tbody = document.querySelector("#anomalies tbody");
if (payload.error) {
tbody.innerHTML = `<tr><td colspan="4" style="color: var(--muted);">${payload.error}</td></tr>`;
return;
}
const events = payload.events ?? [];
if (events.length === 0) {
tbody.innerHTML = `<tr><td colspan="4" style="color: var(--muted);">no anomalies detected</td></tr>`;
return;
}
tbody.innerHTML = events.map(ev => `
<tr>
<td>${fmtTs(ev.detected_at)}</td>
<td>${severityBadge(ev.severity ?? "info")}</td>
<td><code>${ev.kind}</code></td>
<td>${anomalyDetails(ev)}</td>
</tr>
`).join("");
}
function renderTopology(t) {
const primary = t.currentPrimary
? `<code>${t.currentPrimary}</code>`
: `<strong style='color:var(--warn)'>none — failover in progress</strong>`;
const failover = t.lastFailoverAt ? fmtTs(t.lastFailoverAt) : "never";
document.querySelector("#topology").innerHTML =
metricRow("Current primary", primary) +
metricRow("Healthy nodes", `${t.healthyNodes ?? 0} / ${t.totalNodes ?? 0}`) +
metricRow("Unhealthy nodes", (t.unhealthyNodes ?? 0).toLocaleString()) +
metricRow("Last failover", failover);
}
async function refresh() {
try {
const pluginsP = fetch("/plugins").then(async r => {
try { return await r.json(); } catch { return []; }
}).catch(() => []);
const anomaliesP = fetch("/anomalies?limit=50").then(async r => {
try { return await r.json(); } catch { return { events: [] }; }
}).catch(() => ({ events: [] }));
const edgeP = fetch("/api/edge").then(async r => {
try { return await r.json(); } catch { return null; }
}).catch(() => null);
const [nodes, metrics, sessions, version, topo, plugins, anomalies, edge] = await Promise.all([
j("/nodes").catch(() => []),
j("/metrics").catch(() => ({})),
j("/sessions").catch(() => ({ active_sessions: 0 })),
j("/version").catch(() => ({})),
j("/topology").catch(() => ({})),
pluginsP,
anomaliesP,
edgeP,
]);
renderNodes(nodes);
renderTraffic(metrics);
renderCluster(metrics, sessions.active_sessions);
renderTopology(topo);
renderPlugins(plugins);
renderAnomalies(anomalies);
renderEdge(edge);
if (version.version) {
document.querySelector("#version").textContent = "v" + version.version;
}
document.querySelector("#last-refresh").textContent =
"Refreshed " + new Date().toLocaleTimeString();
} catch (e) {
document.querySelector("#last-refresh").textContent =
"Refresh failed: " + e.message;
}
}
document.querySelector("#run").addEventListener("click", async () => {
const sql = document.querySelector("#sql").value;
const status = document.querySelector("#sql-status");
const result = document.querySelector("#sql-result");
status.textContent = "running…";
result.textContent = "";
try {
const r = await fetch("/api/sql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: sql }),
});
const j = await r.json();
status.textContent = r.ok ? "ok" : `error ${r.status}`;
result.textContent = JSON.stringify(j, null, 2);
} catch (e) {
status.textContent = "failed";
result.textContent = e.message;
}
});
function localToRfc3339Utc(s) {
if (!s) return null;
if (/T\d{2}:\d{2}:\d{2}/.test(s)) return s + "Z";
return s + ":00Z";
}
document.querySelector("#replay-run").addEventListener("click", async () => {
const from = localToRfc3339Utc(document.querySelector("#replay-from").value);
const to = localToRfc3339Utc(document.querySelector("#replay-to").value);
const host = document.querySelector("#replay-host").value.trim();
const port = parseInt(document.querySelector("#replay-port").value, 10);
const user = document.querySelector("#replay-user").value.trim();
const password = document.querySelector("#replay-password").value;
const database = document.querySelector("#replay-database").value.trim();
const status = document.querySelector("#replay-status");
const result = document.querySelector("#replay-result");
if (!from || !to || !host || !port) {
status.textContent = "fill from / to / host / port";
return;
}
status.textContent = "replaying…";
result.textContent = "";
const body = { from, to, target_host: host, target_port: port };
if (user) body.target_user = user;
if (password) body.target_password = password;
if (database) body.target_database = database;
try {
const r = await fetch("/api/replay", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const j = await r.json();
if (r.ok) {
status.textContent = `applied ${j.statements_replayed ?? 0}, failed ${j.failures ?? 0}, ${j.elapsed_ms ?? 0} ms`;
} else if (r.status === 503) {
status.textContent = "engine not attached on this proxy";
} else {
status.textContent = `error ${r.status}`;
}
result.textContent = JSON.stringify(j, null, 2);
} catch (e) {
status.textContent = "failed";
result.textContent = e.message;
}
});
async function chaos(action, opts = {}) {
const status = document.querySelector("#chaos-status");
const result = document.querySelector("#chaos-result");
status.textContent = `${action}…`;
try {
const r = await fetch("/api/chaos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action, ...opts }),
});
const j = await r.json();
status.textContent = r.ok ? "ok" : `error ${r.status}`;
const cur = await fetch("/api/chaos").then(r => r.json()).catch(() => ({}));
const keys = Object.keys(cur);
if (keys.length === 0) {
result.textContent = "No active chaos overrides.";
} else {
result.textContent = JSON.stringify(cur, null, 2);
}
const _ = j;
} catch (e) {
status.textContent = "failed";
result.textContent = e.message;
}
}
document.querySelector("#edge-invalidate").addEventListener("click", async () => {
const tablesRaw = document.querySelector("#edge-inv-tables").value.trim();
const tables = tablesRaw === ""
? []
: tablesRaw.split(",").map(s => s.trim()).filter(Boolean);
const status = document.querySelector("#edge-status");
const result = document.querySelector("#edge-result");
status.textContent = "invalidating…";
try {
const r = await fetch("/api/edge/invalidate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tables }),
});
const j = await r.json();
if (r.ok) {
status.textContent = `dropped ${j.dropped_local ?? 0} local; notified ${j.edges_notified ?? 0} edges`;
} else {
status.textContent = `error ${r.status}`;
}
result.textContent = JSON.stringify(j, null, 2);
} catch (e) {
status.textContent = "failed";
result.textContent = e.message;
}
});
document.querySelector("#shadow-run").addEventListener("click", async () => {
const sql = document.querySelector("#shadow-sql").value.trim();
const sh = document.querySelector("#shadow-src-host").value.trim();
const sp = parseInt(document.querySelector("#shadow-src-port").value, 10);
const th = document.querySelector("#shadow-tgt-host").value.trim();
const tp = parseInt(document.querySelector("#shadow-tgt-port").value, 10);
const status = document.querySelector("#shadow-status");
const result = document.querySelector("#shadow-result");
if (!sql || !sh || !sp || !th || !tp) {
status.textContent = "fill SQL + both source and shadow host/port";
return;
}
status.textContent = "diffing…";
result.textContent = "";
try {
const r = await fetch("/api/shadow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sql,
source_host: sh, source_port: sp,
shadow_host: th, shadow_port: tp,
}),
});
const j = await r.json();
if (r.ok) {
status.textContent = j.is_clean
? "clean — both sides match"
: `divergent: count_match=${j.row_count_match} hash_match=${j.row_hash_match}`;
status.style.color = j.is_clean ? "var(--ok)" : "var(--err)";
} else {
status.textContent = `error ${r.status}`;
status.style.color = "var(--err)";
}
result.textContent = JSON.stringify(j, null, 2);
} catch (e) {
status.textContent = "failed";
result.textContent = e.message;
}
});
document.querySelector("#chaos-force").addEventListener("click", () => {
const node = document.querySelector("#chaos-node").value.trim();
if (!node) {
document.querySelector("#chaos-status").textContent = "fill target node first";
return;
}
chaos("force_unhealthy", { target_node: node });
});
document.querySelector("#chaos-restore").addEventListener("click", () => {
const node = document.querySelector("#chaos-node").value.trim();
if (!node) {
document.querySelector("#chaos-status").textContent = "fill target node first";
return;
}
chaos("restore", { target_node: node });
});
document.querySelector("#chaos-reset").addEventListener("click", () => {
chaos("reset");
});
refresh();
setInterval(refresh, REFRESH_MS);
</script>
</body>
</html>