Implement partial refresh, only refresh active tab and stop updates in
the background
This commit is contained in:
parent
ec9f92e147
commit
c756382178
12 changed files with 4538 additions and 20 deletions
2396
opencode/012_opencode_session_selective_refresh_2026-08-31.txt
Normal file
2396
opencode/012_opencode_session_selective_refresh_2026-08-31.txt
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5,18 +5,60 @@
|
|||
const tabBtns = document.querySelectorAll(".tab-btn");
|
||||
const sections = document.querySelectorAll(".tab");
|
||||
|
||||
// per-tab poll interval (ms); htmx "every" timers cannot pause per
|
||||
// element, so only the active tab polls: JS dispatches "dash-poll" on
|
||||
// its section, where every polling element listens via
|
||||
// hx-trigger="dash-poll from:#tab-<name>"
|
||||
const TAB_INTERVALS = { overview: 2000, disks: 2000, processes: 3000, journal: 5000, services: 15000, plugins: 5000 };
|
||||
|
||||
let activeTab = null;
|
||||
let pollTimer = null;
|
||||
let histTimer = null;
|
||||
|
||||
function fireTabEvent(type, name) {
|
||||
const sec = document.getElementById("tab-" + name);
|
||||
if (sec) htmx.trigger(sec, type);
|
||||
}
|
||||
|
||||
function setHistoryPolling(on) {
|
||||
if (on && !histTimer) {
|
||||
pollHistory();
|
||||
histTimer = setInterval(pollHistory, 2000);
|
||||
} else if (!on && histTimer) {
|
||||
clearInterval(histTimer);
|
||||
histTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
setHistoryPolling(false);
|
||||
}
|
||||
|
||||
function startTabPolling(name) {
|
||||
stopPolling();
|
||||
activeTab = name;
|
||||
// "dash-activate" is the one-shot trigger (plugins list), "dash-poll"
|
||||
// the recurring one; firing both refreshes the tab immediately
|
||||
fireTabEvent("dash-activate", name);
|
||||
fireTabEvent("dash-poll", name);
|
||||
pollTimer = setInterval(() => fireTabEvent("dash-poll", name), TAB_INTERVALS[name] || 5000);
|
||||
setHistoryPolling(name === "overview");
|
||||
}
|
||||
|
||||
function showTab(name) {
|
||||
tabBtns.forEach((b) => b.classList.toggle("active", b.dataset.tab === name));
|
||||
sections.forEach((s) => s.classList.toggle("hidden", s.id !== "tab-" + name));
|
||||
try {
|
||||
localStorage.setItem("dash.tab", name);
|
||||
} catch (e) {}
|
||||
startTabPolling(name);
|
||||
}
|
||||
|
||||
tabBtns.forEach((b) => b.addEventListener("click", () => showTab(b.dataset.tab)));
|
||||
try {
|
||||
const saved = localStorage.getItem("dash.tab");
|
||||
if (saved && document.getElementById("tab-" + saved)) showTab(saved);
|
||||
} catch (e) {}
|
||||
|
||||
// ---------- helpers ----------
|
||||
function fmtBytes(n, digits) {
|
||||
|
|
@ -126,8 +168,6 @@
|
|||
}
|
||||
|
||||
initCharts();
|
||||
pollHistory();
|
||||
setInterval(pollHistory, 2000);
|
||||
|
||||
// ---------- journal ----------
|
||||
const journalLog = document.getElementById("journal-log");
|
||||
|
|
@ -229,4 +269,19 @@
|
|||
loadSvcDetail(unit, detailRow.querySelector(".svc-detail"));
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- startup ----------
|
||||
// pause all polling while this browser tab is hidden, resume with a
|
||||
// fresh fetch when it becomes visible again
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) stopPolling();
|
||||
else if (activeTab) startTabPolling(activeTab);
|
||||
});
|
||||
|
||||
let initial = "overview";
|
||||
try {
|
||||
const saved = localStorage.getItem("dash.tab");
|
||||
if (saved && document.getElementById("tab-" + saved)) initial = saved;
|
||||
} catch (e) {}
|
||||
showTab(initial);
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="every 2s" hx-swap="outerHTML">
|
||||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="dash-poll from:#tab-disks" hx-swap="outerHTML">
|
||||
<h2>Filesystems</h2>
|
||||
<div class="part-list">
|
||||
{% for p in partitions %}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
</header>
|
||||
<main>
|
||||
<section id="tab-overview" class="tab">
|
||||
<div id="overview" class="panel" hx-get="/api/overview" hx-trigger="load, every 2s" hx-swap="outerHTML"></div>
|
||||
<div id="overview" class="panel" hx-get="/api/overview" hx-trigger="dash-poll from:#tab-overview" hx-swap="outerHTML"></div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card"><h3>CPU / GPU %</h3><canvas id="chart-cpu"></canvas></div>
|
||||
<div class="chart-card"><h3>Memory / VRAM %</h3><canvas id="chart-mem"></canvas></div>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
</section>
|
||||
|
||||
<section id="tab-disks" class="tab hidden">
|
||||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="load, every 2s" hx-swap="outerHTML"></div>
|
||||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="dash-poll from:#tab-disks" hx-swap="outerHTML"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-processes" class="tab hidden">
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
</select>
|
||||
<button type="submit">apply</button>
|
||||
</form>
|
||||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="load, every 3s" hx-swap="outerHTML" hx-include="#proc-controls"></div>
|
||||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="dash-poll from:#tab-processes" hx-swap="outerHTML" hx-include="#proc-controls"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-journal" class="tab hidden">
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
</form>
|
||||
<div id="journal-logwrap">
|
||||
<input type="hidden" id="journal-cursor" name="cursor" value="">
|
||||
<div id="journal-log" hx-get="/api/journal" hx-trigger="load, every 5s" hx-swap="beforeend" hx-include="#journal-filters, #journal-cursor"></div>
|
||||
<div id="journal-log" hx-get="/api/journal" hx-trigger="dash-poll from:#tab-journal" hx-swap="beforeend" hx-include="#journal-filters, #journal-cursor"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -88,11 +88,11 @@
|
|||
</select>
|
||||
<button type="submit">filter</button>
|
||||
</form>
|
||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="load, every 15s" hx-swap="outerHTML" hx-include="#svc-filters"></div>
|
||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="dash-poll from:#tab-services" hx-swap="outerHTML" hx-include="#svc-filters"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-plugins" class="tab hidden">
|
||||
<div id="plugins" class="panel" hx-get="/api/plugins" hx-trigger="load" hx-swap="outerHTML"></div>
|
||||
<div id="plugins" class="panel" hx-get="/api/plugins" hx-trigger="dash-activate from:#tab-plugins" hx-swap="outerHTML"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="/static/js/app.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="overview" class="panel cards" hx-get="/api/overview" hx-trigger="every 2s" hx-swap="outerHTML">
|
||||
<div id="overview" class="panel cards" hx-get="/api/overview" hx-trigger="dash-poll from:#tab-overview" hx-swap="outerHTML">
|
||||
{% macro bar(pct) %}{% if pct is not none %}<div class="bar"><div class="bar-fill{% if pct > 85 %} hot{% elif pct > 65 %} warm{% endif %}" style="width: {{ pct }}%"></div></div>{% endif %}{% endmacro %}
|
||||
{% macro barrow(label, pct, cls) %}{% if pct is not none %}<div class="barrow"><span class="barrow-label">{{ label }}</span><div class="bar"><div class="bar-fill{% if cls %} {{ cls }}{% endif %}{% if pct > 85 %} hot{% elif pct > 65 %} warm{% endif %}" style="width: {{ pct }}%"></div></div></div>{% endif %}{% endmacro %}
|
||||
<div class="card">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="lact">
|
||||
<div id="lact-state" hx-get="/api/plugins/lact/state" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<div id="lact-state" hx-get="/api/plugins/lact/state" hx-trigger="dash-poll from:#tab-plugins" hx-swap="innerHTML">
|
||||
{% include "plugins/lact_state.html" %}
|
||||
</div>
|
||||
{% if gpus and not error %}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="llama">
|
||||
<div id="llama-state" hx-get="/api/plugins/llamacpp/state" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<div id="llama-state" hx-get="/api/plugins/llamacpp/state" hx-trigger="dash-poll from:#tab-plugins" hx-swap="innerHTML">
|
||||
{% include "plugins/llamacpp_state.html" %}
|
||||
</div>
|
||||
{% if reachable %}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="sessions">
|
||||
<div id="sessions-state" hx-get="/api/plugins/sessions/state" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<div id="sessions-state" hx-get="/api/plugins/sessions/state" hx-trigger="dash-poll from:#tab-plugins" hx-swap="innerHTML">
|
||||
{% include "plugins/sessions_state.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="sleep">
|
||||
<div id="sleep-state" hx-get="/api/plugins/sleep/state" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<div id="sleep-state" hx-get="/api/plugins/sleep/state" hx-trigger="dash-poll from:#tab-plugins" hx-swap="innerHTML">
|
||||
{% include "plugins/sleep_state.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="every 3s" hx-swap="outerHTML" hx-include="#proc-controls">
|
||||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="dash-poll from:#tab-processes" hx-swap="outerHTML" hx-include="#proc-controls">
|
||||
<div class="muted small">showing {{ procs | length }} of {{ total }} processes</div>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="every 15s" hx-swap="outerHTML" hx-include="#svc-filters">
|
||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="dash-poll from:#tab-services" hx-swap="outerHTML" hx-include="#svc-filters">
|
||||
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
||||
<div class="muted small">system: {{ state }} · {{ units | length }} units</div>
|
||||
<div class="table-wrap">
|
||||
|
|
|
|||
Loading…
Reference in a new issue