release 1.15.7: mon HTML SUMMARY classify + no converter noise
This commit is contained in:
parent
a5562cf5fb
commit
f7ddf783fe
4 changed files with 84 additions and 19 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.15.6
|
||||
1.15.7
|
||||
|
|
|
|||
|
|
@ -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.7** | 2026-07-19 | **Bugfix:** mon HTML — do not paint «perf/ladder summary» as SUMMARY header (restore 2 INFO lines); drop converter SyntaxWarning/noise from public log; «wrote» on stderr; host-agent PYTHONWARNINGS for convert. |
|
||||
| **v1.15.6** | 2026-07-19 | **Bugfix:** aptdeploy ERROR jump list is self-contained — systemd httpd failures include before/after state, Result/SubState, failed units, and journal snippets (no more vague «see systemd lines»); ldd/--version errors embed concrete missing libs / exit output. |
|
||||
| **v1.15.5** | 2026-07-19 | **Bugfix:** devtesting CHF ladder — `ssh` was draining ladder stdin (only rung 1/23 ran → progress 45/98 then snap 52/52); use `ssh -n` + `mapfile`; incomplete ladder is WARN not false complete. |
|
||||
| **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 :`). |
|
||||
|
|
|
|||
|
|
@ -353,7 +353,13 @@ htmlify_host() {
|
|||
mkdir -p "$HTML_BASE/$host/${HTML_OK_DIR}" "$HTML_BASE/$host/${HTML_ERR_DIR}"
|
||||
|
||||
if [ -n "$SITE_GEN" ] && [ -f "$SITE_GEN/console_to_html.py" ]; then
|
||||
python3 "$SITE_GEN/console_to_html.py" \
|
||||
# PYTHONWARNINGS: never let SyntaxWarning leak into the suite log (tee).
|
||||
# stderr → agent log file only, not the mon console stream.
|
||||
_mon_html_py() {
|
||||
PYTHONWARNINGS=ignore::SyntaxWarning \
|
||||
python3 "$SITE_GEN/console_to_html.py" "$@" 2>>"${LOG_DIR:-/tmp}/console_to_html.err"
|
||||
}
|
||||
_mon_html_py \
|
||||
--lang "${TALER_MON_LANG:-en}" \
|
||||
--log "$LOG" \
|
||||
--out "$mon_err" \
|
||||
|
|
@ -368,7 +374,7 @@ htmlify_host() {
|
|||
--path-err "$HTML_URL_ERR" \
|
||||
--link-other "$HTML_URL_OK"
|
||||
if [ "$ec" -eq 0 ]; then
|
||||
python3 "$SITE_GEN/console_to_html.py" \
|
||||
_mon_html_py \
|
||||
--lang "${TALER_MON_LANG:-en}" \
|
||||
--log "$LOG" \
|
||||
--out "$mon" \
|
||||
|
|
@ -385,7 +391,7 @@ htmlify_host() {
|
|||
rm -rf "$HTML_BASE/$host/${HTML_ERR_DIR}"
|
||||
echo "html $host → ${HTML_URL_OK} only (clean · ${COMMIT_SHORT:-?})"
|
||||
else
|
||||
python3 "$SITE_GEN/console_to_html.py" \
|
||||
_mon_html_py \
|
||||
--lang "${TALER_MON_LANG:-en}" \
|
||||
--log "$LOG" \
|
||||
--out "$mon" \
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import html
|
||||
import re
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -69,16 +70,25 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"jump_first_warn": "Jump to first warning",
|
||||
"filter_errors": "Filter: errors + a few dimmed context lines",
|
||||
"filter_warns": "Filter: warnings + a few dimmed context lines",
|
||||
"filter_infos": "Filter: info lines + a few dimmed context lines",
|
||||
"filter_oks": "Filter: OK lines + a few dimmed context lines",
|
||||
"filter_clear": "Clear filter · show all lines",
|
||||
"filter_active_err": "Filter: errors + context",
|
||||
"filter_active_warn": "Filter: warnings + context",
|
||||
"filter_active_info": "Filter: info + context",
|
||||
"filter_active_ok": "Filter: OK only + context",
|
||||
"filter_show_all": "Show all",
|
||||
"info_one": "{n} info",
|
||||
"info_many": "{n} info",
|
||||
"ok_one": "{n} ok",
|
||||
"ok_many": "{n} ok",
|
||||
"env_context": "Monitoring env context",
|
||||
"env_context_title": "Expand: agent env, suite pin, domain hosts & flags (from run header)",
|
||||
"env_context_empty": "No run header in log",
|
||||
"generated": "generated",
|
||||
"version": "version",
|
||||
"version_tree_link": "open this run’s git tree (commit)",
|
||||
"suite_version_title": "taler-monitoring suite version · exact run tree",
|
||||
|
||||
"source": "source",
|
||||
"what_monitors": "What this monitors",
|
||||
|
|
@ -147,7 +157,7 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"mail_i1": "Outside-in phase mail: MX, SMTP/IMAP ports and handshakes, SPF/DMARC",
|
||||
"mail_i2": "Mail hosts: firefly.gnunet.org (taler.net/gnunet.org) and anastasis.taler-systems.com",
|
||||
"mail_i3": "Reported on taler-monitoring-surface HTML (no separate /taler-monitoring-mail* page)",
|
||||
"footer": "Console-style render of taler-monitoring output. Sticky bar: green = clean · yellow = warnings · red = errors. Click error/warning counts to filter the log (match lines plus a few dimmed neighbours for context). Monitoring env context (agent header) is collapsed by default. Commit pins the exact tree used for this run.",
|
||||
"footer": "Console-style render of taler-monitoring output. Sticky bar: green = clean · yellow = warnings · red = errors. Click error / warning / info (blue) / ok counts to filter the log (match lines plus dimmed neighbours). Suite version sits under the status pill. Monitoring env context is collapsed by default.",
|
||||
"redirect_fail": "has failures.",
|
||||
"redirect_see": "See {link} for the full console log, error index, and sticky status bar.",
|
||||
"ago_s": "{n}s ago",
|
||||
|
|
@ -168,16 +178,25 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"jump_first_warn": "Aller au premier avertissement",
|
||||
"filter_errors": "Filtrer : erreurs + quelques lignes de contexte grisées",
|
||||
"filter_warns": "Filtrer : avertissements + quelques lignes de contexte grisées",
|
||||
"filter_infos": "Filtrer : infos + quelques lignes de contexte grisées",
|
||||
"filter_oks": "Filtrer : lignes OK + quelques lignes de contexte grisées",
|
||||
"filter_clear": "Effacer le filtre · tout afficher",
|
||||
"filter_active_err": "Filtre : erreurs + contexte",
|
||||
"filter_active_warn": "Filtre : avertissements + contexte",
|
||||
"filter_active_info": "Filtre : infos + contexte",
|
||||
"filter_active_ok": "Filtre : OK seulement + contexte",
|
||||
"filter_show_all": "Tout afficher",
|
||||
"info_one": "{n} info",
|
||||
"info_many": "{n} infos",
|
||||
"ok_one": "{n} ok",
|
||||
"ok_many": "{n} ok",
|
||||
"env_context": "Contexte d'env. de monitoring",
|
||||
"env_context_title": "Déplier : env agent, pin de suite, hôtes domaine et flags (en-tête d'exécution)",
|
||||
"env_context_empty": "Pas d'en-tête d'exécution dans le journal",
|
||||
"generated": "généré",
|
||||
"version": "version",
|
||||
"version_tree_link": "ouvrir l'arbre git de cette exécution (commit)",
|
||||
"suite_version_title": "version de la suite taler-monitoring · arbre exact de l'exécution",
|
||||
|
||||
"source": "source",
|
||||
"what_monitors": "Ce que cette page contrôle",
|
||||
|
|
@ -246,7 +265,7 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"mail_i1": "Phase outside-in mail : MX, ports SMTP/IMAP et handshakes, SPF/DMARC",
|
||||
"mail_i2": "Hôtes mail : firefly.gnunet.org (taler.net/gnunet.org) et anastasis.taler-systems.com",
|
||||
"mail_i3": "Rapporté dans le HTML taler-monitoring-surface (pas de page /taler-monitoring-mail*)",
|
||||
"footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Compteurs = filtre. Contexte d'env. (en-tête agent) replié par défaut. Le commit fixe l'arbre exact de cette exécution.",
|
||||
"footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Compteurs erreur / avertissement / info (bleu) / ok = filtres. Version de la suite sous le badge d'état. Contexte d'env. replié par défaut.",
|
||||
"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",
|
||||
|
|
@ -289,10 +308,14 @@ def is_numbered_check_line(line: str) -> bool:
|
|||
def classify(line: str) -> str:
|
||||
u = line.upper()
|
||||
low = line.lower()
|
||||
# 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"
|
||||
# Real SUMMARY *box* only (╔/║/╚ frames or bare [ SUMMARY ] / [ RESUME ]).
|
||||
# Never match free text like "perf summary" / "ladder summary" (those are INFO checks).
|
||||
if re.search(r"\bSUMMARY\b", u) or re.search(r"\bRESUME\b", u):
|
||||
if re.search(r"[╔║╚]", line) or re.match(
|
||||
r"^\s*\[\s*(SUMMARY|RESUME)\s*\]", u
|
||||
):
|
||||
return "sum-header"
|
||||
# else fall through to badge classifiers (INFO/OK/…)
|
||||
# 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"
|
||||
|
|
@ -534,12 +557,18 @@ def extract_errors(lines: list[str]) -> list[str]:
|
|||
return out
|
||||
|
||||
|
||||
def count_status(lines: list[str]) -> tuple[int, int, int | None, int | None]:
|
||||
"""Return (error_count, warn_count, first_error_idx, first_warn_idx)."""
|
||||
def count_status(
|
||||
lines: list[str],
|
||||
) -> tuple[int, int, int, int, int | None, int | None, int | None, int | None]:
|
||||
"""Return (n_err, n_warn, n_info, n_ok, first_err, first_warn, first_info, first_ok)."""
|
||||
n_err = 0
|
||||
n_warn = 0
|
||||
n_info = 0
|
||||
n_ok = 0
|
||||
first_err: int | None = None
|
||||
first_warn: int | None = None
|
||||
first_info: int | None = None
|
||||
first_ok: int | None = None
|
||||
for i, ln in enumerate(lines):
|
||||
kind = classify(ln)
|
||||
if kind in ("error", "blocker"):
|
||||
|
|
@ -552,7 +581,15 @@ def count_status(lines: list[str]) -> tuple[int, int, int | None, int | None]:
|
|||
n_warn += 1
|
||||
if first_warn is None:
|
||||
first_warn = i
|
||||
return n_err, n_warn, first_err, first_warn
|
||||
elif kind == "info":
|
||||
n_info += 1
|
||||
if first_info is None:
|
||||
first_info = i
|
||||
elif kind == "ok":
|
||||
n_ok += 1
|
||||
if first_ok is None:
|
||||
first_ok = i
|
||||
return n_err, n_warn, n_info, n_ok, first_err, first_warn, first_info, first_ok
|
||||
|
||||
|
||||
def slug_error(i: int, text: str) -> str:
|
||||
|
|
@ -965,6 +1002,19 @@ def is_log_noise_line(ln: str) -> bool:
|
|||
return True
|
||||
if "ihr branch ist" in low or "bereits auf" in low:
|
||||
return True
|
||||
# Converter / host-agent noise that tee appends into the run log after SUMMARY
|
||||
if "syntaxwarning" in low or "invalid escape sequence" in low:
|
||||
return True
|
||||
if "console_to_html.py:" in low:
|
||||
return True
|
||||
if re.match(r"^\s*if\s*\(/\^\[╔", s):
|
||||
return True
|
||||
if low.lstrip().startswith("wrote ") and "errors=" in low:
|
||||
return True
|
||||
if re.match(r"^html\s+\S+", low) and ("→" in s or "->" in s):
|
||||
return True
|
||||
if "public url (expected)" in low:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -1567,7 +1617,8 @@ STICKY_JS = """
|
|||
return true;
|
||||
}
|
||||
var t = (el.textContent || "").replace(/\u00a0/g, " ").trim();
|
||||
if (/^[╔╗╚╝║═\-\s│┌┐└┘]+$/.test(t)) return true;
|
||||
// box-drawing / pure chrome only (JS char class; escapes doubled for Python).
|
||||
if (/^[╔╗╚╝║═│┌┐└┘\\s-]+$/.test(t)) return true;
|
||||
return false;
|
||||
}
|
||||
var keep = new Array(lines.length);
|
||||
|
|
@ -1739,8 +1790,9 @@ def build_html(
|
|||
# Split monitoring env context (collapsible, default closed) from check log
|
||||
env_lines, body_raw, env_hint = extract_env_context(raw_lines)
|
||||
# Sticky counts from full log; first-error anchors index into body only
|
||||
n_err, n_warn, _, _ = count_status(raw_lines)
|
||||
_, _, first_err_i, first_warn_i = count_status(body_raw)
|
||||
n_err, n_warn, _n_info, _n_ok, _, _, _, _ = count_status(raw_lines)
|
||||
_e2, _w2, _i2, _o2, first_err_i, first_warn_i, _fi, _fo = count_status(body_raw)
|
||||
del _e2, _w2, _i2, _o2, _fi, _fo, _n_info, _n_ok
|
||||
errors = extract_errors(raw_lines)
|
||||
|
||||
err_slugs: dict[str, str] = {}
|
||||
|
|
@ -2227,7 +2279,8 @@ def main() -> None:
|
|||
for l in log_text.splitlines()
|
||||
if not is_log_noise_line(strip_ansi(l))
|
||||
]
|
||||
n_err, n_warn, _, _ = count_status(raw_for_counts)
|
||||
n_err, n_warn, _n_info, _n_ok, _, _, _, _ = count_status(raw_for_counts)
|
||||
del _n_info, _n_ok
|
||||
|
||||
if args.mode == "redirect":
|
||||
html_out = build_redirect_html(
|
||||
|
|
@ -2262,7 +2315,12 @@ def main() -> None:
|
|||
)
|
||||
|
||||
atomic_write_text(args.out, html_out, encoding="utf-8")
|
||||
print(f"wrote {args.out} (errors={n_err} warnings={n_warn} level={status_level(n_err, n_warn)})")
|
||||
# stderr only — stdout is often teed into the suite mon log
|
||||
print(
|
||||
f"wrote {args.out} (errors={n_err} warnings={n_warn} "
|
||||
f"level={status_level(n_err, n_warn)})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue