mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
This commit introduces a new `/api/v1/notifications/live` endpoint that utilizes Server-Sent Events (SSE) to stream notifications to the web UI. This is used to display alerts such as when a configuration reload has failed. I opted for SSE over WebSockets because SSE is simpler to implement and more robust for our use case. Since we only need one-way communication from the server to the client, SSE fits perfectly without the overhead of establishing and maintaining a two-way WebSocket connection. When the SSE connection fails, we go back to a classic /api/v1/notifications API endpoint. This commit also contains the required UI changes for the new Mantine UI. Signed-off-by: Julien <roidelapluie@o11y.eu>
132 lines
3.2 KiB
TypeScript
132 lines
3.2 KiB
TypeScript
import { QueryKey, useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
|
import { useSettings } from "../state/settingsSlice";
|
|
|
|
export const API_PATH = "api/v1";
|
|
|
|
export type SuccessAPIResponse<T> = {
|
|
status: "success";
|
|
data: T;
|
|
warnings?: string[];
|
|
infos?: string[];
|
|
};
|
|
|
|
export type ErrorAPIResponse = {
|
|
status: "error";
|
|
errorType: string;
|
|
error: string;
|
|
};
|
|
|
|
export type APIResponse<T> = SuccessAPIResponse<T> | ErrorAPIResponse;
|
|
|
|
const createQueryFn =
|
|
<T>({
|
|
pathPrefix,
|
|
path,
|
|
params,
|
|
recordResponseTime,
|
|
}: {
|
|
pathPrefix: string;
|
|
path: string;
|
|
params?: Record<string, string>;
|
|
recordResponseTime?: (time: number) => void;
|
|
}) =>
|
|
async ({ signal }: { signal: AbortSignal }) => {
|
|
const queryString = params
|
|
? `?${new URLSearchParams(params).toString()}`
|
|
: "";
|
|
|
|
try {
|
|
const startTime = Date.now();
|
|
|
|
const res = await fetch(
|
|
`${pathPrefix}/${API_PATH}${path}${queryString}`,
|
|
{
|
|
cache: "no-store",
|
|
credentials: "same-origin",
|
|
signal,
|
|
}
|
|
);
|
|
|
|
if (
|
|
!res.ok &&
|
|
!res.headers.get("content-type")?.startsWith("application/json")
|
|
) {
|
|
// For example, Prometheus may send a 503 Service Unavailable response
|
|
// with a "text/plain" content type when it's starting up. But the API
|
|
// may also respond with a JSON error message and the same error code.
|
|
throw new Error(res.statusText);
|
|
}
|
|
|
|
const apiRes = (await res.json()) as APIResponse<T>;
|
|
|
|
if (recordResponseTime) {
|
|
recordResponseTime(Date.now() - startTime);
|
|
}
|
|
|
|
if (apiRes.status === "error") {
|
|
throw new Error(
|
|
apiRes.error !== undefined
|
|
? apiRes.error
|
|
: 'missing "error" field in response JSON'
|
|
);
|
|
}
|
|
|
|
return apiRes as SuccessAPIResponse<T>;
|
|
} catch (error) {
|
|
if (!(error instanceof Error)) {
|
|
throw new Error("Unknown error");
|
|
}
|
|
|
|
switch (error.name) {
|
|
case "TypeError":
|
|
throw new Error("Network error or unable to reach the server");
|
|
case "SyntaxError":
|
|
throw new Error("Invalid JSON response");
|
|
default:
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
type QueryOptions = {
|
|
key?: QueryKey;
|
|
path: string;
|
|
params?: Record<string, string>;
|
|
enabled?: boolean;
|
|
refetchInterval?: false | number;
|
|
recordResponseTime?: (time: number) => void;
|
|
};
|
|
|
|
export const useAPIQuery = <T>({
|
|
key,
|
|
path,
|
|
params,
|
|
enabled,
|
|
recordResponseTime,
|
|
refetchInterval,
|
|
}: QueryOptions) => {
|
|
const { pathPrefix } = useSettings();
|
|
|
|
return useQuery<SuccessAPIResponse<T>>({
|
|
queryKey: key !== undefined ? key : [path, params],
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
refetchInterval: refetchInterval,
|
|
gcTime: 0,
|
|
enabled,
|
|
queryFn: createQueryFn({ pathPrefix, path, params, recordResponseTime }),
|
|
});
|
|
};
|
|
|
|
export const useSuspenseAPIQuery = <T>({ key, path, params }: QueryOptions) => {
|
|
const { pathPrefix } = useSettings();
|
|
|
|
return useSuspenseQuery<SuccessAPIResponse<T>>({
|
|
queryKey: key !== undefined ? key : [path, params],
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
gcTime: 0,
|
|
queryFn: createQueryFn({ pathPrefix, path, params }),
|
|
});
|
|
};
|