window.showUpsell = function(featureName) { document.getElementById('upsellFeatureName').innerText = featureName; document.getElementById('upsellModal').style.display = 'flex'; }; function switchTab(tabId, btnElement) { document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.getElementById(tabId).classList.add('active'); if(btnElement) btnElement.classList.add('active'); } // --- CONFIG & USERS LOGIC --- window.deleteUser = async function(id) { if(confirm("Deactivate this user?")) await fetch(`/api/admin/users/${id}`, { method: 'DELETE' }).then(r => r.ok ? window.location.reload() : alert("Failed")); } window.editRole = async function(id, currentRole) { const newRole = prompt("Enter new role (RangeHand, Wrangler, Magistrate, Sheriff):", currentRole); if(newRole && newRole !== currentRole) await fetch(`/api/admin/users/${id}/role`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ global_role: newRole }) }).then(r => r.ok ? window.location.reload() : alert("Failed")); } window.resetPassword = async function(id) { if(confirm("Generate new password?")) await fetch(`/api/admin/users/${id}/reset-password`, { method: 'PATCH' }).then(async r => r.ok ? alert("New Password: \n\n" + await r.text()) : alert("Failed")); } window.deleteRule = async function(id) { if(confirm("Delete rule?")) await fetch(`/api/admin/routing/${id}`, { method: 'DELETE' }).then(r => r.ok ? window.location.reload() : alert("Failed")); } window.updateBackupPolicy = async function() { const pol = document.getElementById("backupPolicy").value; await fetch(`/api/admin/backup-policy`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ policy: pol }) }).then(r => r.ok ? alert("Saved") : alert("Failed")); } window.checkUpdates = async function() { await fetch(`/api/admin/check-updates`).then(async r => alert(await r.text())); } document.addEventListener("DOMContentLoaded", function() { // --- LOGS ENGINE --- let currentLogPage = 1; async function loadLogs() { const filter = document.getElementById("logFilter").value; const container = document.getElementById("logContainer"); container.innerHTML = `
⏳ Fetching Page ${currentLogPage}...
`; try { const res = await fetch(`/api/admin/logs?page=${currentLogPage}&filter=${filter}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); container.innerHTML = ""; if (!data.feed || data.feed.length === 0) container.innerHTML = `

No activity found.

`; else { data.feed.forEach(item => { const badgeStr = item.NewValue ? `${item.NewValue}` : ""; container.innerHTML += `
${item.Actor}[${item.ActivityType.replace('_', ' ')}]
⏱️ ${item.TimeAgo}

${badgeStr}
`; }); } const totalPages = Math.ceil(data.total / data.limit); document.getElementById("logPageInfo").innerText = `Showing page ${data.page} of ${totalPages || 1} (Total: ${data.total})`; document.getElementById("logPrevBtn").disabled = data.page <= 1; document.getElementById("logNextBtn").disabled = data.page >= totalPages; } catch (err) { container.innerHTML = `

🚨 Error: ${err.message}

`; } } const logFilter = document.getElementById("logFilter"); if(logFilter) { logFilter.addEventListener("change", () => { currentLogPage = 1; loadLogs(); }); document.getElementById("logPrevBtn").addEventListener("click", () => { if(currentLogPage > 1) { currentLogPage--; loadLogs(); } }); document.getElementById("logNextBtn").addEventListener("click", () => { currentLogPage++; loadLogs(); }); loadLogs(); } // --- UI INITIALIZERS --- document.querySelectorAll('.risk-row').forEach(row => { const rationaleDiv = row.querySelector('.risk-rationale-cell'); const typeCell = row.querySelector('.risk-type-cell'); if (!rationaleDiv || !typeCell) return; let text = rationaleDiv.innerText.trim(); if (text.includes('[EXTENSION]')) { typeCell.innerHTML = '⏱️ TIME EXTENSION'; rationaleDiv.innerText = text.replace('[EXTENSION]', '').trim(); rationaleDiv.style.borderLeft = "3px solid #ea580c"; } else if (text.includes('[RISK ACCEPTANCE]')) { typeCell.innerHTML = '🛑 RISK ACCEPTANCE'; rationaleDiv.innerText = text.replace('[RISK ACCEPTANCE]', '').trim(); rationaleDiv.style.borderLeft = "3px solid #dc2626"; row.style.backgroundColor = "#fff5f5"; } else { typeCell.innerHTML = '📋 STANDARD'; } }); // --- SLA MATRIX SAVE --- const saveConfigBtn = document.getElementById("saveConfigBtn"); if(saveConfigBtn) { saveConfigBtn.addEventListener("click", async function() { this.innerText = "Saving..."; this.disabled = true; const payload = { timezone: document.getElementById("configTimezone").value, business_start: parseInt(document.getElementById("configBizStart").value), business_end: parseInt(document.getElementById("configBizEnd").value), default_extension_days: parseInt(document.getElementById("configDefExt").value), slas: Array.from(document.querySelectorAll(".sla-row")).map(row => ({ domain: row.getAttribute("data-domain"), severity: row.querySelector("span.badge").innerText.trim(), days_to_triage: parseInt(row.querySelector(".sla-triage").value), days_to_remediate: parseInt(row.querySelector(".sla-patch").value), max_extensions: parseInt(row.querySelector(".sla-ext").value) })) }; const res = await fetch("/api/config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (res.ok) { this.innerText = "Saved!"; this.style.background = "#10b981"; setTimeout(() => { this.innerText = "Save Changes"; this.style.background = ""; this.disabled = false; }, 2000); } else { alert("Failed"); this.innerText = "Save Changes"; this.disabled = false; } }); } // SLA Domain Filter const domainFilter = document.getElementById("slaDomainFilter"); if (domainFilter) { domainFilter.addEventListener("change", function() { document.querySelectorAll(".sla-row").forEach(row => row.style.display = row.getAttribute("data-domain") === this.value ? "table-row" : "none"); }); domainFilter.dispatchEvent(new Event("change")); } // --- MODAL EVENT LISTENERS --- const openUserModal = document.getElementById("openUserModal"); if (openUserModal) { openUserModal.addEventListener("click", () => document.getElementById("userModal").style.display = "flex"); document.getElementById("cancelUser").addEventListener("click", () => document.getElementById("userModal").style.display = "none"); document.getElementById("submitUser").addEventListener("click", async function() { const payload = { full_name: document.getElementById("newUserName").value, email: document.getElementById("newUserEmail").value, password: document.getElementById("newUserPassword").value, global_role: document.getElementById("newUserRole").value }; if (!payload.full_name || !payload.email || !payload.password) return alert("Fill out all fields."); this.disabled = true; await fetch("/api/admin/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).then(async r => r.ok ? window.location.reload() : alert(await r.text())); this.disabled = false; }); } const newRuleType = document.getElementById("newRuleType"); if (newRuleType) { newRuleType.addEventListener("change", function() { document.getElementById("newRuleMatchSource").style.display = this.value === "Source" ? "block" : "none"; document.getElementById("newRuleMatchAsset").style.display = this.value === "Source" ? "none" : "block"; }); document.getElementById("openRuleModal").addEventListener("click", () => document.getElementById("ruleModal").style.display = "flex"); document.getElementById("cancelRule").addEventListener("click", () => document.getElementById("ruleModal").style.display = "none"); document.getElementById("submitRule").addEventListener("click", async function() { const ruleType = document.getElementById("newRuleType").value; const matchVal = ruleType === "Source" ? document.getElementById("newRuleMatchSource").value : document.getElementById("newRuleMatchAsset").value; const assigneeSelect = document.getElementById("newRuleAssignee"); const selectedEmails = Array.from(assigneeSelect.selectedOptions).map(opt => opt.value).join(","); if (!matchVal || !selectedEmails) return alert("Fill out match value and assignee."); this.disabled = true; this.innerText = "Saving..."; await fetch("/api/admin/routing", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rule_type: ruleType, match_value: matchVal, assignee: selectedEmails, role: "RangeHand" }) }).then(async r => r.ok ? window.location.reload() : alert(await r.text())); this.disabled = false; this.innerText = "Deploy Rule"; }); } });