{% extends "ui/_layout.html" %} {% block title %}Images - bty-web{% endblock %} {% block content %}

Images

{{ unified|length }} entr{{ "y" if unified|length == 1 else "ies" }} | image-root {{ image_root }}
Add image by URL
{# type="text" rather than type="url": some browsers reject non-http schemes (oras://) under HTML5 URL validation. Server-side Pydantic validation still runs (regex covers http/https/oras schemes). #}
Image URL is required. For http(s):// URLs, providing a SHA URL (a sha256sum-style manifest) pins the content sha at add time so cache-through can verify it; without it the first flash trusts the upstream bytes and back-fills disk_image_sha with what it observed. Either way the entry is bindable to a machine via its bty_image_ref. For oras:// refs the server resolves the OCI manifest at add time and uses the layer's content-addressed digest -- no SHA URL needed (it's ignored).
Upload image file
{# Streams the file via XHR PUT to /images/ -- the existing API endpoint that takes ``application/octet- stream``. JS handles progress + auth (cookie, same- origin); on success we reload so the new file shows up in the catalog table below. The HashManager picks up the upload via PUT /images/'s post-write hook (auto-enqueue), so a SHA appears in seconds. #}
0%
Pick a file -- it streams directly into the image root via PUT /images/<name>. SHA-256 is auto-computed in the background after upload completes.
Catalog manifest replaces {{ manifest_path or "${BTY_STATE_DIR}/catalog.toml" }}; reloads the download manager in-process
Parses before clobbering an existing manifest -- a bad upload bounces back with the parse error.
Pulls releases/latest/download/catalog.toml from {{ release_repo }}.
{% if not unified %}
No images yet. Add one with the form above, drop a .qcow2 / .img / .img.zst / .img.xz / .img.gz / .img.bz2 file under {{ image_root }}, or author a catalog.toml manifest. Refresh the page after either to see them here.
{% else %}
Unified catalog (dir scan + manifest, SHA-keyed)
{# 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 %}
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). For manifest entries that came from a catalog.toml, deleting the DB row will only stick until the next manifest reload; the operator's right path in that case is to upload a smaller catalog.toml via the "Upload" form above. #} {% 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 %}
Downloads refreshing...
Name Status Progress Bytes Action
No downloads yet.
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.
{% 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. #} {% with events=image_events, title="Recent image-catalog activity", link_to_full="/ui/events?subject_kind=image" %} {% include "ui/_events_card.html" %} {% endwith %} {% endblock %}