/* ======================================================================= File: static/js/log_view.js Purpose: High-performance rolling log viewer - efficient delta polling - append-only mode (no DOM reset) - rolling limit (prevents memory freeze) - supports both global logs and per-book logs ======================================================================= */ console.log(">>> log_view.js LOADING…"); /* --------------------------------------------------------- Global log viewer state --------------------------------------------------------- */ let LOG_FILTER = "ALL"; let LAST_LOG_INDEX = -1; // delta offset const MAX_LOG_LINES = 600; /* --------------------------------------------------------- Apply filter on existing log lines --------------------------------------------------------- */ function applyLogFilter() { const lines = $$(".log-line"); lines.forEach((line) => { const text = line.innerText; const show = LOG_FILTER === "ALL" || (text && text.includes(LOG_FILTER)); line.style.display = show ? "block" : "none"; }); } /* --------------------------------------------------------- DOM Ready — bind clear/filter --------------------------------------------------------- */ document.addEventListener("DOMContentLoaded", () => { console.log(">>> log_view.js DOMContentLoaded"); const clearBtn = $("#log-clear"); const output = $("#log-output"); if (!output) { console.log(">>> log_view.js: No #log-output → viewer disabled"); return; } if (clearBtn) { clearBtn.addEventListener("click", () => { console.log(">>> log_view.js: Clear log viewer"); output.innerHTML = ""; LAST_LOG_INDEX = -1; }); } }); /* --------------------------------------------------------- Append ONE line --------------------------------------------------------- */ function rollingAppend(lineText) { const output = $("#log-output"); if (!output) return; const div = document.createElement("div"); div.classList.add("log-line"); // Type detection if (lineText.includes("[DL]") || lineText.includes("[DOWNLOAD]")) div.classList.add("dl"); else if (lineText.includes("[PARSE]")) div.classList.add("parse"); else if (lineText.includes("[SAVE]")) div.classList.add("save"); else if (lineText.includes("[AUDIO]")) div.classList.add("audio"); else if (lineText.includes("[CTRL]")) div.classList.add("ctrl"); else if (lineText.includes("[ERROR]")) div.classList.add("error"); else div.classList.add("default"); div.textContent = lineText; output.appendChild(div); // Rolling limit while (output.childNodes.length > MAX_LOG_LINES) { output.removeChild(output.firstChild); } } /* --------------------------------------------------------- Primary entry: updateLogs() Accepts: { logs:[...], last:N } OR legacy: { lines:[...], last:N } --------------------------------------------------------- */ function updateLogs(packet) { const output = $("#log-output"); if (!output || !packet) return; let lines = packet.logs || packet.lines || []; if (!Array.isArray(lines)) return; lines.forEach((line) => rollingAppend(line)); // Correct unified delta index handling if (packet.last !== undefined) { LAST_LOG_INDEX = packet.last; } applyLogFilter(); autoScroll(output); } /* --------------------------------------------------------- Delta polling — global logs ONLY (dashboard.js overrides logs per-book) --------------------------------------------------------- */ function pollLogs() { fetch(`/logs?last_index=${LAST_LOG_INDEX}`) .then((r) => r.json()) .then((data) => { const lines = data.lines || []; if (lines.length > 0) { lines.forEach((line) => rollingAppend(line)); LAST_LOG_INDEX = data.last; } }) .catch((err) => { console.warn(">>> log_view.js pollLogs() error:", err); }); } setInterval(pollLogs, 2800); console.log(">>> log_view.js LOADED");