Commit Graph
8 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 81995051dc feat(traefik): keep each router's 4xx and 5xx responses by status code (#5711)
CI / lint (push) Failing after 2s
CI / integration (push) Successful in 1m19s
CI / unit (push) Successful in 1m30s
CI / publish (push) Skipped
The scraper folded traefik_service_requests_total{code} into two shares and
dropped the code, so a problem couldn't say whether it was scanners (404), a
login wall (401/403) or the app down (502/503/504). Each scrape now stores the
responses per router and code that occurred (traefik_status_codes, migration
traefik_006), pruned with the router metrics and left out of backups (System
180 ruling: Traefik history is not backed up). A problem card lists its codes
over the last 15 minutes, most first, or says none occurred, or that codes
aren't recorded yet.

A counter series new since the previous scrape now counts from 0 instead of
being skipped: Traefik creates a router's 502 series at its first 502, so a
first burst of 5xx read as 0% until it happened again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:07:19 -04:00
bvandeusenandClaude Opus 5.5 9f26258c8e feat(core): one Prometheus text parser, with types; Traefik reads through it (#5667)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m11s
CI / unit (push) Failing after 1m27s
CI / publish (push) Skipped
steward/core/prometheus.py parses the 0.0.4 exposition into families keyed
by name, each with its declared TYPE, so a scraper can tell a counter from a
gauge. A histogram or summary owns its _bucket/_sum/_count samples. Label
values are unescaped, and commas or braces inside quotes stay part of the
value. NaN and +/-Inf are read, timestamps are ignored, and a malformed
line is skipped. Traefik's parse_prometheus is now a thin reshaping of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:55:24 -04:00
bvandeusenandClaude Opus 5.5 7ce61a2099 fix(traefik): grade routers on their last 15 minutes; the Config tile reads Traefik's reload time (#5355)
CI / lint (push) Failing after 2s
CI / integration (push) Successful in 54s
CI / unit (push) Successful in 1m24s
CI / publish (push) Skipped
The operator: problem cards "can disappear so quickly". Each router was
graded on its latest scrape, so a 4xx share near the 1% cutoff crossed it
on one scrape and fell back on the next. recent_window reads every
router's last 15 minutes in one query, the error shares weighted by
traffic; grade_routers uses it on the page and the dashboard widget, and
the why says "over the last 15 min".

traefik_config_last_reload_success is the Unix time of the last good
reload, not a 0/1 flag, so the tile said "Reload failed" on every scrape.
traefik_005 stores it as config_last_reload_at, and the tile reads
"Config reloaded 3m ago". The router index also carries the 4xx share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:26:10 -04:00
bvandeusenandClaude Opus 5.5 ea5531ac8f feat(traefik): Routers widget shows current routers only, one header row, certs on the routers they serve (#5254)
CI / lint (push) Successful in 2s
CI / publish (push) Skipped
CI / integration (push) Successful in 52s
CI / unit (push) Failing after 1m17s
#5254 "Traefik — Routers: current routers only, header row, certs on the router they serve".

- Router hosts come from Traefik's API (/api/http|tcp/routers), read
  best-effort on each scrape. An optional api_url overrides it; the default
  is /api on the metrics host. A certificate's expiry then shows in a TLS
  column on the router whose host it covers. An expiring one makes that
  router a problem, graded by Settings → Thresholds.
- Only current: routers and certificates must have appeared in the last
  three scrapes. Certificates are upserted by serial and never deleted, so
  renewed or retired ones lingered on the widget.
- Widget order: verdict, problem routers with the reason, then the busiest
  healthy routers under one header (Router / Req/s / Bandwidth / p95 / TLS).
  A missing value shows "—".
- If the API is unreachable, expiring certificates are listed after the
  routers, not above them.
- The page splits certificates into served (with the routers using each,
  plus SANs) and "No longer served".
- Latest row per router: an index walk (recursive CTE + LATERAL) replaces
  DISTINCT plus a query per router.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:25:11 -04:00
bvandeusenandClaude Opus 4.8 a7a281cb11 feat(plugins): fold first-party plugins in-tree; bundled + external roots
First-party plugins (host_agent, http, snmp, traefik, unifi, docker) are now
tracked under plugins/ and baked into the image, so they version atomically
with core — ending the cross-repo import drift the roundtable->steward rename
exposed. History for these files is preserved in the archived Roundtable-plugins
repo.

Plugin discovery becomes multi-root: PLUGIN_DIR (single) -> PLUGIN_DIRS
(bundled first, then external) + PLUGIN_INSTALL_DIR. Bundled ships in the image;
third-party plugins still mount at runtime into the external root
(STEWARD_PLUGIN_DIR, default /data/plugins) and downloads/installs land there.
Bundled shadows external on a name collision.

- config.py: load_bootstrap returns plugin_dirs + plugin_install_dir
- app.py: iterate PLUGIN_DIRS at the migration + load sites
- migration_runner.py: discover_all_in() unions every plugin root
- plugin_manager.py: resolve_plugin_path() (pure, first-root-wins); load /
  install / hot-reload span all roots; installs target the external root
- settings/routes.py: _discover_plugins scans all roots, dedup bundled-first
- Dockerfile: COPY plugins/ ; docker-compose: drop host bind, document external
- tests/test_plugin_dirs.py: resolution, multi-root discovery, bootstrap split

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 08:37:24 -04:00
bvandeusenandClaude Sonnet 4.6 fa318073f1 chore: move plugins/ to separate repo, gitignore the directory
plugins/ is now tracked at bvandeusen/fabledscryer-plugins.
Removed tracked traefik files from this repo's index (files remain on disk).
Added plugins/ to .gitignore so the directory is ignored going forward.

PLUGIN_DIR in config.yaml still points to plugins/ for local dev and Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:33:22 -04:00
bvandeusen 8ea2b09d05 fix: cap percentile at last finite bucket when target falls in open +Inf bucket 2026-03-18 18:38:03 -04:00
bvandeusen c336f1f774 feat: traefik prometheus scraper (parse, compute rates and percentiles) 2026-03-17 21:37:34 -04:00