A cell reads allow, block or partly (some new connections let through, the rest not), with its exceptions counted by what they do (+3 -1), and what they let through: services with the addresses they are limited to, under the grid in hc matrix and in each cell on the Matrix page. A zone to itself is marked: only traffic routed between its interfaces passes the firewall. The details list each rule's hits. hc matrix pads the grid by visible width: the colours' escape codes had thrown it out of line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
4.7 KiB
JavaScript
51 lines
4.7 KiB
JavaScript
// Matrix: what the rules do to new connections between zones, and to and
|
||
// from the firewall itself.
|
||
import { h, mount, modal, badge } from "../ui.js";
|
||
import { get, state } from "../api.js";
|
||
|
||
export async function render(el) {
|
||
const m = await get("/matrix");
|
||
const cols = [...m.zones, "firewall"];
|
||
const cells = new Map(m.cells.map((c) => [c.from + "\u0000" + c.to, c]));
|
||
const pending = state.draft && !state.immediate && (state.draft.ops || []).length;
|
||
const name = (z) => z === "firewall" ? h("em", null, "firewall") : z;
|
||
mount(el,
|
||
h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "Zone matrix"),
|
||
h("div", { class: "muted" }, "What happens to a new connection from a zone (rows) to a zone (columns), or to and from the firewall itself, worked out from the rules in order: all allowed, all blocked, or partly (some services, addresses or times let through, the rest not). Click a cell for the rules."))),
|
||
pending ? h("div", { class: "msg info" }, "Shown with your pending changes.") : null,
|
||
!m.zones.length ? h("div", { class: "panel" }, h("div", { class: "empty" }, "No zones yet: add them under Objects → Zones.")) :
|
||
h("div", { class: "panel" }, h("div", { class: "table-wrap" }, h("table", { class: "matrix" },
|
||
h("thead", null, h("tr", null, h("th", { class: "corner" }, "from ↓ to →"), cols.map((c) => h("th", null, name(c))))),
|
||
h("tbody", null, cols.map((from) => h("tr", null, h("th", null, name(from)), cols.map((to) => {
|
||
const c = cells.get(from + "\u0000" + to);
|
||
if (!c) return h("td", { class: "matrix-none" }, "–");
|
||
const open = c.open || [];
|
||
const counts = [c.allowed ? "+" + c.allowed : "", c.blocked ? "−" + c.blocked : ""].filter(Boolean).join(" ");
|
||
const title = (c.same_zone ? c.note + ". " : "") + "The rest: " + c.verdict + " by " + c.decided_by +
|
||
(c.allowed ? "; " + c.allowed + " rule(s) let part of it through" : "") + (c.blocked ? "; " + c.blocked + " block part of it" : "");
|
||
return h("td", { class: "matrix-" + c.summary + (c.same_zone ? " matrix-same" : ""), title },
|
||
h("button", { type: "button", class: "matrix-cell", onclick: () => detail(c) },
|
||
c.summary === "partly" ? "partly" : c.verdict, counts ? h("span", { class: "matrix-n" }, counts) : null,
|
||
open.length && !c.same_zone ? h("span", { class: "matrix-open" }, open.slice(0, 3).join(" · ") + (open.length > 3 ? " · +" + (open.length - 3) + " more" : "")) : null));
|
||
})))))),
|
||
h("p", { class: "muted small" }, "Zone to zone is the forward chain; to the firewall, input; from it, output. +N: rules (and built-ins such as ping or port forwards) that let part of the traffic through before the rest falls to the deciding rule or the policy; −N: those that block part of it. Under each cell, what is let through. A zone to itself (faded): only traffic routed between its interfaces passes the firewall. Replies and established connections aren't shown: the built-in lets those through.")));
|
||
}
|
||
|
||
function detail(c) {
|
||
const ruleLink = (id, chain, label) => id ? h("a", { href: "#/rules?chain=" + chain, onclick: () => document.querySelector(".modal-back") && document.querySelector(".modal-back").remove() }, label) : label;
|
||
const v = (verdict) => badge(verdict, { allowed: "ok", blocked: "err", logged: "", unknown: "warn" }[verdict]);
|
||
const hits = (n) => h("td", { class: "small num" }, n === undefined || n === null ? "–" : n.toLocaleString());
|
||
const summary = { allowed: "all allowed", blocked: "all blocked", partly: "partly allowed" }[c.summary];
|
||
modal(c.from + " → " + c.to, h("div", null,
|
||
h("p", null, h("strong", null, summary), h("span", { class: "muted" }, " · new connections, checked in this order (the " + c.chain + " chain):")),
|
||
c.note ? h("div", { class: "msg info" }, c.note[0].toUpperCase() + c.note.slice(1) + ".") : null,
|
||
h("div", { class: "table-wrap" }, h("table", null,
|
||
h("thead", null, h("tr", null, h("th", null, ""), h("th", null, "What"), h("th", null, "By"), h("th", { class: "num" }, "Hits"))),
|
||
h("tbody", null,
|
||
c.exceptions.map((e) => h("tr", null, h("td", null, v(e.verdict)), h("td", null, e.what), h("td", { class: "small" }, ruleLink(e.rule_id, e.chain, e.label)), hits(e.hits))),
|
||
h("tr", { class: "matrix-rest" }, h("td", null, v(c.verdict)), h("td", null, h("strong", null, c.exceptions.length ? "the rest" : "everything")),
|
||
h("td", { class: "small" }, ruleLink(c.rule_id, c.chain, c.decided_by)), hits(c.hits))))),
|
||
h("p", { class: "muted small" }, "Hits: each rule's packets since housecarld started, in all its traffic: a rule can cover several pairs.")),
|
||
[{ label: "Close", value: null }], { wide: true });
|
||
}
|