Files
FabledSteward/steward/templates/timeline/list.html
T
bvandeusenandClaude Opus 5.5 329aa15e3d
CI / lint (push) Successful in 2s
CI / integration (push) Failing after 1m20s
CI / unit (push) Failing after 1m33s
CI / publish (push) Skipped
feat(timeline): record a change by hand from the Timeline, a host's page or MCP log_change (#6350)
A change Steward can't see happen (a config edited, a disk swapped) is
recorded by whoever made it: what was done, its area, when (in the reader's
own zone), the hosts, what it was done to and a detail. Its author or an
admin edits or deletes it; recording, editing and deleting are audited and
those audit rows stay off the timeline. log_change records one as
"<user> via MCP", editable by the token's user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:26:02 -04:00

56 lines
2.7 KiB
HTML

{# timeline/list.html — everything that happened to the infrastructure, newest
first, one list: alerts, playbook runs, changes to Steward's settings.
Filters are by host, area (what was affected) and kind (how it came about);
Settings → Timeline decides each type's area and whether it is shown, and
"Include hidden" shows the types it hides. #}
{% extends "base.html" %}
{% from "_macros.html" import page_header, empty_state %}
{% block title %}Timeline — Steward{% endblock %}
{% block content %}
{% call page_header("Timeline", "What happened, newest first: alerts, playbook runs and changes, on every host.") %}
{% if session.user_role in ('operator', 'admin') %}<a class="btn btn-sm" href="/timeline/changes/new?{{ ({'host': filters.host, 'back': back} if filters.host else {'back': back})|urlencode }}">Record a change</a>{% endif %}
{% if session.user_role == 'admin' %}<a class="btn btn-ghost btn-sm" href="/settings/timeline/">Areas and types</a>{% endif %}
{% endcall %}
<form class="filter-bar" method="get" action="/timeline/" role="search">
<select name="host" aria-label="Host" onchange="this.form.submit()">
<option value="">All hosts</option>
{% for h in hosts %}<option value="{{ h }}"{% if filters.host == h %} selected{% endif %}>{{ h }}</option>{% endfor %}
</select>
<select name="area" aria-label="Area" onchange="this.form.submit()">
<option value="">All areas</option>
{% for key, label in areas %}<option value="{{ key }}"{% if filters.area == key %} selected{% endif %}>{{ label }}</option>{% endfor %}
</select>
<select name="kind" aria-label="Kind" onchange="this.form.submit()">
<option value="">All kinds</option>
{% for key, label in kinds.items() %}<option value="{{ key }}"{% if filters.kind == key %} selected{% endif %}>{{ label }}</option>{% endfor %}
</select>
<label class="field-hint" style="display:flex;align-items:center;gap:0.35rem;">
<input type="checkbox" name="hidden" value="1"{% if filters.hidden %} checked{% endif %} onchange="this.form.submit()">
Include hidden types
</label>
<noscript><button type="submit" class="btn btn-ghost btn-sm">Filter</button></noscript>
</form>
{% if items %}
<div class="card-flush">
<table class="table">
<thead>
<tr>
<th>When ({{ tz_label() }})</th>
<th>What happened</th>
<th>Where</th>
<th>Who</th>
<th>Area</th>
</tr>
</thead>
<tbody>
{% include "timeline/_rows.html" %}
</tbody>
</table>
</div>
{% else %}
<div class="card">{{ empty_state("Nothing on the timeline matches these filters." if keep else "Nothing has been recorded on the timeline yet.") }}</div>
{% endif %}
{% endblock %}