rename web/ to admin/, along with the web-named build and cli identifiers

This commit is contained in:
2026-08-16 00:17:58 +02:00
parent 5b3d1cd65c
commit 1e97c80f6b
136 changed files with 196 additions and 196 deletions
+257
View File
@@ -0,0 +1,257 @@
import type { QueryClient } from "@tanstack/react-query";
import * as stylex from "@stylexjs/stylex";
import {
createRootRouteWithContext,
createRoute,
createRouter,
lazyRouteComponent,
useRouter,
type ErrorComponentProps,
type RouterHistory,
} from "@tanstack/react-router";
import AppShell from "@/shell/AppShell";
import { ApiError } from "@/lib/api";
import { createQueryClient } from "@/lib/queryClient";
import {
blocklistsQuery,
clientPrefixesQuery,
clientsQuery,
forwardZonesQuery,
groupsQuery,
healthQuery,
localRecordsQuery,
queriesInfiniteQuery,
rulesQuery,
settingsQuery,
statsQuery,
timeseriesQuery,
upstreamHealthQuery,
upstreamsQuery,
} from "@/lib/queries";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
export interface RouterContext {
queryClient: QueryClient;
}
const styles = stylex.create({
pending: {
padding: "2rem",
textAlign: "center",
color: colors.textMuted,
},
errorBox: {
margin: "1rem",
borderRadius: "0.25rem",
borderWidth: 1,
borderStyle: "solid",
borderColor: colors.dangerBorder,
backgroundColor: colors.dangerSurface,
padding: "1rem",
},
errorTitle: {
fontWeight: 600,
color: colors.dangerText,
},
errorDetail: {
marginTop: "0.25rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
color: colors.dangerText,
},
});
function RoutePending() {
return (
<div {...stylex.props(styles.pending)} role="status">
<span {...stylex.props(shared.pulse)}>Loading</span>
</div>
);
}
function RouteError({ error }: ErrorComponentProps) {
const router = useRouter();
let title = "Something went wrong";
let detail = error.message;
if (error instanceof ApiError) {
if (error.status === 503) {
title = "Server starting or degraded";
detail = error.message;
} else if (error.status === 429) {
title = "Rate limited";
detail = error.retryAfter !== undefined ? `Try again in ${error.retryAfter}s.` : "Try again shortly.";
} else if (error.status >= 500) {
title = "Internal error";
} else {
title = `Request failed (${error.status})`;
}
}
return (
<div role="alert" {...stylex.props(styles.errorBox)}>
<h2 {...stylex.props(styles.errorTitle)}>{title}</h2>
<p {...stylex.props(styles.errorDetail)}>{detail}</p>
<button
type="button"
onClick={() => void router.invalidate()}
{...stylex.props(shared.retryButton, shared.focusRing)}
>
Retry
</button>
</div>
);
}
const rootRoute = createRootRouteWithContext<RouterContext>()();
const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/login",
validateSearch: (search: Record<string, unknown>): { redirect?: string } => ({
redirect: typeof search["redirect"] === "string" ? search["redirect"] : undefined,
}),
component: lazyRouteComponent(() => import("@/auth/LoginPage")),
});
const shellRoute = createRoute({
getParentRoute: () => rootRoute,
id: "shell",
component: AppShell,
});
const dashboardRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/",
// allSettled, not all: DashboardPage reads these with useQuery so each widget
// can render its own error. A rejecting loader would replace the whole page
// with RouteError and take the three healthy widgets down with the failed one.
loader: ({ context }) =>
Promise.allSettled([
context.queryClient.ensureQueryData(statsQuery("24h")),
context.queryClient.ensureQueryData(timeseriesQuery("24h")),
context.queryClient.ensureQueryData(healthQuery()),
context.queryClient.ensureQueryData(upstreamHealthQuery()),
]),
component: lazyRouteComponent(() => import("@/features/dashboard/DashboardPage")),
});
const queriesRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/queries",
loader: ({ context }) => context.queryClient.ensureInfiniteQueryData(queriesInfiniteQuery({})),
component: lazyRouteComponent(() => import("@/features/queries/QueryLogPage")),
});
const liveRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/live",
component: lazyRouteComponent(() => import("@/features/live/LiveLogPage")),
});
const clientsRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/clients",
loader: ({ context }) =>
Promise.all([
context.queryClient.ensureQueryData(clientsQuery()),
context.queryClient.ensureQueryData(clientPrefixesQuery()),
context.queryClient.ensureQueryData(groupsQuery()),
]),
component: lazyRouteComponent(() => import("@/features/clients/ClientsPage")),
});
const groupsRoute = 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")),
});
const lookupRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/lookup",
loader: ({ context }) => context.queryClient.ensureQueryData(groupsQuery()),
component: lazyRouteComponent(() => import("@/features/lookup/LookupPage")),
});
const settingsRoute = createRoute({
getParentRoute: () => shellRoute,
path: "/settings",
loader: ({ context }) => context.queryClient.ensureQueryData(settingsQuery()),
component: lazyRouteComponent(() => import("@/features/settings/SettingsPage")),
});
const routeTree = rootRoute.addChildren([
loginRoute,
shellRoute.addChildren([
dashboardRoute,
queriesRoute,
liveRoute,
clientsRoute,
groupsRoute,
blocklistsRoute,
rulesRoute,
localDnsRoute,
upstreamsRoute,
lookupRoute,
settingsRoute,
]),
]);
export function createAppRouter(history?: RouterHistory, queryClient: QueryClient = createQueryClient()) {
return createRouter({
routeTree,
history,
context: { queryClient },
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
defaultPendingComponent: RoutePending,
defaultErrorComponent: RouteError,
});
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createAppRouter>;
}
}