Commit Graph
12 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 96c1ea9407 feat(host_agent): how far each host's clock is from Steward's (milestone 490)
CI / lint (push) Successful in 5s
CI / integration (push) Successful in 1m8s
CI / unit (push) Successful in 1m34s
CI / publish (push) Successful in 2m27s
- Agent 1.12.0 stamps each post with sent_at; ingest compares it with the
  moment the post arrived: clock_offset_s / clock_checked_at on the
  registration (host_agent_003), and a clock_off_secs metric (either way)
  that alert rules can watch. Replaces the old skew log, which measured the
  newest sample and was off by up to an interval.
- Settings -> Thresholds: Clock drift, 30 s warn / 5 min critical.
- Host page: 'clock 0.3s ahead' in the vitals line, toned past the warning.
- Hosts list: names hosts whose clock is off; when every reporting host is
  off by about the same amount, says Steward's own clock is the likely one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:00:33 -04:00
bvandeusenandClaude Opus 5.5 2990113e10 feat(hosts): the Hosts list searches, filters by group and status, sorts by column, and shows each host's groups (#5294)
CI / integration (push) Failing after 52s
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m17s
CI / publish (push) Skipped
#5294 "Hosts list: search, group and status filters; sortable columns; a Groups column".
Ansible inventory groups are Steward's grouping system (operator's ruling): a host's
groups are its linked target's, and membership overlaps, so the list filters by group
rather than sectioning by it.

- Filter bar: search on name or address, a group (or "No group"), a status (Down,
  Agent silent, Up, No monitors). The table swaps in alone and the URL keeps the filters.
- Name, Status, Latency, 30d uptime, CPU and Memory headers sort (sort_th in _macros);
  problems-first stays the default, and a host with no value goes last either way.
- A Groups column of chips, each filtering to its group; shown when any group exists.
- A swap is keyed on HX-Target, so Back to a pushed URL still gets the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:53:03 -04:00
bvandeusenandClaude Opus 5.5 fb20415a38 feat(hosts): a host's health tiles span the card and the gaps match; Add host is a dialog on the list (#5290, #5291)
CI / integration (push) Successful in 54s
CI / unit (push) Successful in 1m18s
CI / publish (push) Successful in 1m23s
CI / lint (push) Successful in 3s
#5290 "Host page: the health tiles span the card's width; even spacing above Ansible":
the four tiles take equal quarters (halves on a phone) and their sparklines stretch
across them; the Monitors + Agent row drops the Agent card's own margin, so the gap
above Ansible is 1rem like every other.

#5291 "Add host opens as a dialog over the Hosts list": a native <dialog> on /hosts/,
opened by Add host or /hosts/?add=1. A name clash re-renders the list with it open,
the error and what was typed. GET /hosts/new is gone; hosts/form.html is Edit only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:22:48 -04:00
bvandeusenandClaude Opus 5.5 bfe3618e88 feat(hosts): the Hosts list labels its figures and says how long; SLA tab gone; one place to add a host (#5279–#5283)
CI / integration (push) Successful in 53s
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 1m16s
CI / publish (push) Successful in 1m23s
Hosts list:
- CPU and Memory are their own labelled columns (#5279); a silent
  agent's last readings are greyed, and Status says "agent silent for 7m".
- One uptime figure, 30 days, with 24h and 7d on hover. The SLA tab
  (/hosts/uptime) repeated it and is removed (#5280; one home per fact).
- Status is a word with how long ("Down for 12m"); host_status now carries
  down_since. Edit and Delete live on the host's page only (#5281).
- Figures are right-aligned with tabular digits (.table .num), not a
  monospace face.

Agent fleet (#5282): its add-host form is gone, so hosts are added only on
Hosts -> Add host. That form was also the only way to get a token for a
manual curl install, so that moves to the host's Agent card as "Install
manually" (POST /fleet/<host_id>/register), shown on the fleet page as
before. "Delete" there reads "Unregister".

Add host (#5283): laid out like the add-monitor form; a name clash comes
back inline with what was typed instead of a bare text 400; the address
is optional, as the host page already allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 17:12:17 -04:00
bvandeusenandClaude Opus 5.5 5a14e7c693 feat(ui): alerts, monitors, hosts, schedules and maintenance lists put problems first, with since-times (#5210)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m8s
CI / unit (push) Successful in 1m25s
CI / publish (push) Successful in 1m17s
#5210 "Problems first, with since-time, across every list" (milestone 477),
first batch.

- Alerts: the Active list reads firing, no data, acknowledged, pending, the
  longest-running first, and re-polls itself. An acknowledged alert says how
  long it has been breaching (from the alert's events; acknowledging moves
  last_transition_at) and, apart from that, who acknowledged it and when.
  Rules show what each is doing now; disabled rules sort last. The current
  value reads the latest-value table (#5208) instead of plugin_metrics.
- Monitors: a down monitor says "Down 12m · connection refused" in the list.
- Hosts: sorted down, agent gone quiet, pending, healthy. The agent column
  shows "—" for a missing reading (was 0%) and a Stale badge with its age.
  agent_metrics_by_host is gone; the list reads agent_overview_by_host.
- Schedules: failing first (failed or interrupted run, or a firing that could
  not start one), then running, then the rest, paused last; status badge
  from the shared states.
- Maintenance windows: active (ending soonest), then upcoming (starting
  soonest), with expired folded away; each says when it ends/starts/ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 23:58:56 -04:00
bvandeusenandClaude Opus 5.5 414c942cbf feat(plugins): Traefik problems first, UniFi/SNMP/fleet on shared shapes, audit names its entities, templates on tokens (#5126)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m5s
CI / unit (push) Successful in 1m19s
CI / publish (push) Successful in 57s
Traefik: routers are graded from their latest scrape (router_state: crit on
any real 5xx share, warn on p95 past the latency critical cutoff or a high
4xx share). Failing and slow routers lead as cards that say why; healthy
ones fold into one table that stays open or shut across polls. Proxy
counts are stat tiles; certificates are graded by Settings -> Thresholds
(tls_days).

UniFi: WAN health is a stat strip (latency graded by the thresholds),
devices carry status dots, event categories and firewall actions are
badges, the alarm count reuses .count-badge (renamed from .nav-count).
SNMP: each device wears a status badge and its readings are stat tiles.

Agent fleet: the table comes first, with a Status column (Reporting /
Silent / Never reported, by the host page's stale_after_seconds), silent
agents first, hosts linked, last seen as |ago.

Audit: entries name their entity (one query per type, steward/audit/
service.py) and link it; something deleted since keeps the name its entry
recorded and says "(deleted)".

Tokens: charts read colours through chart-colors.js (stewardColor resolves
a token to the rgb the browser computed; Chart.js defaults take the page's
text and border colours), so host, history, Traefik and SNMP charts drop
their hex. Template hex colours reach 0 (HEX_CEILING = 0), the last rgba
pills go, and heavy weights (600/700/bold, 60 of them) become 500, the
house pair being 400/500; a guard keeps templates there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:25:09 -04:00
bvandeusenandClaude Opus 5.5 f47fc23265 fix(ui): delete prompts use data-confirm, so a quote in a name no longer runs script (#5099)
#5099 "A name containing a quote runs script when an admin clicks Delete".
Inline handlers like onclick="return confirm('Delete {{ rule.name }}?')" are
HTML-decoded before the JavaScript is parsed, so autoescaping did not stop a
name such as  x');fetch(…);//  from running.

- Every prompt (22 handlers, plus the docker prune and token revoke ones)
  is now data-confirm="…", read through dataset by one submit listener in
  base.html; nothing rendered is parsed as JavaScript.
- The remaining inline handlers take integers only (|int), and the time
  range buttons pass this.dataset.range.
- tests/test_inline_handlers.py fails on any {{ }} in an inline handler that
  is not |int, with a self-test; tests/_templates.py is the one template
  walker the template guards now share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:30:13 -04:00
bvandeusenandClaude Opus 5.5 96126b1c83 feat(auth): forged browser requests are refused (#5031)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 49s
CI / unit (push) Successful in 1m3s
CI / publish (push) Successful in 1m18s
#5031 "Forged-request protection: SameSite cookie, per-session CSRF token
on every non-GET, HTMX header". A page on another site could make a
logged-in browser POST to Steward with the session cookie attached.

- steward/auth/csrf.py: a per-session token, checked in one before_request
  for every POST/PUT/PATCH/DELETE (header X-CSRF-Token or form field
  csrf_token, constant-time compare). The bearer-authenticated host-agent
  ingest is exempt; /mcp never reaches Quart.
- Every POST form renders {{ csrf_field() }}; base.html carries the token
  in a meta tag and adds it to every HTMX request; the dashboard's fetch()
  calls send it.
- The session cookie is SameSite=Lax; login issues a fresh token.
- PUBLIC_BASE_URL is now loaded from Settings at startup (it was only set
  when Settings → General was saved).
- tests/test_csrf.py: refusal, acceptance, rotation, the ingest exemption,
  and a guard that every POST form in the templates carries the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:44:19 -04:00
bvandeusenandClaude Opus 4.8 35f658b573 feat(monitors): unify ping/dns/http into one Monitor entity + custom targets
CI / unit (push) Successful in 8s
CI / integration (push) Successful in 2m17s
CI / lint (push) Successful in 2s
CI / publish (push) Successful in 1m10s
Collapse the three former check types into a single core `Monitor` entity
with one management surface (/monitors), one result table (monitor_results),
and a single scheduled task. Every type can now watch a free-standing custom
destination (optional host_id) — not just a registered Host.

- models: Monitor + MonitorResult replace PingResult/DnsResult; Host loses its
  ping/dns facet columns (now Monitor rows linked by host_id).
- checks: monitors/{ping,dns,http}.py pure probes + runner.run_monitor
  dispatcher; one monitor_check scheduler with a per-monitor due-filter.
- status: single monitor_status_source replaces the three sources.
- UI: /monitors blueprint (type-aware add/edit/list/widget); host hub shows a
  host's linked monitors + "add monitor for this host"; nav + widget registry
  + alert metric catalog rewired. http plugin folded into core and removed.
- migration 0022 merges the http branch, data-migrates host facets +
  http_monitors + all three result histories, drops the old tables/columns.

Resolves the per-host ping/dns auto-attach issue (#275): monitors are now
explicit, never auto-added to every host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
2026-06-18 08:56:13 -04:00
bvandeusenandClaude Opus 4.8 f29255039d feat(hosts): Phase 2+3 — agent column, fold fleet + management into Hosts
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 9s
CI / integration (push) Successful in 2m16s
CI / publish (push) Successful in 54s
- Hosts list: new Agent column (latest CPU/mem read from the generic
  PluginMetric table — no host_agent import) + an admin "Agent fleet" button.
- /plugins/host_agent/ (old fleet page) now redirects to /hosts/ (folded into
  the hub; kept as a redirect so widgets/links don't 404).
- Agent management moved off the "settings" URL: the management page is now
  /plugins/host_agent/fleet/ ("Agent fleet" — bulk provision/install/update +
  registrations + curl install), reachable from the Hosts list. Old
  /plugins/host_agent/settings/ redirects there. Per-host management lives on
  the host detail page; this page is now explicitly the bulk/fleet view.

Milestone 70 phases 2-3. Phase 4 (plugins capability/integration split + nav
cleanup) next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:51:33 -04:00
bvandeusenandClaude Opus 4.8 8bdf07f709 feat(hosts): Phase 1 — host detail hub page (unify host IA)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m16s
CI / publish (push) Successful in 48s
Make Hosts the front-and-center hub. A host now has a real detail page at
/hosts/<id> that pulls its facets into one view, instead of management being
scattered across a nav-less Host-Agents area and the edit form.

- hosts: new GET /hosts/<id> detail route + hosts/detail.html. Shows the
  monitors summary (ping/DNS status + latency + uptime 24h/7d/30d), an Ansible
  section (linked target, link/create, run-playbook), and an embedded Agent
  panel. Hosts list name links here; ansible-link redirects here.
- host_agent: GET /plugins/host_agent/panel/<host_id> — a self-contained HTMX
  fragment embedded into the core hub across the plugin boundary (core never
  imports plugin models). Shows live agent metrics + Update/Rotate/Remove when
  installed, or the provisioning path when not: inline "generate managed key"
  warning, a prompt to link an Ansible target first, then Provision (bootstrap
  password) / Install (managed key) tied to the host's target scope.

Part of milestone 70 (Hosts hub). Phase 2+ will enrich the list, redirect the
old fleet/settings pages, and re-taxonomize plugins into capabilities vs
integrations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:29:34 -04:00
bvandeusenandClaude Opus 4.7 88ab5b917e chore: rename project Roundtable → Steward
Renames the Python package directory, CLI command, env var prefix,
docker-compose service/container/image, Postgres role/db, and all
visible branding. Marketing form is "Fabled Steward".

Clean break from the previous rebrand: drops the fabledscryer→roundtable
import shim in __init__.py and the FABLEDSCRYER_* env var fallback in
config.py and migrations/env.py. Env vars are now STEWARD_* only.

Heads-up for existing deployments:
- Postgres user/db renamed fabledscryer → steward in docker-compose.yml.
  Existing volumes need the role/db renamed inside Postgres, or override
  POSTGRES_USER/POSTGRES_DB to keep the old names.
- Host-agent systemd unit is now steward-agent.service. Existing agents
  keep running under the old name; reinstall to switch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 16:20:14 -04:00