milestone 27: diagnostics — operational failures land in one curated log, resolved history purgeable
Gates / frontend (push) Successful in 1m33s
Gates / test (push) Successful in 1m48s
Gates / test-aarch64 (push) Successful in 7m10s
Gates / package (push) Successful in 5m31s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 14m51s

This commit is contained in:
2026-08-20 20:05:59 +02:00
parent 3dd8214ef2
commit 037f209179
50 changed files with 8608 additions and 102 deletions
+51
View File
@@ -12,10 +12,13 @@ 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 {
blocklistsQuery,
clientPrefixesQuery,
clientsQuery,
diagnosticQuery,
diagnosticsInfiniteQuery,
forwardZonesQuery,
groupsQuery,
healthQuery,
@@ -214,6 +217,52 @@ const lookupRoute = createRoute({
component: lazyRouteComponent(() => import("@/features/lookup/LookupPage")),
});
/**
* The three filters live in the url so an episode can be linked to as it was
* read. Anything else in the search object is dropped: an unknown value would
* reach the api as a query parameter the handler rejects with a 400.
*/
const diagnosticsRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/diagnostics",
validateSearch: (
search: Record<string, unknown>,
): { state?: DiagnosticState; severity?: DiagnosticSeverity; component?: string } => {
const state = search["state"];
const severity = search["severity"];
const component = search["component"];
return {
state: state === "active" || state === "resolved" ? state : undefined,
severity: severity === "warning" || severity === "error" ? severity : undefined,
component: typeof component === "string" && component !== "" ? component : undefined,
};
},
loaderDeps: ({ search }) => search,
// allSettled: the two sections render their own state, and the resolved
// history failing must not replace the active list with the error page.
loader: ({ context, deps }) => {
const base: DiagnosticsFilter = {};
if (deps.severity !== undefined) base.severity = deps.severity;
if (deps.component !== undefined) base.component = deps.component;
return Promise.allSettled([
context.queryClient.ensureInfiniteQueryData(diagnosticsInfiniteQuery({ ...base, state: "active" })),
context.queryClient.ensureInfiniteQueryData(diagnosticsInfiniteQuery({ ...base, state: "resolved" })),
]);
},
component: lazyRouteComponent(() => import("@/features/diagnostics/DiagnosticsPage")),
});
const diagnosticDetailRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/diagnostics/$id",
// Swallowed on purpose: an event retention has removed is a 404 the page
// itself explains, with the way back to the list. The whole-page error
// component would state it as a request failure instead.
loader: ({ context, params }) =>
context.queryClient.ensureQueryData(diagnosticQuery(Number(params.id))).catch(() => undefined),
component: lazyRouteComponent(() => import("@/features/diagnostics/DiagnosticDetailPage")),
});
const settingsRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/settings",
@@ -234,6 +283,8 @@ const routeTree = rootRoute.addChildren([
localDnsRoute,
upstreamsRoute,
lookupRoute,
diagnosticsRoute,
diagnosticDetailRoute,
settingsRoute,
]),
]);