i18n: English default, French for FrancPaysan (v1.1)
This commit is contained in:
parent
a2afe690b7
commit
2465347fd4
13 changed files with 483 additions and 86 deletions
|
|
@ -360,3 +360,7 @@ LADDER_LOAD=0 ./taler-monitoring.sh ladder
|
||||||
- SSH `koopa` or `koopa-external` (inside / load / sanity server bits)
|
- SSH `koopa` or `koopa-external` (inside / load / sanity server bits)
|
||||||
- secrets under `koopa-admin-secrets/...` for e2e
|
- secrets under `koopa-admin-secrets/...` for e2e
|
||||||
- `taler-wallet-cli` for e2e
|
- `taler-wallet-cli` for e2e
|
||||||
|
|
||||||
|
## Languages (i18n)
|
||||||
|
|
||||||
|
Default **English**. FrancPaysan stacks use **French** (`TALER_MON_LANG=fr`, auto for `*lefrancpaysan*`). See `VERSIONS.md` and `i18n.sh`.
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.0.0
|
1.1.0
|
||||||
30
VERSIONS.md
30
VERSIONS.md
|
|
@ -1,14 +1,36 @@
|
||||||
# VERSIONS — taler-monitoring
|
# VERSIONS — taler-monitoring
|
||||||
|
|
||||||
|
Release history for the standalone **taler-monitoring** suite
|
||||||
|
(https://git.hacktivism.ch/hernani/taler-monitoring).
|
||||||
|
|
||||||
| Tag | Date (UTC) | Notes |
|
| Tag | Date (UTC) | Notes |
|
||||||
|-----|------------|--------|
|
|-----|------------|--------|
|
||||||
| **v1.0** | 2026-07-18 | Initial standalone release. Split from `koopa-admin-log` at `scripts/taler-monitoring/` (tip after monitoring host-agent/site-gen split commits). |
|
| **v1.1** | 2026-07-18 | **i18n**: default UI/console language **English**; **French** for FrancPaysan (`TALER_MON_LANG=fr`, auto on `*lefrancpaysan*`). Sticky bar “what this monitors” EN/FR; badge/summary chrome + message translation via `i18n.sh`. |
|
||||||
|
| **v1.0** | 2026-07-18 | Initial standalone release. Split from `koopa-admin-log` (`scripts/taler-monitoring/`). Host-agent HTML, surface, aptdeploy, sticky bar. |
|
||||||
|
|
||||||
Source: https://git.hacktivism.ch/hernani/taler-monitoring
|
## Language (v1.1+)
|
||||||
|
|
||||||
|
| Context | Language |
|
||||||
|
|---------|----------|
|
||||||
|
| Default / GOA / hacktivism | **English** |
|
||||||
|
| FrancPaysan prod + stage (`*lefrancpaysan*`) | **French** (auto or `TALER_MON_LANG=fr`) |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# force
|
||||||
|
TALER_MON_LANG=fr ./taler-monitoring.sh -d lefrancpaysan.ch urls
|
||||||
|
TALER_MON_LANG=en ./taler-monitoring.sh -d lefrancpaysan.ch urls
|
||||||
|
```
|
||||||
|
|
||||||
|
Host-agent env examples:
|
||||||
|
- `host-agent/env/francpaysan.env.example` → `TALER_MON_LANG=fr`
|
||||||
|
- `host-agent/env/stagepaysan.env.example` → `TALER_MON_LANG=fr`
|
||||||
|
- `host-agent/env/hacktivism.env.example` → `TALER_MON_LANG=en`
|
||||||
|
|
||||||
|
## Pin a release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.hacktivism.ch/hernani/taler-monitoring.git ~/src/taler-monitoring
|
git clone https://git.hacktivism.ch/hernani/taler-monitoring.git ~/src/taler-monitoring
|
||||||
cd ~/src/taler-monitoring && git checkout v1.0 # or stay on main
|
cd ~/src/taler-monitoring && git checkout v1.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Default host-agent tracks `origin/main` (`SUITE_UPDATE_MODE=reset`). Pin with `SUITE_GIT_REF=v1.0` in `~/.config/taler-monitoring/env`.
|
Default host-agent tracks `origin/main`. Pin with `SUITE_GIT_REF=v1.1` in `~/.config/taler-monitoring/env`.
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,4 @@
|
||||||
# HTML_OUT=$HOME/monitoring-sites-staging
|
# HTML_OUT=$HOME/monitoring-sites-staging
|
||||||
# MON_HOSTS="bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch"
|
# MON_HOSTS="bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch"
|
||||||
# SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
# SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
||||||
|
# TALER_MON_LANG=en # en default; fr for FrancPaysan (auto if domain *lefrancpaysan*)
|
||||||
|
|
|
||||||
3
host-agent/env/francpaysan.env.example
vendored
3
host-agent/env/francpaysan.env.example
vendored
|
|
@ -22,3 +22,6 @@ SUITE_UPDATE_MODE=reset
|
||||||
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
||||||
|
|
||||||
# DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
# DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
||||||
|
|
||||||
|
# UI language for console + HTML sticky bar (en|fr)
|
||||||
|
TALER_MON_LANG=fr
|
||||||
|
|
|
||||||
2
host-agent/env/hacktivism.env.example
vendored
2
host-agent/env/hacktivism.env.example
vendored
|
|
@ -22,3 +22,5 @@ SUITE_UPDATE_MODE=reset
|
||||||
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
||||||
|
|
||||||
DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
||||||
|
|
||||||
|
TALER_MON_LANG=en
|
||||||
|
|
|
||||||
3
host-agent/env/stagepaysan.env.example
vendored
3
host-agent/env/stagepaysan.env.example
vendored
|
|
@ -25,3 +25,6 @@ SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
|
||||||
|
|
||||||
# Optional public docroot if this user can write it
|
# Optional public docroot if this user can write it
|
||||||
# DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
# DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
||||||
|
|
||||||
|
# UI language for console + HTML sticky bar (en|fr)
|
||||||
|
TALER_MON_LANG=fr
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,15 @@ export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
|
||||||
export AUTH401_CONTINUE="${AUTH401_CONTINUE:-${CONTINUE_ON_ERROR}}"
|
export AUTH401_CONTINUE="${AUTH401_CONTINUE:-${CONTINUE_ON_ERROR}}"
|
||||||
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
|
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
|
||||||
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
|
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
|
||||||
|
# UI language: en default; fr for FrancPaysan (override with TALER_MON_LANG)
|
||||||
|
if [ -z "${TALER_MON_LANG:-}" ]; then
|
||||||
|
case "$TALER_DOMAIN" in
|
||||||
|
*lefrancpaysan*|*francpaysan*) export TALER_MON_LANG=fr ;;
|
||||||
|
*) export TALER_MON_LANG=en ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
export TALER_MON_LANG
|
||||||
|
|
||||||
PHASES="${PHASES:-urls inside versions}"
|
PHASES="${PHASES:-urls inside versions}"
|
||||||
HTML_BASE="${HTML_OUT:-$HOME/monitoring-sites-staging}"
|
HTML_BASE="${HTML_OUT:-$HOME/monitoring-sites-staging}"
|
||||||
MON_HOSTS="${MON_HOSTS:-}"
|
MON_HOSTS="${MON_HOSTS:-}"
|
||||||
|
|
@ -285,6 +294,7 @@ htmlify_host() {
|
||||||
|
|
||||||
if [ -n "$SITE_GEN" ] && [ -f "$SITE_GEN/console_to_html.py" ]; then
|
if [ -n "$SITE_GEN" ] && [ -f "$SITE_GEN/console_to_html.py" ]; then
|
||||||
python3 "$SITE_GEN/console_to_html.py" \
|
python3 "$SITE_GEN/console_to_html.py" \
|
||||||
|
--lang "${TALER_MON_LANG:-en}" \
|
||||||
--log "$LOG" \
|
--log "$LOG" \
|
||||||
--out "$mon_err" \
|
--out "$mon_err" \
|
||||||
--hostname "$host" \
|
--hostname "$host" \
|
||||||
|
|
@ -297,6 +307,7 @@ htmlify_host() {
|
||||||
--link-other "$HTML_URL_OK"
|
--link-other "$HTML_URL_OK"
|
||||||
if [ "$ec" -eq 0 ]; then
|
if [ "$ec" -eq 0 ]; then
|
||||||
python3 "$SITE_GEN/console_to_html.py" \
|
python3 "$SITE_GEN/console_to_html.py" \
|
||||||
|
--lang "${TALER_MON_LANG:-en}" \
|
||||||
--log "$LOG" \
|
--log "$LOG" \
|
||||||
--out "$mon" \
|
--out "$mon" \
|
||||||
--hostname "$host" \
|
--hostname "$host" \
|
||||||
|
|
@ -311,6 +322,7 @@ htmlify_host() {
|
||||||
echo "html $host → ${HTML_URL_OK} only (clean · ${COMMIT_SHORT:-?})"
|
echo "html $host → ${HTML_URL_OK} only (clean · ${COMMIT_SHORT:-?})"
|
||||||
else
|
else
|
||||||
python3 "$SITE_GEN/console_to_html.py" \
|
python3 "$SITE_GEN/console_to_html.py" \
|
||||||
|
--lang "${TALER_MON_LANG:-en}" \
|
||||||
--log "$LOG" \
|
--log "$LOG" \
|
||||||
--out "$mon" \
|
--out "$mon" \
|
||||||
--hostname "$host" \
|
--hostname "$host" \
|
||||||
|
|
|
||||||
101
i18n.sh
Normal file
101
i18n.sh
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/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:=}"
|
||||||
|
: "${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
|
||||||
|
case "${TALER_DOMAIN:-}" in
|
||||||
|
*lefrancpaysan*|*francpaysan*)
|
||||||
|
TALER_MON_LANG=fr
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TALER_MON_LANG=en
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
case "${TALER_MON_LANG}" in
|
||||||
|
fr|FR|fra|french) TALER_MON_LANG=fr ;;
|
||||||
|
*) TALER_MON_LANG=en ;;
|
||||||
|
esac
|
||||||
|
export TALER_MON_LANG
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n_tag() {
|
||||||
|
local t="$1"
|
||||||
|
if [ "${TALER_MON_LANG:-en}" != "fr" ]; then
|
||||||
|
printf '%s' "$t"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "$t" in
|
||||||
|
OK) printf 'OK' ;;
|
||||||
|
ERROR) printf 'ERREUR' ;;
|
||||||
|
WARN) printf 'AVERT' ;;
|
||||||
|
INFO) printf 'INFO' ;;
|
||||||
|
BLOCKER) printf 'BLOCAGE' ;;
|
||||||
|
BLOCK) printf 'BLOC' ;;
|
||||||
|
SUMMARY) printf 'RESUME' ;;
|
||||||
|
PROG|PROGRESS) printf 'PROG' ;;
|
||||||
|
*) printf '%s' "$t" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n_text() {
|
||||||
|
local s="$*"
|
||||||
|
if [ "${TALER_MON_LANG:-en}" != "fr" ] || [ -z "$s" ]; then
|
||||||
|
printf '%s' "$s"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "$s" in
|
||||||
|
"numbered checks this run:") printf 'controles numerotes de cette execution :' ;;
|
||||||
|
"failed — see list above"|"failed - see list above") printf 'echec — voir la liste ci-dessus' ;;
|
||||||
|
"BLOCKERS · pay/withdraw cannot finish") printf 'BLOCAGES · paiement/retrait impossible' ;;
|
||||||
|
"BLOCKERS (pay/withdraw cannot finish)") printf 'BLOCAGES (paiement/retrait impossible)' ;;
|
||||||
|
"ERRORS · failed checks") printf 'ERREURS · controles en echec' ;;
|
||||||
|
"ERRORS (failed checks)") printf 'ERREURS (controles en echec)' ;;
|
||||||
|
"pay/withdraw cannot finish") printf 'paiement/retrait impossible a terminer' ;;
|
||||||
|
"SUMMARY") printf 'RESUME' ;;
|
||||||
|
"totals:") printf 'totaux :' ;;
|
||||||
|
"container") printf 'conteneur' ;;
|
||||||
|
"disk") printf 'disque' ;;
|
||||||
|
"package") printf 'paquet' ;;
|
||||||
|
"version") printf 'version' ;;
|
||||||
|
"reachability") printf 'accessibilite' ;;
|
||||||
|
"server-header") printf 'en-tete Server' ;;
|
||||||
|
"inventory") printf 'inventaire' ;;
|
||||||
|
"flags") printf 'options' ;;
|
||||||
|
"target domain") printf 'domaine cible' ;;
|
||||||
|
"currency") printf 'devise' ;;
|
||||||
|
"phases") printf 'phases' ;;
|
||||||
|
"access") printf 'acces' ;;
|
||||||
|
*)
|
||||||
|
local out="$s"
|
||||||
|
out=${out//shared library missing/bibliotheque partagee manquante}
|
||||||
|
out=${out//shared library error/erreur de bibliotheque partagee}
|
||||||
|
out=${out//not active/non actif}
|
||||||
|
out=${out//after_start/apres_demarrage}
|
||||||
|
out=${out//catalogued but not reachable/catalogue mais inaccessible}
|
||||||
|
out=${out//does not resolve/ne resout pas}
|
||||||
|
out=${out//protocol confirmed/protocole confirme}
|
||||||
|
out=${out//catalogued service OK/service catalogue OK}
|
||||||
|
out=${out//from Server header/depuis en-tete Server}
|
||||||
|
out=${out//OSV clean/OSV sans vulnerabilite}
|
||||||
|
out=${out//actionable vuln/vuln. actionnable}
|
||||||
|
out=${out//header version · not Debian pkg/version en-tete · pas paquet Debian}
|
||||||
|
out=${out//RUN_TIMEOUT exceeded/RUN_TIMEOUT depasse}
|
||||||
|
out=${out//public HTTPS/HTTPS public}
|
||||||
|
out=${out//container status/etat du conteneur}
|
||||||
|
out=${out//remote-only/uniquement distant}
|
||||||
|
out=${out//no SSH/sans SSH}
|
||||||
|
out=${out//failed — see list above/echec — voir la liste ci-dessus}
|
||||||
|
printf '%s' "$out"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
61
lib.sh
61
lib.sh
|
|
@ -2,6 +2,18 @@
|
||||||
# Shared helpers for taler-monitoring (laptop or koopa).
|
# Shared helpers for taler-monitoring (laptop or koopa).
|
||||||
|
|
||||||
# Default stack = GOA / hacktivism (overridden by TALER_DOMAIN / --domain)
|
# Default stack = GOA / hacktivism (overridden by TALER_DOMAIN / --domain)
|
||||||
|
# i18n (en default; fr for FrancPaysan — see i18n.sh / TALER_MON_LANG)
|
||||||
|
_I18N_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
if [ -f "$_I18N_DIR/i18n.sh" ]; then
|
||||||
|
# shellcheck source=i18n.sh
|
||||||
|
source "$_I18N_DIR/i18n.sh"
|
||||||
|
i18n_init
|
||||||
|
else
|
||||||
|
i18n_tag() { printf '%s' "$1"; }
|
||||||
|
i18n_text() { printf '%s' "$*"; }
|
||||||
|
i18n_init() { :; }
|
||||||
|
fi
|
||||||
|
|
||||||
: "${TALER_DOMAIN:=hacktivism.ch}"
|
: "${TALER_DOMAIN:=hacktivism.ch}"
|
||||||
: "${BANK_PUBLIC:=https://bank.hacktivism.ch}"
|
: "${BANK_PUBLIC:=https://bank.hacktivism.ch}"
|
||||||
: "${EXCHANGE_PUBLIC:=https://exchange.hacktivism.ch}"
|
: "${EXCHANGE_PUBLIC:=https://exchange.hacktivism.ch}"
|
||||||
|
|
@ -114,6 +126,11 @@ set_taler_stack() {
|
||||||
BANK_PUBLIC=${BANK_PUBLIC%/}
|
BANK_PUBLIC=${BANK_PUBLIC%/}
|
||||||
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
|
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
|
||||||
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
||||||
|
# re-evaluate language after domain profile (FrancPaysan → fr)
|
||||||
|
if [ "${TALER_MON_LANG_SET:-0}" != "1" ]; then
|
||||||
|
TALER_MON_LANG=
|
||||||
|
i18n_init
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load first matching profile from domains.conf.
|
# Load first matching profile from domains.conf.
|
||||||
|
|
@ -265,6 +282,11 @@ apply_taler_domain() {
|
||||||
BANK_PUBLIC=${BANK_PUBLIC%/}
|
BANK_PUBLIC=${BANK_PUBLIC%/}
|
||||||
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
|
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
|
||||||
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
||||||
|
# re-evaluate language after domain profile (FrancPaysan → fr)
|
||||||
|
if [ "${TALER_MON_LANG_SET:-0}" != "1" ]; then
|
||||||
|
TALER_MON_LANG=
|
||||||
|
i18n_init
|
||||||
|
fi
|
||||||
|
|
||||||
# Koopa SSH only for LOCAL_STACK=1. Stage LFP uses INSIDE_SSH (stagepaysan) separately.
|
# Koopa SSH only for LOCAL_STACK=1. Stage LFP uses INSIDE_SSH (stagepaysan) separately.
|
||||||
if [ "${LOCAL_STACK}" != "1" ]; then
|
if [ "${LOCAL_STACK}" != "1" ]; then
|
||||||
|
|
@ -679,6 +701,10 @@ _fmt_badge() {
|
||||||
# $1=badge style $2=tag text $3=label colour $4=label $5=detail
|
# $1=badge style $2=tag text $3=label colour $4=label $5=detail
|
||||||
_msg_line() {
|
_msg_line() {
|
||||||
local badge="$1" tag="$2" lcol="$3" label="$4" detail="${5:-}"
|
local badge="$1" tag="$2" lcol="$3" label="$4" detail="${5:-}"
|
||||||
|
# i18n: tags + free text (en default; fr for FrancPaysan)
|
||||||
|
tag=$(i18n_tag "$tag")
|
||||||
|
label=$(i18n_text "$label")
|
||||||
|
[ -n "$detail" ] && detail=$(i18n_text "$detail")
|
||||||
if [ -n "$detail" ]; then
|
if [ -n "$detail" ]; then
|
||||||
printf -- '%s %s%s%s%s %s·%s %s%s%s\n' \
|
printf -- '%s %s%s%s%s %s·%s %s%s%s\n' \
|
||||||
"$(_fmt_badge "$badge" "$tag")" "$(_fmt_tid)" "$lcol" "$label" "$N" "$D" "$N" "$D" "$detail" "$N"
|
"$(_fmt_badge "$badge" "$tag")" "$(_fmt_tid)" "$lcol" "$label" "$N" "$D" "$N" "$D" "$detail" "$N"
|
||||||
|
|
@ -749,7 +775,8 @@ blocker() {
|
||||||
}
|
}
|
||||||
section() {
|
section() {
|
||||||
# Boxed section header (3 lines when colour on)
|
# Boxed section header (3 lines when colour on)
|
||||||
local title="$*"
|
local title
|
||||||
|
title=$(i18n_text "$*")
|
||||||
local w=${#title}
|
local w=${#title}
|
||||||
[ "$w" -lt 24 ] && w=24
|
[ "$w" -lt 24 ] && w=24
|
||||||
[ "$w" -gt 56 ] && w=56
|
[ "$w" -gt 56 ] && w=56
|
||||||
|
|
@ -783,13 +810,13 @@ summary() {
|
||||||
_progress_bar_line "$PROGRESS_DONE" "$PROGRESS_TOTAL"
|
_progress_bar_line "$PROGRESS_DONE" "$PROGRESS_TOTAL"
|
||||||
fi
|
fi
|
||||||
if [ "$GLOBAL_N" -gt 0 ]; then
|
if [ "$GLOBAL_N" -gt 0 ]; then
|
||||||
printf -- '%s numbered checks this run: #001…#%03d%s\n' "$D" "$GLOBAL_N" "$N"
|
printf -- '%s %s #001…#%03d%s\n' "$D" "$(i18n_text 'numbered checks this run:')" "$GLOBAL_N" "$N"
|
||||||
fi
|
fi
|
||||||
if [ "$blk_n" -gt 0 ]; then
|
if [ "$blk_n" -gt 0 ]; then
|
||||||
if [ "${BOX:-0}" = "1" ]; then
|
if [ "${BOX:-0}" = "1" ]; then
|
||||||
printf -- '%s┌ BLOCKERS · pay/withdraw cannot finish ┐%s\n' "$BG_BLK" "$N"
|
printf -- '%s┌ %s ┐%s\n' "$BG_BLK" "$(i18n_text 'BLOCKERS · pay/withdraw cannot finish')" "$N"
|
||||||
else
|
else
|
||||||
printf -- '%s--- BLOCKERS (pay/withdraw cannot finish) ---%s\n' "$M" "$N"
|
printf -- '%s--- %s ---%s\n' "$M" "$(i18n_text 'BLOCKERS (pay/withdraw cannot finish)')" "$N"
|
||||||
fi
|
fi
|
||||||
local b
|
local b
|
||||||
for b in "${BLOCKERS[@]}"; do
|
for b in "${BLOCKERS[@]}"; do
|
||||||
|
|
@ -798,9 +825,9 @@ summary() {
|
||||||
fi
|
fi
|
||||||
if [ "${#ERRORS[@]}" -gt 0 ] && [ "$blk_n" -lt "${#ERRORS[@]}" ]; then
|
if [ "${#ERRORS[@]}" -gt 0 ] && [ "$blk_n" -lt "${#ERRORS[@]}" ]; then
|
||||||
if [ "${BOX:-0}" = "1" ]; then
|
if [ "${BOX:-0}" = "1" ]; then
|
||||||
printf -- '%s┌ ERRORS · failed checks ┐%s\n' "$BG_ERR" "$N"
|
printf -- '%s┌ %s ┐%s\n' "$BG_ERR" "$(i18n_text 'ERRORS · failed checks')" "$N"
|
||||||
else
|
else
|
||||||
printf -- '%s--- ERRORS (failed checks) ---%s\n' "$R" "$N"
|
printf -- '%s--- %s ---%s\n' "$R" "$(i18n_text 'ERRORS (failed checks)')" "$N"
|
||||||
fi
|
fi
|
||||||
local e
|
local e
|
||||||
for e in "${ERRORS[@]}"; do
|
for e in "${ERRORS[@]}"; do
|
||||||
|
|
@ -811,9 +838,11 @@ summary() {
|
||||||
|
|
||||||
# Coloured totals — same severity badges as check lines (always; NO_COLOR blanks ANSI)
|
# Coloured totals — same severity badges as check lines (always; NO_COLOR blanks ANSI)
|
||||||
if [ "${BOX:-0}" = "1" ]; then
|
if [ "${BOX:-0}" = "1" ]; then
|
||||||
printf -- '\n%s┌ SUMMARY ┐%s\n' "$BG_SEC" "$N"
|
printf -- '\n%s┌ %s ┐%s\n' "$BG_SEC" "$(i18n_tag SUMMARY)" "$N"
|
||||||
_sum_badge() { # $1=bg $2=tag $3=fg $4=n
|
_sum_badge() { # $1=bg $2=tag $3=fg $4=n
|
||||||
printf -- ' %s┌ %-5s┐%s %s%4d%s\n' "$1" "$2" "$N" "$3" "$4" "$N"
|
local _tg
|
||||||
|
_tg=$(i18n_tag "$2")
|
||||||
|
printf -- ' %s┌ %-5s┐%s %s%4d%s\n' "$1" "$_tg" "$N" "$3" "$4" "$N"
|
||||||
}
|
}
|
||||||
_sum_badge "$BG_OK" "OK" "$G" "$PASS_N"
|
_sum_badge "$BG_OK" "OK" "$G" "$PASS_N"
|
||||||
[ "$FAIL_N" -gt 0 ] && _sum_badge "$BG_ERR" "ERROR" "$R" "$FAIL_N"
|
[ "$FAIL_N" -gt 0 ] && _sum_badge "$BG_ERR" "ERROR" "$R" "$FAIL_N"
|
||||||
|
|
@ -821,12 +850,12 @@ summary() {
|
||||||
[ "$INFO_N" -gt 0 ] && _sum_badge "$BG_INFO" "INFO" "$C" "$INFO_N"
|
[ "$INFO_N" -gt 0 ] && _sum_badge "$BG_INFO" "INFO" "$C" "$INFO_N"
|
||||||
[ "$blk_n" -gt 0 ] && _sum_badge "$BG_BLK" "BLOCK" "$M" "$blk_n"
|
[ "$blk_n" -gt 0 ] && _sum_badge "$BG_BLK" "BLOCK" "$M" "$blk_n"
|
||||||
else
|
else
|
||||||
printf -- '\n[ SUMMARY ]\n'
|
printf -- '\n[ %s ]\n' "$(i18n_tag SUMMARY)"
|
||||||
printf -- ' [ OK ] %s%4d%s\n' "$G" "$PASS_N" "$N"
|
printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag OK)" "$G" "$PASS_N" "$N"
|
||||||
[ "$FAIL_N" -gt 0 ] && printf -- ' [ ERROR ] %s%4d%s\n' "$R" "$FAIL_N" "$N"
|
[ "$FAIL_N" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag ERROR)" "$R" "$FAIL_N" "$N"
|
||||||
[ "$WARN_N" -gt 0 ] && printf -- ' [ WARN ] %s%4d%s\n' "$Y" "$WARN_N" "$N"
|
[ "$WARN_N" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag WARN)" "$Y" "$WARN_N" "$N"
|
||||||
[ "$INFO_N" -gt 0 ] && printf -- ' [ INFO ] %s%4d%s\n' "$C" "$INFO_N" "$N"
|
[ "$INFO_N" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag INFO)" "$C" "$INFO_N" "$N"
|
||||||
[ "$blk_n" -gt 0 ] && printf -- ' [ BLOCK ] %s%4d%s\n' "$M" "$blk_n" "$N"
|
[ "$blk_n" -gt 0 ] && printf -- ' [ %-5s ] %s%4d%s\n' "$(i18n_tag BLOCK)" "$M" "$blk_n" "$N"
|
||||||
fi
|
fi
|
||||||
# one-line rollup (bold label, severity-coloured counts)
|
# one-line rollup (bold label, severity-coloured counts)
|
||||||
printf -- ' %stotals:%s %s%d OK%s' "$B" "$N" "$G" "$PASS_N" "$N"
|
printf -- ' %stotals:%s %s%d OK%s' "$B" "$N" "$G" "$PASS_N" "$N"
|
||||||
|
|
@ -850,9 +879,9 @@ summary() {
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ "${BOX:-0}" = "1" ]; then
|
if [ "${BOX:-0}" = "1" ]; then
|
||||||
printf -- ' %s┌ ERROR ┐%s %sfailed — see list above%s\n' "$BG_ERR" "$N" "$R" "$N"
|
printf -- ' %s┌ ERROR ┐%s %s"$(i18n_text "failed — see list above")"%s\n' "$BG_ERR" "$N" "$R" "$N"
|
||||||
else
|
else
|
||||||
printf -- ' %s[ ERROR ] failed — see list above%s\n' "$R" "$N"
|
printf -- ' %s[ ERROR ] "$(i18n_text "failed — see list above")"%s\n' "$R" "$N"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ "$FAIL_N" -eq 0 ]
|
[ "$FAIL_N" -eq 0 ]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,129 @@ import re
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
def ui_lang(explicit: str = "") -> str:
|
||||||
|
"""en (default) or fr. Env: TALER_MON_LANG; auto fr for lefrancpaysan hosts."""
|
||||||
|
import os
|
||||||
|
lang = (explicit or os.environ.get("TALER_MON_LANG") or "").strip().lower()
|
||||||
|
if lang in ("fr", "fra", "french"):
|
||||||
|
return "fr"
|
||||||
|
if lang in ("en", "eng", "english"):
|
||||||
|
return "en"
|
||||||
|
return "en"
|
||||||
|
|
||||||
|
|
||||||
|
def ui(lang: str, key: str, **kwargs) -> str:
|
||||||
|
"""UI chrome strings for sticky bar (English default; French for FrancPaysan)."""
|
||||||
|
en = {
|
||||||
|
"errors": "ERRORS",
|
||||||
|
"warnings": "WARNINGS",
|
||||||
|
"ok": "OK",
|
||||||
|
"error_one": "{n} error",
|
||||||
|
"error_many": "{n} errors",
|
||||||
|
"warn_one": "{n} warning",
|
||||||
|
"warn_many": "{n} warnings",
|
||||||
|
"jump_first_error": "Jump to first error",
|
||||||
|
"jump_first_warn": "Jump to first warning",
|
||||||
|
"generated": "generated",
|
||||||
|
"source": "source",
|
||||||
|
"what_monitors": "What this monitors",
|
||||||
|
"what_monitors_title": "Expand: what this page checks",
|
||||||
|
"surface_title": "Remote ecosystem surface inventory",
|
||||||
|
"surface_summary": "Public catalog hosts · DNS / TCP / HTTPS / TLS · Server version · CVE",
|
||||||
|
"surface_i1": "Outside-in only: no SSH and no podman exec on remote targets",
|
||||||
|
"surface_i2": "Catalog (surface-catalog.conf): taler.net, demo/test, gnunet.org, taler-ops.ch, taler-systems.com, deb.taler.net, ftp.gnu.org, …",
|
||||||
|
"surface_i3": "Per host: DNS resolve, ICMP (info), open ports, protocol probes, TLS cert expiry, Server-header software version, optional CVE check",
|
||||||
|
"surface_i4": "HTML published only on taler.hacktivism.ch (this page host: {host})",
|
||||||
|
"surface_i5": "Timer: taler-monitoring-surface.timer (hourly)",
|
||||||
|
"apt_title": "apt-src merchant deploy tests (podman on koopa)",
|
||||||
|
"apt_summary": "4 containers · fresh + upgrade tracks · trixie & trixie-testing",
|
||||||
|
"apt_i1": "koopa-taler-deploy-test-apt-src-trixie (fresh, suite trixie)",
|
||||||
|
"apt_i2": "koopa-taler-deploy-test-apt-src-trixie-testing (fresh, trixie-testing)",
|
||||||
|
"apt_i3": "koopa-taler-deploy-test-apt-src-trixie-upgrade (upgrade track)",
|
||||||
|
"apt_i4": "koopa-taler-deploy-test-apt-src-trixie-testing-upgrade (upgrade track)",
|
||||||
|
"apt_i5": "Checks: packages, taler-merchant-httpd --version / ldd, systemd unit, optional local /config probe — not public nginx/HTTPS",
|
||||||
|
"apt_i6": "HTML published only on taler.hacktivism.ch (this page host: {host})",
|
||||||
|
"apt_i7": "Timer: taler-monitoring-aptdeploy.timer (4h)",
|
||||||
|
"focus_bank": "Libeufin bank public HTTPS + container inside checks",
|
||||||
|
"focus_exchange": "Exchange public HTTPS + container inside checks",
|
||||||
|
"focus_merchant": "Merchant backend / SPA public HTTPS + container inside checks",
|
||||||
|
"focus_stack": "Taler stack public + inside checks",
|
||||||
|
"stack_title": "Stack monitoring · {host}",
|
||||||
|
"stack_summary": "{phases} · public + inside",
|
||||||
|
"stack_i1": "Focus: {focus}",
|
||||||
|
"stack_i2": "Phases this run: {phases}",
|
||||||
|
"stack_i3": "Typical: public URLs (HTTPS, QR, perf), inside (podman/ssh), package versions",
|
||||||
|
"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}",
|
||||||
|
"footer": "Console-style render of taler-monitoring output. Sticky bar: green = clean · yellow = warnings · red = errors. Commit pins the exact tree used for this run.",
|
||||||
|
"redirect_fail": "has failures.",
|
||||||
|
"redirect_see": "See {link} for the full console log, error index, and sticky status bar.",
|
||||||
|
"ago_s": "{n}s ago",
|
||||||
|
"ago_m": "{n}m ago",
|
||||||
|
"ago_h": "{n}h ago",
|
||||||
|
"ago_d": "{n}d ago",
|
||||||
|
"age_title": "page age · updates every second",
|
||||||
|
}
|
||||||
|
fr = {
|
||||||
|
"errors": "ERREURS",
|
||||||
|
"warnings": "AVERTISSEMENTS",
|
||||||
|
"ok": "OK",
|
||||||
|
"error_one": "{n} erreur",
|
||||||
|
"error_many": "{n} erreurs",
|
||||||
|
"warn_one": "{n} avertissement",
|
||||||
|
"warn_many": "{n} avertissements",
|
||||||
|
"jump_first_error": "Aller à la première erreur",
|
||||||
|
"jump_first_warn": "Aller au premier avertissement",
|
||||||
|
"generated": "généré",
|
||||||
|
"source": "source",
|
||||||
|
"what_monitors": "Ce que cette page contrôle",
|
||||||
|
"what_monitors_title": "Déplier : ce que cette page vérifie",
|
||||||
|
"surface_title": "Inventaire de surface de l'écosystème (distant)",
|
||||||
|
"surface_summary": "Hôtes du catalogue public · DNS / TCP / HTTPS / TLS · version serveur · CVE",
|
||||||
|
"surface_i1": "Uniquement de l'extérieur : pas de SSH ni de podman exec sur les cibles distantes",
|
||||||
|
"surface_i2": "Catalogue (surface-catalog.conf) : taler.net, demo/test, gnunet.org, taler-ops.ch, taler-systems.com, deb.taler.net, ftp.gnu.org, …",
|
||||||
|
"surface_i3": "Par hôte : résolution DNS, ICMP (info), ports ouverts, sondes de protocole, expiration du certificat TLS, version logicielle (en-tête Server), CVE optionnel",
|
||||||
|
"surface_i4": "HTML publié uniquement sur taler.hacktivism.ch (hôte de cette page : {host})",
|
||||||
|
"surface_i5": "Minuterie : taler-monitoring-surface.timer (horaire)",
|
||||||
|
"apt_title": "Tests de déploiement apt-src marchand (podman sur koopa)",
|
||||||
|
"apt_summary": "4 conteneurs · pistes fresh + upgrade · trixie et trixie-testing",
|
||||||
|
"apt_i1": "koopa-taler-deploy-test-apt-src-trixie (fresh, suite trixie)",
|
||||||
|
"apt_i2": "koopa-taler-deploy-test-apt-src-trixie-testing (fresh, trixie-testing)",
|
||||||
|
"apt_i3": "koopa-taler-deploy-test-apt-src-trixie-upgrade (piste upgrade)",
|
||||||
|
"apt_i4": "koopa-taler-deploy-test-apt-src-trixie-testing-upgrade (piste upgrade)",
|
||||||
|
"apt_i5": "Contrôles : paquets, taler-merchant-httpd --version / ldd, unité systemd, sonde locale /config optionnelle — pas de nginx/HTTPS public",
|
||||||
|
"apt_i6": "HTML publié uniquement sur taler.hacktivism.ch (hôte de cette page : {host})",
|
||||||
|
"apt_i7": "Minuterie : taler-monitoring-aptdeploy.timer (4 h)",
|
||||||
|
"focus_bank": "Banque Libeufin HTTPS public + contrôles inside conteneur",
|
||||||
|
"focus_exchange": "Exchange HTTPS public + contrôles inside conteneur",
|
||||||
|
"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_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}",
|
||||||
|
"footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Le commit fixe l'arbre exact de cette exécution.",
|
||||||
|
"redirect_fail": "a des échecs.",
|
||||||
|
"redirect_see": "Voir {link} pour le journal console complet, l'index d'erreurs et la barre de statut.",
|
||||||
|
"ago_s": "il y a {n}s",
|
||||||
|
"ago_m": "il y a {n} min",
|
||||||
|
"ago_h": "il y a {n} h",
|
||||||
|
"ago_d": "il y a {n} j",
|
||||||
|
"age_title": "âge de la page · mise à jour chaque seconde",
|
||||||
|
}
|
||||||
|
table = fr if lang == "fr" else en
|
||||||
|
s = table.get(key) or en.get(key) or key
|
||||||
|
try:
|
||||||
|
return s.format(**kwargs)
|
||||||
|
except Exception:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
# ANSI strip
|
# ANSI strip
|
||||||
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
||||||
ERR_BULLET_RE = re.compile(r"^\s*[•·*-]\s+(?P<body>.+)$")
|
ERR_BULLET_RE = re.compile(r"^\s*[•·*-]\s+(?P<body>.+)$")
|
||||||
|
|
@ -192,82 +315,84 @@ def extract_phases(log_text: str) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def monitoring_scope(
|
def monitoring_scope(
|
||||||
page_label: str,
|
page_label: str,
|
||||||
hostname: str,
|
hostname: str,
|
||||||
log_text: str = "",
|
log_text: str = "",
|
||||||
|
lang: str = "en",
|
||||||
) -> dict[str, object]:
|
) -> dict[str, object]:
|
||||||
"""
|
"""Human-readable description of what this page monitors (en default; fr for FP)."""
|
||||||
Human-readable description of what this monitoring page covers.
|
|
||||||
Returned keys: title, summary, items (list[str]), kind
|
|
||||||
"""
|
|
||||||
label = (page_label or "monitoring").lower().replace("_", "-")
|
label = (page_label or "monitoring").lower().replace("_", "-")
|
||||||
phases = extract_phases(log_text)
|
phases = extract_phases(log_text)
|
||||||
host = hostname or "?"
|
host = hostname or "?"
|
||||||
|
# Auto-fr for FrancPaysan hosts if lang not forced
|
||||||
|
if lang == "en" and re.search(r"lefrancpaysan|francpaysan", host + " " + log_text, re.I):
|
||||||
|
import os
|
||||||
|
if not (os.environ.get("TALER_MON_LANG") or "").strip():
|
||||||
|
lang = "fr"
|
||||||
|
|
||||||
if "surface" in label:
|
if "surface" in label:
|
||||||
return {
|
return {
|
||||||
"kind": "surface",
|
"kind": "surface",
|
||||||
"title": "Remote ecosystem surface inventory",
|
"lang": lang,
|
||||||
"summary": "Public catalog hosts · DNS / TCP / HTTPS / TLS · Server version · CVE",
|
"title": ui(lang, "surface_title"),
|
||||||
|
"summary": ui(lang, "surface_summary"),
|
||||||
"items": [
|
"items": [
|
||||||
"Outside-in only: no SSH and no podman exec on remote targets",
|
ui(lang, "surface_i1"),
|
||||||
"Catalog (surface-catalog.conf): taler.net, demo/test, gnunet.org, "
|
ui(lang, "surface_i2"),
|
||||||
"taler-ops.ch, taler-systems.com, deb.taler.net, ftp.gnu.org, …",
|
ui(lang, "surface_i3"),
|
||||||
"Per host: DNS resolve, ICMP (info), open ports, protocol probes, "
|
ui(lang, "surface_i4", host=host),
|
||||||
"TLS cert expiry, Server-header software version, optional CVE check",
|
ui(lang, "surface_i5"),
|
||||||
f"HTML published only on taler.hacktivism.ch "
|
|
||||||
f"(this page host: {host})",
|
|
||||||
"Timer: taler-monitoring-surface.timer (hourly)",
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
if "aptdeploy" in label or "apt-deploy" in label or "apt_src" in label:
|
if "aptdeploy" in label or "apt-deploy" in label or "apt_src" in label:
|
||||||
return {
|
return {
|
||||||
"kind": "aptdeploy",
|
"kind": "aptdeploy",
|
||||||
"title": "apt-src merchant deploy tests (podman on koopa)",
|
"lang": lang,
|
||||||
"summary": "4 containers · fresh + upgrade tracks · trixie & trixie-testing",
|
"title": ui(lang, "apt_title"),
|
||||||
|
"summary": ui(lang, "apt_summary"),
|
||||||
"items": [
|
"items": [
|
||||||
"koopa-taler-deploy-test-apt-src-trixie (fresh, suite trixie)",
|
ui(lang, "apt_i1"),
|
||||||
"koopa-taler-deploy-test-apt-src-trixie-testing (fresh, trixie-testing)",
|
ui(lang, "apt_i2"),
|
||||||
"koopa-taler-deploy-test-apt-src-trixie-upgrade (upgrade track)",
|
ui(lang, "apt_i3"),
|
||||||
"koopa-taler-deploy-test-apt-src-trixie-testing-upgrade (upgrade track)",
|
ui(lang, "apt_i4"),
|
||||||
"Checks: packages, taler-merchant-httpd --version / ldd, systemd unit, "
|
ui(lang, "apt_i5"),
|
||||||
"optional local /config probe — not public nginx/HTTPS",
|
ui(lang, "apt_i6", host=host),
|
||||||
f"HTML published only on taler.hacktivism.ch (this page host: {host})",
|
ui(lang, "apt_i7"),
|
||||||
"Timer: taler-monitoring-aptdeploy.timer (4h)",
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Default: stack /monitoring pages (bank, exchange, merchant)
|
|
||||||
phase_txt = phases or "urls · inside · versions"
|
phase_txt = phases or "urls · inside · versions"
|
||||||
role = "stack"
|
role = "stack"
|
||||||
if host.startswith("bank."):
|
if host.startswith("bank.") or "bank." in host:
|
||||||
role = "bank"
|
role = "bank"
|
||||||
focus = "Libeufin bank public HTTPS + container inside checks"
|
focus = ui(lang, "focus_bank")
|
||||||
elif host.startswith("exchange.") or host.startswith("stage.exchange."):
|
elif "exchange." in host:
|
||||||
role = "exchange"
|
role = "exchange"
|
||||||
focus = "Exchange public HTTPS + container inside checks"
|
focus = ui(lang, "focus_exchange")
|
||||||
elif host.startswith("taler.") or "merchant" in host or host.startswith("monnaie."):
|
elif host.startswith("taler.") or "merchant" in host or "monnaie." in host:
|
||||||
role = "merchant"
|
role = "merchant"
|
||||||
focus = "Merchant backend / SPA public HTTPS + container inside checks"
|
focus = ui(lang, "focus_merchant")
|
||||||
else:
|
else:
|
||||||
focus = "Taler stack public + inside checks"
|
focus = ui(lang, "focus_stack")
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
f"Focus: {focus}",
|
ui(lang, "stack_i1", focus=focus),
|
||||||
f"Phases this run: {phase_txt}",
|
ui(lang, "stack_i2", phases=phase_txt),
|
||||||
"Typical: public URLs (HTTPS, QR, perf), inside (podman/ssh), package versions",
|
ui(lang, "stack_i3"),
|
||||||
f"HTML host: {host}",
|
ui(lang, "stack_i4", host=host),
|
||||||
"Timer: taler-monitoring-hacktivism (path + 4h) · /monitoring on bank, exchange, taler",
|
ui(lang, "stack_i5"),
|
||||||
]
|
]
|
||||||
if phases:
|
if phases:
|
||||||
items.insert(2, f"Log header phases= {phases}")
|
items.insert(2, ui(lang, "stack_i_phases", phases=phases))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"kind": role,
|
"kind": role,
|
||||||
"title": f"Stack monitoring · {host}",
|
"lang": lang,
|
||||||
"summary": f"{phase_txt} · public + inside",
|
"title": ui(lang, "stack_title", host=host),
|
||||||
|
"summary": ui(lang, "stack_summary", phases=phase_txt),
|
||||||
"items": items,
|
"items": items,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,28 +414,41 @@ def sticky_bar_html(
|
||||||
scope: dict[str, object] | None = None,
|
scope: dict[str, object] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
level = status_level(n_err, n_warn)
|
level = status_level(n_err, n_warn)
|
||||||
if level == "red":
|
lang = "en"
|
||||||
status_txt = "ERRORS"
|
if scope and scope.get("lang"):
|
||||||
elif level == "yellow":
|
lang = str(scope.get("lang"))
|
||||||
status_txt = "WARNINGS"
|
|
||||||
else:
|
else:
|
||||||
status_txt = "OK"
|
lang = ui_lang()
|
||||||
|
# auto-fr for FP hostnames
|
||||||
|
if lang == "en" and re.search(r"lefrancpaysan|francpaysan", hostname or "", re.I):
|
||||||
|
lang = "fr"
|
||||||
|
|
||||||
|
if level == "red":
|
||||||
|
status_txt = ui(lang, "errors")
|
||||||
|
elif level == "yellow":
|
||||||
|
status_txt = ui(lang, "warnings")
|
||||||
|
else:
|
||||||
|
status_txt = ui(lang, "ok")
|
||||||
|
|
||||||
if first_err_href and n_err:
|
if first_err_href and n_err:
|
||||||
|
err_lbl = ui(lang, "error_one", n=n_err) if n_err == 1 else ui(lang, "error_many", n=n_err)
|
||||||
err_stat = (
|
err_stat = (
|
||||||
f'<a class="stat err" href="{html.escape(first_err_href)}" '
|
f'<a class="stat err" href="{html.escape(first_err_href)}" '
|
||||||
f'title="Jump to first error">{n_err} error{"s" if n_err != 1 else ""}</a>'
|
f'title="{html.escape(ui(lang, "jump_first_error"))}">{html.escape(err_lbl)}</a>'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
err_stat = f'<span class="stat err muted">{n_err} errors</span>'
|
err_lbl = ui(lang, "error_many", n=n_err)
|
||||||
|
err_stat = f'<span class="stat err muted">{html.escape(err_lbl)}</span>'
|
||||||
|
|
||||||
if first_warn_href and n_warn:
|
if first_warn_href and n_warn:
|
||||||
|
warn_lbl = ui(lang, "warn_one", n=n_warn) if n_warn == 1 else ui(lang, "warn_many", n=n_warn)
|
||||||
warn_stat = (
|
warn_stat = (
|
||||||
f'<a class="stat warn" href="{html.escape(first_warn_href)}" '
|
f'<a class="stat warn" href="{html.escape(first_warn_href)}" '
|
||||||
f'title="Jump to first warning">{n_warn} warning{"s" if n_warn != 1 else ""}</a>'
|
f'title="{html.escape(ui(lang, "jump_first_warn"))}">{html.escape(warn_lbl)}</a>'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
warn_stat = f'<span class="stat warn muted">{n_warn} warnings</span>'
|
warn_lbl = ui(lang, "warn_many", n=n_warn)
|
||||||
|
warn_stat = f'<span class="stat warn muted">{html.escape(warn_lbl)}</span>'
|
||||||
|
|
||||||
other = ""
|
other = ""
|
||||||
if link_other:
|
if link_other:
|
||||||
|
|
@ -320,7 +458,7 @@ def sticky_bar_html(
|
||||||
)
|
)
|
||||||
|
|
||||||
if scope is None:
|
if scope is None:
|
||||||
scope = monitoring_scope(page_label, hostname, "")
|
scope = monitoring_scope(page_label, hostname, "", lang=ui_lang())
|
||||||
scope_title = html.escape(str(scope.get("title") or "Monitoring"))
|
scope_title = html.escape(str(scope.get("title") or "Monitoring"))
|
||||||
scope_summary = html.escape(str(scope.get("summary") or ""))
|
scope_summary = html.escape(str(scope.get("summary") or ""))
|
||||||
scope_kind = html.escape(str(scope.get("kind") or "monitoring"))
|
scope_kind = html.escape(str(scope.get("kind") or "monitoring"))
|
||||||
|
|
@ -347,21 +485,21 @@ def sticky_bar_html(
|
||||||
<span class="generated"
|
<span class="generated"
|
||||||
data-generated-iso="{html.escape(generated_iso)}"
|
data-generated-iso="{html.escape(generated_iso)}"
|
||||||
title="{html.escape(generated_at)}">
|
title="{html.escape(generated_at)}">
|
||||||
generated <time datetime="{html.escape(generated_iso)}">{html.escape(generated_at)}</time>
|
{html.escape(ui(lang, "generated"))} <time datetime="{html.escape(generated_iso)}">{html.escape(generated_at)}</time>
|
||||||
<span class="age" id="generated-age"></span>
|
<span class="age" id="generated-age"></span>
|
||||||
</span>
|
</span>
|
||||||
<button type="button" class="scope-toggle" id="scope-toggle"
|
<button type="button" class="scope-toggle" id="scope-toggle"
|
||||||
aria-expanded="false" aria-controls="sticky-scope-panel"
|
aria-expanded="false" aria-controls="sticky-scope-panel"
|
||||||
title="Aufklappen: was diese Seite prüft">
|
title="{html.escape(ui(lang, "what_monitors_title"))}">
|
||||||
<span class="scope-chevron" aria-hidden="true"></span>
|
<span class="scope-chevron" aria-hidden="true"></span>
|
||||||
<span class="scope-toggle-label">Was geprüft wird</span>
|
<span class="scope-toggle-label">{html.escape(ui(lang, "what_monitors"))}</span>
|
||||||
<span class="scope-toggle-hint">{scope_summary}</span>
|
<span class="scope-toggle-hint">{scope_summary}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sticky-scope-panel" id="sticky-scope-panel" hidden>
|
<div class="sticky-scope-panel" id="sticky-scope-panel" hidden>
|
||||||
<div class="sticky-bar-row secondary">
|
<div class="sticky-bar-row secondary">
|
||||||
<span class="badge mode-{html.escape(mode)}">{html.escape(mode.upper())}</span>
|
<span class="badge mode-{html.escape(mode)}">{html.escape(mode.upper())}</span>
|
||||||
source {commit_html}{suite_bit}
|
{html.escape(ui(lang, "source"))} {commit_html}{suite_bit}
|
||||||
{(" · " + other) if other else ""}
|
{(" · " + other) if other else ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="scope-body">
|
<div class="scope-body">
|
||||||
|
|
@ -531,10 +669,22 @@ STICKY_JS = """
|
||||||
(function () {
|
(function () {
|
||||||
function fmtAge(sec) {
|
function fmtAge(sec) {
|
||||||
if (sec < 0) sec = 0;
|
if (sec < 0) sec = 0;
|
||||||
if (sec < 60) return sec + "s ago";
|
var lang = (document.documentElement.getAttribute("lang") || "en").toLowerCase();
|
||||||
if (sec < 3600) return Math.floor(sec / 60) + "m ago";
|
var n;
|
||||||
if (sec < 86400) return Math.floor(sec / 3600) + "h ago";
|
if (sec < 60) {
|
||||||
return Math.floor(sec / 86400) + "d ago";
|
n = sec;
|
||||||
|
return lang === "fr" ? ("il y a " + n + "s") : (n + "s ago");
|
||||||
|
}
|
||||||
|
if (sec < 3600) {
|
||||||
|
n = Math.floor(sec / 60);
|
||||||
|
return lang === "fr" ? ("il y a " + n + " min") : (n + "m ago");
|
||||||
|
}
|
||||||
|
if (sec < 86400) {
|
||||||
|
n = Math.floor(sec / 3600);
|
||||||
|
return lang === "fr" ? ("il y a " + n + " h") : (n + "h ago");
|
||||||
|
}
|
||||||
|
n = Math.floor(sec / 86400);
|
||||||
|
return lang === "fr" ? ("il y a " + n + " j") : (n + "d ago");
|
||||||
}
|
}
|
||||||
function tick() {
|
function tick() {
|
||||||
var el = document.querySelector("[data-generated-iso]");
|
var el = document.querySelector("[data-generated-iso]");
|
||||||
|
|
@ -546,7 +696,9 @@ STICKY_JS = """
|
||||||
if (isNaN(t)) return;
|
if (isNaN(t)) return;
|
||||||
var sec = Math.floor((Date.now() - t) / 1000);
|
var sec = Math.floor((Date.now() - t) / 1000);
|
||||||
age.textContent = fmtAge(sec);
|
age.textContent = fmtAge(sec);
|
||||||
age.title = "page age · updates every second";
|
age.title = (document.documentElement.getAttribute("lang") || "en").toLowerCase() === "fr"
|
||||||
|
? "âge de la page · mise à jour chaque seconde"
|
||||||
|
: "page age · updates every second";
|
||||||
}
|
}
|
||||||
tick();
|
tick();
|
||||||
setInterval(tick, 1000);
|
setInterval(tick, 1000);
|
||||||
|
|
@ -679,8 +831,15 @@ def build_html(
|
||||||
|
|
||||||
first_err_href = "#first-error" if first_err_i is not None else None
|
first_err_href = "#first-error" if first_err_i is not None else None
|
||||||
first_warn_href = "#first-warn" if first_warn_i is not None else None
|
first_warn_href = "#first-warn" if first_warn_i is not None else None
|
||||||
scope = monitoring_scope(page_label, hostname, log_text)
|
lang = ui_lang()
|
||||||
|
if re.search(r"lefrancpaysan|francpaysan", hostname + "\n" + log_text, re.I):
|
||||||
|
import os
|
||||||
|
if not (os.environ.get("TALER_MON_LANG") or "").strip():
|
||||||
|
lang = "fr"
|
||||||
|
scope = monitoring_scope(page_label, hostname, log_text, lang=lang)
|
||||||
|
|
||||||
|
page_lang = lang
|
||||||
|
footer_txt = ui(lang, "footer")
|
||||||
bar = sticky_bar_html(
|
bar = sticky_bar_html(
|
||||||
generated_at=generated_at,
|
generated_at=generated_at,
|
||||||
generated_iso=generated_iso,
|
generated_iso=generated_iso,
|
||||||
|
|
@ -698,7 +857,7 @@ def build_html(
|
||||||
)
|
)
|
||||||
|
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{html.escape(lang if "lang" in dir() else "en")}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
|
@ -775,9 +934,7 @@ footer {{
|
||||||
{"".join(body_lines)}
|
{"".join(body_lines)}
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
Console-style render of taler-monitoring output.
|
{html.escape(footer_txt)}
|
||||||
Sticky bar: green = clean · yellow = warnings · red = errors.
|
|
||||||
Commit pins the exact tree used for this run.
|
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
{STICKY_JS}
|
{STICKY_JS}
|
||||||
|
|
@ -830,7 +987,7 @@ def build_redirect_html(
|
||||||
scope=scope,
|
scope=scope,
|
||||||
)
|
)
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{html.escape(lang if "lang" in dir() else "en")}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta http-equiv="refresh" content="2; url={html.escape(link)}"/>
|
<meta http-equiv="refresh" content="2; url={html.escape(link)}"/>
|
||||||
|
|
@ -882,6 +1039,11 @@ def main() -> None:
|
||||||
default="/monitoring_err/",
|
default="/monitoring_err/",
|
||||||
help="URL path for error page (redirect target)",
|
help="URL path for error page (redirect target)",
|
||||||
)
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--lang",
|
||||||
|
default="",
|
||||||
|
help="UI language en|fr (default: env TALER_MON_LANG or auto)",
|
||||||
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--page-label",
|
"--page-label",
|
||||||
default="monitoring",
|
default="monitoring",
|
||||||
|
|
@ -889,6 +1051,9 @@ def main() -> None:
|
||||||
)
|
)
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if getattr(args, "lang", ""):
|
||||||
|
import os
|
||||||
|
os.environ["TALER_MON_LANG"] = args.lang
|
||||||
log_text = args.log.read_text(errors="replace") if args.log.is_file() else ""
|
log_text = args.log.read_text(errors="replace") if args.log.is_file() else ""
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
generated_iso = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
generated_iso = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
|
||||||
51
site-gen/settings.conf
Normal file
51
site-gen/settings.conf
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# monitoring site-gen — non-secret settings (safe as example)
|
||||||
|
# Copy: cp settings.conf.example settings.conf
|
||||||
|
# Never put passwords/tokens here.
|
||||||
|
|
||||||
|
# Runner: public DNS path (firecuda-external)
|
||||||
|
RUNNER_SSH=
|
||||||
|
RUNNER_SSH_FALLBACKS=
|
||||||
|
RUNNER_REMOTE_WORKDIR=/tmp/taler-monitoring-site-gen
|
||||||
|
MONITORING_ROOT=..
|
||||||
|
|
||||||
|
# Deploy: host Caddy on koopa (root service — not in container)
|
||||||
|
DEPLOY_SSH=koopa-external
|
||||||
|
DEPLOY_SSH_FALLBACKS=koopa
|
||||||
|
# Static files for Caddy file_server (created per hostname)
|
||||||
|
DEPLOY_WWW_ROOT=/var/www/monitoring-sites
|
||||||
|
DEPLOY_SUDO=sudo
|
||||||
|
CADDY_CONFIG=/etc/caddy/Caddyfile
|
||||||
|
CADDY_MIRROR_REL=configs/caddy/Caddyfile
|
||||||
|
# Working tree on deploy host for apply (optional)
|
||||||
|
KOOPA_CADDY_DIR=/home/hernani/koopa-caddy
|
||||||
|
|
||||||
|
# Source link for HTML footer (Forgejo)
|
||||||
|
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/koopa-admin-log
|
||||||
|
SOURCE_COMMIT_URL_TMPL={repo}/src/commit/{commit}
|
||||||
|
SOURCE_SUITE_PATH=scripts/taler-monitoring
|
||||||
|
|
||||||
|
CONTINUE_ON_ERROR=1
|
||||||
|
PHASES_ERR=urls versions
|
||||||
|
PHASES_OK=urls versions
|
||||||
|
SKIP_E2E=1
|
||||||
|
|
||||||
|
# Local work dir for logs + HTML
|
||||||
|
WORK_ROOT=/tmp/taler-monitoring-sites-work
|
||||||
|
|
||||||
|
# Sites: hostname|domain_profile (9 fronts)
|
||||||
|
# Start with hacktivism; stage + prod LFP follow
|
||||||
|
SITES="
|
||||||
|
bank.hacktivism.ch|hacktivism.ch
|
||||||
|
exchange.hacktivism.ch|hacktivism.ch
|
||||||
|
taler.hacktivism.ch|hacktivism.ch
|
||||||
|
stage.bank.lefrancpaysan.ch|stage.lefrancpaysan.ch
|
||||||
|
stage.exchange.lefrancpaysan.ch|stage.lefrancpaysan.ch
|
||||||
|
stage.monnaie.lefrancpaysan.ch|stage.lefrancpaysan.ch
|
||||||
|
bank.lefrancpaysan.ch|lefrancpaysan.ch
|
||||||
|
exchange.lefrancpaysan.ch|lefrancpaysan.ch
|
||||||
|
monnaie.lefrancpaysan.ch|lefrancpaysan.ch
|
||||||
|
"
|
||||||
|
|
||||||
|
# Only process this subset (space-separated hostnames). Empty = all SITES.
|
||||||
|
# Example for first deploy: ONLY_HOSTS=bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch
|
||||||
|
ONLY_HOSTS=
|
||||||
|
|
@ -95,6 +95,7 @@ Env (same meaning):
|
||||||
SURFACE_CVE_LEVEL=warn|error bare Server-header versions default warn
|
SURFACE_CVE_LEVEL=warn|error bare Server-header versions default warn
|
||||||
(Debian package versions default error)
|
(Debian package versions default error)
|
||||||
SURFACE_CATALOG=path override surface-catalog.conf
|
SURFACE_CATALOG=path override surface-catalog.conf
|
||||||
|
TALER_MON_LANG=en|fr console+HTML language (default en; auto fr for *lefrancpaysan*)
|
||||||
|
|
||||||
Full load (GOA / hacktivism):
|
Full load (GOA / hacktivism):
|
||||||
./taler-monitoring.sh -d hacktivism.ch full
|
./taler-monitoring.sh -d hacktivism.ch full
|
||||||
|
|
@ -116,6 +117,8 @@ EOF
|
||||||
|
|
||||||
# shellcheck source=lib.sh
|
# shellcheck source=lib.sh
|
||||||
source "$ROOT/lib.sh"
|
source "$ROOT/lib.sh"
|
||||||
|
# ensure i18n after domain may change later
|
||||||
|
i18n_init 2>/dev/null || true
|
||||||
# Optional gitignored secrets.env next to this suite (or MONITORING_SECRETS_ENV=)
|
# Optional gitignored secrets.env next to this suite (or MONITORING_SECRETS_ENV=)
|
||||||
load_monitoring_secrets_env 2>/dev/null || true
|
load_monitoring_secrets_env 2>/dev/null || true
|
||||||
|
|
||||||
|
|
@ -235,6 +238,7 @@ export LADDER_MAX_AMOUNT LADDER_INCLUDE_ZERO LADDER_INCLUDE_MAX
|
||||||
export LADDER_HIGH_FROM LADDER_HIGH_RUNGS LADDER_LOAD
|
export LADDER_HIGH_FROM LADDER_HIGH_RUNGS LADDER_LOAD
|
||||||
export LADDER_PAY LADDER_WITHDRAW_SCALE LADDER_PAY_SETTLE_ROUNDS
|
export LADDER_PAY LADDER_WITHDRAW_SCALE LADDER_PAY_SETTLE_ROUNDS
|
||||||
export TALER_DOMAIN_APPLIED=1
|
export TALER_DOMAIN_APPLIED=1
|
||||||
|
i18n_init 2>/dev/null || true
|
||||||
# Optional: auth401 and other phases may honor CONTINUE_ON_ERROR
|
# Optional: auth401 and other phases may honor CONTINUE_ON_ERROR
|
||||||
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-}"
|
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-}"
|
||||||
export AUTH401_CONTINUE="${AUTH401_CONTINUE:-${CONTINUE_ON_ERROR:-}}"
|
export AUTH401_CONTINUE="${AUTH401_CONTINUE:-${CONTINUE_ON_ERROR:-}}"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue