- 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>
53 lines
2.6 KiB
Python
53 lines
2.6 KiB
Python
"""Who or what started each run, as the runs list and run detail say it.
|
|
|
|
A run records the user who started it (triggered_by) and where it came from
|
|
(origin / origin_id, migration 0031): a schedule, an alert rule, or an agent
|
|
over MCP. This turns those into "schedule nightly", "alert rule nas down",
|
|
"alice via MCP" or "alice", in three queries for a whole page of runs.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import select
|
|
|
|
from steward.models.alerts import AlertRule
|
|
from steward.models.ansible_schedule import AnsibleSchedule
|
|
from steward.models.users import User
|
|
|
|
|
|
def describe_origin(run, users: dict, schedules: dict, rules: dict) -> dict:
|
|
"""{"text", "url", "kind"} for one run, from id → name maps. A schedule or
|
|
rule deleted since is named as such; a run with neither a user nor an
|
|
origin predates origins and was started by Steward itself."""
|
|
who = users.get(run.triggered_by, "a deleted user") if run.triggered_by else None
|
|
if run.origin == "schedule":
|
|
name = schedules.get(run.origin_id)
|
|
text = f"schedule {name}" if name else "a deleted schedule"
|
|
if who:
|
|
text += f" (run now by {who})"
|
|
return {"text": text, "url": f"/ansible/schedules/{run.origin_id}" if name else None, "kind": "schedule"}
|
|
if run.origin == "alert":
|
|
name = rules.get(run.origin_id)
|
|
return {"text": f"alert rule {name}" if name else "a deleted alert rule",
|
|
"url": f"/alerts/history?rule={run.origin_id}" if name else None, "kind": "alert"}
|
|
if run.origin == "mcp":
|
|
return {"text": f"{who or 'an agent'} via MCP", "url": None, "kind": "mcp"}
|
|
if who:
|
|
return {"text": who, "url": None, "kind": "user"}
|
|
return {"text": "Steward", "url": None, "kind": "system"}
|
|
|
|
|
|
async def run_origins(db, runs) -> dict[str, dict]:
|
|
"""{run id: describe_origin(run)} for a page of runs."""
|
|
runs = list(runs)
|
|
user_ids = {r.triggered_by for r in runs if r.triggered_by}
|
|
sched_ids = {r.origin_id for r in runs if r.origin == "schedule" and r.origin_id}
|
|
rule_ids = {r.origin_id for r in runs if r.origin == "alert" and r.origin_id}
|
|
users = dict((await db.execute(
|
|
select(User.id, User.username).where(User.id.in_(user_ids)))).all()) if user_ids else {}
|
|
schedules = dict((await db.execute(
|
|
select(AnsibleSchedule.id, AnsibleSchedule.name).where(AnsibleSchedule.id.in_(sched_ids)))).all()) \
|
|
if sched_ids else {}
|
|
rules = dict((await db.execute(
|
|
select(AlertRule.id, AlertRule.name).where(AlertRule.id.in_(rule_ids)))).all()) if rule_ids else {}
|
|
return {r.id: describe_origin(r, users, schedules, rules) for r in runs}
|