milestone 32: task-shaped configuration, file mode as a rendering, config status api
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
This commit is contained in:
+79
-54
@@ -25,6 +25,7 @@ import {
|
||||
blocklistsQuery,
|
||||
clientPrefixesQuery,
|
||||
clientsQuery,
|
||||
configStatusQuery,
|
||||
diagnosticQuery,
|
||||
diagnosticsInfiniteQuery,
|
||||
forwardZonesQuery,
|
||||
@@ -43,6 +44,13 @@ import {
|
||||
upstreamsQuery,
|
||||
} from "@/lib/queries";
|
||||
import { DEFAULT_PERIOD, parsePeriod } from "@/features/overview/period";
|
||||
import {
|
||||
validateGroupId,
|
||||
validateProtectionSearch,
|
||||
validateResolutionSearch,
|
||||
type ProtectionSearch,
|
||||
type ResolutionSearch,
|
||||
} from "@/features/configuration/search";
|
||||
import type { Period } from "@/lib/types";
|
||||
import { styles as shared } from "@/ui/styles";
|
||||
import { colors } from "@/ui/tokens.stylex";
|
||||
@@ -253,6 +261,11 @@ const activityTestRoute = createRoute({
|
||||
const clientsRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/clients",
|
||||
// `group` is the filter Protection's client count links into. Validated here
|
||||
// so a hand-typed id cannot reach the page as anything but a row id.
|
||||
validateSearch: (search: Record<string, unknown>): { group?: number } => ({
|
||||
group: validateGroupId(search["group"]),
|
||||
}),
|
||||
loader: ({ context }) =>
|
||||
Promise.all([
|
||||
context.queryClient.ensureQueryData(clientsQuery()),
|
||||
@@ -262,51 +275,22 @@ const clientsRoute = createRoute({
|
||||
component: lazyRouteComponent(() => import("@/features/clients/ClientsPage")),
|
||||
});
|
||||
|
||||
const groupsRoute = createRoute({
|
||||
/**
|
||||
* One client, keyed by the row id the client API already identifies it with.
|
||||
*
|
||||
* No endpoint answers for a single client, so the list is the source. It is
|
||||
* awaited, as the other detail routes await theirs: a cold deep link has
|
||||
* nothing to render until it lands, and holding the navigation for one request
|
||||
* beats a page that flashes "no such client" before the rows arrive. The
|
||||
* rejection is swallowed for the same reason theirs are — the page states a
|
||||
* missing row and a failed fetch itself, where the whole-page error component
|
||||
* would call both a request failure.
|
||||
*/
|
||||
const clientDetailRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/groups",
|
||||
loader: ({ context }) =>
|
||||
Promise.all([
|
||||
context.queryClient.ensureQueryData(groupsQuery()),
|
||||
context.queryClient.ensureQueryData(blocklistsQuery()),
|
||||
]),
|
||||
component: lazyRouteComponent(() => import("@/features/groups/GroupsPage")),
|
||||
});
|
||||
|
||||
const blocklistsRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/blocklists",
|
||||
loader: ({ context }) => context.queryClient.ensureQueryData(blocklistsQuery()),
|
||||
component: lazyRouteComponent(() => import("@/features/blocklists/BlocklistsPage")),
|
||||
});
|
||||
|
||||
const rulesRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/rules",
|
||||
loader: ({ context }) =>
|
||||
Promise.all([
|
||||
context.queryClient.ensureQueryData(rulesQuery()),
|
||||
context.queryClient.ensureQueryData(groupsQuery()),
|
||||
]),
|
||||
component: lazyRouteComponent(() => import("@/features/rules/RulesPage")),
|
||||
});
|
||||
|
||||
const localDnsRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/local-dns",
|
||||
loader: ({ context }) =>
|
||||
Promise.all([
|
||||
context.queryClient.ensureQueryData(localRecordsQuery()),
|
||||
context.queryClient.ensureQueryData(forwardZonesQuery()),
|
||||
]),
|
||||
component: lazyRouteComponent(() => import("@/features/local/LocalDnsPage")),
|
||||
});
|
||||
|
||||
const upstreamsRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/upstreams",
|
||||
loader: ({ context }) => context.queryClient.ensureQueryData(upstreamsQuery()),
|
||||
component: lazyRouteComponent(() => import("@/features/upstreams/UpstreamsPage")),
|
||||
path: "/clients/$id",
|
||||
loader: ({ context }) => context.queryClient.ensureQueryData(clientsQuery()).catch(() => undefined),
|
||||
component: lazyRouteComponent(() => import("@/features/clients/ClientDetailPage")),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -366,11 +350,54 @@ const diagnosticDetailRoute = createRoute({
|
||||
component: lazyRouteComponent(() => import("@/features/diagnostics/DiagnosticDetailPage")),
|
||||
});
|
||||
|
||||
const settingsRoute = createRoute({
|
||||
/**
|
||||
* The three task-shaped configuration pages. There is no `/configuration`
|
||||
* landing route: a bare `/configuration` is not a question anyone has, and a
|
||||
* route that only redirects is a second name for a page.
|
||||
*
|
||||
* Every loader here is started and awaited nowhere, the pattern the rest of the
|
||||
* app uses: each panel owns its loading and error surface, so awaiting would
|
||||
* trade that for one blocking navigation on the slowest request. The rejections
|
||||
* are caught only to keep them from going unhandled.
|
||||
*
|
||||
* `configStatusQuery` starts with all of them. Every configuration page renders
|
||||
* nothing — neither form nor definition list — until it answers, so it is on the
|
||||
* critical path of all three.
|
||||
*/
|
||||
function startConfiguration(queryClient: QueryClient, queries: readonly unknown[]): void {
|
||||
const start = (promise: Promise<unknown>) => void promise.catch(() => {});
|
||||
start(queryClient.ensureQueryData(configStatusQuery()));
|
||||
for (const options of queries) {
|
||||
start(queryClient.ensureQueryData(options as Parameters<QueryClient["ensureQueryData"]>[0]));
|
||||
}
|
||||
}
|
||||
|
||||
const protectionRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/settings",
|
||||
loader: ({ context }) => context.queryClient.ensureQueryData(settingsQuery()),
|
||||
component: lazyRouteComponent(() => import("@/features/settings/SettingsPage")),
|
||||
path: "/configuration/protection",
|
||||
validateSearch: validateProtectionSearch,
|
||||
loaderDeps: ({ search }): ProtectionSearch => ({ tab: search.tab, group: search.group }),
|
||||
// Clients rides along because the selected group's detail counts them.
|
||||
loader: ({ context }) =>
|
||||
startConfiguration(context.queryClient, [groupsQuery(), blocklistsQuery(), rulesQuery(), clientsQuery()]),
|
||||
component: lazyRouteComponent(() => import("@/features/configuration/ProtectionPage")),
|
||||
});
|
||||
|
||||
const resolutionRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/configuration/resolution",
|
||||
validateSearch: validateResolutionSearch,
|
||||
loaderDeps: ({ search }): ResolutionSearch => ({ tab: search.tab }),
|
||||
loader: ({ context }) =>
|
||||
startConfiguration(context.queryClient, [upstreamsQuery(), localRecordsQuery(), forwardZonesQuery()]),
|
||||
component: lazyRouteComponent(() => import("@/features/configuration/ResolutionPage")),
|
||||
});
|
||||
|
||||
const systemRoute = createRoute({
|
||||
getParentRoute: () => shellRoute,
|
||||
path: "/configuration/system",
|
||||
loader: ({ context }) => startConfiguration(context.queryClient, [settingsQuery()]),
|
||||
component: lazyRouteComponent(() => import("@/features/configuration/SystemPage")),
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([
|
||||
@@ -382,14 +409,12 @@ const routeTree = rootRoute.addChildren([
|
||||
activityDetailRoute,
|
||||
activityTestRoute,
|
||||
clientsRoute,
|
||||
groupsRoute,
|
||||
blocklistsRoute,
|
||||
rulesRoute,
|
||||
localDnsRoute,
|
||||
upstreamsRoute,
|
||||
clientDetailRoute,
|
||||
diagnosticsRoute,
|
||||
diagnosticDetailRoute,
|
||||
settingsRoute,
|
||||
protectionRoute,
|
||||
resolutionRoute,
|
||||
systemRoute,
|
||||
]),
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user