diff --git a/ISSUES.md b/ISSUES.md index 33f4f2a..4e89ea6 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -1,6 +1,6 @@ # Open issues -Stand **2026-07-16** (koopa, live). Keine Secrets. +Stand **2026-07-17** (koopa, live). Keine Secrets. ## Index @@ -13,6 +13,7 @@ Stand **2026-07-16** (koopa, live). Keine Secrets. | [I-05](#i-2026-07-16-05) | low | open | Alte exited Podman-Container | | [I-06](#i-2026-07-16-06) | low–medium | open | Nym HTTP API `*:9080` | | [I-07](#i-2026-07-16-07) | medium | **fixed** | Tor host vs `koopa-tor-relay` | +| [I-08](#i-2026-07-17-08) | medium | open | Landing-stats → standalone `taler-landing-stats` | --- @@ -131,6 +132,24 @@ podman ps --filter name=koopa-tor-relay --- +### I-2026-07-17-08 + +**Landing-stats / Resource-Monitoring: auf standalone `taler-landing-stats` umstellen** +Severity: medium · Host: koopa · Status: open · **TODO (noch nicht umsetzen auf koopa)** + +**Ist:** Collector + Timer leben unter `scripts/taler-landing/` in **diesem** admin-log, installiert nach `~/.local/bin` / user-systemd, Defaults stark **hacktivism.ch**-lastig (Container-Namen, URLs, `ADMIN_LOG`-Pfade). + +**Soll:** Dieselbe **moderne, host-dynamische** Lösung wie geplant/gebaut für andere Stacks: + +- Eigenes Repo **`taler-landing-stats`** auf **git.hacktivism.ch** (`hernani/taler-landing-stats`) +- Profile/env pro Host (bank/exchange/merchant Container, Landing-Pfade, optional bank full-scan) +- User-systemd timer (`taler-landing-stats.service` / `.timer`), Linger +- Resource-Merge (RSS/loadavg via in-container mem-snapshot + podman stats) host-agnostisch + +**Nicht jetzt auf koopa umbauen** — nur merken. Wenn das Repo steht und auf stage/etc. erprobt ist: koopa von `scripts/taler-landing/` auf Checkout+Install aus `taler-landing-stats` migrieren; hier ggf. nur noch Pointer/README. + +--- + ## Kein Issue (Scan) - Prod-Container mit Boot-Autostart: Up diff --git a/configs/bank-landing/goa-amount.js b/configs/bank-landing/goa-amount.js index 67c3859..cfaaad3 100644 --- a/configs/bank-landing/goa-amount.js +++ b/configs/bank-landing/goa-amount.js @@ -1,29 +1,63 @@ -/* Compact GOA / CUR:amount display using currency alt_unit_names. - * Large values → "1.23 Mega-GOA" (title = full GOA:n) so landing tiles fit. +/* Compact GOA / CUR:amount display using SI-style alt units (kGOA, MGOA, …). + * Large values → "1.23 MGOA" (title = full GOA:n) so landing tiles fit. */ (function (global) { "use strict"; var DEFAULT_ALT = { - "24": "Yotta-GOA", - "21": "Zetta-GOA", - "18": "Exa-GOA", - "15": "Peta-GOA", - "12": "Tera-GOA", - "9": "Giga-GOA", - "6": "Mega-GOA", - "3": "Kilo-GOA", + "24": "YGOA", + "21": "ZGOA", + "18": "EGOA", + "15": "PGOA", + "12": "TGOA", + "9": "GGOA", + "6": "MGOA", + "3": "kGOA", "0": "GOA", - "-1": "Deci-GOA", - "-2": "Centi-GOA", - "-3": "Milli-GOA", - "-6": "Micro-GOA", - "-7": "Deci-Micro-GOA", - "-8": "Atomic-GOA" + "-1": "dGOA", + "-2": "cGOA", + "-3": "mGOA", + "-6": "µGOA", + "-7": "dµGOA", + "-8": "aGOA" + }; + + var LONG_TO_COMPACT = { + "yotta-goa": "YGOA", + "zetta-goa": "ZGOA", + "exa-goa": "EGOA", + "peta-goa": "PGOA", + "tera-goa": "TGOA", + "giga-goa": "GGOA", + "mega-goa": "MGOA", + "kilo-goa": "kGOA", + "goa": "GOA", + "deci-goa": "dGOA", + "centi-goa": "cGOA", + "milli-goa": "mGOA", + "micro-goa": "µGOA", + "deci-micro-goa": "dµGOA", + "atomic-goa": "aGOA" }; var THRESHOLD = 1000; + function compactAlt(altMap) { + var out = {}; + var src = altMap || DEFAULT_ALT; + Object.keys(DEFAULT_ALT).forEach(function (k) { + out[k] = DEFAULT_ALT[k]; + }); + Object.keys(src).forEach(function (k) { + var name = String(src[k] || "").trim(); + var low = name.toLowerCase().replace(/\s+/g, ""); + if (LONG_TO_COMPACT[low]) out[k] = LONG_TO_COMPACT[low]; + else if (DEFAULT_ALT[k] && /goa/i.test(name)) out[k] = DEFAULT_ALT[k]; + else out[k] = name || DEFAULT_ALT[k] || name; + }); + return out; + } + function parseAmount(s) { if (s == null || s === "") return { cur: "GOA", val: 0 }; if (typeof s === "number") return { cur: "GOA", val: s }; @@ -51,7 +85,7 @@ } /** - * @param {string|number} amount + * @param {string|number|object} amount * @param {object} [altMap] * @returns {{ display: string, title: string, raw: string, usedAlt: boolean }} */ @@ -59,24 +93,17 @@ if (amount == null || amount === "") { return { display: "—", title: "", raw: "", usedAlt: false }; } - // Prefer server-provided alt if caller passes object {amount, amount_alt} + // Prefer re-format from canonical amount (keeps kGOA even if server sent Kilo-GOA) if (typeof amount === "object" && amount) { - if (amount.amount_alt) { - var rawO = amount.amount || amount.amount_full || String(amount.amount_alt); - return { - display: String(amount.amount_alt), - title: amount.amount_full || rawO, - raw: String(rawO), - usedAlt: true - }; + if (amount.amount) { + return format(amount.amount, altMap); } - amount = amount.amount || amount.amount_full || ""; + amount = amount.amount_full || amount.amount_alt || ""; } - var alt = altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT; + var alt = compactAlt(altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT); var p = parseAmount(amount); var raw = baseStr(p.cur, p.val); - // Foreign currencies must not pick up Kilo-GOA / Mega-GOA labels var baseName = (alt && alt["0"]) || p.cur; var curU = String(p.cur || "").toUpperCase(); var baseU = String(baseName || "").toUpperCase(); @@ -95,7 +122,9 @@ var n = parseInt(k, 10); if (!isNaN(n)) scales.push({ sc: n, name: String(alt[k]) }); }); - scales.sort(function (a, b) { return b.sc - a.sc; }); + scales.sort(function (a, b) { + return b.sc - a.sc; + }); var abs = Math.abs(p.val); var chosen = null; @@ -136,33 +165,80 @@ setText(document.getElementById(id), amount, altMap); } - /** Prefer amount_alt from stats row, else format amount. */ + /** Prefer formatting from amount field; fall back to amount_alt string. */ function pick(rowOrAmount, altMap) { if (rowOrAmount && typeof rowOrAmount === "object") { + var a = + rowOrAmount.amount || + rowOrAmount.total_amount || + rowOrAmount.amount_paid_sum || + rowOrAmount.amount_sum; + if (a) return format(a, altMap); if (rowOrAmount.amount_alt) { - return format({ - amount: rowOrAmount.amount, - amount_alt: rowOrAmount.amount_alt, - amount_full: rowOrAmount.amount_full - }, altMap); + return { + display: String(rowOrAmount.amount_alt), + title: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + raw: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + usedAlt: true + }; } - return format(rowOrAmount.amount || rowOrAmount.total_amount, altMap); + return format("", altMap); } return format(rowOrAmount, altMap); } function rememberAlt(stats) { if (stats && stats.alt_unit_names && typeof stats.alt_unit_names === "object") { - global.__GOA_ALT_UNITS = stats.alt_unit_names; + global.__GOA_ALT_UNITS = compactAlt(stats.alt_unit_names); } } + /** Small legend HTML (positive scales). */ + function legendHtml(stats) { + var alt = compactAlt((stats && stats.alt_unit_names) || global.__GOA_ALT_UNITS || DEFAULT_ALT); + var rows = []; + Object.keys(alt) + .map(function (k) { + return parseInt(k, 10); + }) + .filter(function (n) { + return !isNaN(n) && n > 0; + }) + .sort(function (a, b) { + return a - b; + }) + .forEach(function (sc) { + rows.push( + "10" + + sc + + "" + + alt[String(sc)] + + "" + ); + }); + if (!rows.length) return ""; + return ( + '' + + rows.join("") + + "
×basename
" + ); + } + + function fillLegend(elOrId, stats) { + var el = typeof elOrId === "string" ? document.getElementById(elOrId) : elOrId; + if (!el) return; + el.innerHTML = legendHtml(stats); + } + global.GoaAmount = { format: format, setText: setText, setById: setById, pick: pick, rememberAlt: rememberAlt, + compactAlt: compactAlt, + legendHtml: legendHtml, + fillLegend: fillLegend, DEFAULT_ALT: DEFAULT_ALT, THRESHOLD: THRESHOLD }; diff --git a/configs/bank-landing/index.html b/configs/bank-landing/index.html index bd47143..3f31226 100644 --- a/configs/bank-landing/index.html +++ b/configs/bank-landing/index.html @@ -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); @@ -1081,7 +1102,17 @@ border-radius: 10px; } .shop-foot a:hover { filter: brightness(1.06); } - + .landing-rev { + margin: 0.75rem 0 0; + font-size: 0.72rem; + color: var(--muted, #9a8fb0); + line-height: 1.45; + text-align: center; + } + .landing-rev a { color: inherit; text-decoration: underline; text-underline-offset: 2px; opacity: 0.95; } + .landing-rev strong { font-weight: 750; } + + @@ -1392,39 +1423,67 @@ tw run-until-done && tw balance @@ -1448,11 +1507,19 @@ tw run-until-done && tw balance
- Loadavg + CPU (container) + +
+
+ Loadavg (host)
- Container + PIDs + +
+
+ Container RSS
@@ -1469,10 +1536,10 @@ tw run-until-done && tw balance
-

Top processes (RSS)

+

Top processes (RSS inside container)

-

Memory from /proc + cgroup inside podman container.

+

RSS from /proc inside container (no memory cgroup in rootless podman). CPU from podman stats.

@@ -1985,46 +2059,65 @@ tw run-until-done && tw balance 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 +2130,30 @@ tw run-until-done && tw balance if (http && http !== "200") s += " (" + http + ")"; return s; } + function escHtml(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } 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 +2162,9 @@ tw run-until-done && tw balance 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 +2185,12 @@ tw run-until-done && tw balance var cmdFull = t.cmd_full || cmd; if (cmd.length > 64) cmd = cmd.slice(0, 61) + "…"; th += '' + - (t.rss_human || "?") + - '' + (t.comm || "") + + escHtml(t.rss_bytes != null ? Number(t.rss_bytes).toLocaleString("en-US") + " B" : "") + '">' + + escHtml(t.rss_human || "?") + + '' + escHtml(t.comm || "") + '' + - cmd + ""; + escHtml(cmdFull) + '">' + + escHtml(cmd) + ""; }); th += ""; topBox.innerHTML = th; @@ -2092,9 +2198,11 @@ tw run-until-done && tw balance } 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; }) diff --git a/configs/exchange-landing/goa-amount.js b/configs/exchange-landing/goa-amount.js index 67c3859..cfaaad3 100644 --- a/configs/exchange-landing/goa-amount.js +++ b/configs/exchange-landing/goa-amount.js @@ -1,29 +1,63 @@ -/* Compact GOA / CUR:amount display using currency alt_unit_names. - * Large values → "1.23 Mega-GOA" (title = full GOA:n) so landing tiles fit. +/* Compact GOA / CUR:amount display using SI-style alt units (kGOA, MGOA, …). + * Large values → "1.23 MGOA" (title = full GOA:n) so landing tiles fit. */ (function (global) { "use strict"; var DEFAULT_ALT = { - "24": "Yotta-GOA", - "21": "Zetta-GOA", - "18": "Exa-GOA", - "15": "Peta-GOA", - "12": "Tera-GOA", - "9": "Giga-GOA", - "6": "Mega-GOA", - "3": "Kilo-GOA", + "24": "YGOA", + "21": "ZGOA", + "18": "EGOA", + "15": "PGOA", + "12": "TGOA", + "9": "GGOA", + "6": "MGOA", + "3": "kGOA", "0": "GOA", - "-1": "Deci-GOA", - "-2": "Centi-GOA", - "-3": "Milli-GOA", - "-6": "Micro-GOA", - "-7": "Deci-Micro-GOA", - "-8": "Atomic-GOA" + "-1": "dGOA", + "-2": "cGOA", + "-3": "mGOA", + "-6": "µGOA", + "-7": "dµGOA", + "-8": "aGOA" + }; + + var LONG_TO_COMPACT = { + "yotta-goa": "YGOA", + "zetta-goa": "ZGOA", + "exa-goa": "EGOA", + "peta-goa": "PGOA", + "tera-goa": "TGOA", + "giga-goa": "GGOA", + "mega-goa": "MGOA", + "kilo-goa": "kGOA", + "goa": "GOA", + "deci-goa": "dGOA", + "centi-goa": "cGOA", + "milli-goa": "mGOA", + "micro-goa": "µGOA", + "deci-micro-goa": "dµGOA", + "atomic-goa": "aGOA" }; var THRESHOLD = 1000; + function compactAlt(altMap) { + var out = {}; + var src = altMap || DEFAULT_ALT; + Object.keys(DEFAULT_ALT).forEach(function (k) { + out[k] = DEFAULT_ALT[k]; + }); + Object.keys(src).forEach(function (k) { + var name = String(src[k] || "").trim(); + var low = name.toLowerCase().replace(/\s+/g, ""); + if (LONG_TO_COMPACT[low]) out[k] = LONG_TO_COMPACT[low]; + else if (DEFAULT_ALT[k] && /goa/i.test(name)) out[k] = DEFAULT_ALT[k]; + else out[k] = name || DEFAULT_ALT[k] || name; + }); + return out; + } + function parseAmount(s) { if (s == null || s === "") return { cur: "GOA", val: 0 }; if (typeof s === "number") return { cur: "GOA", val: s }; @@ -51,7 +85,7 @@ } /** - * @param {string|number} amount + * @param {string|number|object} amount * @param {object} [altMap] * @returns {{ display: string, title: string, raw: string, usedAlt: boolean }} */ @@ -59,24 +93,17 @@ if (amount == null || amount === "") { return { display: "—", title: "", raw: "", usedAlt: false }; } - // Prefer server-provided alt if caller passes object {amount, amount_alt} + // Prefer re-format from canonical amount (keeps kGOA even if server sent Kilo-GOA) if (typeof amount === "object" && amount) { - if (amount.amount_alt) { - var rawO = amount.amount || amount.amount_full || String(amount.amount_alt); - return { - display: String(amount.amount_alt), - title: amount.amount_full || rawO, - raw: String(rawO), - usedAlt: true - }; + if (amount.amount) { + return format(amount.amount, altMap); } - amount = amount.amount || amount.amount_full || ""; + amount = amount.amount_full || amount.amount_alt || ""; } - var alt = altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT; + var alt = compactAlt(altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT); var p = parseAmount(amount); var raw = baseStr(p.cur, p.val); - // Foreign currencies must not pick up Kilo-GOA / Mega-GOA labels var baseName = (alt && alt["0"]) || p.cur; var curU = String(p.cur || "").toUpperCase(); var baseU = String(baseName || "").toUpperCase(); @@ -95,7 +122,9 @@ var n = parseInt(k, 10); if (!isNaN(n)) scales.push({ sc: n, name: String(alt[k]) }); }); - scales.sort(function (a, b) { return b.sc - a.sc; }); + scales.sort(function (a, b) { + return b.sc - a.sc; + }); var abs = Math.abs(p.val); var chosen = null; @@ -136,33 +165,80 @@ setText(document.getElementById(id), amount, altMap); } - /** Prefer amount_alt from stats row, else format amount. */ + /** Prefer formatting from amount field; fall back to amount_alt string. */ function pick(rowOrAmount, altMap) { if (rowOrAmount && typeof rowOrAmount === "object") { + var a = + rowOrAmount.amount || + rowOrAmount.total_amount || + rowOrAmount.amount_paid_sum || + rowOrAmount.amount_sum; + if (a) return format(a, altMap); if (rowOrAmount.amount_alt) { - return format({ - amount: rowOrAmount.amount, - amount_alt: rowOrAmount.amount_alt, - amount_full: rowOrAmount.amount_full - }, altMap); + return { + display: String(rowOrAmount.amount_alt), + title: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + raw: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + usedAlt: true + }; } - return format(rowOrAmount.amount || rowOrAmount.total_amount, altMap); + return format("", altMap); } return format(rowOrAmount, altMap); } function rememberAlt(stats) { if (stats && stats.alt_unit_names && typeof stats.alt_unit_names === "object") { - global.__GOA_ALT_UNITS = stats.alt_unit_names; + global.__GOA_ALT_UNITS = compactAlt(stats.alt_unit_names); } } + /** Small legend HTML (positive scales). */ + function legendHtml(stats) { + var alt = compactAlt((stats && stats.alt_unit_names) || global.__GOA_ALT_UNITS || DEFAULT_ALT); + var rows = []; + Object.keys(alt) + .map(function (k) { + return parseInt(k, 10); + }) + .filter(function (n) { + return !isNaN(n) && n > 0; + }) + .sort(function (a, b) { + return a - b; + }) + .forEach(function (sc) { + rows.push( + "10" + + sc + + "" + + alt[String(sc)] + + "" + ); + }); + if (!rows.length) return ""; + return ( + '' + + rows.join("") + + "
×basename
" + ); + } + + function fillLegend(elOrId, stats) { + var el = typeof elOrId === "string" ? document.getElementById(elOrId) : elOrId; + if (!el) return; + el.innerHTML = legendHtml(stats); + } + global.GoaAmount = { format: format, setText: setText, setById: setById, pick: pick, rememberAlt: rememberAlt, + compactAlt: compactAlt, + legendHtml: legendHtml, + fillLegend: fillLegend, DEFAULT_ALT: DEFAULT_ALT, THRESHOLD: THRESHOLD }; diff --git a/configs/exchange-landing/index.html b/configs/exchange-landing/index.html index 5d73388..0101601 100644 --- a/configs/exchange-landing/index.html +++ b/configs/exchange-landing/index.html @@ -88,6 +88,33 @@ .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; } + .unit-legend { + margin: 0.65rem auto 0; + border-collapse: collapse; + font-size: 0.68rem; + color: var(--muted); + } + .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; font-size: 0.62rem; text-transform: uppercase; } + .unit-legend strong { color: #5eead4; } + .unit-legend-note { margin: 0.35rem 0 0; font-size: 0.68rem; color: var(--muted); text-align: center; } + .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; } + .stat.wide { grid-column: 1 / -1; } .stats-run-note { margin: 0.35rem 0 0; font-size: 0.7rem; color: #f0a090; text-align: center; } @@ -247,7 +274,17 @@ 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; } - + .landing-rev { + margin: 0.75rem 0 0; + font-size: 0.72rem; + color: var(--muted, #9a8fb0); + line-height: 1.45; + text-align: center; + } + .landing-rev a { color: inherit; text-decoration: underline; text-underline-offset: 2px; opacity: 0.95; } + .landing-rev strong { font-weight: 750; } + +

@@ -277,8 +314,8 @@

Live (remaining)
Spent / zero
Remaining amt
-
Withdraw ops
-
Withdraw amt
+
Withdraw ops
+
All withdraw amt
Refresh
Coin deposits
Batch deposits
@@ -307,14 +344,22 @@
-

Reserves · wire

-

Backing money movement

+

Reserves · wire · all money

+

Backing money movement · large GOA as kGOA / MGOA / …

Reserves
-
Wire in
-
Wire in amt
+
Wire-in count
+
All wire-in money
Wire out
+
All withdraws
+
Withdraw ops
+
+ Latest wire-in & withdraws +
+
+

Unit names (hover amounts for exact GOA:n)

+

Loading…

@@ -325,17 +370,19 @@
/keys
/config
-
Loadavg
-
Container
+
CPU (container)
+
Loadavg (host)
+
PIDs
+
Container RSS
PostgreSQL
Taler procs
Nginx
-

Top processes (RSS)

+

Top processes (RSS inside container)

-

Memory from /proc + cgroup inside podman container.

+

RSS from /proc inside container (no memory cgroup in rootless podman). CPU from podman stats.

@@ -442,6 +489,13 @@ sudo apt-get install -y taler-wallet-cli Privacy: processing under Swiss FADP (revDSG). What is retained is listed on /privacy.

+ +

+ Exchange landing v42 + · content + · 921a4f1 +

+ @@ -513,6 +567,8 @@ sudo apt-get install -y taler-wallet-cli set("st-coins-live", d.coins_live); set("st-coins-spent", d.coins_spent); setAmt("st-remaining", d.coins_remaining_amount, "coins_remaining_amount_alt", d); + set("st-withdraw-coins", d.withdraw_ops); + setAmt("st-withdraw-amt-coins", d.withdraw_amount, "withdraw_amount_alt", d); set("st-withdraw", d.withdraw_ops); setAmt("st-withdraw-amt", d.withdraw_amount, "withdraw_amount_alt", d); set("st-refresh", d.refresh_ops); @@ -529,16 +585,48 @@ sudo apt-get install -y taler-wallet-cli setAmt("st-wire-in-amt", d.wire_in_amount, "wire_in_amount_alt", d); set("st-wire-out", d.wire_out); + var recent = document.getElementById("st-recent"); + if (recent) { + recent.innerHTML = ""; + var rows = (d.recent_activity || []).slice(0, 12); + if (!rows.length) { + var empty = document.createElement("li"); + empty.className = "meta"; + empty.textContent = "—"; + recent.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"; + meta.textContent = (row.ts_human || "") + (row.kind ? " · " + row.kind : ""); + li.appendChild(amt); + li.appendChild(meta); + recent.appendChild(li); + }); + } + } + if (window.GoaAmount && GoaAmount.fillLegend) GoaAmount.fillLegend("st-unit-legend", d); + var ladder = document.getElementById("st-ladder"); if (ladder) { ladder.innerHTML = ""; (d.denom_ladder || []).forEach(function (x) { var s = document.createElement("span"); s.className = "pill"; - var lab = x.value_alt || x.value || "?"; - if (window.GoaAmount && !x.value_alt) { + var lab = x.value || "?"; + if (window.GoaAmount) { lab = GoaAmount.format(x.value, d.alt_unit_names).display; - } + } else if (x.value_alt) lab = x.value_alt; s.textContent = lab + (x.keys > 1 ? " ×" + x.keys : ""); if (x.value && lab !== x.value) s.title = x.value; ladder.appendChild(s); @@ -553,10 +641,10 @@ sudo apt-get install -y taler-wallet-cli wrap.hidden = false; var html = ""; by.forEach(function (r) { - var v = r.value_alt || r.value || "?"; - if (window.GoaAmount && !r.value_alt) { + var v = r.value || "?"; + if (window.GoaAmount) { v = GoaAmount.format(r.value, d.alt_unit_names).display; - } + } else if (r.value_alt) v = r.value_alt; html += ""; }); html += "
ValueCoinsLive
" + v + "" + r.coins + "" + r.live + "
"; @@ -569,25 +657,38 @@ sudo apt-get install -y taler-wallet-cli (d.generated_at_human || d.generated_at || ""); } + function escHtml(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } 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 !== "" && Number(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"); } 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-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); @@ -604,11 +705,11 @@ sudo apt-get install -y taler-wallet-cli var cmd = t.cmd || ""; var cmdFull = t.cmd_full || cmd; if (cmd.length > 64) cmd = cmd.slice(0, 61) + "…"; - th += "" + - (t.rss_human || "?") + "" + - (t.comm || "") + "" + - cmd + ""; + th += "" + + escHtml(t.rss_human || "?") + "" + + escHtml(t.comm || "") + "" + + escHtml(cmd) + ""; }); th += ""; topBox.innerHTML = th; @@ -616,9 +717,11 @@ sudo apt-get install -y taler-wallet-cli } 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("/intro/stats-run.json", { cache: "no-store" }) .then(function (r) { return r.ok ? r.json() : null; }) diff --git a/configs/merchant-landing/goa-amount.js b/configs/merchant-landing/goa-amount.js index 67c3859..cfaaad3 100644 --- a/configs/merchant-landing/goa-amount.js +++ b/configs/merchant-landing/goa-amount.js @@ -1,29 +1,63 @@ -/* Compact GOA / CUR:amount display using currency alt_unit_names. - * Large values → "1.23 Mega-GOA" (title = full GOA:n) so landing tiles fit. +/* Compact GOA / CUR:amount display using SI-style alt units (kGOA, MGOA, …). + * Large values → "1.23 MGOA" (title = full GOA:n) so landing tiles fit. */ (function (global) { "use strict"; var DEFAULT_ALT = { - "24": "Yotta-GOA", - "21": "Zetta-GOA", - "18": "Exa-GOA", - "15": "Peta-GOA", - "12": "Tera-GOA", - "9": "Giga-GOA", - "6": "Mega-GOA", - "3": "Kilo-GOA", + "24": "YGOA", + "21": "ZGOA", + "18": "EGOA", + "15": "PGOA", + "12": "TGOA", + "9": "GGOA", + "6": "MGOA", + "3": "kGOA", "0": "GOA", - "-1": "Deci-GOA", - "-2": "Centi-GOA", - "-3": "Milli-GOA", - "-6": "Micro-GOA", - "-7": "Deci-Micro-GOA", - "-8": "Atomic-GOA" + "-1": "dGOA", + "-2": "cGOA", + "-3": "mGOA", + "-6": "µGOA", + "-7": "dµGOA", + "-8": "aGOA" + }; + + var LONG_TO_COMPACT = { + "yotta-goa": "YGOA", + "zetta-goa": "ZGOA", + "exa-goa": "EGOA", + "peta-goa": "PGOA", + "tera-goa": "TGOA", + "giga-goa": "GGOA", + "mega-goa": "MGOA", + "kilo-goa": "kGOA", + "goa": "GOA", + "deci-goa": "dGOA", + "centi-goa": "cGOA", + "milli-goa": "mGOA", + "micro-goa": "µGOA", + "deci-micro-goa": "dµGOA", + "atomic-goa": "aGOA" }; var THRESHOLD = 1000; + function compactAlt(altMap) { + var out = {}; + var src = altMap || DEFAULT_ALT; + Object.keys(DEFAULT_ALT).forEach(function (k) { + out[k] = DEFAULT_ALT[k]; + }); + Object.keys(src).forEach(function (k) { + var name = String(src[k] || "").trim(); + var low = name.toLowerCase().replace(/\s+/g, ""); + if (LONG_TO_COMPACT[low]) out[k] = LONG_TO_COMPACT[low]; + else if (DEFAULT_ALT[k] && /goa/i.test(name)) out[k] = DEFAULT_ALT[k]; + else out[k] = name || DEFAULT_ALT[k] || name; + }); + return out; + } + function parseAmount(s) { if (s == null || s === "") return { cur: "GOA", val: 0 }; if (typeof s === "number") return { cur: "GOA", val: s }; @@ -51,7 +85,7 @@ } /** - * @param {string|number} amount + * @param {string|number|object} amount * @param {object} [altMap] * @returns {{ display: string, title: string, raw: string, usedAlt: boolean }} */ @@ -59,24 +93,17 @@ if (amount == null || amount === "") { return { display: "—", title: "", raw: "", usedAlt: false }; } - // Prefer server-provided alt if caller passes object {amount, amount_alt} + // Prefer re-format from canonical amount (keeps kGOA even if server sent Kilo-GOA) if (typeof amount === "object" && amount) { - if (amount.amount_alt) { - var rawO = amount.amount || amount.amount_full || String(amount.amount_alt); - return { - display: String(amount.amount_alt), - title: amount.amount_full || rawO, - raw: String(rawO), - usedAlt: true - }; + if (amount.amount) { + return format(amount.amount, altMap); } - amount = amount.amount || amount.amount_full || ""; + amount = amount.amount_full || amount.amount_alt || ""; } - var alt = altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT; + var alt = compactAlt(altMap || global.__GOA_ALT_UNITS || DEFAULT_ALT); var p = parseAmount(amount); var raw = baseStr(p.cur, p.val); - // Foreign currencies must not pick up Kilo-GOA / Mega-GOA labels var baseName = (alt && alt["0"]) || p.cur; var curU = String(p.cur || "").toUpperCase(); var baseU = String(baseName || "").toUpperCase(); @@ -95,7 +122,9 @@ var n = parseInt(k, 10); if (!isNaN(n)) scales.push({ sc: n, name: String(alt[k]) }); }); - scales.sort(function (a, b) { return b.sc - a.sc; }); + scales.sort(function (a, b) { + return b.sc - a.sc; + }); var abs = Math.abs(p.val); var chosen = null; @@ -136,33 +165,80 @@ setText(document.getElementById(id), amount, altMap); } - /** Prefer amount_alt from stats row, else format amount. */ + /** Prefer formatting from amount field; fall back to amount_alt string. */ function pick(rowOrAmount, altMap) { if (rowOrAmount && typeof rowOrAmount === "object") { + var a = + rowOrAmount.amount || + rowOrAmount.total_amount || + rowOrAmount.amount_paid_sum || + rowOrAmount.amount_sum; + if (a) return format(a, altMap); if (rowOrAmount.amount_alt) { - return format({ - amount: rowOrAmount.amount, - amount_alt: rowOrAmount.amount_alt, - amount_full: rowOrAmount.amount_full - }, altMap); + return { + display: String(rowOrAmount.amount_alt), + title: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + raw: String(rowOrAmount.amount_full || rowOrAmount.amount_alt), + usedAlt: true + }; } - return format(rowOrAmount.amount || rowOrAmount.total_amount, altMap); + return format("", altMap); } return format(rowOrAmount, altMap); } function rememberAlt(stats) { if (stats && stats.alt_unit_names && typeof stats.alt_unit_names === "object") { - global.__GOA_ALT_UNITS = stats.alt_unit_names; + global.__GOA_ALT_UNITS = compactAlt(stats.alt_unit_names); } } + /** Small legend HTML (positive scales). */ + function legendHtml(stats) { + var alt = compactAlt((stats && stats.alt_unit_names) || global.__GOA_ALT_UNITS || DEFAULT_ALT); + var rows = []; + Object.keys(alt) + .map(function (k) { + return parseInt(k, 10); + }) + .filter(function (n) { + return !isNaN(n) && n > 0; + }) + .sort(function (a, b) { + return a - b; + }) + .forEach(function (sc) { + rows.push( + "10" + + sc + + "" + + alt[String(sc)] + + "" + ); + }); + if (!rows.length) return ""; + return ( + '' + + rows.join("") + + "
×basename
" + ); + } + + function fillLegend(elOrId, stats) { + var el = typeof elOrId === "string" ? document.getElementById(elOrId) : elOrId; + if (!el) return; + el.innerHTML = legendHtml(stats); + } + global.GoaAmount = { format: format, setText: setText, setById: setById, pick: pick, rememberAlt: rememberAlt, + compactAlt: compactAlt, + legendHtml: legendHtml, + fillLegend: fillLegend, DEFAULT_ALT: DEFAULT_ALT, THRESHOLD: THRESHOLD }; diff --git a/configs/merchant-landing/index.html b/configs/merchant-landing/index.html index c14ee0c..96ab5eb 100644 --- a/configs/merchant-landing/index.html +++ b/configs/merchant-landing/index.html @@ -273,6 +273,15 @@ .stats-run-note { margin: 0.35rem 0 0; font-size: 0.7rem; color: #f0a090; text-align: center; } + .unit-legend { + margin: 0.45rem auto 0; border-collapse: collapse; font-size: 0.68rem; color: var(--muted); + display: inline-table; + } + .unit-legend th, .unit-legend td { + padding: 0.1rem 0.4rem; border-bottom: 1px solid rgba(167, 139, 250, 0.15); text-align: left; + } + .unit-legend th { font-weight: 700; font-size: 0.6rem; text-transform: uppercase; } + .unit-legend strong { color: var(--goa); } .step-card { background: var(--card); border: 1px solid var(--border); border-radius: 18px; padding: 1.3rem 1.25rem 1.4rem; margin-bottom: 1.2rem; @@ -508,7 +517,17 @@ .sym-gold path[data-ring], .sym-gold circle[data-ring] { fill: none; stroke: #f0c86a; } .sym-blue path, .sym-blue circle { fill: #3ecfbf; stroke: #3ecfbf; } .sym-blue path[data-ring], .sym-blue circle[data-ring] { fill: none; stroke: #3ecfbf; } - + .landing-rev { + margin: 0.75rem 0 0; + font-size: 0.72rem; + color: var(--muted, #9a8fb0); + line-height: 1.45; + text-align: center; + } + .landing-rev a { color: inherit; text-decoration: underline; text-underline-offset: 2px; opacity: 0.95; } + .landing-rev strong { font-weight: 750; } + +

@@ -704,12 +723,12 @@ sudo apt-get install -y taler-wallet-cli

-

Merchant stats

-

From merchant DB · dual currency side by side

+

Merchant stats · all payments

+

From merchant DB · dual currency · large GOA as kGOA / MGOA / …

Instances
-
Orders
-
Paid
+
All orders
+
All paid
Unpaid
Wired
Refunds
@@ -721,29 +740,31 @@ sudo apt-get install -y taler-wallet-cli
GOA
explorational · hacktivism
- paid volume + all paid volume
Orders
Paid
Wired
Unpaid
-
all orders
+
all orders volume
CHF
taler-ops
- paid volume + all paid volume
Orders
Paid
Wired
Unpaid
-
all orders
+
all orders volume
+

Unit names for large GOA

+

Loading…

@@ -755,22 +776,24 @@ sudo apt-get install -y taler-wallet-cli
/config
/terms
/webui/
-
Loadavg
-
Container
+
CPU (container)
+
Loadavg (host)
+
PIDs
+
Container RSS
PostgreSQL
Taler procs
Nginx
-

Top processes (RSS)

+

Top processes (RSS inside container)

-

Memory from /proc + cgroup inside podman container.

+

RSS from /proc inside container (no memory cgroup in rootless podman). CPU from podman stats.

-

Recent activity

-

Latest 5 payments & refunds per currency

+

Latest payments

+

Latest payments & refunds per currency (all paid totals above)

GOA

@@ -809,9 +832,17 @@ sudo apt-get install -y taler-wallet-cli Privacy: processing under Swiss FADP (revDSG). What is retained is listed on /privacy.

+ +

+ Merchant landing v42 + · content + · 921a4f1 +

+ +