fix(web): backup age in Templates card was frozen by hx-preserve
The backups <details> carried hx-preserve="true" to survive the card's 30s poll without collapsing if the operator had it open -- but hx-preserve freezes the whole node, so "newest X ago" inside it never updated either, same class of bug as the Started column before it got its own JS-side toggle state. Drop hx-preserve; track open/closed the same way (stable id, state re-applied after every swap) so the count/age text stays live.
This commit is contained in:
@@ -239,6 +239,33 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Templates card: each "N backup(s)" row is a <details> the operator may
|
||||
// expand -- it must not collapse every 30s poll. Unlike the time-toggle
|
||||
// buttons, its own content (backup count, "newest X ago") legitimately
|
||||
// needs to keep updating, so this tracks only open/closed the same way
|
||||
// (stable id, state re-applied after every swap) instead of freezing the
|
||||
// whole node with hx-preserve, which used to freeze that text too.
|
||||
var detailsState = {};
|
||||
|
||||
function wireDetails(root) {
|
||||
var list = root.querySelectorAll
|
||||
? root.querySelectorAll("details.backups-details[id]")
|
||||
: [];
|
||||
Array.prototype.forEach.call(list, function (el) {
|
||||
var saved = detailsState[el.id];
|
||||
if (saved !== undefined) {
|
||||
el.open = saved;
|
||||
}
|
||||
if (el.dataset.wired === "true") {
|
||||
return;
|
||||
}
|
||||
el.dataset.wired = "true";
|
||||
el.addEventListener("toggle", function () {
|
||||
detailsState[el.id] = el.open;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.body.addEventListener("htmx:afterSwap", function (evt) {
|
||||
var target = evt.detail && evt.detail.target;
|
||||
if (!target || !target.isConnected) {
|
||||
@@ -253,11 +280,13 @@
|
||||
}
|
||||
if (target) {
|
||||
restoreTimeToggles(target);
|
||||
wireDetails(target);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
observeStack();
|
||||
processSlot(document.getElementById("action-slot"));
|
||||
wireDetails(document);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if bks %}
|
||||
<details class="backups-details" id="backups-{{ tmpl.name }}" hx-preserve="true">
|
||||
<details class="backups-details" id="backups-{{ tmpl.name }}">
|
||||
<summary>{{ bks | length }} backup(s) <span class="text-dim">· newest {{ bks[0].mtime | relative_time }}</span></summary>
|
||||
<ul class="backups-list">
|
||||
{% for b in bks %}
|
||||
|
||||
@@ -180,6 +180,38 @@ def test_templates_route_degrades_on_probe_failure(
|
||||
assert "unavailable" in resp.text.lower()
|
||||
|
||||
|
||||
def test_templates_backups_details_is_not_frozen_by_hx_preserve(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""``hx-preserve`` freezes the *whole* node across the card's 30s poll --
|
||||
including the "newest X ago" text inside it, which must keep updating.
|
||||
Only the <details> open/closed state should persist (via app.js, the
|
||||
same pattern already used for the Started column's click-to-toggle)."""
|
||||
monkeypatch.setattr(app_module, "list_templates", lambda _config: [_TEMPLATE_ROW])
|
||||
monkeypatch.setattr(
|
||||
app_module,
|
||||
"backups_by_template",
|
||||
lambda _config: {
|
||||
"LinuxBuild2404": [
|
||||
FileEntry(
|
||||
name="Template_LinuxBuild2404_20260101_000000.7z",
|
||||
size_bytes=2048,
|
||||
mtime=1_700_000_000.0,
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
app = create_app(_make_config(tmp_path))
|
||||
client = TestClient(app)
|
||||
|
||||
resp = client.get("/templates")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert 'id="backups-LinuxBuild2404"' in resp.text
|
||||
assert "newest" in resp.text
|
||||
assert "hx-preserve" not in resp.text
|
||||
|
||||
|
||||
def test_templates_route_shows_running_ram_snapshot_warning(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user