bank: English landing with taler withdraw QR
This commit is contained in:
parent
faba198081
commit
58ddcfe723
3 changed files with 986 additions and 0 deletions
816
configs/bank-landing/index.html
Normal file
816
configs/bank-landing/index.html
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GOA Exploration Bank</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1a1410;
|
||||
--card: rgba(42, 32, 24, 0.82);
|
||||
--text: #fff6e8;
|
||||
--muted: #c9b8a0;
|
||||
--accent: #e8a838;
|
||||
--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);
|
||||
}
|
||||
* { 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%),
|
||||
radial-gradient(circle 380px at 88% 12%, var(--glow-teal) 0%, transparent 58%),
|
||||
radial-gradient(circle 280px at 70% 70%, rgba(196, 122, 24, 0.18) 0%, transparent 60%),
|
||||
linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.07;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
}
|
||||
main {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 38rem;
|
||||
margin: 0 auto;
|
||||
padding: 2.25rem 1.25rem 3.5rem;
|
||||
}
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin: 0 0 1.75rem;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.85rem;
|
||||
font-weight: 750;
|
||||
margin: 0 0 0.45rem;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
background: linear-gradient(120deg, #fff6e8 0%, #f0c86a 45%, #3ecfbf 120%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
.sub {
|
||||
color: var(--muted);
|
||||
margin: 0 auto;
|
||||
max-width: 28rem;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, rgba(232, 168, 56, 0.2), rgba(62, 207, 191, 0.15));
|
||||
color: #f0d090;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 0.2rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
margin: 0 0 0.85rem;
|
||||
border: 1px solid rgba(232, 168, 56, 0.4);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Big step cards */
|
||||
.step-card {
|
||||
background: var(--card);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
padding: 1.4rem 1.35rem 1.5rem;
|
||||
margin-bottom: 1.35rem;
|
||||
box-shadow:
|
||||
0 14px 42px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 246, 232, 0.06);
|
||||
}
|
||||
.step-card.step-money {
|
||||
border-color: rgba(62, 207, 191, 0.4);
|
||||
}
|
||||
.step-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.1rem;
|
||||
height: 2.1rem;
|
||||
border-radius: 50%;
|
||||
font-weight: 800;
|
||||
font-size: 1.15rem;
|
||||
margin-right: 0.55rem;
|
||||
vertical-align: middle;
|
||||
color: #1a1208;
|
||||
background: linear-gradient(135deg, #f0c86a, #c47a18);
|
||||
box-shadow: 0 4px 14px var(--glow-gold);
|
||||
}
|
||||
.step-card.step-money .step-num {
|
||||
background: linear-gradient(135deg, #5eead4, #0d9488);
|
||||
color: #042f2e;
|
||||
box-shadow: 0 4px 14px var(--glow-teal);
|
||||
}
|
||||
.step-card h2 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 800;
|
||||
margin: 0 0 0.4rem;
|
||||
letter-spacing: -0.01em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.15rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
.step-tagline {
|
||||
margin: 0 0 1.15rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.98rem;
|
||||
text-align: center;
|
||||
}
|
||||
.step-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.1rem;
|
||||
}
|
||||
.step-body ol {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
width: 100%;
|
||||
align-self: stretch;
|
||||
}
|
||||
.step-body li { margin: 0.4rem 0; }
|
||||
.qr-box {
|
||||
background: #fff;
|
||||
padding: 0.85rem;
|
||||
border-radius: 14px;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.qr-box img, .qr-box canvas, .qr-box svg {
|
||||
display: block;
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.qr-label {
|
||||
margin-top: 0.6rem;
|
||||
font-size: 0.85rem;
|
||||
color: #3a342c;
|
||||
font-weight: 700;
|
||||
max-width: 220px;
|
||||
}
|
||||
.cta {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background: linear-gradient(135deg, #f0c86a 0%, #e8a838 40%, #c47a18 100%);
|
||||
color: #1a1208;
|
||||
font-weight: 750;
|
||||
font-size: 1rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 22px var(--glow-gold);
|
||||
}
|
||||
.cta:hover { filter: brightness(1.06); }
|
||||
.cta.teal {
|
||||
background: linear-gradient(135deg, #5eead4 0%, #14b8a6 50%, #0d9488 100%);
|
||||
color: #042f2e;
|
||||
box-shadow: 0 8px 22px var(--glow-teal);
|
||||
}
|
||||
.meta {
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
word-break: break-all;
|
||||
margin: 0.5rem 0 0;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
#status {
|
||||
min-height: 1.1em;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
margin: 0.4rem 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
#status.err { color: #f0a090; }
|
||||
#status.ok { color: var(--accent2); }
|
||||
code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.86em;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 4px;
|
||||
color: #f0d090;
|
||||
}
|
||||
|
||||
/* Distinct “more options” section further down */
|
||||
.more-section {
|
||||
margin-top: 2.75rem;
|
||||
padding-top: 1.75rem;
|
||||
border-top: 1px dashed rgba(201, 184, 160, 0.35);
|
||||
}
|
||||
.more-section > .more-label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.more-card {
|
||||
background: rgba(18, 28, 28, 0.65);
|
||||
border: 1px dashed rgba(62, 207, 191, 0.35);
|
||||
border-radius: 16px;
|
||||
padding: 1.25rem 1.2rem 1.4rem;
|
||||
}
|
||||
.more-card h2 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5rem;
|
||||
color: #d8ebe8;
|
||||
}
|
||||
.more-card p {
|
||||
margin: 0 0 0.85rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.more-card ol {
|
||||
margin: 0 0 1rem;
|
||||
padding-left: 1.2rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.more-card li { margin: 0.35rem 0; }
|
||||
.more-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
.cta-sec {
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--accent2);
|
||||
border: 1px solid rgba(62, 207, 191, 0.4);
|
||||
padding: 0.6rem 0.9rem;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.cta-sec.merchant {
|
||||
color: #f0d090;
|
||||
border-color: rgba(232, 168, 56, 0.35);
|
||||
}
|
||||
.more-qr {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0 0.25rem;
|
||||
}
|
||||
.more-qr .qr-box {
|
||||
transform: scale(0.92);
|
||||
transform-origin: center top;
|
||||
opacity: 0.95;
|
||||
}
|
||||
/* Live stats (written by in-container landing-stats.sh → stats.json) */
|
||||
.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 1.2rem;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
.stats h2 {
|
||||
margin: 0 0 0.85rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.65rem 0.85rem;
|
||||
}
|
||||
@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;
|
||||
min-width: 0;
|
||||
}
|
||||
.stat .k {
|
||||
display: block;
|
||||
font-size: 0.68rem;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.stat .v {
|
||||
display: block;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 750;
|
||||
color: #f0d090;
|
||||
word-break: break-word;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: center;
|
||||
}
|
||||
.stat .v.teal { color: #5eead4; }
|
||||
.stat .v.muted { color: var(--muted); font-size: 0.92rem; font-weight: 600; }
|
||||
.stat.wide { grid-column: 1 / -1; }
|
||||
.wd-list {
|
||||
list-style: none;
|
||||
margin: 0.15rem 0 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.wd-list li {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 0.25rem 0.75rem;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 650;
|
||||
color: #f0d090;
|
||||
font-variant-numeric: tabular-nums;
|
||||
border-bottom: 1px solid rgba(201, 184, 160, 0.12);
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
.wd-list li:last-child { border-bottom: none; padding-bottom: 0; }
|
||||
.wd-list .amt { color: #5eead4; font-weight: 750; }
|
||||
.wd-list .meta {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
footer {
|
||||
margin-top: 2rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
footer a { color: #e8c878; }
|
||||
</style>
|
||||
<base href="/intro/" />
|
||||
<script src="/intro/qrcode.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header class="hero">
|
||||
<span class="badge">Intergalactic · GOA · no IBAN</span>
|
||||
<h1>GOA Exploration Bank</h1>
|
||||
<p class="sub">
|
||||
Two steps to put <strong>GOA</strong> in your GNU Taler wallet.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- ========== LIVE STATS (stats.json from bank container) ========== -->
|
||||
<section class="stats" id="stats" aria-labelledby="stats-title" hidden>
|
||||
<h2 id="stats-title">GOA flow</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<span class="k">Accounts</span>
|
||||
<span class="v teal" id="st-accounts">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">Bank credits</span>
|
||||
<span class="v teal" id="st-incoming">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">Withdraws</span>
|
||||
<span class="v" id="st-withdraw">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">Unique wallets</span>
|
||||
<span class="v teal" id="st-wallets">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">24h</span>
|
||||
<span class="v" id="st-24h">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">7d</span>
|
||||
<span class="v" id="st-7d">—</span>
|
||||
</div>
|
||||
<div class="stat wide">
|
||||
<span class="k">Recent Withdraws</span>
|
||||
<ul class="wd-list" id="st-recent" aria-live="polite">
|
||||
<li class="meta">…</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stats-foot" id="st-foot" hidden></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" style="text-align:center;font-size:0.82rem;color:var(--muted);margin:0 0 0.85rem">
|
||||
Live HTTP probes + container memory (RSS)
|
||||
</p>
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<span class="k">/config</span>
|
||||
<span class="v teal" id="st-config-ms">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">/taler-integration/config</span>
|
||||
<span class="v" id="st-int-ms">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">/webui/</span>
|
||||
<span class="v" id="st-webui-ms">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">Loadavg</span>
|
||||
<span class="v" 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">Java / libeufin</span>
|
||||
<span class="v" id="st-mem-java">—</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="k">Nginx</span>
|
||||
<span class="v" id="st-mem-nginx">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:0.85rem;overflow-x:auto">
|
||||
<p class="note" style="text-align:center;margin-bottom:0.4rem">Top processes (RSS)</p>
|
||||
<div id="st-mem-top"></div>
|
||||
</div>
|
||||
<p class="stats-foot" id="st-perf-foot">Memory from /proc + cgroup inside podman container.</p>
|
||||
</section>
|
||||
|
||||
<!-- ========== STEP 1 ========== -->
|
||||
<section class="step-card" aria-labelledby="step1-title">
|
||||
<h2 id="step1-title"><span class="step-num">1</span> Add the exchange</h2>
|
||||
<p class="step-tagline">Tell the wallet where GOA coins come from.</p>
|
||||
<div class="step-body">
|
||||
<ol>
|
||||
<li>Install the <strong>GNU Taler Wallet</strong> (phone app or browser extension).</li>
|
||||
<li>Scan the QR with the wallet, or tap the button.</li>
|
||||
<li>Confirm / trust <code>exchange.hacktivism.ch</code>.</li>
|
||||
</ol>
|
||||
<div class="qr-box">
|
||||
<div id="qr-exchange"></div>
|
||||
<div class="qr-label">Add exchange in wallet</div>
|
||||
</div>
|
||||
<a class="cta" id="open-exchange" href="taler://add-exchange/exchange.hacktivism.ch/">
|
||||
Open in wallet app →
|
||||
</a>
|
||||
<p class="meta">taler://add-exchange/exchange.hacktivism.ch/</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== STEP 2 ========== -->
|
||||
<section class="step-card step-money" aria-labelledby="step2-title">
|
||||
<h2 id="step2-title"><span class="step-num">2</span> Get the money</h2>
|
||||
<p class="step-tagline">Withdraw GOA into the wallet you just set up.</p>
|
||||
<div class="step-body">
|
||||
<ol>
|
||||
<li>Scan this second QR with the <strong>same wallet</strong>.</li>
|
||||
<li>Default amount is <code>GOA:10</code> — confirm in the wallet.</li>
|
||||
<li>If the wallet waits on a bank transfer: leave it open a moment — demo withdrawals are auto-confirmed.</li>
|
||||
</ol>
|
||||
<div class="qr-box">
|
||||
<div id="qr-withdraw"></div>
|
||||
<div class="qr-label" id="qr-withdraw-label">Withdraw GOA:10</div>
|
||||
</div>
|
||||
<a class="cta teal" id="open-wallet" href="#">Open withdraw in wallet →</a>
|
||||
<p id="status">Preparing withdraw QR…</p>
|
||||
<p class="meta" id="uri-line" hidden></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== FURTHER DOWN: own account ========== -->
|
||||
<div class="more-section">
|
||||
<span class="more-label">Optional · own bank account</span>
|
||||
<div class="more-card">
|
||||
<h2>Need your own balance or merchant setup?</h2>
|
||||
<p>
|
||||
The two steps above use a <strong>demo withdraw</strong> prepared by the bank.
|
||||
For your own GOA balance, repeated withdraws, or receiving payments as a merchant,
|
||||
create an account:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Register in the bank UI (username + password).</li>
|
||||
<li>Get a credit (transfer / ops) — new accounts start at <code>GOA:0</code>.</li>
|
||||
<li>Log in → <strong>Withdraw</strong> (wallet still needs the exchange from step 1).</li>
|
||||
<li>To accept payments: set up a shop at
|
||||
<a href="https://taler.hacktivism.ch/" style="color:#f0c86a">taler.hacktivism.ch</a>.</li>
|
||||
</ol>
|
||||
<div class="more-qr">
|
||||
<div class="qr-box">
|
||||
<div id="qr-webui"></div>
|
||||
<div class="qr-label">Bank UI · register / login</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-actions">
|
||||
<a class="cta-sec" href="/webui/">Open bank UI (register / login) →</a>
|
||||
<a class="cta-sec merchant" href="https://taler.hacktivism.ch/">Merchant backend →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
hacktivism.ch ·
|
||||
<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://bank.hacktivism.ch/terms">Terms</a> ·
|
||||
<a href="https://bank.hacktivism.ch/privacy">Privacy</a> ·
|
||||
<a href="/config">/config</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 is retained is listed on
|
||||
<a href="https://bank.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var WEBUI = "https://bank.hacktivism.ch/webui/";
|
||||
var ADD_EXCHANGE = "taler://add-exchange/exchange.hacktivism.ch/";
|
||||
var statusEl = document.getElementById("status");
|
||||
var uriLine = document.getElementById("uri-line");
|
||||
var openWallet = document.getElementById("open-wallet");
|
||||
var label = document.getElementById("qr-withdraw-label");
|
||||
|
||||
function setStatus(msg, cls) {
|
||||
if (!statusEl) return;
|
||||
statusEl.textContent = msg;
|
||||
statusEl.className = cls || "";
|
||||
}
|
||||
|
||||
function makeQr(el, text) {
|
||||
if (!el) return;
|
||||
el.innerHTML = "";
|
||||
if (typeof QRCode === "undefined") {
|
||||
el.textContent = "QR lib missing";
|
||||
return;
|
||||
}
|
||||
new QRCode(el, {
|
||||
text: text,
|
||||
width: 220,
|
||||
height: 220,
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
}
|
||||
|
||||
makeQr(document.getElementById("qr-exchange"), ADD_EXCHANGE);
|
||||
makeQr(document.getElementById("qr-webui"), WEBUI);
|
||||
|
||||
var defaultAmount = "GOA:10";
|
||||
|
||||
function strip443(uri) {
|
||||
if (!uri) return uri;
|
||||
return uri.replace(/bank\.hacktivism\.ch:443/g, "bank.hacktivism.ch");
|
||||
}
|
||||
|
||||
function useUri(uri, amount) {
|
||||
if (!uri) return false;
|
||||
uri = strip443(uri.trim());
|
||||
var amt = amount || defaultAmount;
|
||||
makeQr(document.getElementById("qr-withdraw"), uri);
|
||||
if (openWallet) openWallet.href = uri;
|
||||
if (uriLine) {
|
||||
uriLine.hidden = false;
|
||||
uriLine.textContent = uri;
|
||||
}
|
||||
if (label) label.textContent = "Withdraw " + amt;
|
||||
setStatus("Ready — default " + amt + ". Scan with the wallet (after step 1).", "ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
function fmtCest(unix, fallback) {
|
||||
var base = "";
|
||||
if (fallback) {
|
||||
// strip trailing CEST/CET/Z if present; we append Europe/Zurich
|
||||
base = String(fallback).replace(/\s*(CEST|CET|UTC|Z)\s*$/i, "").trim();
|
||||
} else if (unix != null && unix !== "") {
|
||||
try {
|
||||
base = new Intl.DateTimeFormat("de-CH", {
|
||||
timeZone: "Europe/Zurich",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false
|
||||
}).format(new Date(Number(unix) * 1000));
|
||||
} catch (e) {
|
||||
base = "";
|
||||
}
|
||||
}
|
||||
return base ? base + " Europe/Zurich" : "";
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
var box = document.getElementById("stats");
|
||||
var foot = document.getElementById("st-foot");
|
||||
if (!box) return;
|
||||
box.hidden = false;
|
||||
fetch("stats.json", { cache: "no-store" })
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function (d) {
|
||||
if (!d || !d.ok) throw new Error((d && d.error) || "stats not ok");
|
||||
var w = d.withdraws || {};
|
||||
var h24 = w.last_24h || {};
|
||||
var h7 = w.last_7d || {};
|
||||
var ba = d.bank_accounts || {};
|
||||
var wl = d.wallets || {};
|
||||
function set(id, v) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.textContent = v == null || v === "" ? "—" : String(v);
|
||||
}
|
||||
var flow = d.flow || {};
|
||||
var fin = flow.incoming || {};
|
||||
var fwd = flow.withdraw || {};
|
||||
set("st-accounts", ba.total != null ? ba.total : "—");
|
||||
set("st-wallets", wl.unique_reserves != null ? wl.unique_reserves : "—");
|
||||
set("st-incoming", fin.amount || flow.total_in || "—");
|
||||
set("st-withdraw", fwd.amount || w.total_amount || "—");
|
||||
set("st-24h", h24.amount || "GOA:0");
|
||||
set("st-7d", h7.amount || "GOA:0");
|
||||
|
||||
var list = document.getElementById("st-recent");
|
||||
if (list) {
|
||||
list.innerHTML = "";
|
||||
var rows = (d.recent_withdraws || []).slice(0, 3);
|
||||
if (!rows.length) {
|
||||
var empty = document.createElement("li");
|
||||
empty.className = "meta";
|
||||
empty.textContent = "—";
|
||||
list.appendChild(empty);
|
||||
} else {
|
||||
rows.forEach(function (row) {
|
||||
var li = document.createElement("li");
|
||||
var amt = document.createElement("span");
|
||||
amt.className = "amt";
|
||||
amt.textContent = row.amount || "?";
|
||||
var meta = document.createElement("span");
|
||||
meta.className = "meta";
|
||||
meta.textContent = fmtCest(row.at_unix, row.at || row.at_iso) || "";
|
||||
li.appendChild(amt);
|
||||
li.appendChild(meta);
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (foot) {
|
||||
foot.hidden = false;
|
||||
foot.className = "stats-foot";
|
||||
foot.textContent = d.generated_at_human || d.generated_at || "";
|
||||
}
|
||||
function msLabel(ms, http) {
|
||||
if (ms == null || ms === "") return "—";
|
||||
var s = ms + " ms";
|
||||
if (http && http !== "200") s += " (" + http + ")";
|
||||
return s;
|
||||
}
|
||||
var p = d.performance || {};
|
||||
set("st-config-ms", msLabel(p.config_ms, p.config_http));
|
||||
set("st-int-ms", msLabel(p.integration_ms, p.integration_http));
|
||||
set("st-webui-ms", msLabel(p.webui_ms, p.webui_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-java", mem.java_rss_human
|
||||
? mem.java_rss_human + (mem.java_n ? " · " + mem.java_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.innerHTML = '<p class="meta">—</p>';
|
||||
} else {
|
||||
var th = '<table style="width:100%;font-size:0.78rem;border-collapse:collapse"><thead><tr>' +
|
||||
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">RSS</th>' +
|
||||
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">Comm</th>' +
|
||||
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">Command</th></tr></thead><tbody>';
|
||||
tops.forEach(function (t) {
|
||||
th += '<tr><td style="padding:0.25rem;color:#5eead4">' + (t.rss_human || "?") +
|
||||
'</td><td style="padding:0.25rem">' + (t.comm || "") +
|
||||
'</td><td style="padding:0.25rem;word-break:break-all;color:var(--muted)">' +
|
||||
(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("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 (e) {
|
||||
if (foot) {
|
||||
foot.hidden = false;
|
||||
foot.className = "stats-foot err";
|
||||
foot.textContent = "Stats offline";
|
||||
}
|
||||
});
|
||||
}
|
||||
loadStats();
|
||||
|
||||
Promise.all([
|
||||
fetch("withdraw.uri", { cache: "no-store" }).then(function (r) {
|
||||
return r.ok ? r.text() : Promise.reject();
|
||||
}),
|
||||
fetch("withdraw.amount", { cache: "no-store" }).then(function (r) {
|
||||
return r.ok ? r.text() : Promise.resolve(defaultAmount);
|
||||
}).catch(function () { return defaultAmount; }),
|
||||
fetch("stats.json", { cache: "no-store" }).then(function (r) {
|
||||
return r.ok ? r.json() : null;
|
||||
}).catch(function () { return null; })
|
||||
])
|
||||
.then(function (parts) {
|
||||
var uri = (parts[0] || "").trim();
|
||||
var amt = (parts[1] || defaultAmount).trim() || defaultAmount;
|
||||
var st = parts[2];
|
||||
if (st && st.demo && st.demo.ready === false) {
|
||||
// Spent demo withdraw — prefer bank UI path
|
||||
fallback("Demo withdraw already used (" + (st.demo.status || "?") + ") — open bank UI below or wait for ops refresh.");
|
||||
return;
|
||||
}
|
||||
if (useUri(uri, amt)) return;
|
||||
fallback();
|
||||
})
|
||||
.catch(function () { fallback(); });
|
||||
|
||||
function fallback(msg) {
|
||||
makeQr(document.getElementById("qr-withdraw"), WEBUI);
|
||||
if (openWallet) openWallet.href = WEBUI;
|
||||
if (label) label.textContent = "Bank UI (no demo withdraw)";
|
||||
setStatus(msg || "No demo withdraw right now — use optional bank account below.", "err");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue