Make "hide empty rules persistent"

It can be a bit annoying to always press "hide empty rules". This commit
uses the session storage of the browser to make it persistent.

Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
leonnicolas 2025-01-10 23:32:05 +01:00
parent 0a19f1268e
commit 6ccd9add1e
No known key found for this signature in database
GPG key ID: 088D0743E2B65C07
4 changed files with 89 additions and 53 deletions

View file

@ -5,7 +5,7 @@ import {
Checkbox, Checkbox,
Stack, Stack,
Group, Group,
NumberInput, NumberInput
} from "@mantine/core"; } from "@mantine/core";
import { IconSettings } from "@tabler/icons-react"; import { IconSettings } from "@tabler/icons-react";
import { FC } from "react"; import { FC } from "react";
@ -21,8 +21,9 @@ const SettingsMenu: FC = () => {
enableSyntaxHighlighting, enableSyntaxHighlighting,
enableLinter, enableLinter,
showAnnotations, showAnnotations,
hideEmptyGroups,
ruleGroupsPerPage, ruleGroupsPerPage,
alertGroupsPerPage, alertGroupsPerPage
} = useSettings(); } = useSettings();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -48,7 +49,7 @@ const SettingsMenu: FC = () => {
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
useLocalTime: event.currentTarget.checked, useLocalTime: event.currentTarget.checked
}) })
) )
} }
@ -63,7 +64,7 @@ const SettingsMenu: FC = () => {
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
enableQueryHistory: event.currentTarget.checked, enableQueryHistory: event.currentTarget.checked
}) })
) )
} }
@ -74,7 +75,7 @@ const SettingsMenu: FC = () => {
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
enableAutocomplete: event.currentTarget.checked, enableAutocomplete: event.currentTarget.checked
}) })
) )
} }
@ -85,7 +86,7 @@ const SettingsMenu: FC = () => {
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
enableSyntaxHighlighting: event.currentTarget.checked, enableSyntaxHighlighting: event.currentTarget.checked
}) })
) )
} }
@ -96,7 +97,7 @@ const SettingsMenu: FC = () => {
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
enableLinter: event.currentTarget.checked, enableLinter: event.currentTarget.checked
}) })
) )
} }
@ -107,17 +108,30 @@ const SettingsMenu: FC = () => {
<Stack> <Stack>
<Fieldset p="md" legend="Alerts page settings"> <Fieldset p="md" legend="Alerts page settings">
<Stack>
<Checkbox <Checkbox
checked={showAnnotations} checked={showAnnotations}
label="Show expanded annotations" label="Show expanded annotations"
onChange={(event) => onChange={(event) =>
dispatch( dispatch(
updateSettings({ updateSettings({
showAnnotations: event.currentTarget.checked, showAnnotations: event.currentTarget.checked
}) })
) )
} }
/> />
<Checkbox
checked={hideEmptyGroups}
label="Hide empty groups"
onChange={(event) =>
dispatch(
updateSettings({
hideEmptyGroups: event.currentTarget.checked
})
)
}
/>
</Stack>
</Fieldset> </Fieldset>
<Fieldset p="md" legend="Alerts page settings"> <Fieldset p="md" legend="Alerts page settings">
<NumberInput <NumberInput
@ -132,7 +146,7 @@ const SettingsMenu: FC = () => {
dispatch( dispatch(
updateSettings({ updateSettings({
alertGroupsPerPage: value, alertGroupsPerPage: value
}) })
); );
}} }}
@ -151,7 +165,7 @@ const SettingsMenu: FC = () => {
dispatch( dispatch(
updateSettings({ updateSettings({
ruleGroupsPerPage: value, ruleGroupsPerPage: value
}) })
); );
}} }}

View file

@ -11,7 +11,7 @@ import {
Alert, Alert,
TextInput, TextInput,
Anchor, Anchor,
Pagination, Pagination
} from "@mantine/core"; } from "@mantine/core";
import { useSuspenseAPIQuery } from "../api/api"; import { useSuspenseAPIQuery } from "../api/api";
import { AlertingRule, AlertingRulesResult } from "../api/responseTypes/rules"; import { AlertingRule, AlertingRulesResult } from "../api/responseTypes/rules";
@ -23,14 +23,14 @@ import { Fragment, useEffect, useMemo } from "react";
import { StateMultiSelect } from "../components/StateMultiSelect"; import { StateMultiSelect } from "../components/StateMultiSelect";
import { IconInfoCircle, IconSearch } from "@tabler/icons-react"; import { IconInfoCircle, IconSearch } from "@tabler/icons-react";
import { LabelBadges } from "../components/LabelBadges"; import { LabelBadges } from "../components/LabelBadges";
import { useSettings } from "../state/settingsSlice"; import { useSettings, updateSettings } from "../state/settingsSlice";
import { useAppDispatch } from "../state/hooks";
import { import {
ArrayParam, ArrayParam,
BooleanParam,
NumberParam, NumberParam,
StringParam, StringParam,
useQueryParam, useQueryParam,
withDefault, withDefault
} from "use-query-params"; } from "use-query-params";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { KVSearch } from "@nexucis/kvsearch"; import { KVSearch } from "@nexucis/kvsearch";
@ -67,7 +67,7 @@ type AlertsPageData = {
const kvSearch = new KVSearch<AlertingRule>({ const kvSearch = new KVSearch<AlertingRule>({
shouldSort: true, shouldSort: true,
indexedKeys: ["name", "labels", ["labels", /.*/]], indexedKeys: ["name", "labels", ["labels", /.*/]]
}); });
const buildAlertsPageData = ( const buildAlertsPageData = (
@ -79,9 +79,9 @@ const buildAlertsPageData = (
globalCounts: { globalCounts: {
inactive: 0, inactive: 0,
pending: 0, pending: 0,
firing: 0, firing: 0
}, },
groups: [], groups: []
}; };
for (const group of data.groups) { for (const group of data.groups) {
@ -89,7 +89,7 @@ const buildAlertsPageData = (
total: 0, total: 0,
inactive: 0, inactive: 0,
pending: 0, pending: 0,
firing: 0, firing: 0
}; };
for (const r of group.rules) { for (const r of group.rules) {
@ -126,9 +126,9 @@ const buildAlertsPageData = (
rule: r, rule: r,
counts: { counts: {
firing: r.alerts.filter((a) => a.state === "firing").length, firing: r.alerts.filter((a) => a.state === "firing").length,
pending: r.alerts.filter((a) => a.state === "pending").length, pending: r.alerts.filter((a) => a.state === "pending").length
}, }
})), }))
}); });
} }
@ -146,11 +146,12 @@ export default function AlertsPage() {
const { data } = useSuspenseAPIQuery<AlertingRulesResult>({ const { data } = useSuspenseAPIQuery<AlertingRulesResult>({
path: `/rules`, path: `/rules`,
params: { params: {
type: "alert", type: "alert"
}, }
}); });
const { showAnnotations } = useSettings(); const { hideEmptyGroups, showAnnotations } = useSettings();
const dispatch = useAppDispatch();
// Define URL query params. // Define URL query params.
const [stateFilter, setStateFilter] = useQueryParam( const [stateFilter, setStateFilter] = useQueryParam(
@ -162,10 +163,6 @@ export default function AlertsPage() {
withDefault(StringParam, "") withDefault(StringParam, "")
); );
const [debouncedSearch] = useDebouncedValue<string>(searchFilter.trim(), 250); const [debouncedSearch] = useDebouncedValue<string>(searchFilter.trim(), 250);
const [showEmptyGroups, setShowEmptyGroups] = useQueryParam(
"showEmptyGroups",
withDefault(BooleanParam, true)
);
const { alertGroupsPerPage } = useSettings(); const { alertGroupsPerPage } = useSettings();
const [activePage, setActivePage] = useQueryParam( const [activePage, setActivePage] = useQueryParam(
@ -181,10 +178,10 @@ export default function AlertsPage() {
const shownGroups = useMemo( const shownGroups = useMemo(
() => () =>
showEmptyGroups !hideEmptyGroups
? alertsPageData.groups ? alertsPageData.groups
: alertsPageData.groups.filter((g) => g.rules.length > 0), : alertsPageData.groups.filter((g) => g.rules.length > 0),
[alertsPageData.groups, showEmptyGroups] [alertsPageData.groups, hideEmptyGroups]
); );
// If we were e.g. on page 10 and the number of total pages decreases to 5 (due to filtering // If we were e.g. on page 10 and the number of total pages decreases to 5 (due to filtering
@ -255,7 +252,13 @@ export default function AlertsPage() {
<Anchor <Anchor
ml="md" ml="md"
fz="1em" fz="1em"
onClick={() => setShowEmptyGroups(false)} onClick={() => {
dispatch(
updateSettings({
hideEmptyGroups: true
})
);
}}
> >
Hide empty groups Hide empty groups
</Anchor> </Anchor>
@ -267,7 +270,13 @@ export default function AlertsPage() {
<Anchor <Anchor
ml="md" ml="md"
fz="1em" fz="1em"
onClick={() => setShowEmptyGroups(false)} onClick={() => {
dispatch(
updateSettings({
hideEmptyGroups: true
})
);
}}
> >
Hide empty groups Hide empty groups
</Anchor> </Anchor>
@ -286,8 +295,8 @@ export default function AlertsPage() {
// have a different background color than their surrounding group card in dark mode, // have a different background color than their surrounding group card in dark mode,
// but it would be better to use CSS to override the light/dark colors for // but it would be better to use CSS to override the light/dark colors for
// collapsed/expanded accordion items. // collapsed/expanded accordion items.
backgroundColor: "#c0c0c015", backgroundColor: "#c0c0c015"
}, }
}} }}
key={j} key={j}
value={j.toString()} value={j.toString()}
@ -403,7 +412,7 @@ export default function AlertsPage() {
)} )}
</Card> </Card>
)), )),
[currentPageGroups, showAnnotations, setShowEmptyGroups] [currentPageGroups, showAnnotations, dispatch]
); );
return ( return (
@ -442,7 +451,7 @@ export default function AlertsPage() {
No rules found. No rules found.
</Alert> </Alert>
) : ( ) : (
!showEmptyGroups && hideEmptyGroups &&
alertsPageData.groups.length !== shownGroups.length && ( alertsPageData.groups.length !== shownGroups.length && (
<Alert <Alert
title="Hiding groups with no matching rules" title="Hiding groups with no matching rules"
@ -450,7 +459,13 @@ export default function AlertsPage() {
> >
Hiding {alertsPageData.groups.length - shownGroups.length} empty Hiding {alertsPageData.groups.length - shownGroups.length} empty
groups due to filters or no rules. groups due to filters or no rules.
<Anchor ml="md" fz="1em" onClick={() => setShowEmptyGroups(true)}> <Anchor
ml="md"
fz="1em"
onClick={() =>
dispatch(updateSettings({ hideEmptyGroups: false }))
}
>
Show empty groups Show empty groups
</Anchor> </Anchor>
</Alert> </Alert>

View file

@ -37,7 +37,7 @@ startAppListening({
effect: ({ payload }) => { effect: ({ payload }) => {
persistToLocalStorage( persistToLocalStorage(
localStorageKeyServiceDiscoveryPageCollapsedPools, localStorageKeyServiceDiscoveryPageCollapsedPools,
payload payload,
); );
}, },
}); });
@ -47,7 +47,7 @@ startAppListening({
effect: (_, { getState }) => { effect: (_, { getState }) => {
persistToLocalStorage( persistToLocalStorage(
localStorageKeyQueryHistory, localStorageKeyQueryHistory,
getState().queryPage.queryHistory getState().queryPage.queryHistory,
); );
}, },
}); });
@ -62,6 +62,7 @@ startAppListening({
case "enableAutocomplete": case "enableAutocomplete":
case "enableSyntaxHighlighting": case "enableSyntaxHighlighting":
case "enableLinter": case "enableLinter":
case "hideEmptyGroups":
case "showAnnotations": case "showAnnotations":
case "ruleGroupsPerPage": case "ruleGroupsPerPage":
return persistToLocalStorage(`settings.${key}`, value); return persistToLocalStorage(`settings.${key}`, value);

View file

@ -13,6 +13,7 @@ interface Settings {
enableAutocomplete: boolean; enableAutocomplete: boolean;
enableSyntaxHighlighting: boolean; enableSyntaxHighlighting: boolean;
enableLinter: boolean; enableLinter: boolean;
hideEmptyGroups: boolean;
showAnnotations: boolean; showAnnotations: boolean;
ruleGroupsPerPage: number; ruleGroupsPerPage: number;
alertGroupsPerPage: number; alertGroupsPerPage: number;
@ -30,6 +31,7 @@ export const localStorageKeyEnableAutocomplete = "settings.enableAutocomplete";
export const localStorageKeyEnableSyntaxHighlighting = export const localStorageKeyEnableSyntaxHighlighting =
"settings.enableSyntaxHighlighting"; "settings.enableSyntaxHighlighting";
export const localStorageKeyEnableLinter = "settings.enableLinter"; export const localStorageKeyEnableLinter = "settings.enableLinter";
export const localStorageKeyHideEmptyGroups = "settings.hideEmptyGroups";
export const localStorageKeyShowAnnotations = "settings.showAnnotations"; export const localStorageKeyShowAnnotations = "settings.showAnnotations";
export const localStorageKeyRuleGroupsPerPage = "settings.ruleGroupsPerPage"; export const localStorageKeyRuleGroupsPerPage = "settings.ruleGroupsPerPage";
export const localStorageKeyAlertGroupsPerPage = "settings.alertGroupsPerPage"; export const localStorageKeyAlertGroupsPerPage = "settings.alertGroupsPerPage";
@ -53,7 +55,7 @@ const getPathPrefix = (path: string) => {
"/flags", "/flags",
"/config", "/config",
"/alertmanager-discovery", "/alertmanager-discovery",
"/agent", "/agent"
]; ];
const pagePath = pagePaths.find((p) => path.endsWith(p)); const pagePath = pagePaths.find((p) => path.endsWith(p));
@ -95,6 +97,10 @@ export const initialState: Settings = {
localStorageKeyEnableLinter, localStorageKeyEnableLinter,
true true
), ),
hideEmptyGroups: initializeFromLocalStorage<boolean>(
localStorageKeyHideEmptyGroups,
false
),
showAnnotations: initializeFromLocalStorage<boolean>( showAnnotations: initializeFromLocalStorage<boolean>(
localStorageKeyShowAnnotations, localStorageKeyShowAnnotations,
true true
@ -106,7 +112,7 @@ export const initialState: Settings = {
alertGroupsPerPage: initializeFromLocalStorage<number>( alertGroupsPerPage: initializeFromLocalStorage<number>(
localStorageKeyAlertGroupsPerPage, localStorageKeyAlertGroupsPerPage,
10 10
), )
}; };
export const settingsSlice = createSlice({ export const settingsSlice = createSlice({
@ -115,8 +121,8 @@ export const settingsSlice = createSlice({
reducers: { reducers: {
updateSettings: (state, { payload }: PayloadAction<Partial<Settings>>) => { updateSettings: (state, { payload }: PayloadAction<Partial<Settings>>) => {
Object.assign(state, payload); Object.assign(state, payload);
}, }
}, }
}); });
export const { updateSettings } = settingsSlice.actions; export const { updateSettings } = settingsSlice.actions;