const SNAPSHOT_REFRESH_DELAY_MS = 5000; function formatRemaining(totalSeconds) { const seconds = Math.max(0, Math.ceil(totalSeconds)); const minutes = Math.floor(seconds / 60); const rest = seconds % 60; return `${minutes}:${String(rest).padStart(2, "0")}`; } function updateTimers() { const now = Date.now() / 1000; document.querySelectorAll("[data-auto-off-at]").forEach((item) => { const target = Number(item.dataset.autoOffAt || 0); if (!target) { return; } const remaining = target - now; if (remaining <= 0) { item.textContent = item.dataset.expiredLabel || "Automatic turn-off pending"; item.classList.add("timer-expired"); return; } item.textContent = `${item.dataset.prefix || "Automatic turn-off in"} ${formatRemaining(remaining)}`; }); } function refreshStatusElement() { return document.querySelector("[data-refresh-status]"); } function showRefreshMessage(message, type = "success") { const status = refreshStatusElement(); if (!status) { return; } status.textContent = message || ""; status.classList.toggle("is-visible", Boolean(message)); status.classList.toggle("is-success", type === "success"); status.classList.toggle("is-error", type === "error"); } function rememberRefreshMessage(message) { try { window.sessionStorage.setItem("frigateRefreshMessage", message); } catch (_error) { // Some browsers may block sessionStorage; the refresh still succeeds. } } function restoreRefreshMessage() { try { const message = window.sessionStorage.getItem("frigateRefreshMessage"); if (!message) { return; } window.sessionStorage.removeItem("frigateRefreshMessage"); showRefreshMessage(message, "success"); } catch (_error) { // Ignore storage errors and keep the GUI usable. } } async function handleRefreshSubmit(form, event) { event.preventDefault(); const button = form.querySelector(".refresh-button"); if (button && button.classList.contains("is-refreshing")) { return; } const status = refreshStatusElement(); const workingLabel = status?.dataset.workingLabel || "Refreshing cameras..."; showRefreshMessage(workingLabel, "success"); if (button) { button.disabled = true; button.classList.add("is-refreshing"); button.setAttribute("aria-busy", "true"); } try { const response = await fetch(form.action, { method: "POST", headers: { Accept: "application/json" }, }); const payload = await response.json(); if (!response.ok || payload.ok === false) { throw new Error(payload.message || "Refresh failed"); } showRefreshMessage(payload.message, "success"); if (payload.changed && payload.redirect_url) { rememberRefreshMessage(payload.message); window.setTimeout(() => { window.location.assign(payload.redirect_url); }, 900); return; } } catch (error) { showRefreshMessage(error.message || "Refresh failed", "error"); } finally { if (button) { button.disabled = false; button.classList.remove("is-refreshing"); button.removeAttribute("aria-busy"); } } } function refreshSnapshot(button) { if (button.classList.contains("is-refreshing")) { return; } const media = button.closest(".media"); const image = media?.querySelector("img.snapshot"); if (!image) { return; } const url = new URL(image.currentSrc || image.src, window.location.href); url.searchParams.set("refresh", "1"); url.searchParams.set("v", String(Date.now())); const cleanup = () => { button.disabled = false; button.classList.remove("is-refreshing"); button.removeAttribute("aria-busy"); }; button.disabled = true; button.classList.add("is-refreshing"); button.setAttribute("aria-busy", "true"); image.addEventListener("load", cleanup, { once: true }); image.addEventListener("error", cleanup, { once: true }); window.setTimeout(() => { image.src = url.toString(); }, SNAPSHOT_REFRESH_DELAY_MS); } document.addEventListener("click", (event) => { const button = event.target.closest(".snapshot-refresh"); if (!(button instanceof HTMLButtonElement)) { return; } refreshSnapshot(button); }); document.addEventListener("submit", (event) => { const form = event.target; if (!(form instanceof HTMLFormElement)) { return; } if (form.classList.contains("refresh-form")) { handleRefreshSubmit(form, event); return; } if (form.dataset.requiresAck !== "true") { return; } const message = form.dataset.ackMessage || "Confirm this action"; if (!window.confirm(message)) { event.preventDefault(); } }); updateTimers(); restoreRefreshMessage(); setInterval(updateTimers, 1000);