milestone 28: query provenance — every logged query is exactly explainable
Gates / frontend (push) Successful in 1m36s
Gates / test (push) Successful in 1m56s
Gates / test-aarch64 (push) Successful in 7m37s
Gates / package (push) Successful in 9m12s
Gates / container (push) Successful in 13s
CI / gates (push) Successful in 19m4s
Gates / frontend (push) Successful in 1m36s
Gates / test (push) Successful in 1m56s
Gates / test-aarch64 (push) Successful in 7m37s
Gates / package (push) Successful in 9m12s
Gates / container (push) Successful in 13s
CI / gates (push) Successful in 19m4s
query rows gain qclass, rcode, group, policy action and reason, the matched rule or list entry with its source, cname and safe-search targets, route kind, forward zone, and the resolver that actually answered — the pool and local markers die. servfails are logged and name the resolver that lost; post-parse protocol refusals become rows. a detail page at /queries/:id renders the ordered explanation, and coverage watermarks distinguish an empty history from a missing one. the schema fingerprint changes: existing query history is recreated with the old file kept aside and the reset filed as a resolved diagnostic. fixes an oversized udp reply being rebuilt as noerror, which handed clients a truncated nxdomain as success.
This commit is contained in:
+39
-2
@@ -12,7 +12,7 @@ import {
|
||||
import AppShell from "@/shell/AppShell";
|
||||
import { ApiError } from "@/lib/api";
|
||||
import { createQueryClient } from "@/lib/queryClient";
|
||||
import type { DiagnosticSeverity, DiagnosticState, DiagnosticsFilter } from "@/lib/types";
|
||||
import type { DiagnosticSeverity, DiagnosticState, DiagnosticsFilter, QueriesFilter } from "@/lib/types";
|
||||
import {
|
||||
blocklistsQuery,
|
||||
clientPrefixesQuery,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
healthQuery,
|
||||
localRecordsQuery,
|
||||
queriesInfiniteQuery,
|
||||
queryDetailQuery,
|
||||
rulesQuery,
|
||||
settingsQuery,
|
||||
statsQuery,
|
||||
@@ -138,13 +139,43 @@ const dashboardRoute = createRoute({
|
||||
component: lazyRouteComponent(() => import("@/features/dashboard/DashboardPage")),
|
||||
});
|
||||
|
||||
/**
|
||||
* `domain` and `client` seed the filter form, so a detail page can link to
|
||||
* "every query for this domain". Anything else in the search object is dropped:
|
||||
* an unknown value would reach the api as a parameter the handler 400s.
|
||||
*/
|
||||
const queriesRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/queries",
|
||||
loader: ({ context }) => context.queryClient.ensureInfiniteQueryData(queriesInfiniteQuery({})),
|
||||
validateSearch: (search: Record<string, unknown>): { domain?: string; client?: string } => {
|
||||
const domain = search["domain"];
|
||||
const client = search["client"];
|
||||
return {
|
||||
domain: typeof domain === "string" && domain !== "" ? domain : undefined,
|
||||
client: typeof client === "string" && client !== "" ? client : undefined,
|
||||
};
|
||||
},
|
||||
loaderDeps: ({ search }) => search,
|
||||
loader: ({ context, deps }) => {
|
||||
const filter: QueriesFilter = {};
|
||||
if (deps.domain !== undefined) filter.domain = deps.domain;
|
||||
if (deps.client !== undefined) filter.client = deps.client;
|
||||
return context.queryClient.ensureInfiniteQueryData(queriesInfiniteQuery(filter));
|
||||
},
|
||||
component: lazyRouteComponent(() => import("@/features/queries/QueryLogPage")),
|
||||
});
|
||||
|
||||
const queryDetailRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/queries/$id",
|
||||
// Swallowed on purpose, as the diagnostics detail route does: a row
|
||||
// retention has pruned is a 404 the page explains, with the way back to the
|
||||
// log. The whole-page error component would call it a request failure.
|
||||
loader: ({ context, params }) =>
|
||||
context.queryClient.ensureQueryData(queryDetailQuery(Number(params.id))).catch(() => undefined),
|
||||
component: lazyRouteComponent(() => import("@/features/queries/QueryDetailPage")),
|
||||
});
|
||||
|
||||
const liveRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/live",
|
||||
@@ -210,9 +241,14 @@ const upstreamsRoute = createRoute({
|
||||
component: lazyRouteComponent(() => import("@/features/upstreams/UpstreamsPage")),
|
||||
});
|
||||
|
||||
/** `domain` prefills and runs the lookup, so a query detail page can link into it. */
|
||||
const lookupRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/lookup",
|
||||
validateSearch: (search: Record<string, unknown>): { domain?: string } => {
|
||||
const domain = search["domain"];
|
||||
return { domain: typeof domain === "string" && domain !== "" ? domain : undefined };
|
||||
},
|
||||
loader: ({ context }) => context.queryClient.ensureQueryData(groupsQuery()),
|
||||
component: lazyRouteComponent(() => import("@/features/lookup/LookupPage")),
|
||||
});
|
||||
@@ -275,6 +311,7 @@ const routeTree = rootRoute.addChildren([
|
||||
shellRoute.addChildren([
|
||||
dashboardRoute,
|
||||
queriesRoute,
|
||||
queryDetailRoute,
|
||||
liveRoute,
|
||||
clientsRoute,
|
||||
groupsRoute,
|
||||
|
||||
Reference in New Issue
Block a user