{% extends "layout.html" %} {% from "_partials/table_helpers.html" import search_input, sort_header, pagination_inline %} {% from "_partials/recent_events.html" import recent_events_card %} {% from "_partials/page_description.html" import page_description %} {% block title %}Catalog - pixie{% endblock %} {% block subnav %} {% endblock %} {% block content %} {{ page_description('journal-code', 'Sources: the URLs pixie can fetch. Add an entry with a source URL, then Fetch to pull the bytes; a fetched disk-image source materialises an Image (follow the Image link for its footprint, live NBD/overlay usage, and delete/GC). Deleting a source here leaves any fetched bytes on disk - reclaim those from Images.') }}
{# Card header: title/label on the left, filter form inline in the middle, pagination + per-page on the right -- same toolbar shape bty uses on /ui/events. #}
Catalog {{ search_input('/ui/catalog', q, preserved, 'name / src / arch / format / description') }} {{ pagination_inline(page_state, preserved, label='entries') }}
{% if entries %}
{{ sort_header('Entry', 'name', sort, preserved) }} {{ sort_header('Type', 'format', sort, preserved) }} {% for e in entries %} {% set st = fetch_states.get(e.name) or {} %} {% endfor %}
Fetch state Image Actions
{% if e.description %}
{{ e.description }}
{% endif %}
{{ e.src }}
{% if e.netboot_src %}
netboot bundle: {{ e.netboot_src }}
{% endif %}
{% if e.bindable %} Disk image {% else %} Netboot bundle {% endif %}
{{ e.format }} {% if e.arch %}{{ e.arch }}{% endif %}
{% if st.state == "fetching" %} {# Live phase pill: server-rendered starting state; the poll script below rewrites this cell's inner HTML every 2s when there's an in-flight fetch. Phases in the sub-state dict: ``downloading`` (+bytes_downloaded / total_bytes), ``decompressing`` (+format), ``unpacking`` (netboot bundles). #} {% if st.phase == "downloading" %} downloading {% if st.total_bytes %}
{{ st.bytes_downloaded or 0 }} / {{ st.total_bytes }} bytes
{% elif st.bytes_downloaded %}
{{ st.bytes_downloaded }} bytes so far
{% endif %} {% elif st.phase == "decompressing" %} decompressing {% if st.format %}
{{ st.format }}
{% endif %} {% elif st.phase == "unpacking" %} unpacking {% else %} fetching… {% endif %} {% if st.started_at %}
since {{ st.started_at | fmt_ts }}
{% endif %} {% elif st.state == "error" %} error {% if st.error %}
{{ st.error }}
{% endif %} {% elif st.state == "unchanged" %} {# The operator clicked Update but the entry was already at the current sha and the on-disk bytes were intact. The pill lingers ~8 s (the handler auto-clears it) so the click has a visible acknowledgement, then the row falls back to the plain "fetched" badge. #} already at latest {% elif e.content_sha256 %} {# Readiness only: the content sha + byte footprint are image facts, surfaced on the linked Image page rather than duplicated on this sources table. #} fetched {% else %} not fetched {% endif %}
{# Catalog is sources only; once a disk-image source is fetched it materialises an Image (identity = the content sha). Link there for the footprint + live usages (NBD exports, overlays) + GC. #} {% if e.bindable and e.content_sha256 %} Image {% else %} - {% endif %} {# Source actions only (NBD-serving + blob GC moved to Images). Fetch verb tracks state: no bytes -> Fetch; a prior fetch failed -> Retry; already fetched -> Update (re-runs the pipeline; picks up a moved oras:// tag); in-flight -> Fetching (disabled). Delete removes the source row (fetched bytes stay on disk -- reclaim from Images). #} {% set fetching = st.state == 'fetching' %} {% set fetch_errored = st.state == 'error' %} {% set already_fetched = e.content_sha256 and not fetch_errored %}
{% else %}

{% if q %} No entries match {{ q }}. {% else %} No entries yet. Paste a catalog URL in the bar above and hit Import; entries land here staged (not fetched) so you can pick the ones to pull with Fetch. {% endif %}

{% endif %}
{{ recent_events_card(catalog_events, "catalog") }} {% endblock %}