rename web/ to admin/, along with the web-named build and cli identifiers
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user