Files
housecarl/internal/web/static/assets/pages/log.js
T
abrown38andClaude Opus 5.5 21ddced1ca Web UI: links to a rule open it, highlighted; rule IDs shown
A rule named in the matrix, the log, the tracer, statistics or the
dashboard opens the Rules page on its chain, scrolled to it and
highlighted (#/rules?rule=ID; the chain is looked up when the link has
none). The Rules page shows each rule's ID beside its order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:18:33 +01:00

38 lines
2.6 KiB
JavaScript

// The log: packets rules (with logging on) and the policies logged,
// refreshed every few seconds.
import { h, mount, input, debounce, badge } from "../ui.js";
import { get, state } from "../api.js";
import { onLeave } from "../app.js";
export async function render(el, params, query) {
const search = input({ type: "search", placeholder: "address, interface, rule…", value: query.get("q") || "" });
const out = h("div");
let live = true;
const pause = h("button", { onclick: () => { live = !live; pause.textContent = live ? "Pause" : "Resume"; } }, "Pause");
const addr = (a, port) => port ? (a.includes(":") ? "[" + a + "]:" + port : a + ":" + port) : a;
const draw = async () => {
const es = await get("/log", { limit: 300, q: search.value.trim() });
mount(out, es.length ? h("div", { class: "table-wrap" }, h("table", { class: "activity" },
h("thead", null, h("tr", null, ["Time", "Why", "In", "Out", "From", "To", "Protocol"].map((t) => h("th", null, t)))),
h("tbody", null, es.map((e) => h("tr", null,
h("td", { class: "mono small" }, new Date(e.time).toLocaleTimeString(undefined, { hourCycle: "h23" })),
h("td", null, e.mark && e.mark.startsWith("r") ? h("a", { href: "#/rules?rule=" + e.mark.slice(1), title: "rule #" + e.mark.slice(1) }, e.prefix) : e.prefix,
e.shadow && /^(drop|reject)$/.test(e.verdict) ? [" ", badge("would " + e.verdict, "warn")] : null),
h("td", { class: "mono" }, e.in || ""), h("td", { class: "mono" }, e.out || ""),
h("td", { class: "mono" }, addr(e.source, e.source_port)), h("td", { class: "mono" }, addr(e.destination, e.destination_port)),
h("td", null, e.protocol, e.icmp_type ? " " + e.icmp_type : "", e.tcp_flags ? h("span", { class: "muted small" }, " " + e.tcp_flags) : null)))))) :
h("div", { class: "empty" }, state.me.mode === "shadow"
? "Nothing logged yet. In shadow mode, what the policies would drop is logged; turn logging on for a rule with Edit → Log."
: "Nothing logged yet. Turn logging on for a rule (Edit → Log), or for what the policies drop (Settings)."));
};
search.addEventListener("input", debounce(draw, 300));
mount(el,
h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "Log"),
h("div", { class: "muted" }, "Packets logged by rules and policies, newest first (the last 2000 are kept)."))),
h("div", { class: "filter-row" }, search, pause),
h("div", { class: "panel" }, out));
await draw();
const t = setInterval(() => { if (live && !document.hidden) draw().catch(() => {}); }, 3000);
onLeave(() => clearInterval(t));
}