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,
|
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,163 +76,153 @@ export default function AlertsPage() {
|
||||||
></Input>
|
></Input>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack>
|
<Stack>
|
||||||
{data.data.groups.map((g, i) => (
|
{data.data.groups.map((g, i) => {
|
||||||
<Card
|
const filteredRules = g.rules.filter(
|
||||||
shadow="xs"
|
(r) => filters.state.length === 0 || filters.state.includes(r.state)
|
||||||
withBorder
|
);
|
||||||
p="md"
|
return (
|
||||||
key={i} // TODO: Find a stable and definitely unique key.
|
<Card
|
||||||
>
|
shadow="xs"
|
||||||
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
withBorder
|
||||||
<Group align="baseline">
|
p="md"
|
||||||
<Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)">
|
key={i} // TODO: Find a stable and definitely unique key.
|
||||||
{g.name}
|
>
|
||||||
</Text>
|
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
||||||
<Text fz="sm" c="gray.6">
|
<Group align="baseline">
|
||||||
{g.file}
|
<Text
|
||||||
</Text>
|
fz="xl"
|
||||||
</Group>
|
fw={600}
|
||||||
</Group>
|
c="var(--mantine-primary-color-filled)"
|
||||||
<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)",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Accordion.Control>
|
{g.name}
|
||||||
<Group wrap="nowrap" justify="space-between" mr="lg">
|
</Text>
|
||||||
<Text>{r.name}</Text>
|
<Text fz="sm" c="gray.6">
|
||||||
<Group gap="xs">
|
{g.file}
|
||||||
{numFiring > 0 && (
|
</Text>
|
||||||
<Badge className={badgeClasses.healthErr}>
|
</Group>
|
||||||
firing ({numFiring})
|
</Group>
|
||||||
</Badge>
|
{filteredRules.length === 0 && (
|
||||||
)}
|
<Alert
|
||||||
{numPending > 0 && (
|
title="No matching rules"
|
||||||
<Badge className={badgeClasses.healthWarn}>
|
icon={<IconInfoCircle size={14} />}
|
||||||
pending ({numPending})
|
>
|
||||||
</Badge>
|
No rules found that match your filter criteria.
|
||||||
)}
|
</Alert>
|
||||||
{/* {numFiring === 0 && numPending === 0 && (
|
)}
|
||||||
<Badge className={badgeClasses.healthOk}>
|
<Accordion multiple variant="separated">
|
||||||
inactive
|
{filteredRules.map((r, j) => {
|
||||||
</Badge>
|
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>
|
||||||
</Group>
|
</Accordion.Control>
|
||||||
</Accordion.Control>
|
<Accordion.Panel>
|
||||||
<Accordion.Panel>
|
<RuleDefinition rule={r} />
|
||||||
<RuleDefinition rule={r} />
|
{r.alerts.length > 0 && (
|
||||||
{r.alerts.length > 0 && (
|
<Table mt="lg">
|
||||||
<Table mt="lg">
|
<Table.Thead>
|
||||||
<Table.Thead>
|
<Table.Tr>
|
||||||
<Table.Tr>
|
<Table.Th>Alert labels</Table.Th>
|
||||||
<Table.Th>Alert labels</Table.Th>
|
<Table.Th>State</Table.Th>
|
||||||
<Table.Th>State</Table.Th>
|
<Table.Th>Active Since</Table.Th>
|
||||||
<Table.Th>Active Since</Table.Th>
|
<Table.Th>Value</Table.Th>
|
||||||
<Table.Th>Value</Table.Th>
|
</Table.Tr>
|
||||||
</Table.Tr>
|
</Table.Thead>
|
||||||
</Table.Thead>
|
<Table.Tbody>
|
||||||
<Table.Tbody>
|
{r.type === "alerting" &&
|
||||||
{r.type === "alerting" &&
|
r.alerts.map((a, k) => (
|
||||||
r.alerts.map((a, k) => (
|
<Fragment key={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 && (
|
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td colSpan={4}>
|
<Table.Td>
|
||||||
<Table mt="md" mb="xl">
|
<LabelBadges labels={a.labels} />
|
||||||
<Table.Tbody>
|
</Table.Td>
|
||||||
{Object.entries(a.annotations).map(
|
<Table.Td>
|
||||||
([k, v]) => (
|
<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.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>
|
</Table.Tr>
|
||||||
</Table.Tr>
|
)}
|
||||||
)}
|
</Fragment>
|
||||||
</Fragment>
|
))}
|
||||||
))}
|
</Table.Tbody>
|
||||||
</Table.Tbody>
|
</Table>
|
||||||
</Table>
|
)}
|
||||||
)}
|
</Accordion.Panel>
|
||||||
</Accordion.Panel>
|
</Accordion.Item>
|
||||||
</Accordion.Item>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</Accordion>
|
||||||
</Accordion>
|
</Card>
|
||||||
</Card>
|
);
|
||||||
))}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue