- The Schedules tab: a verdict line (failing first, paused, next due), each schedule as one line (what runs where, how often, when next, how it last went); the row opens its page; Run now and Pause stay in the row. - /ansible/schedules/new and /ansible/schedules/<id>: the form on classes, dry run beside the button, the playbook's variables rendered with the page; at the foot Delete behind the confirm dialog. ?source=&playbook= start a new one on a playbook (Schedule it on the playbook's page). - A run started by a schedule links to that schedule's page. - The run page's id guard allows the Run again link's ?from=. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
2.4 KiB
Python
46 lines
2.4 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, params=None)
|
|
nxt = {"id": "s1", "name": "nightly", "at": NOW + timedelta(hours=2)}
|
|
rows = [{"s": s, "last_run": None, "next": nxt, "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, next=nxt, failing=0, paused=0)
|
|
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
|
|
assert 'class="row-target" href="/ansible/schedules/s1"' in page # the row opens the schedule
|
|
for control in ("Run now", "Pause", "/delete", "New schedule"):
|
|
assert control not in page, control
|