- 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>
125 lines
6.4 KiB
HTML
125 lines
6.4 KiB
HTML
{# Shared template macros. Import with: {% from "_macros.html" import crumbs %} #}
|
||
|
||
{# Breadcrumb trail. `items` is a list of (label, href) pairs, ordered root→current.
|
||
Intermediate items with a truthy href render as links; the last item (or any
|
||
item with an empty href) renders as plain current text. #}
|
||
{% macro crumbs(items) -%}
|
||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||
{%- for label, href in items -%}
|
||
{%- if loop.last -%}
|
||
<span class="breadcrumb-cur" aria-current="page">{{ label }}</span>
|
||
{%- elif href -%}
|
||
<a href="{{ href }}">{{ label }}</a><span class="breadcrumb-sep" aria-hidden="true">›</span>
|
||
{%- else -%}
|
||
<span>{{ label }}</span><span class="breadcrumb-sep" aria-hidden="true">›</span>
|
||
{%- endif -%}
|
||
{%- endfor -%}
|
||
</nav>
|
||
{%- endmacro %}
|
||
|
||
{# A state as a labelled badge: up, down, stale, firing, pending, acknowledged,
|
||
no_data, ok, warn, crit, resolved, inactive (formatting.STATES). `state` may
|
||
be a string or an enum; `label` replaces the standard wording. #}
|
||
{% macro status_badge(state, label=none, title=none) -%}
|
||
{%- set tone, text = status_info(state) -%}
|
||
<span class="badge badge-{{ tone }}"{% if title %} title="{{ title }}"{% endif %}>{{ label or text }}</span>
|
||
{%- endmacro %}
|
||
|
||
{# A state as a coloured dot. The dot is never the only signal: its label is
|
||
read out to a screen reader, and shown beside it when `show_label`. #}
|
||
{% macro status_dot(state, label=none, show_label=false) -%}
|
||
{%- set tone, text = status_info(state) -%}
|
||
<span class="dot dot-{{ tone }}" aria-hidden="true" title="{{ label or text }}"></span>
|
||
{%- if show_label %}<span class="dot-label">{{ label or text }}</span>{% else %}<span class="sr-only">{{ label or text }}</span>{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{# Threshold colouring: `threshold_style(value, kind)` (an inline style) and
|
||
`status_class(value, kind)` ('ok' / 'warn' / 'crit' / 'unknown') are jinja
|
||
globals reading the cutoffs from Settings → Thresholds. #}
|
||
|
||
{# One headline number. `state` (a STATES name, or a status_class result)
|
||
colours the value; `href` makes the whole tile a link. #}
|
||
{% macro stat_tile(label, value, state=none, hint=none, href=none) -%}
|
||
{%- set tone = status_info(state)[0] if state else none -%}
|
||
{%- set body -%}
|
||
<span class="stat-tile-label">{{ label }}</span>
|
||
<span class="stat-tile-value">{{ value }}</span>
|
||
{%- if hint %}<span class="stat-tile-hint">{{ hint }}</span>{% endif %}
|
||
{%- endset -%}
|
||
{%- if href -%}
|
||
<a href="{{ href }}" class="stat-tile{% if tone %} stat-{{ tone }}{% endif %}">{{ body }}</a>
|
||
{%- else -%}
|
||
<div class="stat-tile{% if tone %} stat-{{ tone }}{% endif %}">{{ body }}</div>
|
||
{%- endif -%}
|
||
{%- endmacro %}
|
||
|
||
{# What a list shows when it has nothing in it, with an optional next step. #}
|
||
{% macro empty_state(message, action_label=none, action_href=none) -%}
|
||
<div class="empty-state">
|
||
<p>{{ message }}</p>
|
||
{%- if action_label and action_href %}<a class="btn btn-ghost btn-sm" href="{{ action_href }}">{{ action_label }}</a>{% endif %}
|
||
</div>
|
||
{%- endmacro %}
|
||
|
||
{# The page's <h1>, with its actions to the right:
|
||
{% call page_header("Alerts") %}<a class="btn" href="…">New rule</a>{% endcall %} #}
|
||
{% macro page_header(title, subtitle=none) -%}
|
||
<div class="page-header">
|
||
<div>
|
||
<h1 class="page-title">{{ title }}</h1>
|
||
{%- if subtitle %}<p class="page-subtitle">{{ subtitle }}</p>{% endif %}
|
||
</div>
|
||
{%- if caller is defined %}<div class="page-actions">{{ caller() }}</div>{% endif %}
|
||
</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") -%}
|
||
{%- set name = required(label, "icon_button label") -%}
|
||
{%- if href -%}
|
||
<a class="btn btn-sm btn-icon {{ cls }}" href="{{ href }}" aria-label="{{ name }}" title="{{ name }}">{{ icon }}</a>
|
||
{%- else -%}
|
||
<button type="{{ kwargs.get('type', 'button') }}" class="btn btn-sm btn-icon {{ cls }}" aria-label="{{ name }}" title="{{ name }}"
|
||
{%- if kwargs.get('confirm') %} data-confirm="{{ kwargs.confirm }}"{% endif %}>{{ icon }}</button>
|
||
{%- endif -%}
|
||
{%- endmacro %}
|
||
|
||
{# A list that continues itself (#5211, pref 172; static/scroll-more.js).
|
||
scroll_more ends a page while there are more rows: it fetches `url` (the
|
||
next page, built with core.paging's cursor) when it comes near the
|
||
viewport, and is replaced by that page's rows. scroll_end ends the last
|
||
page. Pass `cols` inside a table body, to render as a full-width row. #}
|
||
{% macro scroll_more(url, cols=none) -%}
|
||
{%- if cols %}<tr data-scroll-more hx-get="{{ url }}" hx-trigger="scroll-more" hx-swap="outerHTML"><td colspan="{{ cols }}">
|
||
{%- else %}<div data-scroll-more hx-get="{{ url }}" hx-trigger="scroll-more" hx-swap="outerHTML">{% endif -%}
|
||
<span class="field-hint" role="status" aria-live="polite" data-scroll-status>Loading more…</span>
|
||
{%- if cols %}</td></tr>{% else %}</div>{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{% macro scroll_end(cols=none, text="End of the list.") -%}
|
||
{%- if cols %}<tr><td colspan="{{ cols }}"><span class="field-hint" role="status">{{ text }}</span></td></tr>
|
||
{%- else %}<p class="field-hint" role="status">{{ text }}</p>{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{# A table header that sorts its column (#5294). `url` is the list with this
|
||
column as the sort (the route builds it, keeping the other filters);
|
||
`active` and `desc` say whether it is the current sort and which way.
|
||
A plain link: the whole page reloads, so the filter bar stays in step. #}
|
||
{% macro sort_th(label, url, active=false, desc=false, cls=none, title=none) -%}
|
||
<th{% if cls %} class="{{ cls }}"{% endif %}{% if title %} title="{{ title }}"{% endif %} aria-sort="{{ ('descending' if desc else 'ascending') if active else 'none' }}">
|
||
<a class="sort-link{% if active %} sort-active{% endif %}" href="{{ url }}">{{ label }}<span class="sort-arrow" aria-hidden="true">{% if active %}{{ '↓' if desc else '↑' }}{% endif %}</span></a>
|
||
</th>
|
||
{%- endmacro %}
|