{# Sailfish Debugger — main page rendered into Airflow 2.x's FAB layout. This template intentionally avoids extending airflow_main.html because that file has moved across the 2.4 -> 2.10 series. We fall back to the plain FAB base, which works across the entire supported 2.x window. #} {% extends "appbuilder/base.html" %} {% block title %}Sailfish Debugger{% endblock %} {% block content %}
{# Connected/Offline pill renders right next to the title — anchored to the page header so the operator can see at a glance whether the plugin can reach Sailfish before scanning further down. #}

Sailfish Debugger

{% if connection_status == "connected" %} Connected {% else %} Offline {% endif %}
{% if save_status == "saved" %}
Policy saved. Future task runs will use the new contract.
{% endif %} {% if connection_status != "connected" %}
Cannot reach Sailfish at {{ sailfish_base_url }} ({{ connection_error or "unknown error" }}). Saves are disabled. Check that SAILFISH_API_KEY is set and that SAILFISH_BASE_URL is reachable from this Airflow scheduler / webserver.
{% endif %} {# Active Captures — populated when Sailfish AI (or a customer operator) has armed one or more captures targeting DAGs/tasks in this Airflow. The list resets to empty when nothing is armed. This is what makes "Debugger is Active" visible in the plugin: even before any DAG run fires, the operator can see Sailfish is watching. #}

Active Captures ({{ active_captures|length }})

{% if active_captures|length == 0 %}

No captures armed. Sailfish will list any AI-armed or operator-armed captures here as they activate.

{% else %} {% for cap in active_captures %} {% endfor %}
Target Source Armed TTL DAG Run Investigation Issue
{# Target text links to the Airflow DAG / DAG run grid. Relative URL — works whether Airflow is at :8088 or behind a custom hostname. #} {% if cap.dag_id %} {{ cap.target_identifier }} {% else %} {{ cap.target_identifier }} {% endif %} {# Source-code deep-link as a sub-line so the operator can jump from "Sailfish is watching this task" to "show me the actual code." #} {% if cap.view_in_codebase_url %}
View source on GitHub → {% endif %} {% if cap.is_demo %} demo {% endif %}
{{ cap.armed_by_source }} {{ cap.armed_at }} {{ cap.ttl_minutes }} min {% if cap.dag_run_id %} {{ cap.dag_run_id }} {% else %} pending {% endif %} {% if cap.view_investigation_url %} Investigation → {% else %} {% endif %} {% if cap.view_issue_url %} Issue → {% else %} {% endif %}
{% endif %} {# Past Investigations — chronological list of AI investigations that hit this Airflow deployment. Persists after captures expire so the operator can browse history. Each row links to the DAG / DAG run in Airflow AND back to the investigation + issue in Sailfish. #}

Past Investigations ({{ past_investigations|length }})

{% if past_investigations|length == 0 %}

No past investigations have touched this Airflow deployment yet.

{% else %} {% for inv in past_investigations %} {% endfor %}
Investigation Target State Armed DAG Run Issue
{% if inv.view_investigation_url and inv.investigation_title %} {{ inv.investigation_title }} {% elif inv.investigation_title %} {{ inv.investigation_title }} {% else %} operator-armed capture {% endif %} {% if inv.is_demo %} demo {% endif %} {% if inv.dag_id %} {{ inv.dag_id }}{% if inv.task_id %} :: {{ inv.task_id }}{% endif %} {% if inv.view_in_codebase_url %}
View source on GitHub → {% endif %} {% else %} {% endif %}
{% if inv.investigation_state %} {{ inv.investigation_state }} {% else %} {{ inv.capture_state }} {% endif %} {{ inv.armed_at }} {% if inv.dag_run_id %} {{ inv.dag_run_id }} {% else %} {% endif %} {% if inv.view_issue_url %} Issue → {% else %} {% endif %}
{% endif %}
{# CSRF: FAB's BaseView injects csrf_token() via the AppBuilder base. #} {% if csrf_token is defined %} {% endif %}

Global Status

The master switch — when Off, NO debugger capture runs regardless of capture mode.

Capture Mode

Picks which DAG runs are eligible for debugger capture when Global Status is On.

{% for value, label in capture_modes %}
{% endfor %}
{% endblock %}