{% extends "ui/_layout.html" %} {% block title %}{% if section == "downloads" %}Downloads{% elif section == "hashes" %}Hashes{% else %}Images{% endif %} - bty-web{% endblock %} {% block subnav %} {# In-page jump links to the page's two tables (the main list + the activity table), separated by a vertical rule. The List anchor points at whichever table this section shows (catalog / downloads / hashes); Activity points at the shared image-catalog activity table. #} {% endblock %} {% block intro %} {% from "ui/_intro_box.html" import render as intro_box %} {% call intro_box() %} {% if section == "downloads" %} Add a single image (upload a local file, or add one by http(s):// / oras:// URL) and watch the live fetch jobs below. Reach this page any time from the downloads indicator in the navbar, right of Settings. {% elif section == "hashes" %} Background SHA-256 hashing of files under {{ image_root }}: dropped-in images are hashed automatically (one at a time by default, so small hardware stays responsive). Reach this page from the hashes indicator in the navbar. {% else %} Pre-built system images bty flashes onto target disks, content- addressed by sha256. This is the merged catalog (dir scan + catalog entries): its header carries Fetch latest catalog (pull the bty release catalog) and Upload catalog (a catalog.toml). Files dropped into {{ image_root }} are picked up and hashed automatically; add individual images from the Downloads page. {% endif %} {% endcall %} {% endblock %} {% block content %} {% if section == "downloads" %} {# Downloads: the two add-image controls (add by URL, upload a local file) live inline in the table header, split by a vertical rule; the live download-jobs table is below. #}
{# ``py-1`` + ``flex-nowrap`` keep this a single tight row: the two add-image forms used to wrap to a second line (doubling the header height) on a narrow window. The inputs shrink (min-width:0) instead, and the button labels stay on one line (text-nowrap). #}
Downloads refreshing... {# type="text" not type="url": browsers reject oras:// under HTML5 URL validation; server-side Pydantic still validates. #}
{# Streams via XHR PUT /images/; the HashManager auto-enqueues a SHA on the post-write hook. #}
{# Upload progress (hidden until an upload runs). #}
0%
Name Status Progress Bytes Action
No downloads yet.
{% elif section == "hashes" %}
Hashes background SHA-256 of dir-scan files; default 1 at a time so small hardware (Pi / NUC) stays responsive
Name Status Progress Bytes Action
No hashes yet.
{% else %}
{# ``py-1`` + ``flex-nowrap``: keep the catalog actions a single tight row instead of wrapping to a second line on a narrow window. The upload input shrinks; button labels stay intact. #}
Images {# Two catalog actions side by side, separated by a vertical rule: upload a local catalog.toml, or fetch the bty release catalog.toml in one click (both were dropped sub-nav pages). #} {# Parsed before it replaces the current catalog ({{ manifest_path }}); a bad file bounces back. #}
{# Observed content sha (``disk_image_sha`` on the catalog row). Machines bind via ``bty_image_ref`` -- shown in the machine- detail picker, not here. #} {% for u in unified %} {% endfor %} {% if not unified %} {% endif %}
Name(s)Content SHA Format Sources Cached Action
{% for n in u.names %} {{ n }}{% if not loop.last %}, {% endif %} {% endfor %} {% if u.sha256 %} {{ u.sha256[:12] }}... {% else %} unhashed {% endif %} {{ u.format or "?" }} {% for s in u.sources %} {% if s.kind == "local" %} {{ s.location }} {% elif s.kind == "url" %} {{ s.location }} {% else %} {{ s.location }} {% endif %} {% if not loop.last %}
{% endif %} {% endfor %}
{% if u.cached %} cached {% else %} available {% endif %} {# Dispatch by source shape, not just sha presence: - Has a ``local`` source and no sha -> the file is on disk and just needs hashing. Hash button. - No local source -> bytes need fetching. Fetch button. Works for un-sha'd URL / oras entries (the DownloadManager downloads + computes sha + back-fills catalog_entries.disk_image_sha) AND for sha-pinned entries not yet in cache. Previously every un-sha'd entry rendered the Hash button, which 404'd on URL-only rows because HashManager requires a local file. #} {% set has_local = u.sources|selectattr('kind','equalto','local')|list|length > 0 %} {% if not u.sha256 and has_local %} {% elif not u.cached %} {% endif %} {# Evict cached bytes. Available for any entry whose bytes are in the cache dir (``$BTY_STATE_DIR/cache/``). Backed by ``DELETE /catalog/cache/{name}`` which unlinks the cached file but preserves the catalog entry's metadata -- the next Fetch re-downloads. Useful for forcing a re-pull when a rolling tag has rotated upstream. Suppressed for purely-local rows (``local`` source only) since deleting those would unlink the operator's source file rather than just a cache copy. #} {% set has_remote = u.sources|selectattr('kind','in',['manifest','url'])|list|length > 0 %} {% if u.cached and has_remote %} {% endif %} {# Delete the catalog entry. Backed by ``DELETE /catalog/entries?src=`` which wipes the catalog_entries row. The button shows for every entry that has at least one ``manifest`` or ``url``-kind source (i.e. everything except local-only dir-scan rows, where the right delete action is to remove the underlying file). #} {% if has_remote %} {% set entry_src = u.sources|selectattr('kind','in',['manifest','url'])|map(attribute='location')|first %} {% endif %} {% if not has_remote and not u.cached and not (not u.sha256 and has_local) %} - {% endif %}
No images yet. Use Fetch latest or Upload above, or drop a .qcow2 / .img / .img.zst / .img.xz / .img.gz / .img.bz2 file into {{ image_root }}.
{% endif %} {# Image-relevant event slice (uploads, hashes, catalog adds / deletes). The /ui/events drill-down filters to subject_kind= image; catalog rows are picked up via the broader filter at /ui/events itself. Shown on every section so an operator doing an upload sees the recent activity context. #} {% with events=image_events, title="Recent image-catalog activity", link_to_full="/ui/events?subject_kind=image", card_id="images-activity" %} {% include "ui/_events_card.html" %} {% endwith %} {% endblock %}