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
File diff suppressed because one or more lines are too long
+53
View File
@@ -206,6 +206,59 @@
.src-row.clickable > .row-link { pointer-events: auto; }
.src-row.clickable > .row-action { pointer-events: auto; }
/* ---------- dropdown menu (header actions) ----------
* Uses native <details><summary> for keyboard + no-JS support.
* The summary is styled like a .btn, the panel sits absolute below.
* Click-outside-to-close handled by CSS via :has() no JS.
*/
.dropdown { position: relative; display: inline-block; }
.dropdown summary {
list-style: none; cursor: pointer;
/* match .btn shape */
font-size: 12px; font-weight: 500;
padding: 6px 11px; border-radius: 5px;
background: transparent;
border: 1px solid var(--line);
color: var(--ink-mid);
transition: all 120ms ease;
display: inline-flex; align-items: center; gap: 6px;
user-select: none;
}
.dropdown summary::-webkit-details-marker { display: none; }
.dropdown summary::marker { content: ""; }
.dropdown summary:hover { background: var(--panel-hi); color: var(--ink); }
.dropdown summary .chev {
font-size: 9px; color: var(--ink-fade);
transition: transform 120ms ease;
}
.dropdown[open] summary .chev { transform: rotate(180deg); }
.dropdown[open] summary { background: var(--panel-hi); color: var(--ink); }
.dropdown-menu {
position: absolute; top: calc(100% + 4px); right: 0;
z-index: 30;
min-width: 220px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 6px 24px -8px rgba(0,0,0,0.55);
padding: 4px;
}
.dropdown-item {
display: block;
padding: 8px 11px;
border-radius: 4px;
text-decoration: none;
color: var(--ink-mid);
font-size: 12.5px;
line-height: 1.35;
}
.dropdown-item:hover { background: var(--panel-hi); color: var(--ink); }
.dropdown-item .label { display: block; color: var(--ink); font-weight: 500; }
.dropdown-item .hint {
display: block; font-size: 11px; color: var(--ink-mute); margin-top: 2px;
font-family: 'JetBrains Mono', ui-monospace, monospace;
}
/* ---------- snapshot picker rows (Restore wizard step 1) ---------- */
.snap-row {
display: grid; align-items: center;
+24 -3
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).">
<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 href="/api/jobs/{{$job.ID}}/log.ndjson" class="btn btn-ghost" title="Same log as NDJSON for jq / tooling">.ndjson</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) ---------- */}}