feat(ui): each run says who or what started it; run detail without ids (#5212)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 49s
CI / unit (push) Successful in 1m15s
CI / publish (push) Successful in 1m3s

#5212 "Plain labels: no raw ids, scopes, metric keys or unexplained jargon",
batch 2:

- A run records where it came from: AnsibleRun.origin ("schedule", "alert"
  or "mcp") and origin_id, set by the scheduler, Run now, alert actions and
  the MCP tools. Migration 0031 backfills past runs from alert events,
  schedules' last runs, and MCP audit entries.
- The runs list has a Started by column: "schedule nightly", "alert rule
  nas down", "alice via MCP" or "alice", linked where there is a page.
  The raw scope tooltip is gone.
- Run detail: no run id in the subtitle or body, Started by in words, and
  the host summary lists failed and unreachable hosts first. The live
  output only follows new lines while the reader is at the bottom, and
  when the run finishes the page reloads once so the badge and summary
  are final.
- list_ansible_runs and get_ansible_run report origin and origin_id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 00:26:17 -04:00
co-authored by Claude Opus 5.5
parent a96112a0e1
commit e780932673
14 changed files with 192 additions and 27 deletions
@@ -0,0 +1,41 @@
"""Where a run came from: a schedule, an alert rule, or an agent over MCP (#5212)
Revision ID: 0031_ansible_run_origin
Revises: 0030_plugin_metric_latest
Create Date: 2026-10-07
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0031_ansible_run_origin"
down_revision: Union[str, None] = "0030_plugin_metric_latest"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column("ansible_runs", sa.Column("origin", sa.String(16), nullable=True))
op.add_column("ansible_runs", sa.Column("origin_id", sa.String(36), nullable=True))
# Recover what the record already says about past runs. These tables are
# small (runs, alert events that started one, audit entries), unlike the
# metrics seed 0030 left to the background.
op.execute("""
UPDATE ansible_runs r SET origin = 'alert', origin_id = e.rule_id
FROM alert_events e WHERE e.ansible_run_id = r.id
""")
op.execute("""
UPDATE ansible_runs r SET origin = 'schedule', origin_id = s.id
FROM ansible_schedules s WHERE s.last_run_id = r.id AND r.origin IS NULL
""")
op.execute("""
UPDATE ansible_runs r SET origin = 'mcp'
FROM audit_events a
WHERE a.action = 'ansible.run.started' AND a.entity_id = r.id
AND a.username LIKE '% via MCP' AND r.origin IS NULL
""")
def downgrade() -> None:
op.drop_column("ansible_runs", "origin_id")
op.drop_column("ansible_runs", "origin")