1{# Request pill — observer state + request stats, clickable to open Observer panel #}
  2<button
  3    type="button"
  4    class="inline-flex items-center cursor-pointer text-xs rounded-full px-1 py-px bg-white/8 text-white/80 hover:bg-white/12 overflow-hidden divide-x divide-white/10 [&>span]:px-1.5"
  5    data-toolbar-tab="Observer"
  6>
  7    {% if observer.is_persisting() %}
  8    <span class="inline-flex items-center">
  9        <span class="relative inline-flex size-1.5 mr-1.5 flex-shrink-0">
 10            <span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75"></span>
 11            <span class="relative inline-flex size-1.5 rounded-full bg-red-500"></span>
 12        </span>
 13        <span class="text-[11px]">Recording</span>
 14    </span>
 15    {% elif not observer.is_enabled() %}
 16    <span class="inline-flex items-center gap-1.5">
 17        <span class="rounded-full bg-zinc-500 size-1.5 flex-shrink-0"></span>
 18        <span class="text-[11px]">Disabled</span>
 19    </span>
 20    {% endif %}
 21
 22    {% if trace_stats %}
 23    <span
 24        class="text-[11px] text-white/50
 25               data-[level=warn]:text-amber-400
 26               data-[level=danger]:text-red-400"
 27        title="SQL queries"
 28        {% if trace_stats.query_level != "ok" %}data-level="{{ trace_stats.query_level }}"{% endif %}
 29    >{{ trace_stats.query_count }}{% if trace_stats.duplicate_count %}+{{ trace_stats.duplicate_count }}{% endif %} {{ "query" if trace_stats.query_count == 1 else "queries" }}</span>
 30    {% if trace_stats.duration_display %}
 31    <span
 32        class="text-[11px] text-white/50
 33               data-[level=warn]:text-amber-400
 34               data-[level=danger]:text-red-400"
 35        title="Request duration"
 36        {% if trace_stats.duration_level != "ok" %}data-level="{{ trace_stats.duration_level }}"{% endif %}
 37    >{{ trace_stats.duration_display }}</span>
 38    {% endif %}
 39    {% endif %}
 40
 41    <span
 42        data-observer-response-size
 43        class="hidden text-[11px] text-white/50
 44               data-[level=warn]:text-amber-400
 45               data-[level=danger]:text-red-400"
 46        title="Response body size"
 47        data-observer-response-size-value
 48    ></span>
 49</button>
 50
 51{% if system_stats %}
 52{# System pill — CPU and memory, non-interactive #}
 53<span class="inline-flex items-center text-xs rounded-full px-1 py-px bg-white/8 overflow-hidden divide-x divide-white/10 [&>span]:px-1.5">
 54    {% if system_stats.cpu_percent is defined %}
 55    <span class="items-baseline text-[11px] tabular-nums" title="Server process CPU usage">
 56        <span class="text-[9px] text-white/30 mr-0.5">CPU</span>
 57        <span
 58            class="text-white/50
 59                   data-[level=warn]:text-amber-400
 60                   data-[level=danger]:text-red-400"
 61            {% if system_stats.cpu_level != "ok" %}data-level="{{ system_stats.cpu_level }}"{% endif %}
 62        >{{ system_stats.cpu_percent }}%</span>
 63    </span>
 64    {% endif %}
 65    {% if system_stats.mem_display %}
 66    <span class="items-baseline text-[11px] tabular-nums" title="{{ system_stats.mem_title }}">
 67        <span class="text-[9px] text-white/30 mr-0.5">MEM</span>
 68        <span
 69            class="text-white/50
 70                   data-[level=warn]:text-amber-400
 71                   data-[level=danger]:text-red-400"
 72            {% if system_stats.mem_level != "ok" %}data-level="{{ system_stats.mem_level }}"{% endif %}
 73        >{{ system_stats.mem_display }}</span>
 74    </span>
 75    {% endif %}
 76</span>
 77{% endif %}
 78
 79<script nonce="{{ request.csp_nonce }}">
 80(function() {
 81    function formatBytes(bytes) {
 82        if (bytes >= 1000000) return (bytes / 1000000).toFixed(1) + " MB";
 83        if (bytes >= 1000) return Math.round(bytes / 1000) + " KB";
 84        return bytes + " B";
 85    }
 86
 87    window.addEventListener("load", function() {
 88        var nav = performance.getEntriesByType("navigation")[0];
 89        var el = document.querySelector("[data-observer-response-size]");
 90        if (nav && nav.decodedBodySize > 0 && el) {
 91            el.textContent = formatBytes(nav.decodedBodySize);
 92            if (nav.decodedBodySize >= 1000000) {
 93                el.dataset.level = "danger";
 94            } else if (nav.decodedBodySize >= 100000) {
 95                el.dataset.level = "warn";
 96            }
 97            el.classList.remove("hidden");
 98        }
 99    });
100})();
101</script>