mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add silences table (#33138)
* Create table for silences * Style table to figma designs * Add rules table to silences * Rebase with new rules table * Remove redundant reducer * fetch alertmanager alerts (#33142) * fetch alertmanager alerts * show the alerts json * Use matching alerts from alertmanager api * Add handle to expire silence * Get silenced alerts closer to figma designs * fix expire silence endpoint typo * Style affected alerts table * Add default empty string for alertmanager source Co-authored-by: Domas <domasx2@gmail.com>
This commit is contained in:
parent
968935b8b7
commit
1913d304a3
@ -6,27 +6,35 @@ import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||
import { fetchSilencesAction } from './state/actions';
|
||||
import { fetchAmAlertsAction, fetchSilencesAction } from './state/actions';
|
||||
import { SILENCES_POLL_INTERVAL_MS } from './utils/constants';
|
||||
import { initialAsyncRequestState } from './utils/redux';
|
||||
import SilencesTable from './components/silences/SilencesTable';
|
||||
|
||||
const Silences: FC = () => {
|
||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
||||
const [alertManagerSourceName = '', setAlertManagerSourceName] = useAlertManagerSourceName();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
||||
|
||||
useEffect(() => {
|
||||
if (alertManagerSourceName) {
|
||||
dispatch(fetchSilencesAction(alertManagerSourceName));
|
||||
}
|
||||
}, [alertManagerSourceName, dispatch]);
|
||||
const alerts =
|
||||
useUnifiedAlertingSelector((state) => state.amAlerts)[alertManagerSourceName] || initialAsyncRequestState;
|
||||
|
||||
const { result, loading, error } =
|
||||
(alertManagerSourceName && silences[alertManagerSourceName]) || initialAsyncRequestState;
|
||||
useEffect(() => {
|
||||
function fetchAll() {
|
||||
dispatch(fetchSilencesAction(alertManagerSourceName));
|
||||
dispatch(fetchAmAlertsAction(alertManagerSourceName));
|
||||
}
|
||||
fetchAll();
|
||||
const interval = setInterval(() => fetchAll, SILENCES_POLL_INTERVAL_MS);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [alertManagerSourceName, dispatch]);
|
||||
|
||||
if (!alertManagerSourceName) {
|
||||
return <Redirect to="/alerting/silences" />;
|
||||
}
|
||||
const { result, loading, error } = silences[alertManagerSourceName] || initialAsyncRequestState;
|
||||
|
||||
return (
|
||||
<AlertingPageWrapper pageId="silences">
|
||||
@ -41,7 +49,13 @@ const Silences: FC = () => {
|
||||
</Alert>
|
||||
)}
|
||||
{loading && <LoadingPlaceholder text="loading silences..." />}
|
||||
{result && !loading && !error && <pre>{JSON.stringify(result, null, 2)}</pre>}
|
||||
{result && !error && alerts.result && (
|
||||
<SilencesTable
|
||||
silences={result}
|
||||
alertManagerAlerts={alerts.result}
|
||||
alertManagerSourceName={alertManagerSourceName}
|
||||
/>
|
||||
)}
|
||||
</AlertingPageWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { urlUtil } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AlertmanagerAlert,
|
||||
@ -78,23 +79,27 @@ export async function createOrUpdateSilence(
|
||||
|
||||
export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise<void> {
|
||||
await getBackendSrv().delete(
|
||||
`/api/alertmanager/${getDatasourceAPIId(alertmanagerSourceName)}/api/v2/silences/${encodeURIComponent(silenceID)}`
|
||||
`/api/alertmanager/${getDatasourceAPIId(alertmanagerSourceName)}/api/v2/silence/${encodeURIComponent(silenceID)}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchAlerts(
|
||||
alertmanagerSourceName: string,
|
||||
matchers?: SilenceMatcher[]
|
||||
matchers?: SilenceMatcher[],
|
||||
silenced = true,
|
||||
active = true,
|
||||
inhibited = true
|
||||
): Promise<AlertmanagerAlert[]> {
|
||||
const filters =
|
||||
matchers
|
||||
?.map(
|
||||
(matcher) =>
|
||||
`filter=${encodeURIComponent(
|
||||
`${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
|
||||
)}`
|
||||
)
|
||||
.join('&') || '';
|
||||
urlUtil.toUrlParams({ silenced, active, inhibited }) +
|
||||
matchers
|
||||
?.map(
|
||||
(matcher) =>
|
||||
`filter=${encodeURIComponent(
|
||||
`${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
|
||||
)}`
|
||||
)
|
||||
.join('&') || '';
|
||||
|
||||
const result = await getBackendSrv()
|
||||
.fetch<AlertmanagerAlert[]>({
|
||||
|
@ -6,11 +6,13 @@ import { css } from '@emotion/css';
|
||||
interface Props {
|
||||
labelKey: string;
|
||||
value: string;
|
||||
isRegex?: boolean;
|
||||
}
|
||||
|
||||
export const AlertLabel: FC<Props> = ({ labelKey, value }) => (
|
||||
export const AlertLabel: FC<Props> = ({ labelKey, value, isRegex = false }) => (
|
||||
<div className={useStyles(getStyles)}>
|
||||
{labelKey}={value}
|
||||
{labelKey}={isRegex && '~'}
|
||||
{value}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import { SilenceState, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
type Props = {
|
||||
status: PromAlertingRuleState;
|
||||
status: PromAlertingRuleState | SilenceState | AlertState;
|
||||
};
|
||||
|
||||
export const StateTag: FC<Props> = ({ children, status }) => {
|
||||
@ -36,4 +37,20 @@ const getStyles = (theme: GrafanaTheme) => ({
|
||||
background-color: ${theme.palette.brandDanger};
|
||||
border: solid 1px ${theme.palette.brandDanger};
|
||||
`,
|
||||
[SilenceState.Expired]: css`
|
||||
background-color: ${theme.palette.gray33};
|
||||
border: solid 1px ${theme.palette.gray33};
|
||||
`,
|
||||
[SilenceState.Active]: css`
|
||||
background-color: ${theme.palette.brandSuccess};
|
||||
border: solid 1px ${theme.palette.brandSuccess};
|
||||
`,
|
||||
[AlertState.Unprocessed]: css`
|
||||
background-color: ${theme.palette.gray33};
|
||||
border: solid 1px ${theme.palette.gray33};
|
||||
`,
|
||||
[AlertState.Suppressed]: css`
|
||||
background-color: ${theme.palette.brandPrimary};
|
||||
border: solid 1px ${theme.palette.brandPrimary};
|
||||
`,
|
||||
});
|
||||
|
@ -0,0 +1,12 @@
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import React, { FC } from 'react';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export const NoSilencesSplash: FC = () => (
|
||||
<EmptyListCTA
|
||||
title="You haven't created any silences yet"
|
||||
buttonIcon="bell-slash"
|
||||
buttonLink={`${config.appSubUrl ?? ''}alerting/silences/new`}
|
||||
buttonTitle="New silence"
|
||||
/>
|
||||
);
|
@ -0,0 +1,127 @@
|
||||
import React, { FC, Fragment, useState } from 'react';
|
||||
import { dateMath, GrafanaTheme, toDuration } from '@grafana/data';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Silence, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { AlertLabel } from '../AlertLabel';
|
||||
import { StateTag } from '../StateTag';
|
||||
import { CollapseToggle } from '../CollapseToggle';
|
||||
import { ActionButton } from '../rules/ActionButton';
|
||||
import { ActionIcon } from '../rules/ActionIcon';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import SilencedAlertsTable from './SilencedAlertsTable';
|
||||
import { expireSilenceAction } from '../../state/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
interface Props {
|
||||
className?: string;
|
||||
silence: Silence;
|
||||
silencedAlerts: AlertmanagerAlert[];
|
||||
alertManagerSourceName: string;
|
||||
}
|
||||
|
||||
const SilenceTableRow: FC<Props> = ({ silence, className, silencedAlerts, alertManagerSourceName }) => {
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const styles = useStyles(getStyles);
|
||||
const { status, matchers, startsAt, endsAt, comment, createdBy } = silence;
|
||||
|
||||
const dateDisplayFormat = 'YYYY-MM-DD HH:mm';
|
||||
const startsAtDate = dateMath.parse(startsAt);
|
||||
const endsAtDate = dateMath.parse(endsAt);
|
||||
const duration = toDuration(endsAtDate?.diff(startsAtDate || '')).asSeconds();
|
||||
|
||||
const handleExpireSilenceClick = () => {
|
||||
dispatch(expireSilenceAction(alertManagerSourceName, silence.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={className}>
|
||||
<td>
|
||||
<CollapseToggle isCollapsed={isCollapsed} onToggle={(value) => setIsCollapsed(value)} />
|
||||
</td>
|
||||
<td>
|
||||
<StateTag status={status.state}>{status.state}</StateTag>
|
||||
</td>
|
||||
<td className={styles.matchersCell}>
|
||||
{matchers?.map(({ name, value, isRegex }) => {
|
||||
return <AlertLabel key={`${name}-${value}`} labelKey={name} value={value} isRegex={isRegex} />;
|
||||
})}
|
||||
</td>
|
||||
<td>{silencedAlerts.length}</td>
|
||||
<td>
|
||||
{startsAtDate?.format(dateDisplayFormat)} {'-'}
|
||||
<br />
|
||||
{endsAtDate?.format(dateDisplayFormat)}
|
||||
</td>
|
||||
<td className={styles.actionsCell}>
|
||||
{status.state === 'expired' ? (
|
||||
<ActionButton icon="sync">Recreate</ActionButton>
|
||||
) : (
|
||||
<ActionButton icon="bell" onClick={handleExpireSilenceClick}>
|
||||
Unsilence
|
||||
</ActionButton>
|
||||
)}
|
||||
<ActionIcon icon="pen" tooltip="edit" />
|
||||
</td>
|
||||
</tr>
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<tr className={className}>
|
||||
<td />
|
||||
<td>Comment</td>
|
||||
<td colSpan={4}>{comment}</td>
|
||||
</tr>
|
||||
<tr className={className}>
|
||||
<td />
|
||||
<td>Schedule</td>
|
||||
<td colSpan={4}>{`${startsAtDate?.format(dateDisplayFormat)} - ${endsAtDate?.format(
|
||||
dateDisplayFormat
|
||||
)}`}</td>
|
||||
</tr>
|
||||
<tr className={className}>
|
||||
<td />
|
||||
<td>Duration</td>
|
||||
<td colSpan={4}>{duration} seconds</td>
|
||||
</tr>
|
||||
<tr className={className}>
|
||||
<td />
|
||||
<td>Created by</td>
|
||||
<td colSpan={4}>{createdBy}</td>
|
||||
</tr>
|
||||
{!!silencedAlerts.length && (
|
||||
<tr className={cx(className, styles.alertRulesCell)}>
|
||||
<td />
|
||||
<td>Affected alerts</td>
|
||||
<td colSpan={4}>
|
||||
<SilencedAlertsTable silencedAlerts={silencedAlerts} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
matchersCell: css`
|
||||
& > * + * {
|
||||
margin-left: ${theme.spacing.xs};
|
||||
}
|
||||
`,
|
||||
actionsCell: css`
|
||||
text-align: right;
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
|
||||
& > * + * {
|
||||
margin-left: ${theme.spacing.sm};
|
||||
}
|
||||
`,
|
||||
alertRulesCell: css`
|
||||
vertical-align: top;
|
||||
`,
|
||||
});
|
||||
|
||||
export default SilenceTableRow;
|
@ -0,0 +1,66 @@
|
||||
import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||
import React, { FC } from 'react';
|
||||
import { getAlertTableStyles } from '../../styles/table';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { SilencedAlertsTableRow } from './SilencedAlertsTableRow';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
interface Props {
|
||||
silencedAlerts: AlertmanagerAlert[];
|
||||
}
|
||||
|
||||
const SilencedAlertsTable: FC<Props> = ({ silencedAlerts }) => {
|
||||
const tableStyles = useStyles(getAlertTableStyles);
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
if (!!silencedAlerts.length) {
|
||||
return (
|
||||
<table className={cx(tableStyles.table, styles.tableMargin)}>
|
||||
<colgroup>
|
||||
<col className={tableStyles.colExpand} />
|
||||
<col className={styles.colState} />
|
||||
<col />
|
||||
<col className={styles.colName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>State</th>
|
||||
<th></th>
|
||||
<th>Alert name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{silencedAlerts.map((alert, index) => {
|
||||
return (
|
||||
<SilencedAlertsTableRow
|
||||
key={alert.fingerprint}
|
||||
alert={alert}
|
||||
className={index % 2 === 0 ? tableStyles.evenRow : ''}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
tableMargin: css`
|
||||
margin-bottom: ${theme.spacing.sm};
|
||||
`,
|
||||
colState: css`
|
||||
width: 110px;
|
||||
`,
|
||||
colName: css`
|
||||
width: 65%;
|
||||
`,
|
||||
});
|
||||
|
||||
export default SilencedAlertsTable;
|
@ -0,0 +1,51 @@
|
||||
import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { CollapseToggle } from '../CollapseToggle';
|
||||
import { StateTag } from '../StateTag';
|
||||
import { ActionIcon } from '../rules/ActionIcon';
|
||||
import { getAlertTableStyles } from '../../styles/table';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { dateTimeAsMoment, toDuration } from '@grafana/data';
|
||||
import { AlertLabels } from '../AlertLabels';
|
||||
|
||||
interface Props {
|
||||
alert: AlertmanagerAlert;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SilencedAlertsTableRow: FC<Props> = ({ alert, className }) => {
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
const tableStyles = useStyles(getAlertTableStyles);
|
||||
const alertDuration = toDuration(dateTimeAsMoment(alert.endsAt).diff(alert.startsAt)).asSeconds();
|
||||
const alertName = Object.entries(alert.labels).reduce((name, [labelKey, labelValue]) => {
|
||||
if (labelKey === 'alertname' || labelKey === '__alert_rule_title__') {
|
||||
name = labelValue;
|
||||
}
|
||||
return name;
|
||||
}, '');
|
||||
return (
|
||||
<>
|
||||
<tr className={className}>
|
||||
<td>
|
||||
<CollapseToggle isCollapsed={isCollapsed} onToggle={(collapsed) => setIsCollapsed(collapsed)} />
|
||||
</td>
|
||||
<td>
|
||||
<StateTag status={alert.status.state}>{alert.status.state}</StateTag>
|
||||
</td>
|
||||
<td>for {alertDuration} seconds</td>
|
||||
<td>{alertName}</td>
|
||||
<td className={tableStyles.actionsCell}>
|
||||
<ActionIcon icon="chart-line" href={alert.generatorURL} tooltip="View in explorer" />
|
||||
</td>
|
||||
</tr>
|
||||
{!isCollapsed && (
|
||||
<tr className={className}>
|
||||
<td></td>
|
||||
<td colSpan={5}>
|
||||
<AlertLabels labels={alert.labels} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,74 @@
|
||||
import React, { FC } from 'react';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
import { AlertmanagerAlert, Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||
import SilenceTableRow from './SilenceTableRow';
|
||||
import { getAlertTableStyles } from '../../styles/table';
|
||||
import { NoSilencesSplash } from './NoSilencesCTA';
|
||||
|
||||
interface Props {
|
||||
silences: Silence[];
|
||||
alertManagerAlerts: AlertmanagerAlert[];
|
||||
alertManagerSourceName: string;
|
||||
}
|
||||
|
||||
const SilencesTable: FC<Props> = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
|
||||
const styles = useStyles(getStyles);
|
||||
const tableStyles = useStyles(getAlertTableStyles);
|
||||
|
||||
const findSilencedAlerts = (id: string) => {
|
||||
return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
|
||||
};
|
||||
if (!!silences.length) {
|
||||
return (
|
||||
<table className={tableStyles.table}>
|
||||
<colgroup>
|
||||
<col className={tableStyles.colExpand} />
|
||||
<col className={styles.colState} />
|
||||
<col className={styles.colMatchers} />
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>State</th>
|
||||
<th>Matchers</th>
|
||||
<th>Alerts</th>
|
||||
<th>Schedule</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{silences.map((silence, index) => {
|
||||
const silencedAlerts = findSilencedAlerts(silence.id);
|
||||
return (
|
||||
<SilenceTableRow
|
||||
key={silence.id}
|
||||
silence={silence}
|
||||
className={index % 2 === 0 ? tableStyles.evenRow : undefined}
|
||||
silencedAlerts={silencedAlerts}
|
||||
alertManagerSourceName={alertManagerSourceName}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
} else {
|
||||
return <NoSilencesSplash />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
colState: css`
|
||||
width: 110px;
|
||||
`,
|
||||
colMatchers: css`
|
||||
width: 50%;
|
||||
`,
|
||||
});
|
||||
|
||||
export default SilencesTable;
|
@ -2,7 +2,7 @@ import { AppEvents } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { AlertManagerCortexConfig, Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { AlertmanagerAlert, AlertManagerCortexConfig, Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { NotifierDTO, ThunkResult } from 'app/types';
|
||||
import { RuleIdentifier, RuleNamespace, RuleWithLocation } from 'app/types/unified-alerting';
|
||||
import {
|
||||
@ -12,7 +12,13 @@ import {
|
||||
RulerRulesConfigDTO,
|
||||
} from 'app/types/unified-alerting-dto';
|
||||
import { fetchNotifiers } from '../api/grafana';
|
||||
import { fetchAlertManagerConfig, fetchSilences, updateAlertmanagerConfig } from '../api/alertmanager';
|
||||
import {
|
||||
expireSilence,
|
||||
fetchAlertManagerConfig,
|
||||
fetchAlerts,
|
||||
fetchSilences,
|
||||
updateAlertmanagerConfig,
|
||||
} from '../api/alertmanager';
|
||||
import { fetchRules } from '../api/prometheus';
|
||||
import {
|
||||
deleteRulerRulesGroup,
|
||||
@ -341,3 +347,16 @@ export const updateAlertManagerConfigAction = createAsyncThunk<void, UpdateALert
|
||||
})()
|
||||
)
|
||||
);
|
||||
export const fetchAmAlertsAction = createAsyncThunk(
|
||||
'unifiedalerting/fetchAmAlerts',
|
||||
(alertManagerSourceName: string): Promise<AlertmanagerAlert[]> =>
|
||||
withSerializedError(fetchAlerts(alertManagerSourceName, [], true, true, true))
|
||||
);
|
||||
|
||||
export const expireSilenceAction = (alertManagerSourceName: string, silenceId: string): ThunkResult<void> => {
|
||||
return async (dispatch) => {
|
||||
await expireSilence(alertManagerSourceName, silenceId);
|
||||
dispatch(fetchSilencesAction(alertManagerSourceName));
|
||||
dispatch(fetchAmAlertsAction(alertManagerSourceName));
|
||||
};
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { combineReducers } from 'redux';
|
||||
import { createAsyncMapSlice, createAsyncSlice } from '../utils/redux';
|
||||
import {
|
||||
fetchAlertManagerConfigAction,
|
||||
fetchAmAlertsAction,
|
||||
fetchExistingRuleAction,
|
||||
fetchGrafanaNotifiersAction,
|
||||
fetchPromRulesAction,
|
||||
@ -27,6 +28,8 @@ export const reducer = combineReducers({
|
||||
}),
|
||||
grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer,
|
||||
saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer,
|
||||
amAlerts: createAsyncMapSlice('amAlerts', fetchAmAlertsAction, (alertManagerSourceName) => alertManagerSourceName)
|
||||
.reducer,
|
||||
});
|
||||
|
||||
export type UnifiedAlertingState = ReturnType<typeof reducer>;
|
||||
|
@ -4,3 +4,4 @@ export const RULE_LIST_POLL_INTERVAL_MS = 20000;
|
||||
|
||||
export const ALERTMANAGER_NAME_QUERY_KEY = 'alertmanager';
|
||||
export const ALERTMANAGER_NAME_LOCAL_STORAGE_KEY = 'alerting-alertmanager';
|
||||
export const SILENCES_POLL_INTERVAL_MS = 20000;
|
||||
|
Loading…
Reference in New Issue
Block a user