feat(settings): Settings brought into line with the rest of Steward (milestone 493)
The last page of #5214's walk. Settings still described Steward as it was before monitors were unified and SNMP was rebuilt, drew its tabs below the breadcrumb, and showed the report schedule in UTC. - Tabs (#5377): settings/_tabs.html is a settings_tabs() macro called in each page's section_tabs block, above the trail, as Ansible's are. The breadcrumb guard also fails any page that {% include %}s a tab strip. - Report time (#5378): reports.schedule_tz keeps the zone of whoever saved the schedule; check_and_send compares in it (report_due), and the form shows the day and hour on the reader's clock (schedule_in) under their zone's name. "Last sent" uses |when. - Wording (#5379): the report contents list what the report holds (monitors, not ping-enabled hosts); "Ping latency" is "Response time" and says what it grades; plugin detail says enabling loads now and disabling needs a restart; the monitor interval says it applies on the next check (the scheduler reads it every pass); the dead list-config branch is gone. - Traefik (#5380): the 5xx and 4xx cutoffs are settings (traefik.threshold.error_5xx_pct 0.1, error_4xx_pct 1.0) in a "Traefik routers" group on Thresholds while the plugin runs, read by router_state through error_cutoffs(). The Traefik page has a Thresholds button for admins, to /settings/thresholds/#traefik. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+29
-10
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
import html
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from quart import Blueprint, current_app, render_template, request, redirect, url_for, session
|
||||
|
||||
@@ -140,6 +141,8 @@ _THRESHOLD_FIELDS = [
|
||||
("tls_warn", "thresholds.tls_warn_days", False), ("tls_crit", "thresholds.tls_crit_days", False),
|
||||
("clock_warn", "thresholds.clock_warn_s", False), ("clock_crit", "thresholds.clock_crit_s", False),
|
||||
("crash_loop_deaths", "thresholds.crash_loop_deaths", False),
|
||||
("error_5xx_pct", "traefik.threshold.error_5xx_pct", True),
|
||||
("error_4xx_pct", "traefik.threshold.error_4xx_pct", True),
|
||||
]
|
||||
|
||||
# Docker time-series retention windows (days). Read fresh by the cleanup task,
|
||||
@@ -159,7 +162,9 @@ _RETENTION_FIELDS = [
|
||||
async def thresholds():
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
settings = await get_all_settings(db)
|
||||
return await render_template("settings/thresholds.html", settings=settings)
|
||||
from steward.core.plugin_manager import _LOADED_PLUGINS
|
||||
return await render_template("settings/thresholds.html", settings=settings,
|
||||
traefik_on="traefik" in _LOADED_PLUGINS)
|
||||
|
||||
|
||||
@settings_bp.post("/thresholds/")
|
||||
@@ -568,17 +573,36 @@ async def save_auth_settings():
|
||||
|
||||
# ── Reports ───────────────────────────────────────────────────────────────────
|
||||
|
||||
async def _reports_page(settings: dict, flash):
|
||||
"""Settings → Reports, with the schedule and the last send in the reader's
|
||||
zone: the hour is kept in the zone of whoever saved it (#5378)."""
|
||||
from steward.core.formatting import zone
|
||||
from steward.core.reports import schedule_in
|
||||
shown_day, shown_hour = schedule_in(
|
||||
int(settings.get("reports.schedule_day", 6)), int(settings.get("reports.schedule_hour", 8)),
|
||||
zone(settings.get("reports.schedule_tz")), zone(session.get("tz")))
|
||||
raw = settings.get("reports.last_sent_at") or ""
|
||||
try:
|
||||
last_sent = datetime.fromisoformat(raw) if raw else None
|
||||
except ValueError:
|
||||
last_sent = None
|
||||
return await render_template("settings/reports.html", settings=settings, flash=flash,
|
||||
shown_day=shown_day, shown_hour=shown_hour, last_sent=last_sent)
|
||||
|
||||
|
||||
@settings_bp.get("/reports/")
|
||||
@require_role(UserRole.admin)
|
||||
async def reports():
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
settings = await get_all_settings(db)
|
||||
return await render_template("settings/reports.html", settings=settings, flash=None)
|
||||
return await _reports_page(settings, None)
|
||||
|
||||
|
||||
@settings_bp.post("/reports/")
|
||||
@require_role(UserRole.admin)
|
||||
async def save_reports():
|
||||
"""The day and hour are what the reader picked on their own clock, so the
|
||||
schedule is kept in their zone."""
|
||||
form = await request.form
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
async with db.begin():
|
||||
@@ -587,6 +611,7 @@ async def save_reports():
|
||||
int(form.get("reports.schedule_day", 6)))
|
||||
await set_setting(db, "reports.schedule_hour",
|
||||
int(form.get("reports.schedule_hour", 8)))
|
||||
await set_setting(db, "reports.schedule_tz", session.get("tz") or "")
|
||||
return redirect(url_for("settings.reports"))
|
||||
|
||||
|
||||
@@ -597,11 +622,7 @@ async def send_report_now():
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
settings = await get_all_settings(db)
|
||||
ok, message = await send_report(current_app._get_current_object())
|
||||
return await render_template(
|
||||
"settings/reports.html",
|
||||
settings=settings,
|
||||
flash={"ok": ok, "message": message},
|
||||
)
|
||||
return await _reports_page(settings, {"ok": ok, "message": message})
|
||||
|
||||
|
||||
def _build_plugin_cfg_from_form(plugin: dict, form) -> dict:
|
||||
@@ -612,9 +633,7 @@ def _build_plugin_cfg_from_form(plugin: dict, form) -> dict:
|
||||
for cfg_key in plugin.get("config", {}).keys():
|
||||
field_name = f"plugin.{name}.{cfg_key}"
|
||||
default_val = plugin["config"][cfg_key]
|
||||
if isinstance(default_val, list):
|
||||
pass # list configs must be set in plugin.yaml
|
||||
elif isinstance(default_val, dict):
|
||||
if isinstance(default_val, dict):
|
||||
sub_dict = {}
|
||||
for sub_key, sub_default in default_val.items():
|
||||
sub_field = f"plugin.{name}.{cfg_key}.{sub_key}"
|
||||
|
||||
Reference in New Issue
Block a user