Implement Alertmanagers discovery page

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-09-03 15:51:32 +02:00
parent 87a22500e1
commit 9e4ffd044c
5 changed files with 134 additions and 50 deletions

View file

@ -20,6 +20,7 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
IconBell,
IconBellFilled,
IconChevronDown,
IconChevronRight,
@ -62,6 +63,7 @@ import ReadinessWrapper from "./components/ReadinessWrapper";
import { QueryParamProvider } from "use-query-params";
import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6";
import ServiceDiscoveryPage from "./pages/service-discovery/ServiceDiscoveryPage";
import AlertmanagerDiscoveryPage from "./pages/AlertmanagerDiscoveryPage";
const queryClient = new QueryClient();
@ -106,6 +108,13 @@ const monitoringStatusPages = [
element: <ServiceDiscoveryPage />,
inAgentMode: true,
},
{
title: "Alertmanager discovery",
path: "/discovered-alertmanagers",
icon: <IconBell style={navIconStyle} />,
element: <AlertmanagerDiscoveryPage />,
inAgentMode: false,
},
];
const serverStatusPages = [

View file

@ -0,0 +1,10 @@
export type AlertmanagerTarget = {
url: string;
};
// Result type for /api/v1/alertmanagers endpoint.
// See: https://prometheus.io/docs/prometheus/latest/querying/api/#alertmanagers
export type AlertmanagersResult = {
activeAlertmanagers: AlertmanagerTarget[];
droppedAlertmanagers: AlertmanagerTarget[];
};

View file

@ -0,0 +1,115 @@
import {
ActionIcon,
Alert,
Box,
Card,
Group,
Select,
Skeleton,
Stack,
Table,
Text,
} from "@mantine/core";
import {
IconBell,
IconBellOff,
IconInfoCircle,
IconLayoutNavbarCollapse,
IconLayoutNavbarExpand,
IconSearch,
} from "@tabler/icons-react";
import { Suspense } from "react";
import { useAppDispatch, useAppSelector } from "../state/hooks";
import {
ArrayParam,
StringParam,
useQueryParam,
withDefault,
} from "use-query-params";
import ErrorBoundary from "../components/ErrorBoundary";
import ScrapePoolList from "./ServiceDiscoveryPoolsList";
import { useSuspenseAPIQuery } from "../api/api";
import { ScrapePoolsResult } from "../api/responseTypes/scrapePools";
import {
setCollapsedPools,
setShowLimitAlert,
} from "../state/serviceDiscoveryPageSlice";
import { StateMultiSelect } from "../components/StateMultiSelect";
import badgeClasses from "../Badge.module.css";
import { AlertmanagersResult } from "../api/responseTypes/alertmanagers";
import EndpointLink from "../components/EndpointLink";
export const targetPoolDisplayLimit = 20;
export default function AlertmanagerDiscoveryPage() {
// Load the list of all available scrape pools.
const {
data: {
data: { activeAlertmanagers, droppedAlertmanagers },
},
} = useSuspenseAPIQuery<AlertmanagersResult>({
path: `/alertmanagers`,
});
return (
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
<Card shadow="xs" withBorder p="md">
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
<IconBell size={22} />
<Text fz="xl" fw={600}>
Active Alertmanagers
</Text>
</Group>
{activeAlertmanagers.length === 0 ? (
<Alert title="No active alertmanagers" icon={<IconInfoCircle />}>
No active alertmanagers found.
</Alert>
) : (
<Table layout="fixed">
<Table.Tbody>
{activeAlertmanagers.map((alertmanager) => (
<Table.Tr key={alertmanager.url}>
<Table.Td>
<EndpointLink
endpoint={alertmanager.url}
globalUrl={alertmanager.url}
/>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</Card>
<Card shadow="xs" withBorder p="md">
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
<IconBellOff size={22} />
<Text fz="xl" fw={600}>
Dropped Alertmanagers
</Text>
</Group>
{droppedAlertmanagers.length === 0 ? (
<Alert title="No dropped alertmanagers" icon={<IconInfoCircle />}>
No dropped alertmanagers found.
</Alert>
) : (
<Table layout="fixed">
<Table.Tbody>
{droppedAlertmanagers.map((alertmanager) => (
<Table.Tr key={alertmanager.url}>
<Table.Td>
<EndpointLink
endpoint={alertmanager.url}
globalUrl={alertmanager.url}
/>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</Card>
</Stack>
);
}

View file

@ -25,7 +25,6 @@ import {
} from "../../state/serviceDiscoveryPageSlice";
import CustomInfiniteScroll from "../../components/CustomInfiniteScroll";
import TargetLabels from "./TargetLabels";
import { useDebouncedValue } from "@mantine/hooks";
import { targetPoolDisplayLimit } from "./ServiceDiscoveryPage";
import { BooleanParam, useQueryParam, withDefault } from "use-query-params";

View file

@ -1,49 +0,0 @@
import { FC } from "react";
import { Labels } from "../../api/responseTypes/targets";
import { LabelBadges } from "../../components/LabelBadges";
import { ActionIcon, Collapse, Group, Stack, Text } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
type TargetLabelsProps = {
labels: Labels;
discoveredLabels: Labels;
};
const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels }) => {
const [showDiscovered, { toggle: toggleDiscovered }] = useDisclosure(false);
return (
<Stack my={showDiscovered ? "sm" : undefined}>
<Group wrap="nowrap" align="flex-start">
<LabelBadges labels={labels} />
<ActionIcon
size="xs"
color="gray"
variant="light"
onClick={toggleDiscovered}
title={`${showDiscovered ? "Hide" : "Show"} discovered (pre-relabeling) labels`}
>
{showDiscovered ? (
<IconChevronUp
style={{ width: "70%", height: "70%" }}
stroke={1.5}
/>
) : (
<IconChevronDown style={{ width: "70%", height: "70%" }} />
)}
</ActionIcon>
</Group>
<Collapse in={showDiscovered}>
<Text fw={700} size="1em" my="lg" c="gray.7">
Discovered labels:
</Text>
<LabelBadges color="blue" labels={discoveredLabels} />
</Collapse>
</Stack>
);
};
export default TargetLabels;