Files
FabledSteward/steward/templates/ansible/inventory/index.html
T
bvandeusenandClaude Opus 5.5 7c346354f2
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 57s
CI / unit (push) Successful in 1m23s
CI / publish (push) Successful in 1m25s
feat(ansible): Inventory on one page, add behind dialogs, delete behind the confirm (#5333)
/ansible/inventory/ shows every target (address, its linked Steward host,
its groups) and every group (members, the schedules aimed at it) under a
verdict line. The whole row opens the target or group. Add target and
Add group open small dialogs; a failed add comes back with its dialog
open and what was typed. The red x leaves the rows: Delete is on the
target's or group's page, behind the confirm dialog, saying which
schedules would be left with nothing to run on.

The two list pages (/inventory/targets, /inventory/groups) are gone
with no redirect (rule 122); their URLs keep only the Add POST.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:57:54 -04:00

126 lines
6.1 KiB
HTML

{# ansible/inventory/index.html — the Inventory tab (#5333): a verdict line,
every target (address, the Steward host it is linked to, its groups), then
every group (its members, the schedules that run on it). The whole row opens
the target or group, where it is edited and deleted. Add target and Add
group open small dialogs; a failed add reopens its dialog with the error. #}
{% extends "base.html" %}
{% from "_macros.html" import crumbs, page_header, empty_state %}
{% from "ansible/_tabs.html" import ansible_tabs %}
{% block title %}Ansible Inventory — Steward{% endblock %}
{% block breadcrumb %}{{ crumbs([("Ansible", "/ansible/"), ("Inventory", "")]) }}{% endblock %}
{% block content %}
{% set can_edit = session.user_role in ('operator', 'admin') %}
{{ ansible_tabs("inventory") }}
{% call page_header("Inventory") %}
{% if can_edit %}
<button type="button" class="btn btn-ghost" onclick="document.getElementById('add-group').showModal()">Add group</button>
<button type="button" class="btn" onclick="document.getElementById('add-target').showModal()">Add target</button>
{% endif %}
{% endcall %}
{% if not targets and not groups %}
<div class="card">{{ empty_state("No targets yet. A target is a machine Ansible can reach: a name, an address, and the groups it is in.") }}</div>
{% else %}
<p class="page-verdict">
{{ verdict.targets }} target{{ 's' if verdict.targets != 1 }} in {{ verdict.groups }} group{{ 's' if verdict.groups != 1 }}
{%- if verdict.ungrouped %} · <span class="tone-warn">{{ verdict.ungrouped }} in no group</span>{% endif %}
{%- if verdict.unlinked %} · {{ verdict.unlinked }} not linked to a Steward host{% endif %}
</p>
<section class="card-flush" aria-labelledby="inv-targets">
<h2 id="inv-targets" class="table-caption">Targets</h2>
{% if targets %}
<table class="table">
<thead><tr><th>Target</th><th>Steward host</th><th>Groups</th></tr></thead>
<tbody>
{% for t in targets %}
{% set h = hosts.get(t.host_id) %}
<tr class="row-link">
<td class="cell-name">
<a class="row-target" href="/ansible/inventory/targets/{{ t.id }}">{{ t.name }}</a>
<div class="cell-sub mono">{{ t.address }}</div>
</td>
<td>{% if h %}<a class="row-inner" href="/hosts/{{ h.id }}">{{ h.name }}</a>{% else %}<span class="tone-dim">—</span>{% endif %}</td>
<td>
{%- for g in t.groups %}<span class="chip">{{ g.name }}</span>{% else %}<span class="tone-warn">none</span>{% endfor -%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="card-flush-note tone-muted">No targets yet.</p>
{% endif %}
</section>
<section class="card-flush" aria-labelledby="inv-groups">
<h2 id="inv-groups" class="table-caption">Groups</h2>
{% if groups %}
<table class="table">
<thead><tr><th>Group</th><th>Members</th><th>Schedules</th></tr></thead>
<tbody>
{% for g in groups %}
{% set used = users.get('steward:group:' ~ g.id, []) %}
<tr class="row-link">
<td class="cell-name"><a class="row-target" href="/ansible/inventory/groups/{{ g.id }}">{{ g.name }}</a></td>
<td>
{%- for t in g.targets|sort(attribute='name') %}<span class="chip">{{ t.name }}</span>{% else %}<span class="tone-dim">no members</span>{% endfor -%}
</td>
<td>
{%- for s in used %}<a class="row-inner" href="/ansible/schedules/{{ s.id }}">{{ s.name }}</a>{{ ", " if not loop.last }}{% else %}<span class="tone-dim">—</span>{% endfor -%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="card-flush-note tone-muted">No groups yet. A group is what a run or a schedule targets, and what the Hosts list filters by.</p>
{% endif %}
</section>
{% endif %}
{% if can_edit %}
{% set a = add or {} %}
{% set v = a.get('values') or {} %}
<dialog id="add-target" class="modal" aria-labelledby="add-target-title">
<h2 id="add-target-title" class="modal-title">Add target</h2>
{% if a.kind == 'target' and a.error %}<p class="field-error" role="alert">{{ a.error }}</p>{% endif %}
<form method="post" action="/ansible/inventory/targets">{{ csrf_field() }}
<div class="field">
<label class="field-label" for="add-target-name">Name</label>
<input class="input" id="add-target-name" type="text" name="name" required placeholder="web-01"
value="{{ v.name if a.kind == 'target' else '' }}">
<span class="field-hint">What plays call it: <code>hosts: web-01</code>.</span>
</div>
<div class="field">
<label class="field-label" for="add-target-address">Address</label>
<input class="input mono" id="add-target-address" type="text" name="address" required placeholder="192.168.1.10"
value="{{ v.address if a.kind == 'target' else '' }}">
<span class="field-hint">An IP or DNS name. Groups, variables and the Steward host are set on its page next.</span>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-sm btn-ghost" onclick="this.closest('dialog').close()">Cancel</button>
<button type="submit" class="btn btn-sm">Add target</button>
</div>
</form>
</dialog>
<dialog id="add-group" class="modal" aria-labelledby="add-group-title">
<h2 id="add-group-title" class="modal-title">Add group</h2>
{% if a.kind == 'group' and a.error %}<p class="field-error" role="alert">{{ a.error }}</p>{% endif %}
<form method="post" action="/ansible/inventory/groups">{{ csrf_field() }}
<div class="field">
<label class="field-label" for="add-group-name">Name</label>
<input class="input" id="add-group-name" type="text" name="name" required placeholder="webservers"
value="{{ v.name if a.kind == 'group' else '' }}">
<span class="field-hint">Members and variables are set on its page next.</span>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-sm btn-ghost" onclick="this.closest('dialog').close()">Cancel</button>
<button type="submit" class="btn btn-sm">Add group</button>
</div>
</form>
</dialog>
{% if a.kind in ('target', 'group') %}<script>document.getElementById('add-{{ a.kind }}').showModal();</script>{% endif %}
{% endif %}
{% endblock %}