koopa-admin-log/configs/exchange-landing/index.html
2026-07-09 22:32:05 +02:00

353 lines
18 KiB
HTML
Raw 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" />
<title>GOA Exchange</title>
<base href="/intro/" />
<style>
:root {
--bg: #1a1410; --card: rgba(42, 32, 24, 0.82); --text: #fff6e8; --muted: #c9b8a0;
--accent2: #3ecfbf; --border: rgba(232, 168, 56, 0.28);
--glow-gold: rgba(232, 168, 56, 0.35); --glow-teal: rgba(62, 207, 191, 0.22);
--val: #f0d090;
}
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--text); line-height: 1.55; background-color: var(--bg);
background-image:
radial-gradient(ellipse 90% 55% at 50% 108%, rgba(26, 107, 110, 0.55) 0%, transparent 55%),
radial-gradient(circle 420px at 12% 18%, var(--glow-gold) 0%, transparent 62%),
linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%);
background-attachment: fixed;
}
main { max-width: 42rem; margin: 0 auto; padding: 2.25rem 1.25rem 3.5rem; }
.hero { text-align: center; margin: 0 0 1.25rem; }
h1 {
font-size: 1.85rem; font-weight: 750; margin: 0 0 0.45rem;
background: linear-gradient(120deg, #fff6e8 0%, #f0c86a 45%, #3ecfbf 120%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.sub { color: var(--muted); max-width: 32rem; margin: 0 auto; }
.badge {
display: inline-block; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em;
padding: 0.2rem 0.7rem; border-radius: 999px; margin-bottom: 0.75rem;
border: 1px solid rgba(232, 168, 56, 0.4); color: #f0d090;
background: linear-gradient(135deg, rgba(232, 168, 56, 0.2), rgba(62, 207, 191, 0.15));
}
.stats {
background: rgba(18, 28, 28, 0.55); border: 1px solid rgba(62, 207, 191, 0.28);
border-radius: 16px; padding: 1.1rem 1.15rem; margin: 0 0 1.1rem;
}
.stats h2 {
margin: 0 0 0.35rem; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--muted); text-align: center;
}
.note { text-align: center; font-size: 0.82rem; color: var(--muted); margin: 0 0 0.9rem; }
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.65rem; }
@media (min-width: 420px) { .stats-grid { grid-template-columns: 1fr 1fr 1fr; } }
.stat { background: rgba(0,0,0,0.22); border-radius: 10px; padding: 0.55rem 0.65rem; }
.stat .k { display: block; font-size: 0.68rem; color: var(--muted); text-transform: uppercase; text-align: center; margin-bottom: 0.15rem; letter-spacing: 0.04em; }
.stat .v { display: block; font-size: 1.05rem; font-weight: 750; color: var(--val); text-align: center; font-variant-numeric: tabular-nums; word-break: break-word; }
.stat .v.teal { color: #5eead4; }
.stat .v.sm { font-size: 0.92rem; }
.stats-foot { margin: 0.75rem 0 0; font-size: 0.72rem; color: var(--muted); text-align: center; }
.stats-foot.err { color: #f0a090; }
.stats-run-note {
margin: 0.35rem 0 0; font-size: 0.7rem; color: #f0a090; text-align: center;
}
.sub-block {
margin-top: 0.85rem; padding: 0.75rem 0.85rem;
border-radius: 12px; border: 1px solid rgba(232, 168, 56, 0.22);
background: rgba(0,0,0,0.18);
}
.sub-block h3 {
margin: 0 0 0.5rem; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--muted); text-align: center;
}
.ladder {
display: flex; flex-wrap: wrap; gap: 0.35rem; justify-content: center;
}
.pill {
font-size: 0.72rem; font-weight: 700; padding: 0.18rem 0.45rem; border-radius: 999px;
border: 1px solid rgba(62, 207, 191, 0.35); color: #99f6e4;
background: rgba(62, 207, 191, 0.1); font-variant-numeric: tabular-nums;
}
.by-denom { font-size: 0.82rem; color: var(--muted); text-align: center; }
.by-denom table { width: 100%; border-collapse: collapse; margin-top: 0.35rem; }
.by-denom th, .by-denom td {
padding: 0.28rem 0.35rem; border-bottom: 1px solid rgba(255,255,255,0.06);
text-align: center; font-variant-numeric: tabular-nums;
}
.by-denom th { color: var(--muted); font-size: 0.68rem; text-transform: uppercase; font-weight: 700; }
.by-denom td { color: var(--val); font-weight: 650; }
.step-card {
background: var(--card); border: 1px solid var(--border); border-radius: 18px;
padding: 1.3rem 1.25rem; margin-bottom: 1.2rem;
}
.step-card h2 { margin: 0 0 0.4rem; text-align: center; font-size: 1.2rem; }
.step-tagline { text-align: center; color: var(--muted); margin: 0 0 1rem; }
.step-body { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
.qr-box { background: #fff; padding: 0.85rem; border-radius: 14px; text-align: center; }
.qr-label { margin-top: 0.5rem; color: #3a342c; font-weight: 700; font-size: 0.85rem; }
.cta {
display: inline-block; text-decoration: none; font-weight: 750; padding: 0.7rem 1.15rem;
border-radius: 12px; background: linear-gradient(135deg, #f0c86a, #c47a18); color: #1a1208;
}
.cta.teal { background: linear-gradient(135deg, #5eead4, #0d9488); color: #042f2e; }
.links { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.cta-sec {
display: block; text-align: center; text-decoration: none; color: var(--accent2);
border: 1px solid rgba(62, 207, 191, 0.4); padding: 0.55rem; border-radius: 10px; font-weight: 600;
}
.meta { font-size: 0.78rem; color: var(--muted); word-break: break-all; text-align: center; }
footer { margin-top: 1.75rem; text-align: center; color: var(--muted); font-size: 0.85rem; }
footer a { color: #e8c878; }
code { font-family: ui-monospace, Menlo, monospace; font-size: 0.86em; background: rgba(0,0,0,0.35); padding: 0.1em 0.35em; border-radius: 4px; color: #f0d090; }
</style>
<script src="/intro/qrcode.min.js"></script>
</head>
<body>
<main>
<header class="hero">
<span class="badge">GOA · Exchange</span>
<h1>GOA Exchange</h1>
<p class="sub">Explorational intergalactic exchange — <strong>coins</strong>, denominations, withdraw / refresh / deposit.</p>
</header>
<section class="stats" id="stats-coins" aria-labelledby="coins-title">
<h2 id="coins-title">Coins</h2>
<p class="note">From exchange DB · coin lifecycle</p>
<div class="stats-grid">
<div class="stat"><span class="k">Known coins</span><span class="v teal" id="st-coins"></span></div>
<div class="stat"><span class="k">Live (remaining)</span><span class="v" id="st-coins-live"></span></div>
<div class="stat"><span class="k">Spent / zero</span><span class="v" id="st-coins-spent"></span></div>
<div class="stat"><span class="k">Remaining amt</span><span class="v teal sm" id="st-remaining"></span></div>
<div class="stat"><span class="k">Withdraw ops</span><span class="v" id="st-withdraw"></span></div>
<div class="stat"><span class="k">Withdraw amt</span><span class="v teal sm" id="st-withdraw-amt"></span></div>
<div class="stat"><span class="k">Refresh</span><span class="v" id="st-refresh"></span></div>
<div class="stat"><span class="k">Coin deposits</span><span class="v" id="st-coin-dep"></span></div>
<div class="stat"><span class="k">Batch deposits</span><span class="v" id="st-batch-dep"></span></div>
<div class="stat"><span class="k">Recoup</span><span class="v" id="st-recoup"></span></div>
<div class="stat"><span class="k">Refunds</span><span class="v" id="st-refunds"></span></div>
<div class="stat"><span class="k">History events</span><span class="v" id="st-hist"></span></div>
</div>
<div class="sub-block" id="by-denom-wrap" hidden>
<h3>Known coins by value</h3>
<div class="by-denom" id="st-by-denom"></div>
</div>
</section>
<section class="stats" id="stats-denoms" aria-labelledby="denoms-title">
<h2 id="denoms-title">Denominations</h2>
<p class="note">Signed keys in DB · public ladder on <code>/keys</code></p>
<div class="stats-grid">
<div class="stat"><span class="k">Denom keys</span><span class="v teal" id="st-denoms"></span></div>
<div class="stat"><span class="k">Unique values</span><span class="v" id="st-denom-vals"></span></div>
<div class="stat"><span class="k">Withdrawable now</span><span class="v teal" id="st-denoms-wd"></span></div>
</div>
<div class="sub-block">
<h3>Value ladder</h3>
<div class="ladder" id="st-ladder"></div>
</div>
</section>
<section class="stats" id="stats-wire" aria-labelledby="wire-title">
<h2 id="wire-title">Reserves · wire</h2>
<p class="note">Backing money movement</p>
<div class="stats-grid">
<div class="stat"><span class="k">Reserves</span><span class="v teal" id="st-reserves"></span></div>
<div class="stat"><span class="k">Wire in</span><span class="v" id="st-wire-in-n"></span></div>
<div class="stat"><span class="k">Wire in amt</span><span class="v teal sm" id="st-wire-in-amt"></span></div>
<div class="stat"><span class="k">Wire out</span><span class="v" id="st-wire-out"></span></div>
</div>
<p class="stats-foot" id="st-foot">Loading…</p>
<p class="stats-run-note" id="st-run-note" hidden></p>
</section>
<section class="stats" id="stats-perf" aria-labelledby="perf-title">
<h2 id="perf-title">Performance</h2>
<p class="note">Live HTTP probes + container memory (RSS)</p>
<div class="stats-grid">
<div class="stat"><span class="k">/keys</span><span class="v teal" id="st-keys-ms"></span></div>
<div class="stat"><span class="k">/config</span><span class="v" id="st-config-ms"></span></div>
<div class="stat"><span class="k">Loadavg</span><span class="v sm" id="st-load"></span></div>
<div class="stat"><span class="k">Container</span><span class="v teal" id="st-mem-ctr"></span></div>
<div class="stat"><span class="k">PostgreSQL</span><span class="v" id="st-mem-pg"></span></div>
<div class="stat"><span class="k">Taler procs</span><span class="v" id="st-mem-taler"></span></div>
<div class="stat"><span class="k">Nginx</span><span class="v sm" id="st-mem-nginx"></span></div>
</div>
<div class="sub-block" style="margin-top:0.85rem">
<h3>Top processes (RSS)</h3>
<div id="st-mem-top" class="by-denom"></div>
</div>
<p class="stats-foot" id="st-perf-foot">Memory from /proc + cgroup inside podman container.</p>
</section>
<section class="step-card">
<h2>Add the exchange</h2>
<p class="step-tagline">Trust <code>exchange.hacktivism.ch</code></p>
<div class="step-body">
<div class="qr-box">
<div id="qr-exchange"></div>
<div class="qr-label">Add exchange</div>
</div>
<a class="cta" href="taler://add-exchange/exchange.hacktivism.ch/">Open in wallet →</a>
<p class="meta">taler://add-exchange/exchange.hacktivism.ch/</p>
<div class="links">
<a class="cta teal" href="https://bank.hacktivism.ch/intro/">Withdraw GOA at bank →</a>
<a class="cta-sec" href="https://exchange.hacktivism.ch/keys">/keys</a>
<a class="cta-sec" href="https://exchange.hacktivism.ch/terms">/terms</a>
<a class="cta-sec" href="https://exchange.hacktivism.ch/privacy">/privacy</a>
<a class="cta-sec" href="https://exchange.hacktivism.ch/config">/config</a>
</div>
</div>
</section>
<footer>
<a href="https://bank.hacktivism.ch/intro/">Bank</a> ·
<a href="https://exchange.hacktivism.ch/intro/">Exchange</a> ·
<a href="https://taler.hacktivism.ch/intro/">Merchant</a> ·
<a href="https://exchange.hacktivism.ch/terms">Terms</a> ·
<a href="https://exchange.hacktivism.ch/privacy">Privacy</a>
<p class="privacy-note" style="margin:0.65rem 0 0;font-size:0.78rem;color:var(--muted);line-height:1.4">
Privacy: processing under Swiss FADP (revDSG). What we retain is listed on
<a href="https://exchange.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p>
</footer>
</main>
<script>
(function () {
function set(id, v) {
var el = document.getElementById(id);
if (el) el.textContent = v == null || v === "" ? "—" : String(v);
}
function msLabel(ms, http) {
if (ms == null || ms === "") return "—";
var s = ms + " ms";
if (http && http !== "200") s += " (" + http + ")";
return s;
}
fetch("/intro/stats.json", { cache: "no-store" })
.then(function (r) { if (!r.ok) throw new Error("x"); return r.json(); })
.then(function (d) {
if (!d || !d.ok) throw new Error("bad");
set("st-coins", d.known_coins);
set("st-coins-live", d.coins_live);
set("st-coins-spent", d.coins_spent);
set("st-remaining", d.coins_remaining_amount);
set("st-withdraw", d.withdraw_ops);
set("st-withdraw-amt", d.withdraw_amount);
set("st-refresh", d.refresh_ops);
set("st-coin-dep", d.coin_deposits);
set("st-batch-dep", d.batch_deposits);
set("st-recoup", d.recoup);
set("st-refunds", d.refunds);
set("st-hist", d.coin_history_events);
set("st-denoms", d.denominations);
set("st-denom-vals", d.denom_values);
set("st-denoms-wd", d.denoms_withdrawable);
set("st-reserves", d.reserves);
set("st-wire-in-n", d.wire_in_count);
set("st-wire-in-amt", d.wire_in_amount);
set("st-wire-out", d.wire_out);
var ladder = document.getElementById("st-ladder");
if (ladder) {
ladder.innerHTML = "";
(d.denom_ladder || []).forEach(function (x) {
var s = document.createElement("span");
s.className = "pill";
s.textContent = x.value + (x.keys > 1 ? " ×" + x.keys : "");
ladder.appendChild(s);
});
if (!(d.denom_ladder || []).length) ladder.textContent = "—";
}
var by = d.by_denom || [];
var wrap = document.getElementById("by-denom-wrap");
var box = document.getElementById("st-by-denom");
if (wrap && box && by.length) {
wrap.hidden = false;
var html = "<table><thead><tr><th>Value</th><th>Coins</th><th>Live</th></tr></thead><tbody>";
by.forEach(function (r) {
html += "<tr><td>" + r.value + "</td><td>" + r.coins + "</td><td>" + r.live + "</td></tr>";
});
html += "</tbody></table>";
box.innerHTML = html;
}
var foot = document.getElementById("st-foot");
if (foot) {
foot.textContent = (d.wirewatch_running ? "wirewatch on · " : "wirewatch off · ") +
(d.generated_at_human || d.generated_at || "");
}
var p = d.performance || {};
set("st-keys-ms", msLabel(p.keys_ms, p.keys_http));
set("st-config-ms", msLabel(p.config_ms, p.config_http));
set("st-load", p.loadavg || "—");
var mem = p.memory || {};
set("st-mem-ctr", mem.container_rss_human || "—");
set("st-mem-pg", mem.postgres_rss_human
? mem.postgres_rss_human + (mem.postgres_n ? " · " + mem.postgres_n + "p" : "")
: "—");
set("st-mem-taler", mem.taler_rss_human
? mem.taler_rss_human + (mem.taler_n ? " · " + mem.taler_n + "p" : "")
: "—");
set("st-mem-nginx", mem.nginx_rss_human || "—");
var topBox = document.getElementById("st-mem-top");
if (topBox) {
var tops = mem.top || [];
if (!tops.length) {
topBox.textContent = "—";
} else {
var th = "<table><thead><tr><th>RSS</th><th>Comm</th><th>Command</th></tr></thead><tbody>";
tops.forEach(function (t) {
th += "<tr><td>" + (t.rss_human || "?") + "</td><td>" +
(t.comm || "") + "</td><td style=\"font-size:0.72rem;word-break:break-all\">" +
(t.cmd || "") + "</td></tr>";
});
th += "</tbody></table>";
topBox.innerHTML = th;
}
}
var pf = document.getElementById("st-perf-foot");
if (pf) {
pf.textContent = "Container RSS + process groups · " +
(d.generated_at_human || d.generated_at || "");
}
return fetch("/intro/stats-run.json", { cache: "no-store" })
.then(function (r) { return r.ok ? r.json() : null; })
.catch(function () { return null; });
})
.then(function (run) {
var note = document.getElementById("st-run-note");
if (!note) return;
if (run && run.ok === false) {
note.hidden = false;
note.textContent = "Last refresh failed" +
(run.at_human ? " · " + run.at_human : "") +
" · showing last good data";
} else {
note.hidden = true;
note.textContent = "";
}
})
.catch(function () {
var foot = document.getElementById("st-foot");
if (foot) foot.innerHTML = 'Stats offline · <a href="https://bank.hacktivism.ch/intro/" style="color:#e8c878">bank.hacktivism.ch</a>';
});
var el = document.getElementById("qr-exchange");
if (el && typeof QRCode !== "undefined") {
new QRCode(el, {
text: "taler://add-exchange/exchange.hacktivism.ch/",
width: 220, height: 220, correctLevel: QRCode.CorrectLevel.M
});
}
})();
</script>
</body>
</html>