fix: error-pages graded healthy, unwired sensors listed apart, Maintenance failed reads 0 (#5780, #5781, #5782)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m23s
CI / unit (push) Successful in 1m29s
CI / publish (push) Successful in 18s

- #5780 "The error-pages service is graded on other routers' errors". The
  Traefik scrape reads which services an errors middleware sends to, and
  grades them healthy: their 4xx and 5xx are the codes of the errors they
  cover for, already graded on the routers that failed.
- #5781 "The Maintenance failed rules sit at no data until a host has had
  a maintenance job". Every host with an agent reads 0 until a job fails.
- #5782 "VDNT-Util01's host page lists SYSTIN at 115 °C in red". The host
  page lists sensors that counts_as_hottest leaves out (unwired board
  inputs, 0 °C or below) on one muted line, not as heat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:15:44 -04:00
co-authored by Claude Opus 5.5
parent 0f604ecb4a
commit 72e754b580
9 changed files with 120 additions and 13 deletions
+14 -5
View File
@@ -18,7 +18,7 @@ from steward.core.settings import floor_of
from steward.models.metrics import PluginMetricLatest
from . import meaning
from .models import TraefikAccessSummary, TraefikCert, TraefikGlobalStat, TraefikMetric, TraefikStatusCode
from .scheduler import router_domains
from .scheduler import error_page_services, router_domains
from .scraper import cert_names, covers
traefik_bp = Blueprint("traefik", __name__, template_folder="templates")
@@ -175,12 +175,21 @@ async def status_codes(db, names: list[str], since: datetime) -> dict[str, list[
return out
def grade_routers(routers: list[dict], window: dict, thresholds: dict, norms: dict | None = None) -> None:
ERROR_PAGES_WHY = "serves other routers' error pages: its 4xx and 5xx are theirs, graded on them"
def grade_routers(routers: list[dict], window: dict, thresholds: dict, norms: dict | None = None,
error_pages: frozenset[str] = frozenset()) -> None:
"""Set each router's state and why from its recent window, or from its
latest scrape when the window holds nothing, against its normals
(router_norms') where it has them."""
(router_norms') where it has them. A service Traefik's errors middleware
sends error pages to (`error_pages`) is healthy whatever its codes: it
answers with the code of the error it covers for (#5780)."""
norms = norms or {}
for r in routers:
if r["name"] in error_pages:
r["state"], r["why"], r["kind"], r["normed"] = "ok", ERROR_PAGES_WHY, None, False
continue
reading, own = window.get(r["name"]), norms.get(r["name"])
r["state"], r["why"] = (router_state(reading, thresholds, STATE_WINDOW_WORDS, own) if reading
else router_state(r["latest"], thresholds, norms=own))
@@ -365,7 +374,7 @@ async def rows():
history = await router_history(db, names, since, bucket_seconds(since))
phases.mark("history")
grade_routers(current, await recent_window(db, names, now), thresholds,
await router_norms(db, names, now))
await router_norms(db, names, now), error_page_services())
phases.mark("window")
# The routers' metric anomalies, last 30 days (#5741).
anomalies = await anom.views(db, await anom.list_anomalies(
@@ -479,7 +488,7 @@ async def widget():
served, _old = current_certs(certs, scrape)
domains = router_domains()
by_router = certs_by_router([r["name"] for r in current], domains or {}, served, now)
grade_routers(current, window, thresholds, norms)
grade_routers(current, window, thresholds, norms, error_page_services())
for r in current:
r["cert"] = by_router.get(r["name"])
if r["cert"]: