diff --git a/web/ui/mantine-ui/src/pages/AlertStateMultiSelect.tsx b/web/ui/mantine-ui/src/pages/AlertStateMultiSelect.tsx new file mode 100644 index 0000000000..5d237ec1e9 --- /dev/null +++ b/web/ui/mantine-ui/src/pages/AlertStateMultiSelect.tsx @@ -0,0 +1,135 @@ +import { useState } from "react"; +import { + Badge, + CheckIcon, + CloseButton, + Combobox, + ComboboxChevron, + ComboboxClearButton, + Group, + Input, + Pill, + PillGroup, + PillsInput, + useCombobox, +} from "@mantine/core"; +import badgeClasses from "../Badge.module.css"; +import { IconFilter } from "@tabler/icons-react"; +import { IconFilterSearch } from "@tabler/icons-react"; + +interface StatePillProps extends React.ComponentPropsWithoutRef<"div"> { + value: string; + onRemove?: () => void; +} + +export function StatePill({ value, onRemove, ...others }: StatePillProps) { + return ( + + {value} + + ); +} + +export function AlertStateMultiSelect() { + const combobox = useCombobox({ + onDropdownClose: () => combobox.resetSelectedOption(), + onDropdownOpen: () => combobox.updateSelectedOptionIndex("active"), + }); + + const [values, setValues] = useState([]); + + const handleValueSelect = (val: string) => + setValues((current) => + current.includes(val) + ? current.filter((v) => v !== val) + : [...current, val] + ); + + const handleValueRemove = (val: string) => + setValues((current) => current.filter((v) => v !== val)); + + const renderedValues = values.map((item) => ( + handleValueRemove(item)} + key={item} + /> + )); + + const options = ["inactive", "pending", "firing"].map((value) => { + return ( + + + {values.includes(value) ? : null} + + + + ); + }); + + return ( + + + combobox.toggleDropdown()} + miw={200} + leftSection={} + rightSection={ + values.length > 0 ? ( + setValues([])} /> + ) : ( + + ) + } + > + + {renderedValues.length > 0 ? ( + renderedValues + ) : ( + Filter by alert state + )} + + + combobox.closeDropdown()} + onKeyDown={(event) => { + if (event.key === "Backspace") { + event.preventDefault(); + handleValueRemove(values[values.length - 1]); + } + }} + /> + + + + + + + {options} + + + ); +} diff --git a/web/ui/mantine-ui/src/pages/AlertsPage.tsx b/web/ui/mantine-ui/src/pages/AlertsPage.tsx index b1762004c4..19bd631bef 100644 --- a/web/ui/mantine-ui/src/pages/AlertsPage.tsx +++ b/web/ui/mantine-ui/src/pages/AlertsPage.tsx @@ -7,14 +7,24 @@ import { Badge, Tooltip, Box, - Switch, + Divider, + Button, + Fieldset, + Checkbox, + MultiSelect, + Pill, + Stack, + Input, } 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 { Fragment, useState } from "react"; +import { Fragment } from "react"; +import { AlertStateMultiSelect } from "./AlertStateMultiSelect"; +import { useAppSelector } from "../state/hooks"; +import { IconSearch } from "@tabler/icons-react"; export default function AlertsPage() { const { data } = useSuspenseAPIQuery({ @@ -23,7 +33,9 @@ export default function AlertsPage() { type: "alert", }, }); - const [showAnnotations, setShowAnnotations] = useState(false); + const showAnnotations = useAppSelector( + (state) => state.settings.showAnnotations + ); const ruleStatsCount = { inactive: 0, @@ -37,167 +49,173 @@ export default function AlertsPage() { return ( <> - setShowAnnotations(event.currentTarget.checked)} - mb="md" - /> - {data.data.groups.map((g, i) => ( - - - - - {g.name} - - - {g.file} - + + + } + placeholder="Filter by alert name or labels" + > + + + {data.data.groups.map((g, i) => ( + + + + + {g.name} + + + {g.file} + + - - - {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; + + {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 ( - 0 - ? "5px solid var(--mantine-color-red-4)" - : numPending > 0 - ? "5px solid var(--mantine-color-orange-5)" - : "5px solid var(--mantine-color-green-4)", - }} - > - - - {r.name} - - {numFiring > 0 && ( - - firing ({numFiring}) - - )} - {numPending > 0 && ( - - pending ({numPending}) - - )} - {/* {numFiring === 0 && numPending === 0 && ( - - inactive - - )} */} + return ( + 0 + ? "5px solid var(--mantine-color-red-4)" + : numPending > 0 + ? "5px solid var(--mantine-color-orange-5)" + : "5px solid var(--mantine-color-green-4)", + }} + > + + + {r.name} + + {numFiring > 0 && ( + + firing ({numFiring}) + + )} + {numPending > 0 && ( + + pending ({numPending}) + + )} + {/* {numFiring === 0 && numPending === 0 && ( + + inactive + + )} */} + - - - - - {r.alerts.length > 0 && ( - - - - Alert labels - State - Active Since - Value - - - - {r.type === "alerting" && - r.alerts.map((a, k) => ( - - - - - {Object.entries(a.labels).map( - ([k, v]) => { - return ( - - {/* TODO: Proper quote escaping */} - {k}="{v}" - - ); - } - )} - - - - - {a.state} - - - - - - {formatRelative(a.activeAt, now(), "")} - - - - {a.value} - - {showAnnotations && ( + + + + {r.alerts.length > 0 && ( +
+ + + Alert labels + State + Active Since + Value + + + + {r.type === "alerting" && + r.alerts.map((a, k) => ( + - -
- - {Object.entries(a.annotations).map( - ([k, v]) => ( - - {k} - {v} - - ) - )} - -
+ + + {Object.entries(a.labels).map( + ([k, v]) => { + return ( + + {/* TODO: Proper quote escaping */} + {k}="{v}" + + ); + } + )} + + + + {a.state} + + + + + + {formatRelative( + a.activeAt, + now(), + "" + )} + + + + {a.value} - )} - - ))} - - - )} -
-
- ); - })} -
-
- ))} + {showAnnotations && ( + + + + + {Object.entries(a.annotations).map( + ([k, v]) => ( + + {k} + {v} + + ) + )} + +
+
+
+ )} + + ))} + + + )} + + + ); + })} + + + ))} + ); }