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:
@@ -73,6 +73,17 @@
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{# A section's own pages as tabs under the crumbs (#5328): items are
|
||||
(key, label, href); the one whose key is `active` is the current page.
|
||||
Links, not a tablist: each tab is its own page. #}
|
||||
{% macro section_tabs(items, active, label) -%}
|
||||
<nav class="section-tabs" aria-label="{{ label }}">
|
||||
{%- for key, text, href in items %}
|
||||
<a href="{{ href }}"{% if key == active %} aria-current="page"{% endif %}>{{ text }}</a>
|
||||
{%- endfor %}
|
||||
</nav>
|
||||
{%- endmacro %}
|
||||
|
||||
{# A button that shows only an icon. The label is required: it is the button's
|
||||
accessible name and its tooltip, and rendering one without it raises. #}
|
||||
{% macro icon_button(icon, label, href=none, cls="btn-ghost") -%}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
{# ansible/_runs_page.html — one page of runs, then the sentinel that loads the
|
||||
next page as the reader nears it (core.paging, pref 172). The whole row
|
||||
opens the run (.row-link); its title is the one real link. #}
|
||||
opens the run (.row-link); its title is the one real link. Under the status,
|
||||
what the run did across its hosts (ansible.recap.run_recap). #}
|
||||
{% from "_macros.html" import status_badge, scroll_more, scroll_end %}
|
||||
{% for row in runs %}
|
||||
{% set run = row.run %}
|
||||
<tr class="row-link">
|
||||
<td>
|
||||
<a class="row-target" href="/ansible/runs/{{ run.id }}">{{ row.title }}</a>
|
||||
{% if row.check %}<span class="badge badge-dim" title="Ansible check mode: nothing was changed">dry run</span>{% endif %}
|
||||
<div class="field-hint">{{ run.source_name }}</div>
|
||||
<td class="cell-name">
|
||||
<a class="row-target" href="/ansible/runs/{{ run.id }}" title="{{ row.title }}">{{ row.title }}</a>
|
||||
<div class="cell-sub">{{ run.source_name }}{% if row.check %} · <span class="tone-info" title="Ansible check mode: nothing was changed">dry run</span>{% endif %}</div>
|
||||
</td>
|
||||
<td>{{ status_badge(run.status) }}</td>
|
||||
<td style="font-size:0.85rem;">{% if row.origin %}{% if row.origin.url %}<a href="{{ row.origin.url }}">{{ row.origin.text }}</a>{% else %}{{ row.origin.text }}{% endif %}{% endif %}</td>
|
||||
<td title="{{ run.started_at.strftime('%Y-%m-%d %H:%M:%S UTC') }}">{{ run.started_at|ago }}</td>
|
||||
<td>{{ row.duration_s|duration }}{% if not run.finished_at and run.status.value in ('running', 'queued') %} so far{% endif %}</td>
|
||||
<td>
|
||||
{{ status_badge(run.status) }}
|
||||
{%- if row.recap %}
|
||||
<div class="cell-sub">{% for text, tone in row.recap %}{% if not loop.first %} · {% endif %}{% if tone %}<span class="tone-{{ tone }}">{{ text }}</span>{% else %}{{ text }}{% endif %}{% endfor %}</div>
|
||||
{%- endif %}
|
||||
</td>
|
||||
<td>{% if row.origin %}{{ row.origin.text }}{% endif %}</td>
|
||||
<td class="nowrap" title="{{ run.started_at.strftime('%Y-%m-%d %H:%M:%S UTC') }}">{{ run.started_at|ago }}</td>
|
||||
<td class="num nowrap">{{ row.duration_s|duration }}{% if not run.finished_at and run.status.value in ('running', 'queued') %} so far{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if after %}
|
||||
{{ scroll_more("/ansible/?before=" ~ (after|urlencode), cols=5) }}
|
||||
{% if more_url %}
|
||||
{{ scroll_more(more_url, cols=5) }}
|
||||
{% else %}
|
||||
{{ scroll_end(cols=5) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{# ansible/_runs_table.html — the runs table under the filter bar; the bar's
|
||||
HTMX requests swap this alone. #}
|
||||
{% from "_macros.html" import empty_state %}
|
||||
{% if not runs %}
|
||||
<div class="card">
|
||||
{%- if filtered %}{{ empty_state("No runs match these filters.", "Show every run", "/ansible/") }}
|
||||
{%- else %}{{ empty_state("No runs yet.", "Run a playbook", "/ansible/browse") }}{% endif -%}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card-flush">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Result</th>
|
||||
<th>Started by</th>
|
||||
<th>Started</th>
|
||||
<th class="num">Took</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% include "ansible/_runs_page.html" %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{# ansible/_tabs.html — the Ansible section's pages as one strip of tabs (#5328),
|
||||
on every Ansible page; `active` names the page or the one it sits under. #}
|
||||
{% from "_macros.html" import section_tabs %}
|
||||
{% macro ansible_tabs(active) -%}
|
||||
{{ section_tabs([("runs", "Runs", "/ansible/"), ("playbooks", "Playbooks", "/ansible/browse"),
|
||||
("schedules", "Schedules", "/ansible/schedules"),
|
||||
("inventory", "Inventory", "/ansible/inventory/targets")], active, "Ansible") }}
|
||||
{%- endmacro %}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}Browse Playbooks — Steward{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
@@ -9,14 +10,13 @@
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("playbooks") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Browse Playbooks</h1>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
{% if session.user_role == 'admin' %}
|
||||
<a href="/ansible/playbooks/new" class="btn btn-sm">New playbook</a>
|
||||
{% endif %}
|
||||
<a href="/ansible/inventory/targets" class="btn btn-ghost btn-sm">Inventory</a>
|
||||
<a href="/ansible/schedules" class="btn btn-ghost btn-sm">Schedules</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
{# ansible/index.html — the Runs tab (#5329): a verdict line (how the last
|
||||
30 days went, failures first, what runs next), a filter bar, then every run
|
||||
newest first as the reader scrolls (ansible/_runs_table.html). #}
|
||||
{% extends "base.html" %}
|
||||
{% from "_macros.html" import page_header, status_badge, empty_state %}
|
||||
{% from "_macros.html" import page_header %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% block title %}Ansible Runs — Steward{% endblock %}
|
||||
{% block content %}
|
||||
{% call page_header("Ansible Runs", "Each run by what it ran where, newest first.") %}
|
||||
<a href="/ansible/inventory/targets" class="btn btn-ghost">Inventory</a>
|
||||
<a href="/ansible/schedules" class="btn btn-ghost">Schedules</a>
|
||||
<a href="/ansible/browse" class="btn">Browse Playbooks</a>
|
||||
{{ ansible_tabs("runs") }}
|
||||
{% call page_header("Ansible Runs") %}
|
||||
<a href="/ansible/browse" class="btn">Run a playbook</a>
|
||||
{% endcall %}
|
||||
{% if not runs %}
|
||||
<div class="card">{{ empty_state("No runs yet.", "Browse playbooks", "/ansible/browse") }}</div>
|
||||
{% else %}
|
||||
<div class="card-flush">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Run</th>
|
||||
<th>Status</th>
|
||||
<th>Started by</th>
|
||||
<th>Started</th>
|
||||
<th>Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% include "ansible/_runs_page.html" %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set v = verdict %}
|
||||
<p class="page-verdict">
|
||||
{%- if not v.total %}<span class="tone-muted">No runs in the last {{ v.days }} days</span>
|
||||
{%- elif v.failed %}<span class="tone-crit">{{ v.failed }} of {{ v.total }} run{{ 's' if v.total != 1 }} failed</span> in the last {{ v.days }} days
|
||||
{%- if v.latest_failed %} — latest <a href="/ansible/runs/{{ v.latest_failed.run.id }}">{{ v.latest_failed.title }}</a> {{ v.latest_failed.run.started_at|ago }}{% endif %}
|
||||
{%- else %}<span class="tone-ok">{{ v.total }} run{{ 's' if v.total != 1 }} in the last {{ v.days }} days, {{ 'all' if v.total > 1 else 'it' }} succeeded</span>{% endif %}
|
||||
{%- if v.running %} · <span class="tone-info">{{ v.running }} running now</span>{% endif %}
|
||||
{%- if v.next %} · next: <a href="/ansible/schedules">{{ v.next.name }}</a> {% if v.next.at %}{{ v.next.at|ago }}{% else %}due now{% endif %}{% endif -%}
|
||||
</p>
|
||||
|
||||
<form class="filter-bar" method="get" action="/ansible/" role="search"
|
||||
hx-target="#runs-table" hx-push-url="true" hx-include="closest form">
|
||||
<input type="search" name="q" value="{{ f.q }}" class="input" placeholder="Search playbooks"
|
||||
aria-label="Search runs by playbook" autocomplete="off"
|
||||
hx-get="/ansible/" hx-trigger="input changed delay:250ms, search">
|
||||
<select name="status" aria-label="Status" hx-get="/ansible/" hx-trigger="change">
|
||||
<option value="">Any status</option>
|
||||
{% for key, (label, _) in statuses.items() %}<option value="{{ key }}"{% if f.status == key %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
<select name="origin" aria-label="Started by" hx-get="/ansible/" hx-trigger="change">
|
||||
<option value="">Started by anyone</option>
|
||||
{% for key, label in origins.items() %}<option value="{{ key }}"{% if f.origin == key %} selected{% endif %}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
<noscript><button type="submit" class="btn btn-sm">Filter</button></noscript>
|
||||
</form>
|
||||
<div id="runs-table">{% include "ansible/_runs_table.html" %}</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}Group: {{ group.name }} — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Inventory", "/ansible/inventory/targets"), ("Groups", "/ansible/inventory/groups"), (group.name, "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("inventory") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Group: {{ group.name }}</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}Inventory Groups — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Inventory", "/ansible/inventory/targets"), ("Groups", "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("inventory") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Inventory Groups</h1>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}Target: {{ target.name }} — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Inventory", "/ansible/inventory/targets"), ("Targets", "/ansible/inventory/targets"), (target.name, "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("inventory") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Target: {{ target.name }}</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}Inventory Targets — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Inventory", "/ansible/inventory/targets"), ("Targets", "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("inventory") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Inventory Targets</h1>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs %}
|
||||
{% block title %}{{ "Edit" if editing else "New" }} Playbook — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Browse", "/ansible/browse"), (("Edit " ~ playbook_path) if editing else "New playbook", "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{{ ansible_tabs("playbooks") }}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;gap:1rem;flex-wrap:wrap;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">{{ "Edit playbook" if editing else "New playbook" }}</h1>
|
||||
</div>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "ansible/_tabs.html" import ansible_tabs %}
|
||||
{% from "_macros.html" import crumbs, status_badge %}
|
||||
{% block title %}Ansible Schedules — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Schedules", "")]) }}{% endblock %}
|
||||
@@ -9,13 +10,10 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
{{ ansible_tabs("schedules") }}
|
||||
{% set can_edit = session.user_role in ('operator', 'admin') %}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;gap:1rem;flex-wrap:wrap;">
|
||||
<h1 class="page-title" style="margin-bottom:0;">Ansible Schedules</h1>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
<a href="/ansible/inventory/targets" class="btn btn-ghost">Inventory</a>
|
||||
<a href="/ansible/browse" class="btn btn-ghost">Browse</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Existing schedules ───────────────────────────────────────────────────── #}
|
||||
|
||||
Reference in New Issue
Block a user