From 817cd1a625a52e082478c7d224fbb05cd9185311 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Wed, 8 Jul 2026 23:23:18 -0500 Subject: [PATCH] purgatory improvements (UI) --- Overlord-Server/public/assets/purgatory-ui.js | 115 ++++++++++++++---- Overlord-Server/public/purgatory.html | 23 ++++ 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/Overlord-Server/public/assets/purgatory-ui.js b/Overlord-Server/public/assets/purgatory-ui.js index fe13e43..489002c 100644 --- a/Overlord-Server/public/assets/purgatory-ui.js +++ b/Overlord-Server/public/assets/purgatory-ui.js @@ -40,12 +40,25 @@ const denyReasonInput = document.getElementById("deny-reason-input"); const denyReasonModalConfirm = document.getElementById("deny-reason-modal-confirm"); const denyReasonModalCancel = document.getElementById("deny-reason-modal-cancel"); const denyReasonModalBackdrop = document.getElementById("deny-reason-modal-backdrop"); +const paginationEl = document.getElementById("enrollment-pagination"); +const pageSummaryEl = document.getElementById("enrollment-page-summary"); +const pageSizeEl = document.getElementById("enrollment-page-size"); +const prevPageBtn = document.getElementById("enrollment-prev-page"); +const nextPageBtn = document.getElementById("enrollment-next-page"); +const pageLabelEl = document.getElementById("enrollment-page-label"); let currentFilter = "pending"; let searchQuery = ""; let clients = []; const expandedCells = new Set(); // tracks "clientId:field" keys let lastEnrollmentDigest = ""; +let currentPage = 1; +let rowsPerPage = 100; +try { + rowsPerPage = Number(localStorage.getItem("purgatory_rows_per_page") || 100); +} catch {} +if (![50, 100, 200].includes(rowsPerPage)) rowsPerPage = 100; +if (pageSizeEl) pageSizeEl.value = String(rowsPerPage); const SUSPICIOUS_FLAG_LABELS = { hwid_flood: "HWID Flood (40+ same hardware ID)", @@ -105,6 +118,7 @@ async function loadClients() { const hasRows = body.querySelector("tr[data-id]"); loadingRow.classList.toggle("hidden", !!hasRows); emptyEl.classList.add("hidden"); + if (!hasRows) paginationEl?.classList.add("hidden"); const fetchFilter = currentFilter; try { @@ -117,20 +131,8 @@ async function loadClients() { // Sort by newest first (highest lastSeen = most recent) clients.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)); - // Apply suspicious tab filter - let base = clients; - if (currentFilter === "suspicious") { - base = clients.filter((c) => (c.suspiciousFlags || []).length > 0); - } - - // Apply search filter + const filtered = getFilteredClients(); const q = searchQuery.toLowerCase().trim(); - const filtered = q - ? base.filter((c) => { - const fields = [c.host, c.user, c.ip, c.id, c.os, c.country, c.keyFingerprint, c.cpu, c.gpu, c.ram]; - return fields.some((f) => f && String(f).toLowerCase().includes(q)); - }) - : base; loadingRow.classList.add("hidden"); @@ -143,11 +145,20 @@ async function loadClients() { lastEnrollmentDigest = `empty:${currentFilter}:${q}`; body.querySelectorAll("tr:not(#enrollment-loading)").forEach((r) => r.remove()); emptyEl.classList.remove("hidden"); + paginationEl?.classList.add("hidden"); + paginationEl?.classList.remove("flex"); updateBulkButtons(); return; } - const nextDigest = tableDigest(filtered, [ + const totalPages = Math.max(1, Math.ceil(filtered.length / rowsPerPage)); + if (currentPage > totalPages) currentPage = totalPages; + if (currentPage < 1) currentPage = 1; + const startIndex = (currentPage - 1) * rowsPerPage; + const visible = filtered.slice(startIndex, startIndex + rowsPerPage); + updatePagination(filtered.length, startIndex, visible.length, totalPages); + + const nextDigest = tableDigest(visible, [ "id", "host", "user", @@ -162,7 +173,7 @@ async function loadClients() { "enrollmentStatus", "denyReason", "suspiciousFlags", - ]); + ], { total: filtered.length, page: currentPage, rowsPerPage, filter: currentFilter, q }); if (nextDigest === lastEnrollmentDigest) { updateBulkButtons(); return; @@ -172,7 +183,8 @@ async function loadClients() { const selectedBeforeRender = new Set(getSelectedIds()); body.querySelectorAll("tr:not(#enrollment-loading)").forEach((r) => r.remove()); - for (const c of filtered) { + const fragment = document.createDocumentFragment(); + for (const c of visible) { const tr = document.createElement("tr"); tr.className = "group hover:bg-slate-800/45 transition-colors"; tr.dataset.id = c.id; @@ -208,17 +220,45 @@ async function loadClients() {
${actionButtons(c)}
`; tr.classList.add("cursor-pointer"); - body.appendChild(tr); + fragment.appendChild(tr); } + body.appendChild(fragment); updateBulkButtons(); } -function tableDigest(items, fields) { - return JSON.stringify(items.map((item) => { +function getFilteredClients() { + let base = clients; + if (currentFilter === "suspicious") { + base = clients.filter((c) => (c.suspiciousFlags || []).length > 0); + } + + const q = searchQuery.toLowerCase().trim(); + if (!q) return base; + + return base.filter((c) => { + const fields = [c.host, c.user, c.ip, c.id, c.os, c.country, c.keyFingerprint, c.cpu, c.gpu, c.ram]; + return fields.some((f) => f && String(f).toLowerCase().includes(q)); + }); +} + +function tableDigest(items, fields, meta = {}) { + return JSON.stringify({ meta, rows: items.map((item) => { const row = {}; for (const field of fields) row[field] = item[field] ?? null; return row; - })); + }) }); +} + +function updatePagination(total, startIndex, visibleCount, totalPages) { + if (!paginationEl || !pageSummaryEl || !pageLabelEl || !prevPageBtn || !nextPageBtn) return; + paginationEl.classList.toggle("hidden", total <= rowsPerPage); + paginationEl.classList.toggle("flex", total > rowsPerPage); + const from = total === 0 ? 0 : startIndex + 1; + const to = startIndex + visibleCount; + pageSummaryEl.textContent = `Showing ${from}-${to} of ${total}`; + pageLabelEl.textContent = `Page ${currentPage} / ${totalPages}`; + prevPageBtn.disabled = currentPage <= 1; + nextPageBtn.disabled = currentPage >= totalPages; } function statusBadge(status) { @@ -369,6 +409,8 @@ function updateBulkButtons() { document.querySelectorAll(".enrollment-tab").forEach((tab) => { tab.addEventListener("click", () => { currentFilter = tab.dataset.filter; + currentPage = 1; + lastEnrollmentDigest = ""; document.querySelectorAll(".enrollment-tab").forEach((t) => { t.className = "enrollment-tab px-4 py-2 rounded-md text-sm font-medium bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700"; @@ -467,10 +509,36 @@ searchInput.addEventListener("input", () => { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { searchQuery = searchInput.value; + currentPage = 1; + lastEnrollmentDigest = ""; loadClients(); }, 250); }); +pageSizeEl?.addEventListener("change", () => { + rowsPerPage = Number(pageSizeEl.value) || 100; + if (![50, 100, 200].includes(rowsPerPage)) rowsPerPage = 100; + try { + localStorage.setItem("purgatory_rows_per_page", String(rowsPerPage)); + } catch {} + currentPage = 1; + lastEnrollmentDigest = ""; + loadClients(); +}); + +prevPageBtn?.addEventListener("click", () => { + if (currentPage <= 1) return; + currentPage -= 1; + lastEnrollmentDigest = ""; + loadClients(); +}); + +nextPageBtn?.addEventListener("click", () => { + currentPage += 1; + lastEnrollmentDigest = ""; + loadClients(); +}); + // ── Ban confirmation modal ────────────────────────────────────────── let banModalResolve = null; @@ -661,10 +729,9 @@ unlessSuspiciousToggle.addEventListener("change", async () => { // ── Approve All / Deny All ──────────────────────────────────────── function getActionablePendingIds() { - const base = currentFilter === "suspicious" - ? clients.filter((c) => (c.suspiciousFlags || []).length > 0) - : clients; - return base.filter((c) => (c.enrollmentStatus || "pending") === "pending").map((c) => c.id); + return getFilteredClients() + .filter((c) => (c.enrollmentStatus || "pending") === "pending") + .map((c) => c.id); } approveAllBtn.addEventListener("click", async () => { diff --git a/Overlord-Server/public/purgatory.html b/Overlord-Server/public/purgatory.html index 39280e8..a97146c 100644 --- a/Overlord-Server/public/purgatory.html +++ b/Overlord-Server/public/purgatory.html @@ -181,6 +181,29 @@

No clients in this category.

+