#5212 "Plain labels: no raw ids, scopes, metric keys or unexplained jargon", batch 1: - Audit log: each action reads as a sentence ("paused monitor"), with the code as its tooltip. Detail shows flags as yes/no, lists joined, a run's scope by name, spans as spans, and no ids the entity column already gives. The filter is now a picker of areas. - Alert rules, history and Status say the condition in words: "is down", "Fullest disk >= 85%". Readings use metric_value, and history times use |ago. - Rule form: sources and metrics by name, the threshold's unit, "fires when it is down/up" for an up/down metric instead of 0.5, and a metric reference built from the catalog. - Schedules and Maintenance: viewers no longer see Run now, Pause, Edit, Delete or New. Intervals and next runs read as "every 2h" and "in 3h"; a scope reads "Every alert on Host agent". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
2.2 KiB
Python
44 lines
2.2 KiB
Python
"""A viewer is not offered what they cannot do (#5212): Schedules and
|
|
Maintenance draw no Run now / Pause / Edit / Delete / New for them, and
|
|
say intervals and scopes in words."""
|
|
from datetime import datetime, timedelta, timezone
|
|
from types import SimpleNamespace as NS
|
|
|
|
from quart import render_template, session
|
|
|
|
NOW = datetime.now(timezone.utc)
|
|
|
|
|
|
def _window(name, source, resource):
|
|
return NS(id=f"w-{name}", name=name, scope_source=source, scope_resource=resource,
|
|
start_at=NOW - timedelta(hours=1), end_at=NOW + timedelta(hours=1))
|
|
|
|
|
|
async def _maintenance(app, role):
|
|
windows = [_window("patching", "host_agent", None), _window("nas", "http", "nas web")]
|
|
async with app.test_request_context("/alerts/maintenance"):
|
|
session["user_role"] = role
|
|
return await render_template("alerts/maintenance.html", windows=windows, active=windows,
|
|
upcoming=[], expired=[])
|
|
|
|
|
|
async def test_maintenance_offers_changes_only_to_those_who_can_make_them(app):
|
|
viewer, operator = await _maintenance(app, "viewer"), await _maintenance(app, "operator")
|
|
assert "New window" not in viewer and "/delete" not in viewer
|
|
assert "New window" in operator and operator.count("/delete") == 2
|
|
assert "Every alert on Host agent" in viewer and "host_agent/*" not in viewer
|
|
assert "nas web</span>" in viewer and "HTTP monitors" in viewer
|
|
|
|
|
|
async def test_schedules_show_a_viewer_the_list_without_its_controls(app):
|
|
s = NS(id="s1", name="nightly", enabled=True, source_name="local", playbook_path="prune.yml",
|
|
inventory_scope="steward:all", interval_seconds=7200, last_error=None, last_run_at=None)
|
|
rows = [{"s": s, "last_run": None, "next_run": NOW + timedelta(hours=2), "scope_label": "all inventory targets"}]
|
|
async with app.test_request_context("/ansible/schedules"):
|
|
session["user_role"] = "viewer"
|
|
page = await render_template("ansible/schedules.html", rows=rows, editing=None)
|
|
assert "nightly" in page and "every 2h" in page and "7200s" not in page
|
|
assert ">in 2h</time>" in page or ">in 1h 59m</time>" in page
|
|
for control in ("Run now", "Pause", "?edit=", "/delete", "New schedule"):
|
|
assert control not in page, control
|