feat(ansible): one strip of tabs, a run list that opens on its verdict, a run page in one line (#5328, #5329, #5330)
- section_tabs macro; Runs · Playbooks · Schedules · Inventory on every Ansible page, replacing each page's own set of header buttons. - Runs: a verdict line (last 30 days, failures first with the latest named, running now, the schedule due next); under each status what the run did across its hosts (ansible/recap.py); a filter bar (playbook, status, who started it) that swaps the table and carries into the scroll paging. - Run page: status, duration, start and who started it in one line; each host's result in words, worst first; the playbook it ran links to it; the log and failures on classes rather than inline colours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
{# ansible/run_detail.html — one run (#5330): a verdict line (how it ended,
|
||||
how long, when, who started it, what it did across its hosts), what it ran
|
||||
with, each host's result in words, worst first, then its output. #}
|
||||
{% extends "base.html" %}
|
||||
{% from "_macros.html" import crumbs, page_header, status_badge %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% block title %}{{ row.title }} — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), (row.title, "")]) }}{% endblock %}
|
||||
{% macro words(parts) -%}
|
||||
{% for text, tone in parts %}{% if not loop.first %} · {% endif %}{% if tone %}<span class="tone-{{ tone }}">{{ text }}</span>{% else %}{{ text }}{% endif %}{% endfor %}
|
||||
{%- endmacro %}
|
||||
{% block content %}
|
||||
{% call page_header(row.title, "From the " ~ run.source_name ~ " playbook source") %}
|
||||
{{ ansible_tabs("runs") }}
|
||||
{% call page_header(row.title) %}
|
||||
{% if run.status.value in ("running", "queued") and session.user_role in ("operator", "admin") %}
|
||||
<form method="post" action="/ansible/runs/{{ run.id }}/cancel" data-confirm="Cancel this run? Ansible stops where it is; tasks already applied stay applied." data-confirm-dismiss="Keep running">{{ csrf_field() }}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Cancel run</button>
|
||||
@@ -12,15 +20,19 @@
|
||||
<a href="/ansible/runs/{{ run.id }}/log" class="btn btn-sm btn-ghost">Download log</a>
|
||||
{% endcall %}
|
||||
|
||||
<p class="page-verdict">
|
||||
{{- status_badge(run.status) }}
|
||||
{%- if row.check %} <span class="tone-info" title="Ansible check mode: nothing was changed">dry run</span>{% endif %}
|
||||
{%- if row.duration_s is not none %} {% if run.finished_at %}in{% else %}for{% endif %} {{ row.duration_s|duration }}{% if not run.finished_at %} so far{% endif %}{% endif %}
|
||||
· started {{ run.started_at|ago }}
|
||||
· by {% if origin.url %}<a href="{{ origin.url }}">{{ origin.text }}</a>{% else %}{{ origin.text }}{% endif %}
|
||||
{%- if row.recap %} · {{ words(row.recap) }}{% endif -%}
|
||||
</p>
|
||||
|
||||
<div class="card">
|
||||
<dl class="kv">
|
||||
<dt>Status</dt><dd>{{ status_badge(run.status) }}{% if row.check %} <span class="badge badge-dim" title="Ansible check mode: nothing was changed">dry run</span>{% endif %}</dd>
|
||||
<dt>Started</dt><dd title="{{ run.started_at.strftime('%Y-%m-%d %H:%M:%S UTC') }}">{{ run.started_at|ago }}</dd>
|
||||
<dt>Duration</dt><dd>{{ row.duration_s|duration }}{% if not run.finished_at %} so far{% endif %}</dd>
|
||||
<dt>Playbook</dt><dd>{{ run.playbook_path }}</dd>
|
||||
<dt>Scope</dt><dd title="{{ run.inventory_scope }}">{{ row.scope_label }}</dd>
|
||||
<dt>Source</dt><dd>{{ run.source_name }}</dd>
|
||||
<dt>Started by</dt><dd>{% if origin.url %}<a href="{{ origin.url }}">{{ origin.text }}</a>{% else %}{{ origin.text }}{% endif %}</dd>
|
||||
<dt>Playbook</dt><dd><a href="/ansible/browse/{{ run.source_name }}/{{ run.playbook_path }}">{{ run.playbook_path }}</a> <span class="tone-dim">from {{ run.source_name }}</span></dd>
|
||||
<dt>On</dt><dd>{{ row.scope_label }}</dd>
|
||||
{% if run.params %}
|
||||
{% if run.params.limit %}<dt>Limit</dt><dd class="mono">{{ run.params.limit }}</dd>{% endif %}
|
||||
{% if run.params.tags %}<dt>Tags</dt><dd class="mono">{{ run.params.tags }}</dd>{% endif %}
|
||||
@@ -30,45 +42,30 @@
|
||||
</div>
|
||||
|
||||
{% if hosts %}
|
||||
<div class="card">
|
||||
<div class="section-title" style="margin-bottom:0.6rem;">Host summary</div>
|
||||
<section class="card-flush" aria-labelledby="run-hosts">
|
||||
<h2 id="run-hosts" class="table-caption">Hosts <span class="tone-muted">worst first</span></h2>
|
||||
<table class="table">
|
||||
<thead><tr>
|
||||
<th>Host</th>
|
||||
<th style="text-align:center;">ok</th><th style="text-align:center;">changed</th>
|
||||
<th style="text-align:center;">unreachable</th><th style="text-align:center;">failed</th>
|
||||
<th style="text-align:center;">skipped</th>
|
||||
</tr></thead>
|
||||
<thead><tr><th>Host</th><th>Result</th></tr></thead>
|
||||
<tbody>
|
||||
{% for host, c in hosts %}
|
||||
<tr>
|
||||
<td style="font-family:ui-monospace,monospace;">{{ host }}</td>
|
||||
<td style="text-align:center;">{{ c.ok }}</td>
|
||||
<td style="text-align:center;color:{% if c.changed %}var(--yellow){% else %}var(--text-dim){% endif %};">{{ c.changed }}</td>
|
||||
<td style="text-align:center;color:{% if c.unreachable %}var(--red){% else %}var(--text-dim){% endif %};">{{ c.unreachable }}</td>
|
||||
<td style="text-align:center;color:{% if c.failed %}var(--red){% else %}var(--text-dim){% endif %};">{{ c.failed }}</td>
|
||||
<td style="text-align:center;color:var(--text-dim);">{{ c.skipped }}</td>
|
||||
</tr>
|
||||
{% for host, parts in hosts %}
|
||||
<tr><td class="mono">{{ host }}</td><td>{{ words(parts) if parts else '—' }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if run.results.failures %}
|
||||
<div class="section-title" style="margin:1rem 0 0.4rem;">Failures</div>
|
||||
<pre style="margin:0;padding:0.75rem;background:var(--bg);border-radius:4px;font-size:0.75rem;color:var(--red);max-height:240px;overflow:auto;white-space:pre-wrap;">{{ run.results.failures | join("\n") }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if run.results and run.results.failures %}
|
||||
<section class="card" aria-labelledby="run-failures">
|
||||
<h2 id="run-failures" class="section-title">What failed</h2>
|
||||
<pre class="run-log run-failures">{{ run.results.failures | join("\n") }}</pre>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" style="padding:0;">
|
||||
<div style="padding:0.75rem 1rem;background:var(--bg-elevated);border-bottom:1px solid var(--border);">
|
||||
<span style="color:var(--text-muted);font-size:0.85rem;">Output</span>
|
||||
</div>
|
||||
<section class="card-flush" aria-labelledby="run-output">
|
||||
<h2 id="run-output" class="table-caption">Output</h2>
|
||||
{% if run.status.value in ("running", "queued") %}
|
||||
<pre id="live-output"
|
||||
style="margin:0;padding:1rem;background:var(--bg);font-size:0.78rem;color:var(--green);max-height:600px;overflow-y:auto;white-space:pre-wrap;">{{ run.output or "" }}</pre>
|
||||
<div id="run-done-status" style="padding:0.5rem 1rem;font-size:0.85rem;color:var(--text-muted);">
|
||||
Streaming…
|
||||
</div>
|
||||
<pre id="live-output" class="run-log">{{ run.output or "" }}</pre>
|
||||
<p id="run-done-status" class="field-hint run-log-status">Streaming…</p>
|
||||
<script>
|
||||
(function() {
|
||||
var es = new EventSource('/ansible/runs/{{ run.id }}/stream');
|
||||
@@ -97,7 +94,7 @@
|
||||
})();
|
||||
</script>
|
||||
{% else %}
|
||||
<pre style="margin:0;padding:1rem;background:var(--bg);font-size:0.78rem;color:var(--text-muted);max-height:600px;overflow-y:auto;white-space:pre-wrap;">{{ run.output or "(no output)" }}</pre>
|
||||
<pre class="run-log">{{ run.output or "(no output)" }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user