Explore: a filter bar, and Explore the answers on the Statistics page
The explore page has a filter bar to type filters in (client, name or it and below, view, type, result, blocked), starting from the current filters and suggesting values from what is shown, so taking every filter out no longer means going back. The Statistics page opens it with Explore the answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5443e95897
commit
a8c7760fc8
5 files changed
+47
-11
No files matched your search
@@ -2,7 +2,7 @@
|
||||
// last days, filtered by any mix of client, name, view, record type, result
|
||||
// and blocking. Every value is a link that adds it to the filter, so a
|
||||
// client leads to its names, a name to the clients asking for it, and so on.
|
||||
import { h, mount, fmtTime, errorBox, badge, backLink } from "../ui.js";
|
||||
import { h, mount, fmtTime, errorBox, badge, backLink, input, select, checkbox } from "../ui.js";
|
||||
import { get } from "../api.js";
|
||||
import { lineChart, chartCard, legend, timeTable, barList, compact } from "../charts.js";
|
||||
import { viewsOff } from "./stats.js";
|
||||
@@ -35,21 +35,18 @@ export async function render(el, params, query) {
|
||||
|
||||
// The Statistics page's view switches apply here too.
|
||||
const off = viewsOff();
|
||||
let views = "";
|
||||
if (off.size) {
|
||||
const all = (await get("/views").catch(() => [])).map((v) => v.name);
|
||||
views = all.filter((v) => !off.has(v)).join(",");
|
||||
}
|
||||
const allViews = (await get("/views").catch(() => [])).map((v) => v.name);
|
||||
const views = off.size ? allViews.filter((v) => !off.has(v)).join(",") : "";
|
||||
let ex;
|
||||
try {
|
||||
ex = await get("/stats/explore", { ...f, since, views: views || undefined });
|
||||
} catch (e) {
|
||||
mount(el, head(f, without, since, null), e.code === "query_log_off"
|
||||
mount(el, head(f, without, since, null), filterBar(f, since, null, allViews), e.code === "query_log_off"
|
||||
? h("div", { class: "msg info" }, e.message) : errorBox(e));
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = [head(f, without, since, ex)];
|
||||
const parts = [head(f, without, since, ex), filterBar(f, since, ex, allViews)];
|
||||
const maxSecs = retentionSecs(ex.retention);
|
||||
parts.push(h("div", { class: "filter-row", role: "group", "aria-label": "Time range" },
|
||||
h("span", { class: "muted" }, "Range"),
|
||||
@@ -86,6 +83,32 @@ export async function render(el, params, query) {
|
||||
mount(el, parts);
|
||||
}
|
||||
|
||||
// filterBar types filters in: a client, a name (or it and the names below
|
||||
// it), a view, a type, a result, blocked or not. It starts from the current
|
||||
// filters and suggests values from what is shown.
|
||||
function filterBar(f, since, ex, views) {
|
||||
const sugg = (id, entries) => h("datalist", { id }, (entries || []).map((e) => h("option", { value: e.key }, e.name || null)));
|
||||
const client = input({ value: f.client || "", placeholder: "address", class: "mono", list: "ex-clients", style: { width: "170px" } });
|
||||
const name = input({ value: f.name || f.domain || "", placeholder: "nas.adbraun.lol", class: "mono", list: "ex-names", style: { width: "220px" } });
|
||||
const below = checkbox("and below", !!f.domain);
|
||||
const view = select([["", "any view"], ...views.map((v) => [v, v])], f.view || "");
|
||||
const type = input({ value: f.type || "", placeholder: "any type", class: "mono", list: "ex-types", style: { width: "110px" } });
|
||||
const rcode = select([["", "any result"], "NOERROR", "NXDOMAIN", "SERVFAIL", "REFUSED"], f.rcode || "");
|
||||
const blocked = select([["", "blocked or not"], ["yes", "blocked"], ["no", "not blocked"], ...((ex && ex.by.blocked) || []).map((e) => [e.key, "blocked by " + blockedLabel(e.key)])], f.blocked || "");
|
||||
if (f.blocked && ![...blocked.options].some((o) => o.value === f.blocked)) blocked.append(h("option", { value: f.blocked, selected: true }, "blocked by " + blockedLabel(f.blocked)));
|
||||
const apply = () => {
|
||||
const n = name.value.trim().toLowerCase().replace(/\.$/, "");
|
||||
const g = { client: client.value.trim(), view: view.value, type: type.value.trim().toUpperCase(), rcode: rcode.value, blocked: blocked.value };
|
||||
if (n) g[below.input.checked ? "domain" : "name"] = n;
|
||||
location.hash = exploreHref(g, since);
|
||||
};
|
||||
const form = h("form", { class: "explore-filters", onsubmit: (e) => { e.preventDefault(); apply(); } },
|
||||
h("label", null, "Client ", client), h("label", null, "Name ", name), below, view, h("label", null, "Type ", type), rcode, blocked,
|
||||
h("button", { type: "submit", class: "primary small" }, "Show"),
|
||||
sugg("ex-clients", ex && ex.by.client), sugg("ex-names", ex && ex.by.name), sugg("ex-types", (ex && ex.by.type) || ["A", "AAAA", "PTR", "MX", "TXT", "HTTPS", "SRV"].map((k) => ({ key: k }))));
|
||||
return h("div", { class: "panel explore-filter-panel" }, form);
|
||||
}
|
||||
|
||||
// head is the title and the filter chips, each with a ✕ to take it out.
|
||||
function head(f, without, since, ex) {
|
||||
const chips = keys.filter((k) => f[k]).map((k) => h("span", { class: "chip filter-chip" },
|
||||
|
||||
@@ -43,7 +43,9 @@ export async function render(el, params, query) {
|
||||
h("div", { class: "muted" }, status.enabled
|
||||
? "From named's counters" + (status.query_log ? " and response log" : "") + " · data since " + (status.first_data ? fmtTime(status.first_data) : "–") +
|
||||
" · kept: minutes " + status.retention.minute + ", hours " + status.retention.hour + ", days " + status.retention.day
|
||||
: "Query statistics are off")));
|
||||
: "Query statistics are off")),
|
||||
status.enabled && status.retention && status.retention.queries
|
||||
? h("div", { class: "actions" }, h("a", { class: "button", href: "#/stats/explore", title: "Every answer of the last " + status.retention.queries + ", by client, name, view, type and result" }, "Explore the answers")) : null);
|
||||
if (!status.enabled) {
|
||||
mount(el, head, disabledPanel());
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user