{# One backend's disk card. Context: `b` = {name, type, ssh, scanning, result, start_error?}. Swapped whole into #disk- by the Refresh button and the scan poller. #}

{{ b.name }}

{{ b.type }} {% if b.result and b.result.disk_avail_bytes is not none and b.result.disk_total_bytes %} {% set used_pct = ((b.result.disk_total_bytes - b.result.disk_avail_bytes) * 100 / b.result.disk_total_bytes) | round(0, 'floor') | int %} {{ b.result.disk_avail_bytes | disk_bytes }} free ({{ used_pct }}% used) {% endif %}
{% if b.result %} scanned {{ b.result.scanned_at | age }} in {{ (b.result.duration_ms / 1000) | round(1) }}s {% endif %} {% if not b.scanning and not b.cleaning %} {% if b.result %} {% set bulk = b.result.entries | selectattr("classification.value", "equalto", "orphaned") | selectattr("kind.value", "in", ["clone", "agent", "stack", "log"]) | list %} {% if bulk %} {% set bulk_bytes = bulk | map(attribute="size_bytes") | select | sum %}
{% for e in bulk %}{% endfor %}
{% endif %} {% endif %} {% endif %}
{% if b.start_error %}

{{ b.start_error }}

{% endif %} {% if b.scanning or b.cleaning %}
{% if b.cleaning %}Cleaning up… a rescan follows automatically.{% else %}Scanning… this can take a while on large shared filesystems.{% endif %} {# Self-poller: re-swaps the card every few seconds; once the work is done the fresh card no longer contains this div, so polling stops. #}
{% elif b.result %} {% if b.report %} {% set rc = b.report.counts %}
Cleanup {{ b.report.finished_at | age }}: deleted {{ rc.deleted }} · freed {% if b.report.freed_is_lower_bound %}at least {% endif %}{{ b.report.freed_bytes | disk_bytes or "0" }} · skipped {{ rc.skipped }} · failed {{ rc.failed }} {% if rc.skipped or rc.failed or b.report.errors %}
details {% for err in b.report.errors %}
{{ err }}
{% endfor %} {% for o in b.report.outcomes if o.outcome != "deleted" %}
{{ o.path.rsplit("/", 1)[-1] }} — {{ o.outcome }}{% if o.reason %}: {{ o.reason }}{% endif %}
{% endfor %}
{% endif %}
{% endif %} {% set totals = b.result.totals_by_class %}
{% for cls, label, chip in [ ("active", "Active", "bg-blue-100 text-blue-700"), ("referenced", "Referenced", "bg-green-100 text-green-700"), ("orphaned", "Orphaned", "bg-amber-100 text-amber-700"), ("unknown", "Unrecognized", "bg-gray-100 text-gray-600"), ] %} {% if cls in totals %} {{ label }} · {{ totals[cls][0] }} {{ "entry" if totals[cls][0] == 1 else "entries" }} · {{ totals[cls][1] | disk_bytes or "0" }} {% endif %} {% endfor %} Total {{ b.result.total_bytes | disk_bytes or "0" }}
{# Batch-delete bar: hidden until the disk.html controller sees a checked row in this card, then it shows the count and posts every selected path (with allow_referenced for the referenced ones) to /disk/clean. #} {% for kind, title in [ ("clone", "Repository clones"), ("agent", "Agent workspaces"), ("stack", "Software stacks"), ("log", "Logs & outputs"), {# Cache-root strays land here: most often an env a stack's prep built outside its STACK_DIR, which no other section can see. #} ("other", "Other files & environments"), ] %} {% set items = b.result.entries | selectattr("kind.value", "equalto", kind) | list %} {# clones/agents are created from a source checkout; show which one. #} {% set show_source = kind in ["clone", "agent"] %} {% set selectable = not b.scanning and not b.cleaning %} {% if items %}

{{ title }} ({{ items | length }})

{# Columns sort client-side off the tr data-* attributes (disk.html). #} {% if show_source %} {% endif %} {% for e in items %} {% set row_label = e.detail or e.path.rsplit("/", 1)[-1] %} {% set deletable = e.kind.value in ["clone", "agent", "stack", "log"] and e.classification.value in ["orphaned", "referenced"] %} {% if show_source %} {% endif %} {% endfor %}
{% if selectable %} {% endif %} NameSourceClass Size Modified Runs
{% if deletable and selectable %} {% endif %} {{ row_label }} {{ e.source or "–" }} {% set badge = { "active": "bg-blue-100 text-blue-700", "referenced": "bg-green-100 text-green-700", "orphaned": "bg-amber-100 text-amber-700", "unknown": "bg-gray-100 text-gray-600", }[e.classification.value] %} {{ e.classification.value }} {{ e.size_bytes | disk_bytes or "?" }} {{ e.mtime | age }} {% for rid in e.run_ids[:3] %} {{ rid[:8] }}{% if not loop.last %}, {% endif %} {% endfor %} {% if e.run_ids | length > 3 %}+{{ e.run_ids | length - 3 }}{% endif %} {% if not e.run_ids %}{% endif %} {% if selectable and deletable %}
{% if e.classification.value == "referenced" %} {% endif %}
{% endif %}
{% endif %} {% endfor %} {% if not b.result.entries %}

No scripthut directories found on this backend.

{% endif %} {% if b.result.errors %}
{% for err in b.result.errors %}
{{ err }}
{% endfor %}
{% endif %} {% else %}

Never scanned — press Scan to inventory this backend.

{% endif %}