const body = document.getElementById("enrollment-body"); const loadingRow = document.getElementById("enrollment-loading"); const emptyEl = document.getElementById("enrollment-empty"); const toggleEl = document.getElementById("require-approval-toggle"); const selectAllEl = document.getElementById("select-all"); const bulkApproveBtn = document.getElementById("bulk-approve-btn"); const bulkDenyBtn = document.getElementById("bulk-deny-btn"); const statPending = document.getElementById("stat-pending"); const statApproved = document.getElementById("stat-approved"); const statDenied = document.getElementById("stat-denied"); const statBannedIps = document.getElementById("stat-banned-ips"); const searchInput = document.getElementById("search-input"); const bannedIpsSection = document.getElementById("banned-ips-section"); const bannedIpsBody = document.getElementById("banned-ips-body"); const bannedIpsEmpty = document.getElementById("banned-ips-empty"); const clientsTable = document.getElementById("enrollment-table")?.closest(".bg-slate-900\\/50"); const addBanBtn = document.getElementById("add-ban-btn"); const manualBanForm = document.getElementById("manual-ban-form"); const banIpInput = document.getElementById("ban-ip-input"); const banReasonInput = document.getElementById("ban-reason-input"); const confirmBanBtn = document.getElementById("confirm-ban-btn"); const cancelBanBtn = document.getElementById("cancel-ban-btn"); const bulkBanBtn = document.getElementById("bulk-ban-btn"); const banModal = document.getElementById("ban-confirm-modal"); const banModalBody = document.getElementById("ban-modal-body"); const banModalConfirm = document.getElementById("ban-modal-confirm"); const banModalCancel = document.getElementById("ban-modal-cancel"); const banModalBackdrop = document.getElementById("ban-modal-backdrop"); let currentFilter = "pending"; let searchQuery = ""; let clients = []; // ── API helpers ──────────────────────────────────────────────────── async function api(path, opts = {}) { const res = await fetch(path, { credentials: "include", ...opts }); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res.json(); } async function loadStats() { try { const s = await api("/api/enrollment/stats"); statPending.textContent = s.pending ?? 0; statApproved.textContent = s.approved ?? 0; statDenied.textContent = s.denied ?? 0; // Load banned IPs count try { const b = await api("/api/enrollment/banned-ips"); statBannedIps.textContent = (b.items || []).length; } catch { statBannedIps.textContent = 0; } // Update nav badge const badge = document.getElementById("enrollment-badge"); if (badge) { if (s.pending > 0) { badge.textContent = s.pending; badge.classList.remove("hidden"); } else { badge.classList.add("hidden"); } } } catch {} } async function loadSettings() { try { const s = await api("/api/enrollment/settings"); toggleEl.checked = s.requireApproval; } catch {} } async function loadClients() { loadingRow.classList.remove("hidden"); emptyEl.classList.add("hidden"); body.querySelectorAll("tr:not(#enrollment-loading)").forEach((r) => r.remove()); try { const data = await api(`/api/clients?page=1&pageSize=500&enrollmentFilter=${currentFilter}`); clients = data.items || []; } catch { clients = []; } // Sort by newest first (highest lastSeen = most recent) clients.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)); // Apply search filter const q = searchQuery.toLowerCase().trim(); const filtered = q ? clients.filter((c) => { const fields = [c.host, c.user, c.ip, c.id, c.os, c.country, c.keyFingerprint]; return fields.some((f) => f && String(f).toLowerCase().includes(q)); }) : clients; loadingRow.classList.add("hidden"); if (filtered.length === 0) { emptyEl.classList.remove("hidden"); return; } for (const c of filtered) { const tr = document.createElement("tr"); tr.className = "hover:bg-slate-800/40 transition-colors"; tr.dataset.id = c.id; const statusPill = statusBadge(c.enrollmentStatus || "pending"); const fp = c.keyFingerprint ? c.keyFingerprint.substring(0, 16) + "..." : "-"; const lastSeen = c.lastSeen ? timeAgo(c.lastSeen) : "-"; tr.innerHTML = `
${message}
`; if (ips && ips.length > 0 && ips.length <= 10) { html += `