feat(admin): add security and activity management features
This commit is contained in:
104
static/js/admin/activity.js
Normal file
104
static/js/admin/activity.js
Normal file
@@ -0,0 +1,104 @@
|
||||
(() => {
|
||||
const menuController = window.WarpBoxUI?.bindMenuBar?.() || { close() {} };
|
||||
const dataNode = document.getElementById("activity-data");
|
||||
const body = document.getElementById("activity-body");
|
||||
const searchInput = document.getElementById("activity-search");
|
||||
const severityFilter = document.getElementById("activity-severity");
|
||||
const kindFilter = document.getElementById("activity-kind");
|
||||
const statusLeft = document.getElementById("activity-status-left");
|
||||
const toast = document.getElementById("toast");
|
||||
|
||||
if (!dataNode || !body || !searchInput) return;
|
||||
const events = parseData();
|
||||
|
||||
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 renderKindFilter() {
|
||||
const kinds = new Set(events.map((event) => event.kind || "unknown"));
|
||||
Array.from(kinds).sort().forEach((kind) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = kind;
|
||||
option.textContent = kind;
|
||||
kindFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function createdLabel(value) {
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return "-";
|
||||
return parsed.toISOString().replace("T", " ").slice(0, 16) + " UTC";
|
||||
}
|
||||
|
||||
function filtered() {
|
||||
const query = searchInput.value.trim().toLowerCase();
|
||||
const severity = severityFilter.value;
|
||||
const kind = kindFilter.value;
|
||||
return events.filter((event) => {
|
||||
const haystack = [event.kind, event.message, event.ip, event.path, event.method].join(" ").toLowerCase();
|
||||
const matchesQuery = !query || haystack.includes(query);
|
||||
const matchesSeverity = severity === "all" || event.severity === severity;
|
||||
const matchesKind = kind === "all" || event.kind === kind;
|
||||
return matchesQuery && matchesSeverity && matchesKind;
|
||||
});
|
||||
}
|
||||
|
||||
function render() {
|
||||
const rows = filtered();
|
||||
body.innerHTML = "";
|
||||
rows.forEach((event) => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>${createdLabel(event.created_at)}</td>
|
||||
<td>${escapeHtml(event.kind || "-")}</td>
|
||||
<td>${escapeHtml(event.severity || "-")}</td>
|
||||
<td>${escapeHtml(event.ip || "-")}</td>
|
||||
<td>${escapeHtml(event.method || "-")}</td>
|
||||
<td title="${escapeHtml(event.path || "-")}">${escapeHtml(event.path || "-")}</td>
|
||||
<td title="${escapeHtml(event.message || "-")}">${escapeHtml(event.message || "-")}</td>
|
||||
`;
|
||||
body.appendChild(row);
|
||||
});
|
||||
statusLeft.textContent = `${rows.length} activity event(s) visible`;
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return window.WarpBoxUI?.htmlEscape?.(value) || String(value ?? "");
|
||||
}
|
||||
|
||||
[searchInput, severityFilter, kindFilter].forEach((element) => {
|
||||
element.addEventListener(element.tagName === "INPUT" ? "input" : "change", render);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-command]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
menuController.close();
|
||||
if (button.dataset.command === "refresh") {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
if (button.dataset.command === "export") {
|
||||
const blob = new Blob([JSON.stringify(filtered(), null, 2)], { type: "application/json;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = `warpbox-activity-${new Date().toISOString().replaceAll(":", "-")}.json`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showToast("Visible activity exported");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
renderKindFilter();
|
||||
render();
|
||||
})();
|
||||
Reference in New Issue
Block a user