Files
core/ui/static/admin.js

147 lines
11 KiB
JavaScript
Raw Normal View History

2026-04-02 10:57:36 -04:00
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 = `<div style="text-align: center; color: #3b82f6; padding: 40px; font-weight: bold;">⏳ Fetching Page ${currentLogPage}...</div>`;
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 = `<p style="color: #94a3b8; text-align: center; padding: 40px;">No activity found.</p>`;
else {
data.feed.forEach(item => {
const badgeStr = item.NewValue ? `<span style="font-family: monospace; background: #f1f5f9; padding: 2px 6px; border-radius: 3px; border: 1px solid #e2e8f0; display: inline-block; margin-top: 4px; font-size: 0.85rem;">${item.NewValue}</span>` : "";
container.innerHTML += `<div style="margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px dashed #e2e8f0;"><span style="font-weight: bold; color: #0f172a;">${item.Actor}</span><span style="color: #64748b; font-size: 0.85rem; text-transform: uppercase; margin-left: 5px;">[${item.ActivityType.replace('_', ' ')}]</span><div style="font-size: 0.8rem; color: #94a3b8; float: right;">⏱️ ${item.TimeAgo}</div><br>${badgeStr}</div>`;
});
}
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 = `<p style="color: #dc2626; text-align: center; padding: 40px; font-weight: bold;">🚨 Error: ${err.message}</p>`; }
}
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 = '<span style="background: #ffedd5; color: #ea580c; border: 1px solid #fdba74; padding: 6px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: bold;">⏱️ TIME EXTENSION</span>';
rationaleDiv.innerText = text.replace('[EXTENSION]', '').trim();
rationaleDiv.style.borderLeft = "3px solid #ea580c";
} else if (text.includes('[RISK ACCEPTANCE]')) {
typeCell.innerHTML = '<span style="background: #fee2e2; color: #dc2626; border: 1px solid #fca5a5; padding: 6px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: bold;">🛑 RISK ACCEPTANCE</span>';
rationaleDiv.innerText = text.replace('[RISK ACCEPTANCE]', '').trim();
rationaleDiv.style.borderLeft = "3px solid #dc2626";
row.style.backgroundColor = "#fff5f5";
} else {
typeCell.innerHTML = '<span style="background: #f1f5f9; color: #64748b; border: 1px solid #e2e8f0; padding: 6px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: bold;">📋 STANDARD</span>';
}
});
// --- 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";
});
}
});