From 25cb8216b38eb6b7b17877f65daeb1245362c133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 04:35:01 +0200 Subject: [PATCH 01/42] release 1.15.0: CHF fake-incoming ladder (23 rungs to 50k) devtesting runs a log-spaced amount ladder up to CHF:50000 after the baseline plumbing probe. Each rung uses geniban debit funding. Defaults DEVTESTING_LADDER=1 STEPS=23 MAX=50000; stage RUN_TIMEOUT example 1800s. --- README.md | 5 +- TESTS.md | 3 +- VERSION | 2 +- VERSIONS.md | 1 + check_devtesting.sh | 292 +++++++++++++++++++----- host-agent/env/mytops-stage.env.example | 8 +- taler-monitoring.sh | 11 +- 7 files changed, 257 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 79c4ffa..81802c9 100644 --- a/README.md +++ b/README.md @@ -433,7 +433,10 @@ Taler Operations **rusty** host (`taler-devtesting` forced command): ``` Needs SSH to `devtesting@rusty.taler-ops.ch` (Host `taler-rusty-devtesting`). -Probes: CLI help, `geniban` (CH IBAN), `fake-incoming` CHF to exchange credit +Probes: CLI help, `geniban` (CH IBAN / synthetic debit funding), baseline `fake-incoming`, +optional **amount ladder** (`DEVTESTING_LADDER=1`, default **23** rungs up to **CHF:50000**). +Each rung reuses geniban debit payto so volume covers the max rung without a real bank withdraw. +`fake-incoming` CHF to exchange credit IBAN. A nexus bounce *missing reserve public key* is **OK** for the plumbing probe (no real wallet reserve required). diff --git a/TESTS.md b/TESTS.md index 9826fd7..c88506e 100644 --- a/TESTS.md +++ b/TESTS.md @@ -202,4 +202,5 @@ Always use `printf --` friendly plain headers when colour is off (leading `---` | **devtesting.cli-** | SSH to rusty · `taler-devtesting` CLI | | **devtesting.geniban-** | synthetic CH IBAN | | **devtesting.credit-payto-** | exchange credit IBAN for nexus | -| **devtesting.fake-incoming-** | CHF IN via nexus; bounce missing reserve pub = plumbing OK | +| **devtesting.fake-incoming-** / **baseline-** | CHF IN via nexus; bounce missing reserve pub = plumbing OK | +| **devtesting.ladder-** | CHF fake-incoming amount ladder (default 23 rungs → CHF:50000; synthetic geniban debit each rung) | diff --git a/VERSION b/VERSION index 850e742..141f2e8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0 diff --git a/VERSIONS.md b/VERSIONS.md index b1e03e0..f1ac38d 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. | | **v1.14.0** | 2026-07-19 | **Feature:** SUMMARY box rows are **colour-distinct** (per-severity badge + tinted background + bold counts; verdict-coloured header); HTML mon pages style `sum-ok` / `sum-warn` / `sum-err` / … panels the same way. | | **v1.13.13** | 2026-07-19 | **Bugfix:** progress estimate for **devtesting** / fake-franken is 8 (happy-path numbered checks), not 10 — avoids mid-run 42/48 then end snap 46/46. | | **v1.13.12** | 2026-07-19 | **Bugfix:** SUMMARY chrome (`[ WARN ] 2`, `warnings only`, OK/ERROR counts without `#NNN`) is **meta**, not warn/error — sticky counts + warn filter no longer double SUMMARY rows; filter context skips blank lines. | diff --git a/check_devtesting.sh b/check_devtesting.sh index 088ddd7..a4723cb 100755 --- a/check_devtesting.sh +++ b/check_devtesting.sh @@ -6,18 +6,21 @@ # # Probes: # 1) CLI reachable (fake-incoming --help) -# 2) geniban → Swiss IBAN -# 3) fake-incoming CHF amount to exchange credit IBAN (nexus IN) -# Bounce "missing reserve public key" = plumbing OK (no real wallet reserve) -# Full credit needs a real reserve pub as --subject (optional later) +# 2) geniban → Swiss IBAN (debit side — “enough money” is synthetic per rung) +# 3) baseline fake-incoming CHF:1.00 (plumbing) +# 4) optional amount ladder: DEVTESTING_LADDER=1 (default) — 23 rungs up to 50k CHF # # Env: -# DEVTESTING_SSH default taler-rusty-devtesting (or user@host) -# DEVTESTING_AMOUNT default CHF:1.00 -# DEVTESTING_CREDIT_PAYTO exchange IBAN payto (auto from EXCHANGE_PUBLIC/keys if empty) -# DEVTESTING_EXCHANGE_IBAN fallback CH6808573105529100001 (TOPS CHF exchange) -# DEVTESTING_SKIP=1 skip phase -# SKIP_SSH=1 skip (same as other SSH phases) +# DEVTESTING_SSH default taler-rusty-devtesting (or user@host) +# DEVTESTING_AMOUNT baseline amount (default CHF:1.00) +# DEVTESTING_CREDIT_PAYTO exchange IBAN payto +# DEVTESTING_EXCHANGE_IBAN fallback CH6808573105529100001 (TOPS CHF exchange) +# DEVTESTING_LADDER 1 (default) run amount ladder; 0 = baseline only +# DEVTESTING_LADDER_STEPS default 23 +# DEVTESTING_LADDER_MAX default 50000 (CHF major units) +# DEVTESTING_LADDER_MIN default 0.01 +# DEVTESTING_LADDER_STOP_ON_FAIL 1 (default) stop ladder on hard fail; 0 = continue +# DEVTESTING_SKIP=1 skip phase # set -euo pipefail ROOT=$(cd "$(dirname "$0")" && pwd) @@ -37,8 +40,13 @@ fi : "${DEVTESTING_SSH:=}" : "${DEVTESTING_AMOUNT:=CHF:1.00}" : "${DEVTESTING_EXCHANGE_IBAN:=CH6808573105529100001}" +: "${DEVTESTING_LADDER:=1}" +: "${DEVTESTING_LADDER_STEPS:=23}" +: "${DEVTESTING_LADDER_MAX:=50000}" +: "${DEVTESTING_LADDER_MIN:=0.01}" +: "${DEVTESTING_LADDER_STOP_ON_FAIL:=1}" : "${SSH_CONNECT_TIMEOUT:=8}" -: "${SSH_CMD_TIMEOUT:=45}" +: "${SSH_CMD_TIMEOUT:=60}" ssh_dt() { # Prefer Host alias; fall back to explicit user@host if config missing @@ -61,8 +69,57 @@ ssh_dt() { fi } +# Run one fake-incoming. Sets globals: FI_RC FI_FLAT FI_STATUS +# FI_STATUS: ok_bounce | ok_in | ok_cli | fail_ssh | fail_creditor | fail_nexus | fail_other +run_fake_incoming() { + local amount="$1" subject="$2" credit="$3" debit="$4" + local fi_out + FI_RC=0 + FI_FLAT="" + FI_STATUS="fail_other" + fi_out=$(ssh_dt fake-incoming \ + --amount "${amount}" \ + --subject "${subject}" \ + --credit-payto "${credit}" \ + --debit-payto "${debit}" 2>&1) || FI_RC=$? + FI_FLAT=$(printf '%s' "$fi_out" | tr '\n' ' ') + + if printf '%s' "$FI_FLAT" | grep -qiE 'Permission denied|Connection refused|Could not resolve|No such command'; then + FI_STATUS=fail_ssh + return 1 + fi + if printf '%s' "$FI_FLAT" | grep -qiE 'Creditor must be the exchange'; then + FI_STATUS=fail_creditor + return 1 + fi + if printf '%s' "$FI_FLAT" | grep -qiE 'Missing amount|unable to read secrets|CalledProcessError'; then + if ! printf '%s' "$FI_FLAT" | grep -qE 'libeufin-nexus - IN .* CHF'; then + FI_STATUS=fail_nexus + return 1 + fi + fi + if printf '%s' "$FI_FLAT" | grep -qE 'libeufin-nexus - IN .* CHF:[0-9]'; then + if printf '%s' "$FI_FLAT" | grep -qiE 'bounced.*missing reserve public key'; then + FI_STATUS=ok_bounce + return 0 + fi + if printf '%s' "$FI_FLAT" | grep -qiE 'bounced'; then + FI_STATUS=ok_bounce + return 0 + fi + FI_STATUS=ok_in + return 0 + fi + if [ "$FI_RC" -eq 0 ] && printf '%s' "$FI_FLAT" | grep -qi 'Faking incoming CHF'; then + FI_STATUS=ok_cli + return 0 + fi + FI_STATUS=fail_other + return 1 +} + set_group cli -info "ssh" "DEVTESTING_SSH=${DEVTESTING_SSH} amount=${DEVTESTING_AMOUNT}" +info "ssh" "DEVTESTING_SSH=${DEVTESTING_SSH} baseline=${DEVTESTING_AMOUNT} ladder=${DEVTESTING_LADDER} steps=${DEVTESTING_LADDER_STEPS} max=CHF:${DEVTESTING_LADDER_MAX}" help_out=$(ssh_dt fake-incoming --help 2>&1) || true if printf '%s' "$help_out" | grep -q 'fake-incoming'; then @@ -77,7 +134,7 @@ set_group geniban iban_out=$(ssh_dt geniban 2>&1) || true iban=$(printf '%s\n' "$iban_out" | grep -E '^CH[0-9A-Z]+$' | tail -1 || true) if [ -n "$iban" ]; then - ok "geniban" "synthetic IBAN $iban" + ok "geniban" "synthetic IBAN $iban (debit / “withdraw” side for each rung)" else fail "geniban" "expected CH… IBAN" "$(printf '%s' "$iban_out" | tr '\n' ' ' | head -c 160)" exit 1 @@ -92,55 +149,174 @@ if [ -z "$credit_payto" ]; then fi ok "credit-payto" "$credit_payto" -# Subject: not a real reserve — nexus should bounce missing reserve public key -# (proves CHF fake-incoming path). Real reserve pub would credit wirewatch. -subject="mon-fake-franken-$(date -u +%Y%m%d%H%M%S)-$$" debit_payto="payto://iban/${iban}?receiver-name=MonDevTest" +ts_base=$(date -u +%Y%m%d%H%M%S) -set_group fake-incoming +# --- baseline plumbing probe --- +set_group baseline +subject="mon-fake-franken-${ts_base}-base-$$" info "fake-incoming" "amount=${DEVTESTING_AMOUNT} subject=${subject}" -fi_rc=0 -fi_out=$(ssh_dt fake-incoming \ - --amount "${DEVTESTING_AMOUNT}" \ - --subject "${subject}" \ - --credit-payto "${credit_payto}" \ - --debit-payto "${debit_payto}" 2>&1) || fi_rc=$? - -# Normalize multi-line for matching -fi_flat=$(printf '%s' "$fi_out" | tr '\n' ' ') - -if printf '%s' "$fi_flat" | grep -qiE 'Permission denied|Connection refused|Could not resolve|No such command'; then - fail "fake-incoming" "SSH/CLI failure" "$(printf '%s' "$fi_flat" | head -c 200)" - exit 1 -fi - -if printf '%s' "$fi_flat" | grep -qiE 'Creditor must be the exchange'; then - fail "fake-incoming" "wrong credit IBAN (not exchange)" "$(printf '%s' "$fi_flat" | head -c 220)" - exit 1 -fi - -if printf '%s' "$fi_flat" | grep -qiE 'Missing amount|unable to read secrets|CalledProcessError'; then - # secrets warn alone is soft if we still see IN line - if ! printf '%s' "$fi_flat" | grep -qE 'libeufin-nexus - IN .* CHF'; then - fail "fake-incoming" "nexus rejected request" "$(printf '%s' "$fi_flat" | head -c 220)" - exit 1 - fi -fi - -if printf '%s' "$fi_flat" | grep -qE 'libeufin-nexus - IN .* CHF:[0-9]'; then - if printf '%s' "$fi_flat" | grep -qiE 'bounced.*missing reserve public key'; then - ok "fake-incoming" "CHF IN accepted by nexus · bounced (no reserve pub) — fake-franken plumbing OK" - info "note" "use real reserve pub as --subject for wirewatch credit (not required for mon probe)" - elif printf '%s' "$fi_flat" | grep -qiE 'bounced'; then - warn "fake-incoming" "CHF IN bounced (other reason)" "$(printf '%s' "$fi_flat" | grep -oiE 'bounced[^ ]* [^ ]*' | head -c 160)" - else - ok "fake-incoming" "CHF IN recorded by nexus (no bounce line)" - fi -elif [ "$fi_rc" -eq 0 ] && printf '%s' "$fi_flat" | grep -qi 'Faking incoming CHF'; then - ok "fake-incoming" "CLI completed CHF fake-incoming" +if run_fake_incoming "$DEVTESTING_AMOUNT" "$subject" "$credit_payto" "$debit_payto"; then + case "$FI_STATUS" in + ok_bounce) + ok "fake-incoming" "CHF IN accepted by nexus · bounce/missing reserve pub — plumbing OK" + info "note" "use real reserve pub as --subject for wirewatch credit (not required for mon probe)" + ;; + ok_in) ok "fake-incoming" "CHF IN recorded by nexus (no bounce line)" ;; + ok_cli) ok "fake-incoming" "CLI completed CHF fake-incoming" ;; + *) ok "fake-incoming" "status=${FI_STATUS}" ;; + esac else - fail "fake-incoming" "no CHF IN line from nexus" "rc=${fi_rc} $(printf '%s' "$fi_flat" | head -c 200)" - exit 1 + case "$FI_STATUS" in + fail_ssh) + fail "fake-incoming" "SSH/CLI failure" "$(printf '%s' "$FI_FLAT" | head -c 200)" + exit 1 + ;; + fail_creditor) + fail "fake-incoming" "wrong credit IBAN (not exchange)" "$(printf '%s' "$FI_FLAT" | head -c 220)" + exit 1 + ;; + fail_nexus) + fail "fake-incoming" "nexus rejected request" "$(printf '%s' "$FI_FLAT" | head -c 220)" + exit 1 + ;; + *) + fail "fake-incoming" "no CHF IN line from nexus" "rc=${FI_RC} $(printf '%s' "$FI_FLAT" | head -c 200)" + exit 1 + ;; + esac +fi + +# --- CHF amount ladder (fake-incoming rungs) --- +if [ "${DEVTESTING_LADDER}" = "1" ]; then + set_group ladder + section "devtesting · CHF fake-incoming ladder (→ ${DEVTESTING_LADDER_MAX})" + + LADDER_FILE=$(mktemp "${TMPDIR:-/tmp}/devtesting-ladder.XXXXXX") + python3 - <<'PY' "${DEVTESTING_LADDER_MAX}" "${DEVTESTING_LADDER_STEPS}" "${DEVTESTING_LADDER_MIN}" "$LADDER_FILE" +import math, sys +from decimal import Decimal, ROUND_HALF_UP +from pathlib import Path + +max_amt = Decimal(sys.argv[1]) +steps = max(1, int(sys.argv[2])) +min_amt = Decimal(sys.argv[3]) +out = Path(sys.argv[4]) +if min_amt <= 0: + min_amt = Decimal("0.01") +if min_amt >= max_amt: + min_amt = max_amt / Decimal(1000) +# CHF: 2 decimal places +q2 = Decimal("0.01") + +def quant(v: Decimal) -> Decimal: + if v < min_amt: + v = min_amt + return v.quantize(q2, rounding=ROUND_HALF_UP) + +def fmt(v: Decimal) -> str: + q = quant(v) + if q == q.to_integral(): + return str(int(q)) + return format(q, "f") + +if steps == 1: + amts = [quant(max_amt)] +elif steps == 2: + amts = [quant(min_amt), quant(max_amt)] +else: + # Exactly `steps` log-spaced rungs from min → max (includes both ends). + # Guarantees max rung = DEVTESTING_LADDER_MAX (“enough” volume at the top). + amts = [] + lo, hi = float(min_amt), float(max_amt) + if hi <= lo: + hi = lo * 10 + for i in range(steps): + if steps == 1: + v = max_amt + else: + t = i / (steps - 1) + v = Decimal(str(math.exp(math.log(lo) + t * (math.log(hi) - math.log(lo))))) + amts.append(quant(v)) + amts[0] = quant(min_amt) + amts[-1] = quant(max_amt) + # enforce strictly increasing + for i in range(1, len(amts)): + if amts[i] <= amts[i - 1]: + amts[i] = quant(amts[i - 1] + min_amt) + amts[-1] = quant(max_amt) + if amts[-1] <= amts[-2]: + # last-but-one squeezed; leave max exact and drop collision by nudging previous + amts[-2] = quant(max(amts[-3] + min_amt if len(amts) > 2 else min_amt, max_amt - Decimal(1))) + if amts[-2] >= amts[-1]: + amts[-2] = quant(amts[-1] - min_amt) + +lines = ["CHF:%s" % fmt(a) for a in amts] +out.write_text("\n".join(lines) + "\n") +PY + n_rungs=$(grep -cE '^CHF:' "$LADDER_FILE" || echo 0) + ladder_sum=$(python3 -c ' +from decimal import Decimal +s=Decimal(0) +for ln in open("'"$LADDER_FILE"'"): + ln=ln.strip() + if not ln: continue + s += Decimal(ln.split(":",1)[-1]) +print(s) +' 2>/dev/null || echo "?") + + info "ladder plan" "steps=${n_rungs} max=CHF:${DEVTESTING_LADDER_MAX} min=CHF:${DEVTESTING_LADDER_MIN} sum≈CHF:${ladder_sum} (synthetic debit geniban each rung)" + info "ladder funding" "each rung uses geniban debit payto — no real bank withdraw; volume covers max rung CHF:${DEVTESTING_LADDER_MAX}" + + LADDER_OK=0 + LADDER_FAIL=0 + LADDER_WARN=0 + rung=0 + while IFS= read -r amt || [ -n "$amt" ]; do + [ -n "$amt" ] || continue + rung=$((rung + 1)) + subject="mon-fake-ladder-${ts_base}-r${rung}-$$" + info "rung ${rung}/${n_rungs}" "amount=${amt} subject=${subject}" + if run_fake_incoming "$amt" "$subject" "$credit_payto" "$debit_payto"; then + case "$FI_STATUS" in + ok_bounce) + ok "rung ${rung}" "${amt} · nexus IN + bounce (plumbing OK)" + LADDER_OK=$((LADDER_OK + 1)) + ;; + ok_in) + ok "rung ${rung}" "${amt} · nexus IN recorded" + LADDER_OK=$((LADDER_OK + 1)) + ;; + ok_cli) + ok "rung ${rung}" "${amt} · CLI ok" + LADDER_OK=$((LADDER_OK + 1)) + ;; + *) + warn "rung ${rung}" "${amt} · status=${FI_STATUS}" + LADDER_WARN=$((LADDER_WARN + 1)) + ;; + esac + else + fail "rung ${rung}" "${amt} · ${FI_STATUS}" "$(printf '%s' "$FI_FLAT" | head -c 180)" + LADDER_FAIL=$((LADDER_FAIL + 1)) + if [ "${DEVTESTING_LADDER_STOP_ON_FAIL}" = "1" ]; then + warn "ladder stop" "DEVTESTING_LADDER_STOP_ON_FAIL=1 after rung ${rung}" + break + fi + fi + done <"$LADDER_FILE" + rm -f "$LADDER_FILE" + + info "ladder summary" "ok=${LADDER_OK} warn=${LADDER_WARN} fail=${LADDER_FAIL} / planned=${n_rungs} · max=CHF:${DEVTESTING_LADDER_MAX}" + if [ "$LADDER_FAIL" -gt 0 ] && [ "$LADDER_OK" -eq 0 ]; then + fail "ladder" "all rungs failed" + elif [ "$LADDER_FAIL" -gt 0 ]; then + warn "ladder" "partial failures fail=${LADDER_FAIL} ok=${LADDER_OK}" + else + ok "ladder complete" "ok=${LADDER_OK} through max CHF:${DEVTESTING_LADDER_MAX}" + fi +else + info "ladder" "skipped (DEVTESTING_LADDER=0)" fi info "done" "devtesting fake-franken probe finished" diff --git a/host-agent/env/mytops-stage.env.example b/host-agent/env/mytops-stage.env.example index 5db18bf..0da4f19 100644 --- a/host-agent/env/mytops-stage.env.example +++ b/host-agent/env/mytops-stage.env.example @@ -18,7 +18,8 @@ SKIP_SSH=1 LOCAL_STACK=0 INSIDE_PODMAN=0 CONTINUE_ON_ERROR=1 -RUN_TIMEOUT=600 +# ladder needs wall time (23× fake-incoming SSH) +RUN_TIMEOUT=1800 STRICT_EXIT=1 MONPAGES_REQUIRE_PUBLIC=1 MONPAGES_INVENTORY=job @@ -32,4 +33,9 @@ CHECK_LANDING=0 # rusty devtesting (SSH Host taler-rusty-devtesting → devtesting@rusty.taler-ops.ch) DEVTESTING_SSH=taler-rusty-devtesting DEVTESTING_AMOUNT=CHF:1.00 +# CHF fake-incoming amount ladder (23 rungs → 50k) +DEVTESTING_LADDER=1 +DEVTESTING_LADDER_STEPS=23 +DEVTESTING_LADDER_MAX=50000 +DEVTESTING_LADDER_MIN=0.01 # DEVTESTING_SKIP=1 diff --git a/taler-monitoring.sh b/taler-monitoring.sh index dd2b6d4..67ddf76 100755 --- a/taler-monitoring.sh +++ b/taler-monitoring.sh @@ -448,9 +448,14 @@ _progress_estimate_phase() { ;; mattermost) n=25 ;; mail) n=40 ;; - # Happy path: ssh + cli + geniban + credit-payto + fake-incoming×2 + note + done ≈ 8 - # (was 10 → mid-run 42/48 then snap 46/46 after last phase) - devtesting|franken|fake-franken|fake_franken) n=8 ;; + # baseline ~8 + ladder rungs (default 23 × ~2 lines) + summary + devtesting|franken|fake-franken|fake_franken) + n=8 + if [ "${DEVTESTING_LADDER:-1}" != "0" ]; then + _ls="${DEVTESTING_LADDER_STEPS:-23}" + n=$((8 + _ls * 2 + 6)) + fi + ;; *) n=15 ;; esac printf '%s' "$n" From 66fa471d004f327988bbd3840d393586458b658f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 04:41:24 +0200 Subject: [PATCH 02/42] release 1.15.1: SUMMARY severity backgrounds on mon HTML; fix section paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase section() ╔═║╚ boxes are class section (no sum-header). Real SUMMARY rows keep solid OK/WARN/ERROR/INFO backgrounds on the web. --- VERSION | 2 +- VERSIONS.md | 1 + lib.sh | 6 +- site-gen/console_to_html.py | 124 +++++++++++++++++++++++------------- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/VERSION b/VERSION index 141f2e8..ace4423 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.0 +1.15.1 diff --git a/VERSIONS.md b/VERSIONS.md index f1ac38d..45ca9b2 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). | | **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. | | **v1.14.0** | 2026-07-19 | **Feature:** SUMMARY box rows are **colour-distinct** (per-severity badge + tinted background + bold counts; verdict-coloured header); HTML mon pages style `sum-ok` / `sum-warn` / `sum-err` / … panels the same way. | | **v1.13.13** | 2026-07-19 | **Bugfix:** progress estimate for **devtesting** / fake-franken is 8 (happy-path numbered checks), not 10 — avoids mid-run 42/48 then end snap 46/46. | diff --git a/lib.sh b/lib.sh index 358e6a6..d9ca60c 100755 --- a/lib.sh +++ b/lib.sh @@ -921,9 +921,11 @@ _summary_totals_box() { local ROW_BLK=$'\e[1;97;48;5;53m' # bold white on dark magenta local ROW_TOT=$'\e[1;37;48;5;236m' # bold white on dark gray - printf -- '\n%s╔══════════════════════════════════════╗%s\n' "$hdr_bg" "$N" + # Include SUMMARY on all three frame lines so HTML classify maps them to sum-header + # (phase section() boxes use the same ╔═║╚ glyphs but must not get sum-* paint). + printf -- '\n%s╔════════ %s ════════════════════╗%s\n' "$hdr_bg" "$_sum_lbl" "$N" printf -- '%s║ %-34s ║%s\n' "$hdr_bg" "$_sum_lbl" "$N" - printf -- '%s╚══════════════════════════════════════╝%s\n' "$hdr_bg" "$N" + printf -- '%s╚════════ %s ════════════════════╝%s\n' "$hdr_bg" "$_sum_lbl" "$N" # $1=row_bg $2=badge_bg $3=tag $4=n $5=label_fg — full-width-ish row _sum_row() { diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 6633f87..43ffcd2 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -277,19 +277,13 @@ def is_numbered_check_line(line: str) -> bool: def classify(line: str) -> str: u = line.upper() low = line.lower() - # section / summary headers before badge matching (they contain the word ERROR) - if re.search(r"[╔═║╚]|SUMMARY", u) and ( - "SUMMARY" in u or "═══" in line or "╔" in line or "╚" in line - ): - if "SUMMARY" in u or ("╔" in line and "═" in line) or ("╚" in line and "═" in line): - if "SUMMARY" in u or (not is_numbered_check_line(line) and ("╔" in line or "╚" in line or "║" in line)): - if "SUMMARY" in u or re.match(r"^\s*[╔╚║]", line): - if "SUMMARY" in u: - return "sum-header" - if re.match(r"^\s*[╔╚]", line) and "═" in line: - return "sum-header" - if re.match(r"^\s*║", line) and not is_numbered_check_line(line): - return "sum-header" + # SUMMARY box header only when the word SUMMARY is present (not phase section() boxes). + # Phase section() also uses ╔═║╚ with blue BG_SEC — those must stay class "section". + if "SUMMARY" in u: + return "sum-header" + # Double-line box frames from section() → section (no sum-* paint) + if re.match(r"^\s*[╔╚].*═", line) or re.match(r"^\s*║\s", line): + return "section" if "--- ERRORS" in u or "ERRORS ·" in u or "ERRORS (" in u: return "meta" if "numbered checks" in low: @@ -1883,59 +1877,99 @@ main {{ padding: 12px 14px 48px; max-width: 1200px; }} .line.warn {{ color: var(--warn); }} .line.info {{ color: var(--info); }} .line.blocker {{ color: var(--blocker); }} -.line.section {{ color: var(--sec); margin-top: 8px; font-weight: 600; }} +/* Phase section titles (╔═║╚ from section()) — light chrome, NOT sum-* paint */ +.line.section {{ + color: #9cf; + margin-top: 10px; + font-weight: 700; + background: transparent; + border-left: 3px solid #345; + padding: 2px 0 2px 8px; +}} .line.meta {{ color: var(--dim); }} .line.plain {{ color: var(--fg); }} -/* v1.14.0: SUMMARY box rows — distinct bg + bold per severity */ +/* SUMMARY box — full-width solid backgrounds matching terminal ROW_* tints */ +.line.sum-header, +.line.sum-ok, +.line.sum-err, +.line.sum-warn, +.line.sum-info, +.line.sum-block, +.line.sum-totals, +.line.sum-verdict-ok, +.line.sum-verdict-warn, +.line.sum-verdict-err {{ + display: block; + box-sizing: border-box; + width: 100%; + margin: 2px 0; + padding: 6px 10px; + border-radius: 3px; + font-weight: 800; + letter-spacing: 0.02em; +}} .line.sum-header {{ - color: #fff; font-weight: 800; letter-spacing: 0.04em; - background: linear-gradient(90deg, #1a3a1a 0%, #0a1a0a 100%); - border-left: 4px solid var(--ok); padding: 4px 8px; margin-top: 10px; + color: #e8ffe8; + background: #145214; /* solid green header (not blue) */ + border: 1px solid #2a8a2a; + border-left: 6px solid #3d3; + margin-top: 14px; + text-shadow: 0 1px 0 #0008; }} .line.sum-ok {{ - color: #b8f0b8; font-weight: 700; - background: #0d1f0d; border-left: 4px solid var(--ok); - padding: 3px 8px; margin: 2px 0; + color: #d4ffd4; + background: #0f3d0f; + border: 1px solid #1a6b1a; + border-left: 6px solid var(--ok); }} .line.sum-err {{ - color: #ffc9c9; font-weight: 700; - background: #2a0a0a; border-left: 4px solid var(--err); - padding: 3px 8px; margin: 2px 0; + color: #ffe0e0; + background: #4a1010; + border: 1px solid #822; + border-left: 6px solid var(--err); }} .line.sum-warn {{ - color: #1a1400; font-weight: 800; - background: #c9a227; border-left: 4px solid #8a7010; - padding: 3px 8px; margin: 2px 0; + color: #1a1400; + background: #e6c200; /* bright gold — clearly not blue */ + border: 1px solid #a68b00; + border-left: 6px solid #8a7010; }} .line.sum-info {{ - color: #c8e7ff; font-weight: 700; - background: #0a1520; border-left: 4px solid var(--info); - padding: 3px 8px; margin: 2px 0; + color: #e0f2ff; + background: #0c2a4a; /* navy INFO row only */ + border: 1px solid #1a4a7a; + border-left: 6px solid var(--info); }} .line.sum-block {{ - color: #f5d0ff; font-weight: 700; - background: #1a0a1a; border-left: 4px solid var(--blocker); - padding: 3px 8px; margin: 2px 0; + color: #ffe0ff; + background: #3a1040; + border: 1px solid #6a2a70; + border-left: 6px solid var(--blocker); }} .line.sum-totals {{ - color: #eee; font-weight: 700; - background: #1a1a1a; border-left: 4px solid #666; - padding: 4px 8px; margin: 4px 0 2px; + color: #f0f0f0; + background: #2a2a2a; + border: 1px solid #555; + border-left: 6px solid #888; + margin-top: 6px; }} .line.sum-verdict-ok {{ - color: #b8f0b8; font-weight: 800; - background: #0d1f0d; border: 1px solid #2a5a2a; - padding: 4px 8px; margin: 4px 0 8px; + color: #d4ffd4; + background: #0f3d0f; + border: 2px solid #2a8a2a; + margin-bottom: 10px; }} .line.sum-verdict-warn {{ - color: #1a1400; font-weight: 800; - background: #c9a227; border: 1px solid #8a7010; - padding: 4px 8px; margin: 4px 0 8px; + color: #1a1400; + background: #e6c200; + border: 2px solid #8a7010; + margin-bottom: 10px; }} .line.sum-verdict-err {{ - color: #ffc9c9; font-weight: 800; - background: #2a0a0a; border: 1px solid #622; - padding: 4px 8px; margin: 4px 0 8px; + color: #ffe0e0; + background: #4a1010; + border: 2px solid #a33; + margin-bottom: 10px; }} a.jump {{ color: inherit; text-decoration: underline dotted; }} footer {{ From bf3a2e34dc7bcbeadf15010a5e1340e9f4130d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 04:43:49 +0200 Subject: [PATCH 03/42] =?UTF-8?q?release=201.15.2:=20compact=20warn/error?= =?UTF-8?q?=20filter=20=E2=80=94=20no=20black=20voids?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Omit blank log lines from mon HTML. Filter mode collapses non-matches to zero size, uses tighter useful context, and a minimal cluster gap. --- VERSION | 2 +- VERSIONS.md | 1 + site-gen/console_to_html.py | 118 ++++++++++++++++++++++++++++-------- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/VERSION b/VERSION index ace4423..42cf067 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.1 +1.15.2 diff --git a/VERSIONS.md b/VERSIONS.md index 45ca9b2..470fe10 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.15.2** | 2026-07-19 | **Bugfix:** warn/error filter — drop blank log lines from HTML; compact console (no black voids above/mid/below); context skips SUMMARY chrome & pure box frames; tighter filter-gap. | | **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). | | **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. | | **v1.14.0** | 2026-07-19 | **Feature:** SUMMARY box rows are **colour-distinct** (per-severity badge + tinted background + bold counts; verdict-coloured header); HTML mon pages style `sum-ok` / `sum-warn` / `sum-err` / … panels the same way. | diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 43ffcd2..89dd4ee 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -549,6 +549,10 @@ def slug_error(i: int, text: str) -> str: def render_line(ln: str, err_slugs: dict[str, str], extra_ids: list[str] | None = None) -> str: + # Drop blank log lines entirely — they only create black voids in the console + # (especially visible under warn/error filter). Anchors still attach to next real line. + if not (ln or "").strip(): + return "" kind = classify(ln) esc = html.escape(ln) for tid, slug in err_slugs.items(): @@ -563,8 +567,11 @@ def render_line(ln: str, err_slugs: dict[str, str], extra_ids: list[str] | None # HTML allows one id — join carefully id_attr = f' id="{html.escape(extra_ids[0])}"' if len(extra_ids) > 1: - # secondary anchors as empty spans prepended - spans = "".join(f'' for x in extra_ids[1:]) + # secondary anchors as empty spans prepended (inline, zero layout impact) + spans = "".join( + f'' + for x in extra_ids[1:] + ) return f'{spans}{esc}\n' return f'{esc}\n' @@ -1175,38 +1182,83 @@ body.filter-warn .filter-chip { border-color: #a68b2d; background: #1a1608; colo padding: 1px 8px; } .filter-clear:hover { border-color: #aaa; background: #222; } -/* Console filter: sticky + overviews stay; match lines + dimmed neighbours (v1.13.11) */ -body.filter-error #mon-console .line:not(.error):not(.blocker):not(.filter-ctx) { display: none !important; } -body.filter-warn #mon-console .line:not(.warn):not(.filter-ctx) { display: none !important; } +/* Console filter: sticky + overviews stay; match lines + dimmed neighbours (v1.13.11+) + v1.15.2: stamp out black voids — hide blanks, zero-size non-matches, compact console */ +body.filter-error #mon-console, +body.filter-warn #mon-console { + padding: 6px 8px; + min-height: 0; + display: flex; + flex-direction: column; + gap: 1px; +} +body.filter-error #mon-console .line:not(.error):not(.blocker):not(.filter-ctx), +body.filter-warn #mon-console .line:not(.warn):not(.filter-ctx) { + display: none !important; + height: 0 !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; + overflow: hidden !important; + line-height: 0 !important; +} +/* empty / whitespace-only lines never take space in filter mode */ +body.filter-error #mon-console .line:empty, +body.filter-warn #mon-console .line:empty, +body.filter-error #mon-console .line.line-blank, +body.filter-warn #mon-console .line.line-blank { + display: none !important; +} /* Context lines: grayed / muted so the warn|error still stands out */ body.filter-error #mon-console .line.filter-ctx, body.filter-warn #mon-console .line.filter-ctx { - opacity: 0.48; - color: #7a7a7a !important; - filter: grayscale(0.9); + opacity: 0.55; + color: #8a8a8a !important; + filter: grayscale(0.85); + margin: 0 !important; + padding: 1px 4px !important; + border: none !important; + background: transparent !important; } body.filter-error #mon-console .line.filter-ctx a.jump, body.filter-warn #mon-console .line.filter-ctx a.jump { color: #6a6a6a !important; } -/* Visual break when filter jumps across a gap in the log */ +/* Match lines stay tight */ +body.filter-error #mon-console .line.error, +body.filter-error #mon-console .line.blocker, +body.filter-warn #mon-console .line.warn { + margin: 1px 0 !important; + padding: 3px 6px !important; +} +/* Compact ellipsis between distant clusters (not a black slab) */ body.filter-error #mon-console .filter-gap, body.filter-warn #mon-console .filter-gap { display: block; - color: #555; + color: #666; text-align: center; - padding: 6px 0; - margin: 4px 0; - letter-spacing: 0.35em; - font-size: 11px; + padding: 0; + margin: 2px 0; + letter-spacing: 0.25em; + font-size: 10px; + line-height: 1.2; user-select: none; - border-top: 1px dashed #333; - border-bottom: 1px dashed #333; + border: none; + background: transparent; + height: auto; } body:not(.filter-error):not(.filter-warn) #mon-console .filter-gap { display: none !important; } body.filter-error .env-context, body.filter-warn .env-context { display: none !important; } body.filter-warn .err-top { display: none; } +/* secondary jump anchors: never affect layout */ +#mon-console .anchor-only { + display: inline; + width: 0; + height: 0; + overflow: hidden; + position: absolute; +} /* v1.12.0: collapsible monitoring env context (default closed) */ .env-context { margin: 0 0 12px; @@ -1436,14 +1488,14 @@ STICKY_JS = """ } // v1.11.0: click error/warn stats → filter log; sticky + overviews stay - // v1.13.11: keep a few neighbour lines (dimmed) so phase/group context stays readable + // v1.13.11: dimmed neighbour context; v1.15.2: stamp out black voids, compact layout var bar = document.getElementById("status-bar"); var chip = document.getElementById("filter-chip"); var chipLabel = document.getElementById("filter-chip-label"); var clearBtn = document.getElementById("filter-clear"); var consoleEl = document.getElementById("mon-console"); - // lines above + below each match (same count either side) - var FILTER_CTX = 3; + // useful neighbours each side (skip blanks / SUMMARY chrome / pure box frames) + var FILTER_CTX = 2; function currentFilter() { if (document.body.classList.contains("filter-error")) return "error"; @@ -1459,11 +1511,13 @@ STICKY_JS = """ consoleEl.querySelectorAll(".filter-gap").forEach(function (el) { el.parentNode && el.parentNode.removeChild(el); }); + document.body.classList.remove("filter-compact"); } function markFilterContext(mode) { clearFilterContext(); if (!consoleEl || !mode) return; + document.body.classList.add("filter-compact"); var lines = Array.prototype.slice.call(consoleEl.querySelectorAll(".line")); if (!lines.length) return; function isMatch(el) { @@ -1477,19 +1531,30 @@ STICKY_JS = """ var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); return t.length === 0; } - // 0 = hide, 1 = match, 2 = context + function isLowValue(el) { + if (isBlankLine(el)) return true; + if (el.classList.contains("sum-header") || el.classList.contains("sum-ok") + || el.classList.contains("sum-warn") || el.classList.contains("sum-err") + || el.classList.contains("sum-info") || el.classList.contains("sum-block") + || el.classList.contains("sum-totals") || el.classList.contains("sum-verdict-ok") + || el.classList.contains("sum-verdict-warn") || el.classList.contains("sum-verdict-err")) { + return true; + } + var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); + if (/^[╔╗╚╝║═\-\s│┌┐└┘]+$/.test(t)) return true; + return false; + } var keep = new Array(lines.length); var i, j, k, n, idx; for (i = 0; i < lines.length; i++) keep[i] = 0; for (i = 0; i < lines.length; i++) { if (!isMatch(lines[i])) continue; keep[i] = 1; - // ±FILTER_CTX *non-empty* neighbours (skip blank lines so filter is not full of voids) n = 0; idx = i - 1; while (idx >= 0 && n < FILTER_CTX) { if (keep[idx] === 1) { idx--; continue; } - if (isBlankLine(lines[idx])) { idx--; continue; } + if (isLowValue(lines[idx])) { idx--; continue; } keep[idx] = 2; n++; idx--; @@ -1498,7 +1563,7 @@ STICKY_JS = """ idx = i + 1; while (idx < lines.length && n < FILTER_CTX) { if (keep[idx] === 1) { idx++; continue; } - if (isBlankLine(lines[idx])) { idx++; continue; } + if (isLowValue(lines[idx])) { idx++; continue; } keep[idx] = 2; n++; idx++; @@ -1506,10 +1571,11 @@ STICKY_JS = """ } for (j = 0; j < lines.length; j++) { if (keep[j] === 2) lines[j].classList.add("filter-ctx"); - // never show pure blank lines in filter mode - if (isBlankLine(lines[j]) && keep[j] !== 1) keep[j] = 0; + if (isBlankLine(lines[j])) { + keep[j] = 0; + lines[j].classList.add("line-blank"); + } } - // dashed gap between non-contiguous visible clusters var prevVisible = -2; for (k = 0; k < lines.length; k++) { if (keep[k] === 0) continue; From ae8a72a5f3cdc3d2a551fdb0dde9c73f5936c2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 04:45:12 +0200 Subject: [PATCH 04/42] release 1.15.3: auto-French console for lefrancpaysan domains Do not treat a non-empty TALER_MON_LANG from env as explicit language. Domain *lefrancpaysan* selects fr unless TALER_MON_LANG_SET=1 (--lang). --- VERSION | 2 +- VERSIONS.md | 1 + i18n.sh | 21 ++++++++++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index 42cf067..f2380cc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.2 +1.15.3 diff --git a/VERSIONS.md b/VERSIONS.md index 470fe10..b900ef4 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.15.3** | 2026-07-19 | **Bugfix:** console language auto-**fr** for `*lefrancpaysan*` domains again — env `TALER_MON_LANG=en` no longer locks English; only `--lang` / `TALER_MON_LANG_SET=1` is explicit. | | **v1.15.2** | 2026-07-19 | **Bugfix:** warn/error filter — drop blank log lines from HTML; compact console (no black voids above/mid/below); context skips SUMMARY chrome & pure box frames; tighter filter-gap. | | **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). | | **v1.15.0** | 2026-07-19 | **Feature:** CHF **fake-incoming amount ladder** in `devtesting` (`DEVTESTING_LADDER=1`, default **23** log-spaced rungs **CHF:0.01 → CHF:50000**); synthetic geniban debit funds each rung; baseline plumbing probe kept. | diff --git a/i18n.sh b/i18n.sh index 5ca1d18..1b0c90d 100644 --- a/i18n.sh +++ b/i18n.sh @@ -1,23 +1,29 @@ #!/usr/bin/env bash # i18n.sh — language for taler-monitoring console + HTML chrome # -# TALER_MON_LANG=en|fr (default en) -# Auto: domains *lefrancpaysan* → fr unless TALER_MON_LANG is set explicitly. +# TALER_MON_LANG=en|fr +# TALER_MON_LANG_SET=1 → language is explicit (--lang or host-agent wrapper); do not override. +# Auto (SET≠1): domain *lefrancpaysan* / *francpaysan* → fr, else keep env or en. +# +# Important: a bare TALER_MON_LANG=en in ~/.config/taler-monitoring/env must NOT lock +# language for FP domains (that used to force English on stage.monnaie…). : "${TALER_MON_LANG:=}" : "${TALER_MON_LANG_SET:=0}" i18n_init() { - if [ -n "${TALER_MON_LANG:-}" ]; then - TALER_MON_LANG_SET=1 - fi - if [ "${TALER_MON_LANG_SET}" != "1" ]; then + # Only TALER_MON_LANG_SET=1 is "explicit". Do not promote non-empty TALER_MON_LANG + # to SET=1 — env files often ship LANG=en for the default GOA workstation. + if [ "${TALER_MON_LANG_SET:-0}" != "1" ]; then case "${TALER_DOMAIN:-}" in *lefrancpaysan*|*francpaysan*) TALER_MON_LANG=fr ;; *) - TALER_MON_LANG=en + # Non-FP: keep pre-set env value if any, else English + if [ -z "${TALER_MON_LANG:-}" ]; then + TALER_MON_LANG=en + fi ;; esac fi @@ -26,6 +32,7 @@ i18n_init() { *) TALER_MON_LANG=en ;; esac export TALER_MON_LANG + export TALER_MON_LANG_SET } i18n_tag() { From b4f5f060ccb07bfe405b82d7515cfe67b4bee975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 04:51:42 +0200 Subject: [PATCH 05/42] release 1.15.4: French sticky titles and SUMMARY verdict strings Localize monitoring host label, stack summary (public + interne), and SUMMARY all-clear / warnings-only / totals for FR console and HTML. --- VERSION | 2 +- VERSIONS.md | 1 + i18n.sh | 5 +++++ lib.sh | 18 ++++++++------- site-gen/console_to_html.py | 44 +++++++++++++++++++++++++++++-------- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index f2380cc..e34208c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.3 +1.15.4 diff --git a/VERSIONS.md b/VERSIONS.md index b900ef4..ee61b4c 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.15.4** | 2026-07-19 | **Bugfix:** French sticky titles — page label *surveillance*, stack summary *public + interne*, SUMMARY verdict/totals via i18n (`tout est clair`, `totaux :`). | | **v1.15.3** | 2026-07-19 | **Bugfix:** console language auto-**fr** for `*lefrancpaysan*` domains again — env `TALER_MON_LANG=en` no longer locks English; only `--lang` / `TALER_MON_LANG_SET=1` is explicit. | | **v1.15.2** | 2026-07-19 | **Bugfix:** warn/error filter — drop blank log lines from HTML; compact console (no black voids above/mid/below); context skips SUMMARY chrome & pure box frames; tighter filter-gap. | | **v1.15.1** | 2026-07-19 | **Bugfix:** mon HTML — phase `section()` boxes no longer painted as SUMMARY (were blue/green `sum-header`); only real SUMMARY rows get solid severity backgrounds (OK/WARN/ERROR/INFO). | diff --git a/i18n.sh b/i18n.sh index 1b0c90d..a5a2285 100644 --- a/i18n.sh +++ b/i18n.sh @@ -73,6 +73,11 @@ i18n_text() { "pay/withdraw cannot finish") printf 'paiement/retrait impossible a terminer' ;; "SUMMARY") printf 'RESUME' ;; "totals:") printf 'totaux :' ;; + "all clear") printf 'tout est clair' ;; + "warnings only (no hard fail)") printf 'avertissements seulement (pas d'\''echec dur)' ;; + "monitoring") printf 'surveillance' ;; + "surface") printf 'surface' ;; + "aptdeploy") printf 'aptdeploy' ;; "container") printf 'conteneur' ;; "disk") printf 'disque' ;; "package") printf 'paquet' ;; diff --git a/lib.sh b/lib.sh index d9ca60c..4e7f137 100755 --- a/lib.sh +++ b/lib.sh @@ -944,7 +944,7 @@ _summary_totals_box() { [ "$blk_n" -gt 0 ] && _sum_row "$ROW_BLK" "$BG_BLK" "BLOCK" "$blk_n" "$M" # totals rollup on tinted bar - printf -- ' %s %s totals: %s%d OK%s' "$ROW_TOT" "$B" "$G$B" "$pass_n" "$N$ROW_TOT" + printf -- ' %s %s %s %s%d OK%s' "$ROW_TOT" "$B" "$(i18n_text 'totals:')" "$G$B" "$pass_n" "$N$ROW_TOT" [ "$fail_n" -gt 0 ] && printf -- ' %s·%s %s%d ERROR%s' "$D" "$N$ROW_TOT" "$R$B" "$fail_n" "$N$ROW_TOT" [ "$warn_n" -gt 0 ] && printf -- ' %s·%s %s%d WARN%s' "$D" "$N$ROW_TOT" "$Y$B" "$warn_n" "$N$ROW_TOT" [ "$info_n" -gt 0 ] && printf -- ' %s·%s %s%d INFO%s' "$D" "$N$ROW_TOT" "$C$B" "$info_n" "$N$ROW_TOT" @@ -957,28 +957,30 @@ _summary_totals_box() { [ "$warn_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag WARN)" "$Y$B" "$warn_n" "$N" [ "$info_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag INFO)" "$C$B" "$info_n" "$N" [ "$blk_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag BLOCK)" "$M$B" "$blk_n" "$N" - printf -- ' %stotals:%s %s%d OK%s' "$B" "$N" "$G$B" "$pass_n" "$N" + printf -- ' %s%s%s %s%d OK%s' "$B" "$(i18n_text 'totals:')" "$N" "$G$B" "$pass_n" "$N" [ "$fail_n" -gt 0 ] && printf -- ' · %s%d ERROR%s' "$R$B" "$fail_n" "$N" [ "$warn_n" -gt 0 ] && printf -- ' · %s%d WARN%s' "$Y$B" "$warn_n" "$N" [ "$info_n" -gt 0 ] && printf -- ' · %s%d INFO%s' "$C$B" "$info_n" "$N" [ "$blk_n" -gt 0 ] && printf -- ' · %s%d BLOCKER%s' "$M$B" "$blk_n" "$N" printf '\n' fi - # overall verdict — bold label, filled badge + # overall verdict — bold label, filled badge (i18n for FR console/HTML) + local _ok_msg _warn_msg _fail_msg + _ok_msg=$(i18n_text 'all clear') + _warn_msg=$(i18n_text 'warnings only (no hard fail)') if [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ] && [ "$warn_n" -eq 0 ]; then if [ "${BOX:-0}" = "1" ]; then - printf -- ' %s┌ OK ┐%s %s%sall clear%s\n' "$BG_OK" "$N" "$G" "$B" "$N" + printf -- ' %s┌ OK ┐%s %s%s%s%s\n' "$BG_OK" "$N" "$G" "$B" "$_ok_msg" "$N" else - printf -- ' %s[ OK ] %sall clear%s\n' "$G" "$B" "$N" + printf -- ' %s[ OK ] %s%s%s\n' "$G" "$B" "$_ok_msg" "$N" fi elif [ "$fail_n" -eq 0 ] && [ "$blk_n" -eq 0 ]; then if [ "${BOX:-0}" = "1" ]; then - printf -- ' %s┌ WARN ┐%s %s%swarnings only (no hard fail)%s\n' "$BG_WARN" "$N" "$Y" "$B" "$N" + printf -- ' %s┌ WARN ┐%s %s%s%s%s\n' "$BG_WARN" "$N" "$Y" "$B" "$_warn_msg" "$N" else - printf -- ' %s[ WARN ] %swarnings only (no hard fail)%s\n' "$Y" "$B" "$N" + printf -- ' %s[ WARN ] %s%s%s\n' "$Y" "$B" "$_warn_msg" "$N" fi else - local _fail_msg _fail_msg=$(i18n_text 'failed — see list above') if [ "${BOX:-0}" = "1" ]; then printf -- ' %s┌ ERROR ┐%s %s%s%s%s\n' "$BG_ERR" "$N" "$R" "$B" "$_fail_msg" "$N" diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 89dd4ee..0874c1e 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -114,6 +114,12 @@ def ui(lang: str, key: str, **kwargs) -> str: "stack_i4": "HTML host: {host}", "stack_i5": "Timer: host-agent path + 4h · /monitoring on bank, exchange, merchant fronts", "stack_i_phases": "Log header phases= {phases}", + "page_label_monitoring": "monitoring", + "page_label_surface": "surface", + "page_label_aptdeploy": "aptdeploy", + "mode_ok": "OK", + "mode_err": "ERR", + "mode_redirect": "REDIR", "pages_title": "Monitoring pages (suite HTML)", "pages_summary": "Public sticky-bar reports · phase monpages · part of taler-monitoring", "pages_i1": "Suite name taler-monitoring ≠ URL. Only three public families (bare + slash each):", @@ -200,14 +206,20 @@ def ui(lang: str, key: str, **kwargs) -> str: "focus_merchant": "Backend marchand / SPA HTTPS public + contrôles inside conteneur", "focus_stack": "Pile Taler public + inside", "stack_title": "Surveillance de pile · {host}", - "stack_summary": "{phases} · public + inside", + "stack_summary": "{phases} · public + interne", "stack_i1": "Focus : {focus}", "stack_i2": "Phases de cette exécution : {phases}", "stack_i3": "Typique : URL publiques (HTTPS, QR, perf), inside (podman/ssh), versions des paquets", "stack_i4": "Hôte HTML : {host}", "stack_i5": "Minuterie : host-agent path + 4 h · /monitoring sur bank, exchange, fronts marchands", "stack_i_phases": "En-tête de journal phases= {phases}", - "pages_title": "Pages de monitoring (HTML de la suite)", + "page_label_monitoring": "surveillance", + "page_label_surface": "surface", + "page_label_aptdeploy": "aptdeploy", + "mode_ok": "OK", + "mode_err": "ERR", + "mode_redirect": "REDIR", + "pages_title": "Pages de monitoring (HTML de la suite)", "pages_summary": "Rapports publics sticky-bar · phase monpages · partie de taler-monitoring", "pages_i1": "Nom de suite taler-monitoring ≠ URL. Trois familles publiques seulement (sans et avec /) :", "pages_i2": "1) /monitoring(+_err) · 2) /taler-monitoring-surface(+_err) · 3) /taler-monitoring-aptdeploy(+_err) sur taler.hacktivism.ch", @@ -288,14 +300,14 @@ def classify(line: str) -> str: return "meta" if "numbered checks" in low: return "meta" - if "totals:" in low: + if "totals:" in low or "totaux :" in low or low.strip().startswith("totaux"): return "sum-totals" # SUMMARY verdict chrome (not a check) — keep distinct for HTML boxes - if "warnings only" in low: + if "warnings only" in low or "avertissements seulement" in low: return "sum-verdict-warn" - if "all clear" in low: + if "all clear" in low or "tout est clair" in low: return "sum-verdict-ok" - if "failed — see" in low or "failed - see" in low: + if "failed — see" in low or "failed - see" in low or "echec — voir" in low or "échec — voir" in low: return "sum-verdict-err" # host-agent tees converter "wrote … (errors=N …)" into the same log if line.lstrip().lower().startswith("wrote ") and "errors=" in low: @@ -443,8 +455,12 @@ def is_summary_error_line(ln: str) -> bool: return True if "warnings only" in low or "all clear" in low: return True + if "avertissements seulement" in low or "tout est clair" in low: + return True + if "totaux" in low and not is_numbered_check_line(ln): + return True # SUMMARY count rows without #NNN / tid (e.g. " [ ERROR ] 3") - if re.search(r"[\[\u250c]\s*(ERROR|WARN|OK|INFO|BLOCK)", ln, re.I) and not is_numbered_check_line( + if re.search(r"[\[\u250c]\s*(ERROR|WARN|OK|INFO|BLOCK|ERREUR|AVERT)", ln, re.I) and not is_numbered_check_line( ln ): return True @@ -1066,6 +1082,16 @@ def sticky_bar_html( version_html = f'{html.escape(ver)}' else: version_html = "" + # Localize page label for sticky host title (monitoring → surveillance in FR) + pl = (page_label or "monitoring").lower().replace("_", "-") + if "surface" in pl: + pl_disp = ui(lang, "page_label_surface") + elif "aptdeploy" in pl or "apt-deploy" in pl: + pl_disp = ui(lang, "page_label_aptdeploy") + else: + pl_disp = ui(lang, "page_label_monitoring") + mode_key = f"mode_{mode}" if mode in ("ok", "err", "redirect") else "" + mode_disp = ui(lang, mode_key) if mode_key and ui(lang, mode_key) != mode_key else mode.upper() # Compact sticky row always visible; "Was geprüft wird" expands inside sticky-bar return f"""