// Shared helpers for the Intrusion Detection web UI. async function api(path, options = {}) { const opts = { headers: {}, ...options }; if (opts.body && typeof opts.body !== "string") { opts.headers["Content-Type"] = "application/json"; opts.body = JSON.stringify(opts.body); } const res = await fetch(path, opts); let data = null; try { data = await res.json(); } catch (e) { /* non-JSON */ } if (!res.ok) { const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`; throw new Error(msg); } return data; } function escapeHtml(s) { return String(s ?? "").replace( /[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", })[c], ); } function toast(message, type = "info") { const box = document.getElementById("toasts"); if (!box) return; const node = document.createElement("div"); node.className = `toast toast-${type}`; node.textContent = message; box.appendChild(node); setTimeout(() => node.remove(), 4000); } function fmtTime(s) { if (!s) return "—"; const d = new Date(s); return isNaN(d) ? s : d.toLocaleString(); } function badge(state) { const node = document.createElement("span"); node.className = `badge ${state}`; node.textContent = state; return node; } function el(tag, attrs = {}, ...children) { const node = document.createElement(tag); // Allow el("tr", child1, child2, ...): if the 2nd argument is a node or an // array, treat it as the first child instead of an attributes object. if (attrs && (attrs.nodeType === 1 || Array.isArray(attrs))) { children.unshift(attrs); attrs = {}; } for (const [k, v] of Object.entries(attrs || {})) { if (k === "class") node.className = v; else if (k === "text") node.textContent = v; else if (k === "html") node.innerHTML = v; else if (k.startsWith("on")) node.addEventListener(k.slice(2), v); else node.setAttribute(k, v); } for (const c of children) { node.append(c == null ? "" : c); } return node; } async function startScan(kind, params) { return api(`/api/scan/${kind}`, { method: "POST", body: params }); }