feat(ansible): runs named by what ran where, with ago, duration and one status badge (#5125)
Runs read "update.yml on web-1" (run_title over the scope label, beside describe_scope) on the runs list, the run page's title and breadcrumb, and the run-started panel. The list shows started |ago (full time on hover), |duration (counting "so far" while a run is going) and a dry-run tag. Run statuses join STATES, so the list and the run page use status_badge: queued dim, running info (blue, so work in progress doesn't read as a problem), success ok "Succeeded", failed crit, interrupted warn, cancelled dim. run_started.html leaves its hard-coded colours for tokens; the run page's facts move to the .kv list and its failures to var(--red). Template hex colours: 54 -> 47. Also fixes #5124's sidebar count, which CI run 8389 failed on: the stylesheet guard allows no weight above 500, and .nav-count asked for 600. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,15 @@ def describe_scope(scope: str, targets: dict[str, str], groups: dict[str, str])
|
||||
return scope
|
||||
|
||||
|
||||
def run_title(playbook: str, scope_label: str | None) -> str:
|
||||
"""A run named by what it ran where: "update.yml on web-1",
|
||||
"site.yml on group Docker Swarm", "ping.yml on all inventory targets"."""
|
||||
where = scope_label or ""
|
||||
if where.startswith("target "):
|
||||
where = where[len("target "):]
|
||||
return f"{playbook} on {where}" if where else playbook
|
||||
|
||||
|
||||
async def scope_labels(db, scopes) -> dict[str, str]:
|
||||
"""{scope: describe_scope(scope)} for the given scopes, in two queries."""
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -9,7 +9,7 @@ from quart import (
|
||||
from sqlalchemy import select
|
||||
|
||||
from steward.ansible import executor, guards, sources as src_module
|
||||
from steward.ansible.inventory_gen import scope_labels
|
||||
from steward.ansible.inventory_gen import run_title, scope_labels
|
||||
from steward.auth.middleware import require_role
|
||||
from steward.core.audit import audit_request as _audit
|
||||
from steward.models.ansible import AnsibleRun, AnsibleRunStatus
|
||||
@@ -41,7 +41,24 @@ async def index():
|
||||
select(AnsibleRun).order_by(AnsibleRun.started_at.desc()).limit(50)
|
||||
)
|
||||
runs = result.scalars().all()
|
||||
return await render_template("ansible/index.html", runs=runs)
|
||||
labels = await scope_labels(db, [r.inventory_scope for r in runs])
|
||||
now = datetime.now(timezone.utc)
|
||||
return await render_template("ansible/index.html", runs=[run_view(r, labels, now) for r in runs])
|
||||
|
||||
|
||||
def run_duration(run: AnsibleRun, now: datetime) -> float | None:
|
||||
"""Seconds a run took, or has taken so far while it is still going."""
|
||||
if run.started_at is None:
|
||||
return None
|
||||
return ((run.finished_at or now) - run.started_at).total_seconds()
|
||||
|
||||
|
||||
def run_view(run: AnsibleRun, labels: dict[str, str], now: datetime) -> dict:
|
||||
"""A run as the pages name it: its title, scope label and duration."""
|
||||
label = labels.get(run.inventory_scope, run.inventory_scope)
|
||||
return {"run": run, "title": run_title(run.playbook_path, label), "scope_label": label,
|
||||
"duration_s": run_duration(run, now),
|
||||
"check": bool((run.params or {}).get("check"))}
|
||||
|
||||
|
||||
@ansible_bp.get("/browse")
|
||||
@@ -285,10 +302,12 @@ async def create_run():
|
||||
"check": bool((params_or_none or {}).get("check")),
|
||||
"vars": sorted(_run_vars(params_or_none, secret_vars))})
|
||||
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
labels = await scope_labels(db, [run.inventory_scope])
|
||||
return await render_template(
|
||||
"ansible/run_started.html",
|
||||
run=run,
|
||||
source=source,
|
||||
title=run_title(run.playbook_path, labels.get(run.inventory_scope, run.inventory_scope)),
|
||||
)
|
||||
|
||||
|
||||
@@ -340,10 +359,10 @@ async def run_detail(run_id: str):
|
||||
res = await db.execute(
|
||||
select(User.username).where(User.id == run.triggered_by))
|
||||
triggered_label = res.scalar_one_or_none() or "(deleted user)"
|
||||
scope_label = (await scope_labels(db, [run.inventory_scope])).get(run.inventory_scope)
|
||||
labels = await scope_labels(db, [run.inventory_scope])
|
||||
return await render_template(
|
||||
"ansible/run_detail.html", run=run, triggered_label=triggered_label,
|
||||
scope_label=scope_label)
|
||||
row=run_view(run, labels, datetime.now(timezone.utc)))
|
||||
|
||||
|
||||
@ansible_bp.post("/runs/<run_id>/cancel")
|
||||
|
||||
Reference in New Issue
Block a user