release 1.11.0: sticky error/warn log filter
Click error or warning counts in the sticky bar to show only matching console lines; sticky stats and top overviews remain. Hashes #filter-error / #filter-warn (and #first-*) activate the filter.
This commit is contained in:
parent
341504f1ad
commit
db6722e40f
5 changed files with 213 additions and 50 deletions
|
|
@ -31,6 +31,12 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"warn_many": "{n} warnings",
|
||||
"jump_first_error": "Jump to first error",
|
||||
"jump_first_warn": "Jump to first warning",
|
||||
"filter_errors": "Filter: show all errors (hide other log lines)",
|
||||
"filter_warns": "Filter: show all warnings (hide other log lines)",
|
||||
"filter_clear": "Clear filter · show all lines",
|
||||
"filter_active_err": "Filter: errors only",
|
||||
"filter_active_warn": "Filter: warnings only",
|
||||
"filter_show_all": "Show all",
|
||||
"generated": "generated",
|
||||
"version": "version",
|
||||
|
||||
|
|
@ -94,7 +100,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. 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 counts to filter the log (other lines hidden; sticky + overviews stay). 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",
|
||||
|
|
@ -113,6 +119,12 @@ def ui(lang: str, key: str, **kwargs) -> str:
|
|||
"warn_many": "{n} avertissements",
|
||||
"jump_first_error": "Aller à la première erreur",
|
||||
"jump_first_warn": "Aller au premier avertissement",
|
||||
"filter_errors": "Filtrer : toutes les erreurs (masquer le reste du journal)",
|
||||
"filter_warns": "Filtrer : tous les avertissements (masquer le reste du journal)",
|
||||
"filter_clear": "Effacer le filtre · tout afficher",
|
||||
"filter_active_err": "Filtre : erreurs seulement",
|
||||
"filter_active_warn": "Filtre : avertissements seulement",
|
||||
"filter_show_all": "Tout afficher",
|
||||
"generated": "généré",
|
||||
"version": "version",
|
||||
|
||||
|
|
@ -176,7 +188,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. Le commit fixe l'arbre exact de cette exécution.",
|
||||
"footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Cliquer sur les compteurs erreur/avertissement filtre le journal (le reste est masqué ; barre et vues d'ensemble restent). 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",
|
||||
|
|
@ -701,26 +713,50 @@ def sticky_bar_html(
|
|||
else:
|
||||
status_txt = ui(lang, "ok")
|
||||
|
||||
if first_err_href and n_err:
|
||||
# Click error/warn stats → filter console to matching lines only (v1.11.0).
|
||||
# Hash #filter-error / #filter-warn; #first-error / #first-warn also activate filter.
|
||||
if n_err:
|
||||
err_lbl = ui(lang, "error_one", n=n_err) if n_err == 1 else ui(lang, "error_many", n=n_err)
|
||||
err_href = first_err_href or "#filter-error"
|
||||
# Prefer filter hash; keep first-error as secondary scroll target via JS
|
||||
err_stat = (
|
||||
f'<a class="stat err" href="{html.escape(first_err_href)}" '
|
||||
f'title="{html.escape(ui(lang, "jump_first_error"))}">{html.escape(err_lbl)}</a>'
|
||||
f'<a class="stat err" href="#filter-error" data-filter="error" '
|
||||
f'role="button" aria-pressed="false" '
|
||||
f'data-first="{html.escape(err_href)}" '
|
||||
f'title="{html.escape(ui(lang, "filter_errors"))}">{html.escape(err_lbl)}</a>'
|
||||
)
|
||||
else:
|
||||
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 n_warn:
|
||||
warn_lbl = ui(lang, "warn_one", n=n_warn) if n_warn == 1 else ui(lang, "warn_many", n=n_warn)
|
||||
warn_href = first_warn_href or "#filter-warn"
|
||||
warn_stat = (
|
||||
f'<a class="stat warn" href="{html.escape(first_warn_href)}" '
|
||||
f'title="{html.escape(ui(lang, "jump_first_warn"))}">{html.escape(warn_lbl)}</a>'
|
||||
f'<a class="stat warn" href="#filter-warn" data-filter="warn" '
|
||||
f'role="button" aria-pressed="false" '
|
||||
f'data-first="{html.escape(warn_href)}" '
|
||||
f'title="{html.escape(ui(lang, "filter_warns"))}">{html.escape(warn_lbl)}</a>'
|
||||
)
|
||||
else:
|
||||
warn_lbl = ui(lang, "warn_many", n=n_warn)
|
||||
warn_stat = f'<span class="stat warn muted">{html.escape(warn_lbl)}</span>'
|
||||
|
||||
filter_chip = (
|
||||
f'<span class="filter-chip" id="filter-chip" hidden>'
|
||||
f'<span class="filter-chip-label" id="filter-chip-label"></span>'
|
||||
f'<button type="button" class="filter-clear" id="filter-clear" '
|
||||
f'title="{html.escape(ui(lang, "filter_clear"))}">'
|
||||
f'{html.escape(ui(lang, "filter_show_all"))}</button>'
|
||||
f"</span>"
|
||||
)
|
||||
# data attributes for JS i18n
|
||||
filter_i18n = (
|
||||
f'data-i18n-filter-err="{html.escape(ui(lang, "filter_active_err"))}" '
|
||||
f'data-i18n-filter-warn="{html.escape(ui(lang, "filter_active_warn"))}" '
|
||||
f'data-i18n-filter-clear="{html.escape(ui(lang, "filter_clear"))}"'
|
||||
)
|
||||
|
||||
other = ""
|
||||
if link_other:
|
||||
other = (
|
||||
|
|
@ -758,7 +794,7 @@ def sticky_bar_html(
|
|||
return f"""
|
||||
<div class="sticky-bar sticky-{level}" id="status-bar" role="status"
|
||||
data-errors="{n_err}" data-warnings="{n_warn}" data-level="{level}"
|
||||
data-scope-kind="{scope_kind}">
|
||||
data-scope-kind="{scope_kind}" {filter_i18n}>
|
||||
<div class="sticky-bar-row primary">
|
||||
<span class="status-pill sticky-{level}">{html.escape(status_txt)}</span>
|
||||
<span class="host">{html.escape(page_label)} · {html.escape(hostname)}</span>
|
||||
|
|
@ -766,6 +802,7 @@ def sticky_bar_html(
|
|||
{err_stat}
|
||||
<span class="sep">·</span>
|
||||
{warn_stat}
|
||||
{filter_chip}
|
||||
<span class="sep">·</span>
|
||||
{version_html}
|
||||
<span class="sep">·</span>
|
||||
|
|
@ -849,6 +886,30 @@ STICKY_CSS = """
|
|||
.stat.warn { color: var(--warn); border-color: #664; background: #1a1608; }
|
||||
.stat.muted { opacity: 0.55; font-weight: 600; }
|
||||
a.stat:hover { text-decoration: underline; filter: brightness(1.15); }
|
||||
a.stat[aria-pressed="true"] {
|
||||
box-shadow: 0 0 0 1px currentColor inset;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
.filter-chip {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
font-size: 11px; font-weight: 600;
|
||||
padding: 1px 4px 1px 8px; border-radius: 3px;
|
||||
border: 1px solid #456; background: #0a1520; color: #9cf;
|
||||
}
|
||||
.filter-chip[hidden] { display: none !important; }
|
||||
body.filter-error .filter-chip { border-color: #a33; background: #1a0a0a; color: var(--err); }
|
||||
body.filter-warn .filter-chip { border-color: #a68b2d; background: #1a1608; color: var(--warn); }
|
||||
.filter-clear {
|
||||
font: inherit; font-size: 11px; font-weight: 700;
|
||||
cursor: pointer; color: inherit;
|
||||
background: #0006; border: 1px solid #555; border-radius: 3px;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
.filter-clear:hover { border-color: #aaa; background: #222; }
|
||||
/* Console filter: sticky + overviews stay; hide non-matching log lines */
|
||||
body.filter-error #mon-console .line:not(.error):not(.blocker) { display: none !important; }
|
||||
body.filter-warn #mon-console .line:not(.warn) { display: none !important; }
|
||||
body.filter-warn .err-top { display: none; }
|
||||
.version-link {
|
||||
font-weight: 700; font-size: 12px; color: var(--info);
|
||||
text-decoration: none; padding: 1px 8px; border-radius: 3px;
|
||||
|
|
@ -1015,6 +1076,107 @@ STICKY_JS = """
|
|||
if (localStorage.getItem(key) === "1") setOpen(true);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// v1.11.0: click error/warn stats → show only those log lines; sticky + overviews stay
|
||||
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");
|
||||
|
||||
function currentFilter() {
|
||||
if (document.body.classList.contains("filter-error")) return "error";
|
||||
if (document.body.classList.contains("filter-warn")) return "warn";
|
||||
return "";
|
||||
}
|
||||
|
||||
function setFilter(mode, opts) {
|
||||
opts = opts || {};
|
||||
document.body.classList.remove("filter-error", "filter-warn");
|
||||
var errA = document.querySelector('a.stat.err[data-filter="error"]');
|
||||
var warnA = document.querySelector('a.stat.warn[data-filter="warn"]');
|
||||
if (errA) errA.setAttribute("aria-pressed", "false");
|
||||
if (warnA) warnA.setAttribute("aria-pressed", "false");
|
||||
|
||||
if (mode === "error") {
|
||||
document.body.classList.add("filter-error");
|
||||
if (errA) errA.setAttribute("aria-pressed", "true");
|
||||
} else if (mode === "warn") {
|
||||
document.body.classList.add("filter-warn");
|
||||
if (warnA) warnA.setAttribute("aria-pressed", "true");
|
||||
}
|
||||
|
||||
if (chip && chipLabel && bar) {
|
||||
if (mode === "error") {
|
||||
chip.hidden = false;
|
||||
chipLabel.textContent = bar.getAttribute("data-i18n-filter-err") || "Filter: errors only";
|
||||
} else if (mode === "warn") {
|
||||
chip.hidden = false;
|
||||
chipLabel.textContent = bar.getAttribute("data-i18n-filter-warn") || "Filter: warnings only";
|
||||
} else {
|
||||
chip.hidden = true;
|
||||
chipLabel.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.updateHash !== false) {
|
||||
var want = mode === "error" ? "#filter-error"
|
||||
: mode === "warn" ? "#filter-warn" : "";
|
||||
if (want) {
|
||||
if (location.hash !== want) {
|
||||
try { history.replaceState(null, "", want); } catch (e) { location.hash = want; }
|
||||
}
|
||||
} else if (location.hash === "#filter-error" || location.hash === "#filter-warn"
|
||||
|| location.hash === "#first-error" || location.hash === "#first-warn") {
|
||||
try { history.replaceState(null, "", location.pathname + location.search); } catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.scrollFirst && mode) {
|
||||
var id = mode === "error" ? "first-error" : "first-warn";
|
||||
var el = document.getElementById(id);
|
||||
if (el && el.scrollIntoView) {
|
||||
setTimeout(function () { el.scrollIntoView({ block: "start", behavior: "smooth" }); }, 30);
|
||||
} else if (consoleEl) {
|
||||
var sel = mode === "error" ? ".line.error, .line.blocker" : ".line.warn";
|
||||
var first = consoleEl.querySelector(sel);
|
||||
if (first && first.scrollIntoView) {
|
||||
setTimeout(function () { first.scrollIntoView({ block: "start", behavior: "smooth" }); }, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFilter(mode) {
|
||||
if (currentFilter() === mode) setFilter("", { updateHash: true });
|
||||
else setFilter(mode, { updateHash: true, scrollFirst: true });
|
||||
}
|
||||
|
||||
document.querySelectorAll("a.stat[data-filter]").forEach(function (a) {
|
||||
a.addEventListener("click", function (ev) {
|
||||
ev.preventDefault();
|
||||
var mode = a.getAttribute("data-filter");
|
||||
if (mode === "error" || mode === "warn") toggleFilter(mode);
|
||||
});
|
||||
});
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener("click", function () {
|
||||
setFilter("", { updateHash: true });
|
||||
});
|
||||
}
|
||||
|
||||
function applyHash() {
|
||||
var h = (location.hash || "").replace(/^#/, "");
|
||||
if (h === "filter-error" || h === "first-error") {
|
||||
setFilter("error", { updateHash: false, scrollFirst: h === "first-error" });
|
||||
} else if (h === "filter-warn" || h === "first-warn") {
|
||||
setFilter("warn", { updateHash: false, scrollFirst: h === "first-warn" });
|
||||
} else if (currentFilter()) {
|
||||
// keep filter if hash is something else (e.g. err-slug)
|
||||
}
|
||||
}
|
||||
applyHash();
|
||||
window.addEventListener("hashchange", applyHash);
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue