(function () { "use strict"; var $ = window.MailSharp; var state = { page: 1, pageSize: 50, totalCount: 0, statusLoaded: false, metricsLoaded: false, logsLoaded: false }; function reportError(err) { if (err.cancelled) return; window.alert(err.message); } function formatSize(bytes) { if (!bytes) return "0 KB"; var kb = bytes / 1024; return kb >= 1024 ? (kb / 1024).toFixed(1) + " MB" : kb.toFixed(1) + " KB"; } function bootstrap() { $.initHeader().then(function () { loadStatus(); }).catch(reportError); } function loadStatus() { state.statusLoaded = true; $.apiFetch("api/maintenance/status").then(function (s) { renderIndexingStatus(s.indexing); renderIndexTotals(s.index); renderCacheStats(s.cache); }).catch(reportError); } function renderIndexingStatus(indexing) { var el = document.getElementById("maintenanceStatus"); var text = "Achtergrondindexering: " + (indexing.maxConcurrentIndexing - indexing.currentlyAvailableSlots) + " / " + indexing.maxConcurrentIndexing + " actief."; if (indexing.inProgress.length > 0) { text += " Bezig met: " + indexing.inProgress.map(function (entry) { if (!entry.total) return entry.key; var percent = Math.round((entry.processed / entry.total) * 100); return entry.key + " (" + entry.processed + " / " + entry.total + ", " + percent + "%)"; }).join(", "); } else { text += " Niets in behandeling."; } el.textContent = text; } function renderIndexTotals(index) { document.getElementById("indexTotals").textContent = index.totalIndexedFolders + " geïndexeerde mappen, " + index.totalIndexedMessages + " berichten, " + formatSize(index.totalIndexedSizeBytes) + " totaal."; var rows = index.accounts.map(function (a) { return "" + $.esc(a.emailAddress) + "" + a.indexedFolderCount + "" + a.indexedMessageCount + "" + formatSize(a.indexedSizeBytes) + ""; }); document.getElementById("indexSummaryBody").innerHTML = rows.join(""); } function renderCacheStats(cache) { var rows = [ ["Berichtenlijst-cache (TTL)", cache.messageListCacheEntries + " entries", ""], ["Contactzoekopdracht-cache (TTL)", cache.contactMailSearchEntries + " entries", ""], ["Afbeeldingsvoorkeuren per afzender", cache.senderImagePreferences + " entries", ""], ["Afbeelding-URL mappings", cache.imageUrlMappings + " entries", ""], ["Uid → Message-ID mappings", cache.messageIdMappings + " entries", ""], ["Gedownloade berichten (.eml)", cache.cachedMessages.fileCount + " bestanden", formatSize(cache.cachedMessages.totalBytes)], ["Gedownloade bijlagen", cache.cachedAttachments.fileCount + " bestanden", formatSize(cache.cachedAttachments.totalBytes)], ["Gedownloade afbeeldingen", cache.cachedImages.fileCount + " bestanden", formatSize(cache.cachedImages.totalBytes)] ]; document.getElementById("cacheStatsBody").innerHTML = rows.map(function (r) { return "" + $.esc(r[0]) + "" + $.esc(r[1]) + "" + $.esc(r[2]) + ""; }).join(""); } function populateMetricsAccountFilter(accounts) { var sel = document.getElementById("metricsAccountFilter"); if (sel.options.length > 1) return; // already populated on an earlier load accounts.forEach(function (a) { var opt = document.createElement("option"); opt.value = a.accountId; opt.textContent = a.emailAddress; sel.appendChild(opt); }); } function loadMetrics() { state.metricsLoaded = true; var accountId = document.getElementById("metricsAccountFilter").value; var url = "api/maintenance/metrics" + (accountId ? "?accountId=" + encodeURIComponent(accountId) : ""); $.apiFetch(url).then(function (m) { if (!accountId) populateMetricsAccountFilter(m.accounts); var rows = []; m.accounts.forEach(function (a) { if (a.folders.length === 0) { rows.push("" + $.esc(a.emailAddress) + "(nog niet geïndexeerd)"); return; } a.folders.forEach(function (f) { rows.push("" + $.esc(a.emailAddress) + "" + $.esc(f.folder) + "" + f.messageCount + "" + formatSize(f.sizeBytes) + ""); }); }); document.getElementById("metricsBody").innerHTML = rows.join(""); }).catch(reportError); } document.getElementById("metricsAccountFilter").addEventListener("change", function () { loadMetrics(); }); function buildLogsQuery() { var params = new URLSearchParams(); var level = document.getElementById("logLevelFilter").value; var category = document.getElementById("logCategoryFilter").value.trim(); var accountId = document.getElementById("logAccountFilter").value.trim(); var search = document.getElementById("logSearchFilter").value.trim(); if (level) params.set("level", level); if (category) params.set("category", category); if (accountId) params.set("accountId", accountId); if (search) params.set("search", search); params.set("page", state.page); params.set("pageSize", state.pageSize); return params.toString(); } function loadLogs() { state.logsLoaded = true; $.apiFetch("api/maintenance/logs?" + buildLogsQuery()).then(function (result) { state.totalCount = result.totalCount; var rows = result.items.map(function (e) { var meta = $.formatDate(e.timestamp) + " · " + e.category + (e.accountId != null ? " · account " + e.accountId : ""); var body = $.esc(e.message) + (e.exception ? "\n" + $.esc(e.exception) : ""); return '
' + '
' + $.esc(e.level) + " " + $.esc(meta) + "
" + '
' + body + "
" + "
"; }); document.getElementById("logsBody").innerHTML = rows.join(""); var totalPages = Math.max(1, Math.ceil(state.totalCount / state.pageSize)); document.getElementById("logsPageInfo").textContent = "Pagina " + state.page + " van " + totalPages + " (" + state.totalCount + " logregels)"; }).catch(reportError); } document.getElementById("logFilterBtn").addEventListener("click", function (e) { e.preventDefault(); state.page = 1; loadLogs(); }); document.getElementById("logClearBtn").addEventListener("click", function (e) { e.preventDefault(); if (!window.confirm("Alle logregels definitief verwijderen?")) return; $.apiFetch("api/maintenance/logs/clear", { method: "POST" }).then(function () { state.page = 1; loadLogs(); }).catch(reportError); }); // The level select filters immediately on change, unlike the free-text/account inputs (which // wait for the Filteren button so every keystroke doesn't fire a request) - a dropdown change // is already a single deliberate action, so there's no "still typing" state to debounce. document.getElementById("logLevelFilter").addEventListener("change", function () { state.page = 1; loadLogs(); }); document.getElementById("logsPrevBtn").addEventListener("click", function (e) { e.preventDefault(); if (state.page > 1) { state.page--; loadLogs(); } }); document.getElementById("logsNextBtn").addEventListener("click", function (e) { e.preventDefault(); var totalPages = Math.max(1, Math.ceil(state.totalCount / state.pageSize)); if (state.page < totalPages) { state.page++; loadLogs(); } }); // ---------- tabs ---------- document.getElementById("maintenanceTabs").addEventListener("click", function (e) { var tab = e.target.closest(".subheader-tab"); if (!tab) return; e.preventDefault(); var name = tab.getAttribute("data-tab"); document.querySelectorAll(".subheader-tab").forEach(function (t) { t.classList.toggle("active", t === tab); }); document.querySelectorAll("[data-tab-panel]").forEach(function (p) { p.style.display = p.getAttribute("data-tab-panel") === name ? "" : "none"; }); if (name === "metrics" && !state.metricsLoaded) loadMetrics(); if (name === "logs" && !state.logsLoaded) loadLogs(); }); bootstrap(); })();