{% include '_sidebar.html' %}
{% block breadcrumb %}{% endblock %}
{% if active_ops %}
{# Persistent running-ops banner. Only renders chips for ops
whose log page is NOT the current URL — clicking a chip
should never be a no-op self-link. We compare against the
full path+query, so two ops on the same experiment (e.g.
?type=evaluate vs ?type=report) remain distinct. #}
{% set _ns = namespace(chips=[]) %}
{% set _current = request.url.path + ('?' + request.url.query if request.url.query else '') %}
{% for op in active_ops %}
{% if op.log_url != _current %}
{% set _ = _ns.chips.append(op) %}
{% endif %}
{% endfor %}
{# Banner renders whenever ANY op is active for this project,
even if every chip is self-link-suppressed on the current
page. That way the "Clear stale" recovery button is
always reachable from a project page when something is
reported running. #}
{% if _ns.chips %}
Running:
{% for op in _ns.chips %}
{{ op.type | replace('_', ' ') }}
{% if op.experiment_id %} · {{ op.experiment_id }}{% endif %}
{% endfor %}
{% else %}
{# All chips were suppressed (we're on the log page being
pointed at). Still show the label + the recovery
button so the user has a way out of a stale lock. #}
Running on this page
{% endif %}
{# Stale-lock recovery. POSTs to clear-stale which only
removes locks whose PID is dead / empty / corrupt; live
locks stay put. So clicking it during a real run is a
no-op — the chips above won't disappear. #}
{% endif %}
{% block heading %}{% endblock %}
{% block content %}{% endblock %}
{# Log-page helpers: both scripts are no-ops on pages that don't
contain their target elements (querySelector returns null and
they exit early). Loaded site-wide so the SSE templates don't
each have to remember to include them. #}