diff --git a/internal/web/static/assets/pages/explore.js b/internal/web/static/assets/pages/explore.js index 3098cb6..bffe552 100644 --- a/internal/web/static/assets/pages/explore.js +++ b/internal/web/static/assets/pages/explore.js @@ -49,7 +49,11 @@ function copy(f) { const g = {}; for (const k of kinds) g[k] = [...(f[k] || [])] const includes = (f, k) => (f[k] || []).filter((v) => !isNot(v)); const anyFilter = (f) => kinds.some((k) => (f[k] || []).length); +let generation = 0; + export async function render(el, params, query) { + const gen = ++generation; + const hash = location.hash; const f = parse(query); const since = query.get("since") || "24h"; const go = (g) => exploreHref(g, since); @@ -105,7 +109,25 @@ export async function render(el, params, query) { if (lists.length) parts.push(h("div", { class: "chart-grid" }, lists)); parts.push(recentPanel(ex, (k, v) => go(addTo(f, k, v)))); + if (gen !== generation) return; // the user moved on + el.classList.remove("refreshing"); mount(el, parts); + scheduleRefresh(el, params, query, gen, hash); +} + +// scheduleRefresh redraws the page a minute later with the same filters, +// so the graphs move along; not while a filter is being typed in. +function scheduleRefresh(el, params, query, gen, hash) { + setTimeout(() => { + if (gen !== generation || location.hash !== hash || !el.isConnected) return; + const form = el.querySelector("form.explore-filters"); + if (form && (form.contains(document.activeElement) || form.dataset.dirty)) { + scheduleRefresh(el, params, query, gen, hash); // try again later + return; + } + el.classList.add("refreshing"); + render(el, params, query).catch(() => el.classList.remove("refreshing")); + }, 60_000); } // filterBar types filters in. Each field takes several values, comma @@ -126,7 +148,7 @@ function filterBar(f, since, ex, views) { type: split(type.value).map((v) => v.toUpperCase()), rcode: split(rcode.value).map((v) => v.toUpperCase()), blocked: split(blocked.value) }; location.hash = exploreHref(g, since); }; - const form = h("form", { class: "explore-filters", onsubmit: (e) => { e.preventDefault(); apply(); } }, + const form = h("form", { class: "explore-filters", onsubmit: (e) => { e.preventDefault(); apply(); }, oninput: () => { form.dataset.dirty = "1"; } }, h("label", null, "Client ", client), h("label", null, "Name ", name), h("label", null, "View ", view), h("label", null, "Type ", type), h("label", null, "Result ", rcode), h("label", null, "Blocked ", blocked), h("button", { type: "submit", class: "primary small" }, "Show"), diff --git a/internal/web/static/assets/pages/stats.js b/internal/web/static/assets/pages/stats.js index 6893078..4e148ba 100644 --- a/internal/web/static/assets/pages/stats.js +++ b/internal/web/static/assets/pages/stats.js @@ -183,7 +183,9 @@ async function loadAll(since, heatSince, queryLog, vf, filtered) { // keeping the current render (dimmed) until the new one is ready. function scheduleRefresh(el, params, query, gen) { setTimeout(() => { - if (gen !== generation || !location.hash.startsWith("#/stats") || !el.isConnected) return; + // Only on the Statistics page itself: not #/stats/explore. + const h = location.hash; + if (gen !== generation || !(h === "#/stats" || h.startsWith("#/stats?")) || !el.isConnected) return; el.classList.add("refreshing"); render(el, params, query).catch(() => el.classList.remove("refreshing")); }, 60_000);