{% extends "ui/_layout.html" %} {% from "ui/_table_macros.html" import sort_header, pagination_inline with context %} {% block title %}Machines - bty-web{% endblock %} {% block subnav %} {# Left: in-page jump links (List | Activity). Right: the inline add-by-MAC action, moved here from the card header so the page-level "create a new machine" action lives in the page chrome and the card-body stays focused on the data table. The form's id + JS submit are unchanged so the existing upsert handler + the inline-add tests keep working. #} {% endblock %} {% block intro %} {% from "ui/_intro_box.html" import render as intro_box %} {% call intro_box() %} Machines auto-appear on first remote contact (PXE or manual tui --server .. --mac ..); to stage one before it boots, use the Add field in the table header (image binding + boot mode are set per-machine on the detail page). {% endcall %} {% endblock %} {% block content %} {# Post-delete flash banner. A non-trivial action (delete a machine record) deserves operator feedback whether or not the row actually existed. ``?deleted=`` after a real removal; ``?missing=`` when the row was already gone (stale tab, re-clicked delete). Auto-dismiss after 5 seconds via the bootstrap data-bs-dismiss + setTimeout below. #} {% if flash_deleted %} {% elif flash_missing %} {% endif %} {% if flash_deleted or flash_missing %} {% endif %} {# The live table of all machines, with a minimal inline add-by-MAC field in the header. Machines also auto-appear on first PXE contact; the inline Add just stages a row early. #}
{# Card header: "Machines | Filter: " flexes to fill the left side; pagination sits on the right. The Add-MAC action moved into the subnav strip; this card stays focused on the data view. #}
{% if active_filter %}{% endif %} {% if sort.column != 'mac' %}{% endif %} {% if sort.direction != 'asc' %}{% endif %} {% if page.per_page != per_page_choices[0] %}{% endif %} Machines {% if q %} Clear {% endif %}
{{ pagination_inline(page, preserved, label='machines') }}
{# Column order is identity (MAC, Labels) -> configuration (Image, Boot) -> timeline (Last seen, Last IP, Last flashed) -> actions. Narrow columns get width:1% + nowrap so they stay tight and only MAC / Labels / Image absorb the slack. Labels is unsortable -- a machine carries a SET of tags, "sort by labels" has no natural meaning when each row has several. #} {{ sort_header('MAC', 'mac', sort, preserved, 'style="white-space: nowrap;"') }} {{ sort_header('Image', 'bty_image_ref', sort, preserved) }} {{ sort_header('Boot', 'boot_mode', sort, preserved, 'style="width: 1%; white-space: nowrap;"') }} {{ sort_header('Last seen', 'last_seen_at', sort, preserved, 'style="width: 1%; white-space: nowrap;"') }} {{ sort_header('Last IP', 'last_seen_ip', sort, preserved, 'style="width: 1%; white-space: nowrap;"') }} {{ sort_header('Last flashed', 'last_flashed_at', sort, preserved, 'style="width: 1%; white-space: nowrap;"') }} {# The tbody subscribes to the SSE stream so it auto-updates when the operator (or auto-discovery) mutates machines. The browser carries the bty-token cookie automatically; that is what authenticates the EventSource connection. ``sse-target`` opts in to the layout-level flash animation. When a server-side filter is active (``?filter=...``) the SSE wiring is dropped: the next ``machines-update`` would replace the filtered tbody with the full un-filtered list, which is operator-confusing. The page becomes static under filter; reload to refresh. #} {% include "ui/_machines_tbody.html" %}
Labels
{# Recent machine activity (discoveries, flashes, inventory posts). The "Activity" sub-nav anchor jumps here; always rendered (empty-state row when there's nothing yet). #} {% with events=machine_events, title="Last " + recent_events_limit|string + " Events", link_to_full="/ui/events?q=machine", card_id="machines-activity" %} {% include "ui/_events_card.html" %} {% endwith %} {% endblock %}