(() => { const menuController = window.WarpBoxUI?.bindMenuBar?.() || { close() {} }; const dataNode = document.getElementById("alerts-data"); const alertsBody = document.getElementById("alerts-body"); const searchInput = document.getElementById("search-input"); const severityFilter = document.getElementById("severity-filter"); const statusFilter = document.getElementById("status-filter"); const sourceFilter = document.getElementById("source-filter"); const sortFilter = document.getElementById("sort-filter"); const selectAll = document.getElementById("select-all"); const selectedCountEl = document.getElementById("selected-count"); const totalPill = document.getElementById("alerts-total-pill"); const toast = document.getElementById("toast"); const detailEls = { title: document.getElementById("detail-title"), severity: document.getElementById("detail-severity"), status: document.getElementById("detail-status"), code: document.getElementById("detail-code"), trace: document.getElementById("detail-trace"), time: document.getElementById("detail-time"), description: document.getElementById("detail-description"), metadata: document.getElementById("detail-metadata") }; if (!dataNode || !alertsBody) return; const state = { alerts: parseData(), selected: new Set(), activeID: null }; function parseData() { try { return JSON.parse(dataNode.textContent || "[]"); } catch (_) { return []; } } function showToast(message, type = "info", duration = 1800) { window.WarpBoxUI?.toast?.(message, type, { target: toast, duration }); } function createdLabel(value) { const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) return "-"; return parsed.toISOString().replace("T", " ").slice(0, 16) + " UTC"; } function allAlerts() { return state.alerts.slice(); } function filteredAlerts() { const query = searchInput.value.trim().toLowerCase(); const severity = severityFilter.value; const status = statusFilter.value; const group = sourceFilter.value; const rows = allAlerts().filter((alert) => { const haystack = [ alert.title, alert.message, alert.code, alert.trace, alert.group ].join(" ").toLowerCase(); const matchesSearch = !query || haystack.includes(query); const matchesSeverity = severity === "all" || alert.severity === severity; const matchesStatus = status === "all" || alert.status === status; const matchesGroup = group === "all" || alert.group === group; return matchesSearch && matchesSeverity && matchesStatus && matchesGroup; }); const order = { high: 3, medium: 2, low: 1 }; rows.sort((a, b) => { if (sortFilter.value === "severity") return (order[b.severity] || 0) - (order[a.severity] || 0); if (sortFilter.value === "oldest") return String(a.created_at).localeCompare(String(b.created_at)); return String(b.created_at).localeCompare(String(a.created_at)); }); return rows; } function ensureActive(rows) { if (rows.length === 0) { state.activeID = null; return null; } const found = rows.find((item) => item.id === state.activeID); if (found) return found; state.activeID = rows[0].id; return rows[0]; } function render() { const rows = filteredAlerts(); alertsBody.innerHTML = ""; rows.forEach((alert) => alertsBody.appendChild(buildRow(alert))); const active = ensureActive(rows); if (active) renderDetails(active); renderSummary(rows); syncSelected(); syncSelectAll(rows); } function buildRow(alert) { const row = document.createElement("tr"); if (state.activeID === alert.id) row.classList.add("is-selected"); row.innerHTML = ` ${escapeHtml(alert.title || "-")} ${escapeHtml(alert.severity || "low")} ${escapeHtml(alert.status || "open")} ${escapeHtml(alert.code || "-")} ${escapeHtml(alert.trace || "-")} ${createdLabel(alert.created_at)} `; row.addEventListener("click", (event) => { if (event.target.closest("button") || event.target.closest("input")) return; state.activeID = alert.id; render(); }); row.querySelector(".row-open")?.addEventListener("click", () => { state.activeID = alert.id; render(); }); row.querySelector(".row-check")?.addEventListener("change", (event) => { if (event.target.checked) state.selected.add(alert.id); else state.selected.delete(alert.id); syncSelected(); syncSelectAll(filteredAlerts()); }); return row; } function renderDetails(alert) { detailEls.title.textContent = alert.title || ""; detailEls.severity.textContent = alert.severity || ""; detailEls.status.textContent = alert.status || ""; detailEls.code.textContent = alert.code || ""; detailEls.trace.textContent = alert.trace || ""; detailEls.time.textContent = createdLabel(alert.created_at); detailEls.description.textContent = alert.message || ""; detailEls.metadata.textContent = JSON.stringify(alert.meta || {}, null, 2); } function renderSummary(rows) { const open = rows.filter((item) => item.status === "open").length; const high = rows.filter((item) => item.severity === "high" && item.status !== "closed").length; const ack = rows.filter((item) => item.status === "acked").length; const closed = rows.filter((item) => item.status === "closed").length; document.querySelector("[data-open-count]").textContent = String(open); document.querySelector("[data-high-count]").textContent = String(high); document.querySelector("[data-ack-count]").textContent = String(ack); document.querySelector("[data-closed-count]").textContent = String(closed); totalPill.textContent = `${rows.length} alerts`; } function syncSelected() { selectedCountEl.textContent = `Selected: ${state.selected.size}`; } function syncSelectAll(rows) { selectAll.checked = rows.length > 0 && rows.every((alert) => state.selected.has(alert.id)); } async function postAction(action, ids) { const response = await fetch("/admin/alerts/actions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, ids }) }); const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload.error || "Request failed"); state.alerts = payload.alerts || []; } async function runAction(action) { const ids = Array.from(state.selected); if (!ids.length && (action === "ack" || action === "close" || action === "delete")) { showToast("Select one or more alerts first", "warning"); return; } if (action === "open-only") { statusFilter.value = "open"; render(); showToast("Showing open alerts only"); return; } if (action === "refresh") { window.location.reload(); return; } if (action === "copy-meta") { const active = allAlerts().find((item) => item.id === state.activeID); if (active) { navigator.clipboard?.writeText(JSON.stringify(active.meta || {}, null, 2)).catch(() => {}); } showToast("Metadata copied"); return; } if (action === "export") { const blob = new Blob([JSON.stringify(filteredAlerts(), null, 2)], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = `warpbox-alerts-${new Date().toISOString().replaceAll(":", "-")}.json`; anchor.click(); URL.revokeObjectURL(url); showToast("Visible alerts exported"); return; } if (action === "help-codes") { showToast("Codes map to internal security and service traces."); return; } if (action === "help-meta") { showToast("Metadata shows extra context for each alert."); return; } await postAction(action, ids); state.selected.clear(); render(); showToast(`Action complete: ${action}`, "success"); } function escapeHtml(value) { return window.WarpBoxUI?.htmlEscape?.(value) || String(value ?? ""); } [searchInput, severityFilter, statusFilter, sourceFilter, sortFilter].forEach((control) => { control.addEventListener(control.tagName === "INPUT" ? "input" : "change", render); }); selectAll?.addEventListener("change", () => { const rows = filteredAlerts(); rows.forEach((alert) => { if (selectAll.checked) state.selected.add(alert.id); else state.selected.delete(alert.id); }); render(); }); document.querySelectorAll("[data-command]").forEach((button) => { button.addEventListener("click", async () => { menuController.close(); try { await runAction(button.dataset.command); } catch (error) { showToast(error.message, "error", 3200); } }); }); document.addEventListener("keydown", async (event) => { if (event.key === "Escape") menuController.close(); if (event.key === "F5") { event.preventDefault(); await runAction("refresh"); } }); render(); })();