Add state filtering to alerts page

Signed-off-by: Julius Volz <julius.volz@gmail.com>
pull/14448/head
Julius Volz 8 months ago
parent 2be782df77
commit 3c44f43815

@ -15,16 +15,19 @@ import {
Pill, Pill,
Stack, Stack,
Input, Input,
Alert,
} from "@mantine/core"; } from "@mantine/core";
import { useSuspenseAPIQuery } from "../api/api"; import { useSuspenseAPIQuery } from "../api/api";
import { AlertingRulesMap } from "../api/responseTypes/rules"; import { AlertingRulesMap } from "../api/responseTypes/rules";
import badgeClasses from "../Badge.module.css"; import badgeClasses from "../Badge.module.css";
import RuleDefinition from "../RuleDefinition"; import RuleDefinition from "../RuleDefinition";
import { formatRelative, now } from "../lib/formatTime"; import { humanizeDurationRelative, now } from "../lib/formatTime";
import { Fragment } from "react"; import { Fragment } from "react";
import { AlertStateMultiSelect } from "./AlertStateMultiSelect"; import { StateMultiSelect } from "../StateMultiSelect";
import { useAppSelector } from "../state/hooks"; import { useAppDispatch, useAppSelector } from "../state/hooks";
import { IconSearch } from "@tabler/icons-react"; import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
import { LabelBadges } from "../LabelBadges";
import { updateAlertFilters } from "../state/alertsPageSlice";
export default function AlertsPage() { export default function AlertsPage() {
const { data } = useSuspenseAPIQuery<AlertingRulesMap>({ const { data } = useSuspenseAPIQuery<AlertingRulesMap>({
@ -33,9 +36,12 @@ export default function AlertsPage() {
type: "alert", type: "alert",
}, },
}); });
const dispatch = useAppDispatch();
const showAnnotations = useAppSelector( const showAnnotations = useAppSelector(
(state) => state.settings.showAnnotations (state) => state.settings.showAnnotations
); );
const filters = useAppSelector((state) => state.alertsPage.filters);
const ruleStatsCount = { const ruleStatsCount = {
inactive: 0, inactive: 0,
@ -50,7 +56,19 @@ export default function AlertsPage() {
return ( return (
<> <>
<Group mb="md" mt="xs"> <Group mb="md" mt="xs">
<AlertStateMultiSelect /> <StateMultiSelect
options={["inactive", "pending", "firing"]}
optionClass={(o) =>
o === "inactive"
? badgeClasses.healthOk
: o === "pending"
? badgeClasses.healthWarn
: badgeClasses.healthErr
}
placeholder="Filter by alert state"
values={filters.state}
onChange={(values) => dispatch(updateAlertFilters({ state: values }))}
/>
<Input <Input
flex={1} flex={1}
leftSection={<IconSearch size={14} />} leftSection={<IconSearch size={14} />}
@ -58,7 +76,11 @@ export default function AlertsPage() {
></Input> ></Input>
</Group> </Group>
<Stack> <Stack>
{data.data.groups.map((g, i) => ( {data.data.groups.map((g, i) => {
const filteredRules = g.rules.filter(
(r) => filters.state.length === 0 || filters.state.includes(r.state)
);
return (
<Card <Card
shadow="xs" shadow="xs"
withBorder withBorder
@ -67,7 +89,11 @@ export default function AlertsPage() {
> >
<Group mb="md" mt="xs" ml="xs" justify="space-between"> <Group mb="md" mt="xs" ml="xs" justify="space-between">
<Group align="baseline"> <Group align="baseline">
<Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)"> <Text
fz="xl"
fw={600}
c="var(--mantine-primary-color-filled)"
>
{g.name} {g.name}
</Text> </Text>
<Text fz="sm" c="gray.6"> <Text fz="sm" c="gray.6">
@ -75,8 +101,16 @@ export default function AlertsPage() {
</Text> </Text>
</Group> </Group>
</Group> </Group>
{filteredRules.length === 0 && (
<Alert
title="No matching rules"
icon={<IconInfoCircle size={14} />}
>
No rules found that match your filter criteria.
</Alert>
)}
<Accordion multiple variant="separated"> <Accordion multiple variant="separated">
{g.rules.map((r, j) => { {filteredRules.map((r, j) => {
const numFiring = r.alerts.filter( const numFiring = r.alerts.filter(
(a) => a.state === "firing" (a) => a.state === "firing"
).length; ).length;
@ -111,11 +145,6 @@ export default function AlertsPage() {
pending ({numPending}) pending ({numPending})
</Badge> </Badge>
)} )}
{/* {numFiring === 0 && numPending === 0 && (
<Badge className={badgeClasses.healthOk}>
inactive
</Badge>
)} */}
</Group> </Group>
</Group> </Group>
</Accordion.Control> </Accordion.Control>
@ -137,29 +166,7 @@ export default function AlertsPage() {
<Fragment key={k}> <Fragment key={k}>
<Table.Tr> <Table.Tr>
<Table.Td> <Table.Td>
<Group gap="xs"> <LabelBadges labels={a.labels} />
{Object.entries(a.labels).map(
([k, v]) => {
return (
<Badge
variant="light"
className={
badgeClasses.labelBadge
}
styles={{
label: {
textTransform: "none",
},
}}
key={k}
>
{/* TODO: Proper quote escaping */}
{k}="{v}"
</Badge>
);
}
)}
</Group>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Badge <Badge
@ -175,7 +182,7 @@ export default function AlertsPage() {
<Table.Td> <Table.Td>
<Tooltip label={a.activeAt}> <Tooltip label={a.activeAt}>
<Box> <Box>
{formatRelative( {humanizeDurationRelative(
a.activeAt, a.activeAt,
now(), now(),
"" ""
@ -190,14 +197,14 @@ export default function AlertsPage() {
<Table.Td colSpan={4}> <Table.Td colSpan={4}>
<Table mt="md" mb="xl"> <Table mt="md" mb="xl">
<Table.Tbody> <Table.Tbody>
{Object.entries(a.annotations).map( {Object.entries(
([k, v]) => ( a.annotations
).map(([k, v]) => (
<Table.Tr key={k}> <Table.Tr key={k}>
<Table.Th>{k}</Table.Th> <Table.Th>{k}</Table.Th>
<Table.Td>{v}</Table.Td> <Table.Td>{v}</Table.Td>
</Table.Tr> </Table.Tr>
) ))}
)}
</Table.Tbody> </Table.Tbody>
</Table> </Table>
</Table.Td> </Table.Td>
@ -214,7 +221,8 @@ export default function AlertsPage() {
})} })}
</Accordion> </Accordion>
</Card> </Card>
))} );
})}
</Stack> </Stack>
</> </>
); );

Loading…
Cancel
Save