ui: tidy job-page download into a single dropdown

Replace the floating 'Download log' button + bare '.ndjson' link with
one cohesive dropdown menu — same affordance as the rest of the
header, opens to two well-described options.

- Native <details><summary> for keyboard + no-JS support; only the
  click-outside-to-close handler is JS (a few lines).
- New .dropdown / .dropdown-menu / .dropdown-item tokens in
  web/styles/input.css. Reusable for future header menus
  (host-detail overflow, source-group action menus, etc).
- Chevron flips 180 degrees when open via .dropdown[open] selector.
- Each option has a label + a mono hint line explaining when to pick it
  (.txt for humans / paste into a ticket; .ndjson for jq / tooling).
This commit is contained in:
2026-05-04 17:36:57 +01:00
parent a781e95c94
commit bec7f6d2b9
3 changed files with 80 additions and 6 deletions
+26 -5
View File
@@ -63,11 +63,22 @@
</div>
</div>
<div class="flex items-center gap-2">
<a href="/api/jobs/{{$job.ID}}/log.txt" class="btn"
title="Download the full log up to this moment (works while running too).">
Download log
</a>
<a href="/api/jobs/{{$job.ID}}/log.ndjson" class="btn btn-ghost" title="Same log as NDJSON for jq / tooling">.ndjson</a>
<details class="dropdown" id="download-menu">
<summary>
Download log
<span class="chev"></span>
</summary>
<div class="dropdown-menu">
<a class="dropdown-item" href="/api/jobs/{{$job.ID}}/log.txt">
<span class="label">Plain text</span>
<span class="hint">.txt · for humans / paste into a ticket</span>
</a>
<a class="dropdown-item" href="/api/jobs/{{$job.ID}}/log.ndjson">
<span class="label">JSON Lines</span>
<span class="hint">.ndjson · pipe into jq / tooling</span>
</a>
</div>
</details>
{{if $page.IsActive}}
<button class="btn btn-danger" id="cancel-btn"
hx-post="/api/jobs/{{$job.ID}}/cancel"
@@ -76,6 +87,16 @@
<a href="/hosts/{{$host.ID}}" class="btn">Back to host</a>
{{end}}
</div>
<script>
// Close the download dropdown when clicking outside it.
(function() {
var dd = document.getElementById('download-menu');
if (!dd) return;
document.addEventListener('click', function(e) {
if (dd.open && !dd.contains(e.target)) dd.open = false;
});
})();
</script>
</div>
{{/* ---------- progress (running only) ---------- */}}