landing: kGOA units, full money stats, rootless resource display

Public bank/exchange/merchant landing stats and performance UI only.
Secret goa-ui overlay stays on local/merchant-secret-ui (never push).
This commit is contained in:
Hernâni Marques 2026-07-17 13:30:23 +02:00
parent bf8a915a01
commit fb1a0b0b8e
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
12 changed files with 1046 additions and 333 deletions

View file

@ -995,6 +995,27 @@
color: #f0a090;
text-align: center;
}
.unit-legend {
margin: 0.65rem auto 0;
border-collapse: collapse;
font-size: 0.68rem;
color: var(--muted);
width: auto;
max-width: 100%;
}
.unit-legend th, .unit-legend td {
padding: 0.12rem 0.45rem;
border-bottom: 1px solid rgba(201, 184, 160, 0.12);
text-align: left;
}
.unit-legend th { font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; font-size: 0.62rem; }
.unit-legend strong { color: #5eead4; font-weight: 750; }
.unit-legend-note {
margin: 0.35rem 0 0;
font-size: 0.68rem;
color: var(--muted);
text-align: center;
}
footer {
margin-top: 2rem;
color: var(--muted);
@ -1392,39 +1413,67 @@ tw run-until-done &amp;&amp; tw balance</pre>
<!-- ========== LIVE STATS (before performance) ========== -->
<section class="stats" id="stats" aria-labelledby="stats-title" hidden>
<h2 id="stats-title">GOA flow</h2>
<h2 id="stats-title">GOA flow · all money</h2>
<div class="stats-grid">
<div class="stat">
<span class="k">All money (explorer)</span>
<span class="v teal" id="st-all-money"></span>
</div>
<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="k">All credits (in)</span>
<span class="v teal" id="st-incoming"></span>
</div>
<div class="stat">
<span class="k">Credits count</span>
<span class="v" id="st-incoming-n"></span>
</div>
<div class="stat">
<span class="k">Latest credit</span>
<span class="v sm" id="st-incoming-last"></span>
</div>
<div class="stat">
<span class="k">All withdraws</span>
<span class="v" id="st-withdraw"></span>
</div>
<div class="stat">
<span class="k">Withdraw count</span>
<span class="v" id="st-withdraw-n"></span>
</div>
<div class="stat">
<span class="k">Latest withdraw</span>
<span class="v sm" id="st-withdraw-last"></span>
</div>
<div class="stat">
<span class="k">Withdraws 24h</span>
<span class="v" id="st-24h"></span>
</div>
<div class="stat">
<span class="k">7d</span>
<span class="k">Withdraws 7d</span>
<span class="v" id="st-7d"></span>
</div>
<div class="stat wide">
<span class="k">Recent withdraws (10)</span>
<span class="k">Latest withdraws</span>
<ul class="wd-list" id="st-recent" aria-live="polite">
<li class="meta"></li>
</ul>
</div>
<div class="stat wide">
<span class="k">Latest credits (incoming)</span>
<ul class="wd-list" id="st-recent-in" aria-live="polite">
<li class="meta"></li>
</ul>
</div>
</div>
<p class="unit-legend-note">Large GOA → kGOA / MGOA / … (hover for exact GOA:n)</p>
<div id="st-unit-legend"></div>
<p class="stats-foot" id="st-foot" hidden></p>
<p class="stats-run-note" id="st-run-note" hidden></p>
</section>
@ -1448,11 +1497,19 @@ tw run-until-done &amp;&amp; tw balance</pre>
<span class="v" id="st-webui-ms"></span>
</div>
<div class="stat">
<span class="k">Loadavg</span>
<span class="k">CPU (container)</span>
<span class="v teal" id="st-cpu"></span>
</div>
<div class="stat">
<span class="k">Loadavg (host)</span>
<span class="v" id="st-load"></span>
</div>
<div class="stat">
<span class="k">Container</span>
<span class="k">PIDs</span>
<span class="v" id="st-pids"></span>
</div>
<div class="stat">
<span class="k">Container RSS</span>
<span class="v teal" id="st-mem-ctr"></span>
</div>
<div class="stat">
@ -1469,10 +1526,10 @@ tw run-until-done &amp;&amp; tw balance</pre>
</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>
<p class="note" style="text-align:center;margin-bottom:0.4rem">Top processes (RSS inside container)</p>
<div id="st-mem-top"></div>
</div>
<p class="stats-foot" id="st-perf-foot">Memory from /proc + cgroup inside podman container.</p>
<p class="stats-foot" id="st-perf-foot">RSS from /proc inside container (no memory cgroup in rootless podman). CPU from podman stats.</p>
</section>
<footer>
@ -1985,46 +2042,65 @@ tw run-until-done &amp;&amp; tw balance</pre>
var fwd = flow.withdraw || {};
set("st-accounts", ba.total != null ? ba.total : "—");
set("st-wallets", wl.unique_reserves != null ? wl.unique_reserves : "—");
setAmt("st-incoming", fin.amount_alt ? fin : (fin.amount || flow.total_in || "—"));
setAmt("st-withdraw", fwd.amount_alt ? fwd : (fwd.amount || w.total_amount || "—"));
setAmt("st-24h", h24.amount_alt ? h24 : (h24.amount || "GOA:0"));
setAmt("st-7d", h7.amount_alt ? h7 : (h7.amount || "GOA:0"));
setAmt("shared-balance", d.balance_explorer_alt
setAmt("st-all-money", d.balance_explorer
? { amount: d.balance_explorer, amount_alt: d.balance_explorer_alt, amount_full: d.balance_explorer_full }
: (d.balance_explorer || "—"));
: "—");
setAmt("st-incoming", fin.amount ? fin : (flow.total_in || "—"));
set("st-incoming-n", fin.count != null ? fin.count : "—");
setAmt("st-withdraw", fwd.amount ? fwd : (w.total_amount ? { amount: w.total_amount, amount_alt: w.total_amount_alt } : "—"));
set("st-withdraw-n", (fwd.count != null ? fwd.count : w.count) != null ? (fwd.count != null ? fwd.count : w.count) : "—");
setAmt("st-withdraw-last", w.last_amount
? { amount: w.last_amount, amount_alt: w.last_amount_alt, amount_full: w.last_amount_full }
: "—");
setAmt("st-24h", h24.amount ? h24 : (h24.amount || "GOA:0"));
setAmt("st-7d", h7.amount ? h7 : (h7.amount || "GOA:0"));
setAmt("shared-balance", d.balance_explorer
? { amount: d.balance_explorer, amount_alt: d.balance_explorer_alt, amount_full: d.balance_explorer_full }
: "—");
var list = document.getElementById("st-recent");
if (list) {
function fillMoneyList(listId, rows, emptyLabel) {
var list = document.getElementById(listId);
if (!list) return;
list.innerHTML = "";
/* Full list from stats.json (generator emits 10) */
var rows = (d.recent_withdraws || []).slice(0, 10);
rows = rows || [];
if (!rows.length) {
var empty = document.createElement("li");
empty.className = "meta";
empty.textContent = "—";
empty.textContent = emptyLabel || "—";
list.appendChild(empty);
} else {
rows.forEach(function (row) {
var li = document.createElement("li");
var amt = document.createElement("span");
amt.className = "amt";
if (window.GoaAmount) {
var af = GoaAmount.pick(row, d.alt_unit_names);
amt.textContent = af.display;
if (af.title && af.title !== af.display) amt.setAttribute("title", af.title);
} else {
amt.textContent = row.amount_alt || row.amount || "?";
}
var meta = document.createElement("span");
meta.className = "meta";
var when = fmtCest(row.at_unix, row.at || row.at_iso) || "";
var who = row.account ? " · " + row.account : "";
meta.textContent = when + who;
li.appendChild(amt);
li.appendChild(meta);
list.appendChild(li);
});
return;
}
rows.forEach(function (row) {
var li = document.createElement("li");
var amt = document.createElement("span");
amt.className = "amt";
if (window.GoaAmount) {
var af = GoaAmount.pick(row, d.alt_unit_names);
amt.textContent = af.display;
if (af.title && af.title !== af.display) amt.setAttribute("title", af.title);
} else {
amt.textContent = row.amount_alt || row.amount || "?";
}
var meta = document.createElement("span");
meta.className = "meta";
var when = fmtCest(row.at_unix, row.at || row.at_iso) || "";
var who = row.account ? " · " + row.account : "";
meta.textContent = when + who;
li.appendChild(amt);
li.appendChild(meta);
list.appendChild(li);
});
}
fillMoneyList("st-recent", (d.recent_withdraws || []).slice(0, 10));
var recentIn = (d.recent_incoming || []).slice(0, 10);
fillMoneyList("st-recent-in", recentIn);
if (recentIn.length) {
setAmt("st-incoming-last", recentIn[0]);
} else {
set("st-incoming-last", "—");
}
if (window.GoaAmount && GoaAmount.fillLegend) {
GoaAmount.fillLegend("st-unit-legend", d);
}
if (foot) {
foot.hidden = false;
@ -2037,19 +2113,30 @@ tw run-until-done &amp;&amp; tw balance</pre>
if (http && http !== "200") s += " (" + http + ")";
return s;
}
function escHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;").replace(/</g, "&lt;")
.replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function setMem(id, human, n, bytes) {
var el = document.getElementById(id);
if (!el) return;
if (!human && human !== 0) {
var b = bytes != null && bytes !== "" ? Number(bytes) : NaN;
var nn = n != null && n !== "" ? Number(n) : NaN;
var empty =
human == null || human === "" || human === "—" ||
human === "0 B" || human === "0B" ||
(isFinite(b) && b <= 0 && (!isFinite(nn) || nn <= 0));
if (empty) {
el.textContent = "—";
el.removeAttribute("title");
return;
}
var h = String(human);
if (n != null && n !== "" && Number(n) > 0) h += " · " + n + "p";
if (isFinite(nn) && nn > 0) h += " · " + nn + "p";
el.textContent = h;
if (bytes != null && bytes !== "" && bytes !== 0) {
el.setAttribute("title", Number(bytes).toLocaleString("en-US") + " B");
if (isFinite(b) && b > 0) {
el.setAttribute("title", b.toLocaleString("en-US") + " B");
} else {
el.removeAttribute("title");
}
@ -2058,7 +2145,9 @@ tw run-until-done &amp;&amp; tw balance</pre>
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-cpu", p.cpu_percent_label || (p.cpu_percent != null ? (Number(p.cpu_percent).toFixed(2) + "%") : "—"));
set("st-load", p.loadavg || "—");
set("st-pids", p.pids != null ? p.pids : ((p.memory && p.memory.pids) != null ? p.memory.pids : "—"));
var mem = p.memory || {};
setMem("st-mem-ctr", mem.container_rss_label || mem.container_rss_human || "—", null, mem.container_rss_bytes);
setMem("st-mem-pg", mem.postgres_rss_human, mem.postgres_n, mem.postgres_rss_bytes);
@ -2079,12 +2168,12 @@ tw run-until-done &amp;&amp; tw balance</pre>
var cmdFull = t.cmd_full || cmd;
if (cmd.length > 64) cmd = cmd.slice(0, 61) + "…";
th += '<tr><td style="padding:0.25rem;color:#5eead4" title="' +
(t.rss_bytes != null ? Number(t.rss_bytes).toLocaleString("en-US") + " B" : "") + '">' +
(t.rss_human || "?") +
'</td><td style="padding:0.25rem">' + (t.comm || "") +
escHtml(t.rss_bytes != null ? Number(t.rss_bytes).toLocaleString("en-US") + " B" : "") + '">' +
escHtml(t.rss_human || "?") +
'</td><td style="padding:0.25rem">' + escHtml(t.comm || "") +
'</td><td style="padding:0.25rem;word-break:break-all;color:var(--muted)" title="' +
String(cmdFull).replace(/"/g, "&quot;") + '">' +
cmd + "</td></tr>";
escHtml(cmdFull) + '">' +
escHtml(cmd) + "</td></tr>";
});
th += "</tbody></table>";
topBox.innerHTML = th;
@ -2092,9 +2181,11 @@ tw run-until-done &amp;&amp; tw balance</pre>
}
var pf = document.getElementById("st-perf-foot");
if (pf) {
pf.textContent = "Container RSS + process groups" +
(mem.source ? " · " + mem.source : "") + " · " +
(d.generated_at_human || d.generated_at || "");
var basis = mem.memory_basis === "cgroup" ? "cgroup" : "proc RSS sum";
pf.textContent = "Memory: " + basis +
(mem.source ? " · " + mem.source : "") +
(p.loadavg_source ? " · loadavg=" + p.loadavg_source : "") +
" · " + (d.generated_at_human || d.generated_at || "");
}
return fetch("stats-run.json", { cache: "no-store" })
.then(function (r) { return r.ok ? r.json() : null; })