mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Add state filtering to alerts page
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
2be782df77
commit
3c44f43815
|
@ -15,16 +15,19 @@ import {
|
|||
Pill,
|
||||
Stack,
|
||||
Input,
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { useSuspenseAPIQuery } from "../api/api";
|
||||
import { AlertingRulesMap } from "../api/responseTypes/rules";
|
||||
import badgeClasses from "../Badge.module.css";
|
||||
import RuleDefinition from "../RuleDefinition";
|
||||
import { formatRelative, now } from "../lib/formatTime";
|
||||
import { humanizeDurationRelative, now } from "../lib/formatTime";
|
||||
import { Fragment } from "react";
|
||||
import { AlertStateMultiSelect } from "./AlertStateMultiSelect";
|
||||
import { useAppSelector } from "../state/hooks";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { StateMultiSelect } from "../StateMultiSelect";
|
||||
import { useAppDispatch, useAppSelector } from "../state/hooks";
|
||||
import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
|
||||
import { LabelBadges } from "../LabelBadges";
|
||||
import { updateAlertFilters } from "../state/alertsPageSlice";
|
||||
|
||||
export default function AlertsPage() {
|
||||
const { data } = useSuspenseAPIQuery<AlertingRulesMap>({
|
||||
|
@ -33,9 +36,12 @@ export default function AlertsPage() {
|
|||
type: "alert",
|
||||
},
|
||||
});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const showAnnotations = useAppSelector(
|
||||
(state) => state.settings.showAnnotations
|
||||
);
|
||||
const filters = useAppSelector((state) => state.alertsPage.filters);
|
||||
|
||||
const ruleStatsCount = {
|
||||
inactive: 0,
|
||||
|
@ -50,7 +56,19 @@ export default function AlertsPage() {
|
|||
return (
|
||||
<>
|
||||
<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
|
||||
flex={1}
|
||||
leftSection={<IconSearch size={14} />}
|
||||
|
@ -58,163 +76,153 @@ export default function AlertsPage() {
|
|||
></Input>
|
||||
</Group>
|
||||
<Stack>
|
||||
{data.data.groups.map((g, i) => (
|
||||
<Card
|
||||
shadow="xs"
|
||||
withBorder
|
||||
p="md"
|
||||
key={i} // TODO: Find a stable and definitely unique key.
|
||||
>
|
||||
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
||||
<Group align="baseline">
|
||||
<Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)">
|
||||
{g.name}
|
||||
</Text>
|
||||
<Text fz="sm" c="gray.6">
|
||||
{g.file}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Accordion multiple variant="separated">
|
||||
{g.rules.map((r, j) => {
|
||||
const numFiring = r.alerts.filter(
|
||||
(a) => a.state === "firing"
|
||||
).length;
|
||||
const numPending = r.alerts.filter(
|
||||
(a) => a.state === "pending"
|
||||
).length;
|
||||
|
||||
return (
|
||||
<Accordion.Item
|
||||
key={j}
|
||||
value={j.toString()}
|
||||
style={{
|
||||
borderLeft:
|
||||
numFiring > 0
|
||||
? "5px solid var(--mantine-color-red-4)"
|
||||
: numPending > 0
|
||||
? "5px solid var(--mantine-color-orange-5)"
|
||||
: "5px solid var(--mantine-color-green-4)",
|
||||
}}
|
||||
{data.data.groups.map((g, i) => {
|
||||
const filteredRules = g.rules.filter(
|
||||
(r) => filters.state.length === 0 || filters.state.includes(r.state)
|
||||
);
|
||||
return (
|
||||
<Card
|
||||
shadow="xs"
|
||||
withBorder
|
||||
p="md"
|
||||
key={i} // TODO: Find a stable and definitely unique key.
|
||||
>
|
||||
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
||||
<Group align="baseline">
|
||||
<Text
|
||||
fz="xl"
|
||||
fw={600}
|
||||
c="var(--mantine-primary-color-filled)"
|
||||
>
|
||||
<Accordion.Control>
|
||||
<Group wrap="nowrap" justify="space-between" mr="lg">
|
||||
<Text>{r.name}</Text>
|
||||
<Group gap="xs">
|
||||
{numFiring > 0 && (
|
||||
<Badge className={badgeClasses.healthErr}>
|
||||
firing ({numFiring})
|
||||
</Badge>
|
||||
)}
|
||||
{numPending > 0 && (
|
||||
<Badge className={badgeClasses.healthWarn}>
|
||||
pending ({numPending})
|
||||
</Badge>
|
||||
)}
|
||||
{/* {numFiring === 0 && numPending === 0 && (
|
||||
<Badge className={badgeClasses.healthOk}>
|
||||
inactive
|
||||
</Badge>
|
||||
)} */}
|
||||
{g.name}
|
||||
</Text>
|
||||
<Text fz="sm" c="gray.6">
|
||||
{g.file}
|
||||
</Text>
|
||||
</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">
|
||||
{filteredRules.map((r, j) => {
|
||||
const numFiring = r.alerts.filter(
|
||||
(a) => a.state === "firing"
|
||||
).length;
|
||||
const numPending = r.alerts.filter(
|
||||
(a) => a.state === "pending"
|
||||
).length;
|
||||
|
||||
return (
|
||||
<Accordion.Item
|
||||
key={j}
|
||||
value={j.toString()}
|
||||
style={{
|
||||
borderLeft:
|
||||
numFiring > 0
|
||||
? "5px solid var(--mantine-color-red-4)"
|
||||
: numPending > 0
|
||||
? "5px solid var(--mantine-color-orange-5)"
|
||||
: "5px solid var(--mantine-color-green-4)",
|
||||
}}
|
||||
>
|
||||
<Accordion.Control>
|
||||
<Group wrap="nowrap" justify="space-between" mr="lg">
|
||||
<Text>{r.name}</Text>
|
||||
<Group gap="xs">
|
||||
{numFiring > 0 && (
|
||||
<Badge className={badgeClasses.healthErr}>
|
||||
firing ({numFiring})
|
||||
</Badge>
|
||||
)}
|
||||
{numPending > 0 && (
|
||||
<Badge className={badgeClasses.healthWarn}>
|
||||
pending ({numPending})
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<RuleDefinition rule={r} />
|
||||
{r.alerts.length > 0 && (
|
||||
<Table mt="lg">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Alert labels</Table.Th>
|
||||
<Table.Th>State</Table.Th>
|
||||
<Table.Th>Active Since</Table.Th>
|
||||
<Table.Th>Value</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{r.type === "alerting" &&
|
||||
r.alerts.map((a, k) => (
|
||||
<Fragment key={k}>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
{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>
|
||||
<Badge
|
||||
className={
|
||||
a.state === "firing"
|
||||
? badgeClasses.healthErr
|
||||
: badgeClasses.healthWarn
|
||||
}
|
||||
>
|
||||
{a.state}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Tooltip label={a.activeAt}>
|
||||
<Box>
|
||||
{formatRelative(
|
||||
a.activeAt,
|
||||
now(),
|
||||
""
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Table.Td>
|
||||
<Table.Td>{a.value}</Table.Td>
|
||||
</Table.Tr>
|
||||
{showAnnotations && (
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<RuleDefinition rule={r} />
|
||||
{r.alerts.length > 0 && (
|
||||
<Table mt="lg">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Alert labels</Table.Th>
|
||||
<Table.Th>State</Table.Th>
|
||||
<Table.Th>Active Since</Table.Th>
|
||||
<Table.Th>Value</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{r.type === "alerting" &&
|
||||
r.alerts.map((a, k) => (
|
||||
<Fragment key={k}>
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={4}>
|
||||
<Table mt="md" mb="xl">
|
||||
<Table.Tbody>
|
||||
{Object.entries(a.annotations).map(
|
||||
([k, v]) => (
|
||||
<Table.Td>
|
||||
<LabelBadges labels={a.labels} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
className={
|
||||
a.state === "firing"
|
||||
? badgeClasses.healthErr
|
||||
: badgeClasses.healthWarn
|
||||
}
|
||||
>
|
||||
{a.state}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Tooltip label={a.activeAt}>
|
||||
<Box>
|
||||
{humanizeDurationRelative(
|
||||
a.activeAt,
|
||||
now(),
|
||||
""
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Table.Td>
|
||||
<Table.Td>{a.value}</Table.Td>
|
||||
</Table.Tr>
|
||||
{showAnnotations && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={4}>
|
||||
<Table mt="md" mb="xl">
|
||||
<Table.Tbody>
|
||||
{Object.entries(
|
||||
a.annotations
|
||||
).map(([k, v]) => (
|
||||
<Table.Tr key={k}>
|
||||
<Table.Th>{k}</Table.Th>
|
||||
<Table.Td>{v}</Table.Td>
|
||||
</Table.Tr>
|
||||
)
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
</Card>
|
||||
))}
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue