Add server readiness check and WAL replay progress display

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-07-15 22:21:14 +02:00
parent 8fae131733
commit 1aa79e29a2
4 changed files with 134 additions and 4 deletions

View file

@ -62,6 +62,7 @@ import { Notifications } from "@mantine/notifications";
import { useAppDispatch } from "./state/hooks";
import { updateSettings, useSettings } from "./state/settingsSlice";
import SettingsMenu from "./components/SettingsMenu";
import ReadinessWrapper from "./components/ReadinessWrapper";
const queryClient = new QueryClient();
@ -379,15 +380,42 @@ function App() {
}
/>
{agentMode ? (
<Route path="/agent" element={<AgentPage />} />
<Route
path="/agent"
element={
<ReadinessWrapper>
<AgentPage />
</ReadinessWrapper>
}
/>
) : (
<>
<Route path="/query" element={<QueryPage />} />
<Route path="/alerts" element={<AlertsPage />} />
<Route
path="/query"
element={
<ReadinessWrapper>
<QueryPage />
</ReadinessWrapper>
}
/>
<Route
path="/alerts"
element={
<ReadinessWrapper>
<AlertsPage />
</ReadinessWrapper>
}
/>
</>
)}
{allStatusPages.map((p) => (
<Route key={p.path} path={p.path} element={p.element} />
<Route
key={p.path}
path={p.path}
element={
<ReadinessWrapper>{p.element}</ReadinessWrapper>
}
/>
))}
</Routes>
</Suspense>

View file

@ -0,0 +1,7 @@
// Result type for /api/v1/status/walreplay endpoint.
// See: https://prometheus.io/docs/prometheus/latest/querying/api/#wal-replay-stats
export interface WALReplayStatus {
min: number;
max: number;
current: number;
}

View file

@ -0,0 +1,92 @@
import { FC, PropsWithChildren, useEffect, useState } from "react";
import { useAppDispatch } from "../state/hooks";
import { updateSettings, useSettings } from "../state/settingsSlice";
import { useSuspenseAPIQuery } from "../api/api";
import { WALReplayStatus } from "../api/responseTypes/walreplay";
import { Progress, Stack, Title } from "@mantine/core";
import { useSuspenseQuery } from "@tanstack/react-query";
const ReadinessLoader: FC = () => {
const dispatch = useAppDispatch();
// Query key is incremented every second to retrigger the status fetching.
const [queryKey, setQueryKey] = useState(0);
// Query readiness status.
const { data: ready } = useSuspenseQuery<boolean>({
queryKey: [`ready-${queryKey}`],
retry: false,
refetchOnWindowFocus: false,
gcTime: 0,
queryFn: async ({ signal }: { signal: AbortSignal }) => {
try {
const res = await fetch("/-/ready", {
cache: "no-store",
credentials: "same-origin",
signal,
});
switch (res.status) {
case 200:
return true;
case 503:
return false;
default:
throw new Error(res.statusText);
}
} catch (error) {
throw new Error("Unexpected error while fetching ready status");
}
},
});
// Query WAL replay status.
const {
data: {
data: { min, max, current },
},
} = useSuspenseAPIQuery<WALReplayStatus>({
path: `/status/walreplay`,
key: `walreplay-${queryKey}`,
});
useEffect(() => {
if (ready) {
dispatch(updateSettings({ ready: ready }));
}
}, [ready, dispatch]);
useEffect(() => {
const interval = setInterval(() => setQueryKey((v) => v + 1), 1000);
return () => clearInterval(interval);
}, []);
return (
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
<Title order={2}>Starting up...</Title>
{max > 0 && (
<>
<p>
Replaying WAL ({current}/{max})
</p>
<Progress
size="xl"
animated
value={((current - min + 1) / (max - min + 1)) * 100}
/>
</>
)}
</Stack>
);
};
export const ReadinessWrapper: FC<PropsWithChildren> = ({ children }) => {
const { ready } = useSettings();
if (ready) {
return <>{children}</>;
}
return <ReadinessLoader />;
};
export default ReadinessWrapper;

View file

@ -9,6 +9,9 @@ export default defineConfig({
"/api": {
target: "http://localhost:9090",
},
"/-/": {
target: "http://localhost:9090",
},
},
},
});