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:
@@ -6,27 +6,35 @@ import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
|||||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
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 { initialAsyncRequestState } from './utils/redux';
|
||||||
|
import SilencesTable from './components/silences/SilencesTable';
|
||||||
|
|
||||||
const Silences: FC = () => {
|
const Silences: FC = () => {
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const [alertManagerSourceName = '', setAlertManagerSourceName] = useAlertManagerSourceName();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
||||||
|
|
||||||
useEffect(() => {
|
const alerts =
|
||||||
if (alertManagerSourceName) {
|
useUnifiedAlertingSelector((state) => state.amAlerts)[alertManagerSourceName] || initialAsyncRequestState;
|
||||||
dispatch(fetchSilencesAction(alertManagerSourceName));
|
|
||||||
}
|
|
||||||
}, [alertManagerSourceName, dispatch]);
|
|
||||||
|
|
||||||
const { result, loading, error } =
|
useEffect(() => {
|
||||||
(alertManagerSourceName && silences[alertManagerSourceName]) || initialAsyncRequestState;
|
function fetchAll() {
|
||||||
|
dispatch(fetchSilencesAction(alertManagerSourceName));
|
||||||
|
dispatch(fetchAmAlertsAction(alertManagerSourceName));
|
||||||
|
}
|
||||||
|
fetchAll();
|
||||||
|
const interval = setInterval(() => fetchAll, SILENCES_POLL_INTERVAL_MS);
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [alertManagerSourceName, dispatch]);
|
||||||
|
|
||||||
if (!alertManagerSourceName) {
|
if (!alertManagerSourceName) {
|
||||||
return <Redirect to="/alerting/silences" />;
|
return <Redirect to="/alerting/silences" />;
|
||||||
}
|
}
|
||||||
|
const { result, loading, error } = silences[alertManagerSourceName] || initialAsyncRequestState;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="silences">
|
<AlertingPageWrapper pageId="silences">
|
||||||
@@ -41,7 +49,13 @@ const Silences: FC = () => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{loading && <LoadingPlaceholder text="loading silences..." />}
|
{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>
|
</AlertingPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { urlUtil } from '@grafana/data';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
AlertmanagerAlert,
|
AlertmanagerAlert,
|
||||||
@@ -78,23 +79,27 @@ export async function createOrUpdateSilence(
|
|||||||
|
|
||||||
export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise<void> {
|
export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise<void> {
|
||||||
await getBackendSrv().delete(
|
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(
|
export async function fetchAlerts(
|
||||||
alertmanagerSourceName: string,
|
alertmanagerSourceName: string,
|
||||||
matchers?: SilenceMatcher[]
|
matchers?: SilenceMatcher[],
|
||||||
|
silenced = true,
|
||||||
|
active = true,
|
||||||
|
inhibited = true
|
||||||
): Promise<AlertmanagerAlert[]> {
|
): Promise<AlertmanagerAlert[]> {
|
||||||
const filters =
|
const filters =
|
||||||
matchers
|
urlUtil.toUrlParams({ silenced, active, inhibited }) +
|
||||||
?.map(
|
matchers
|
||||||
(matcher) =>
|
?.map(
|
||||||
`filter=${encodeURIComponent(
|
(matcher) =>
|
||||||
`${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
|
`filter=${encodeURIComponent(
|
||||||
)}`
|
`${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"`
|
||||||
)
|
)}`
|
||||||
.join('&') || '';
|
)
|
||||||
|
.join('&') || '';
|
||||||
|
|
||||||
const result = await getBackendSrv()
|
const result = await getBackendSrv()
|
||||||
.fetch<AlertmanagerAlert[]>({
|
.fetch<AlertmanagerAlert[]>({
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { css } from '@emotion/css';
|
|||||||
interface Props {
|
interface Props {
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
value: 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)}>
|
<div className={useStyles(getStyles)}>
|
||||||
{labelKey}={value}
|
{labelKey}={isRegex && '~'}
|
||||||
|
{value}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { useStyles } from '@grafana/ui';
|
import { useStyles } from '@grafana/ui';
|
||||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
|
import { SilenceState, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: PromAlertingRuleState;
|
status: PromAlertingRuleState | SilenceState | AlertState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateTag: FC<Props> = ({ children, status }) => {
|
export const StateTag: FC<Props> = ({ children, status }) => {
|
||||||
@@ -36,4 +37,20 @@ const getStyles = (theme: GrafanaTheme) => ({
|
|||||||
background-color: ${theme.palette.brandDanger};
|
background-color: ${theme.palette.brandDanger};
|
||||||
border: solid 1px ${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 { locationService } from '@grafana/runtime';
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { appEvents } from 'app/core/core';
|
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 { NotifierDTO, ThunkResult } from 'app/types';
|
||||||
import { RuleIdentifier, RuleNamespace, RuleWithLocation } from 'app/types/unified-alerting';
|
import { RuleIdentifier, RuleNamespace, RuleWithLocation } from 'app/types/unified-alerting';
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +12,13 @@ import {
|
|||||||
RulerRulesConfigDTO,
|
RulerRulesConfigDTO,
|
||||||
} from 'app/types/unified-alerting-dto';
|
} from 'app/types/unified-alerting-dto';
|
||||||
import { fetchNotifiers } from '../api/grafana';
|
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 { fetchRules } from '../api/prometheus';
|
||||||
import {
|
import {
|
||||||
deleteRulerRulesGroup,
|
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 { createAsyncMapSlice, createAsyncSlice } from '../utils/redux';
|
||||||
import {
|
import {
|
||||||
fetchAlertManagerConfigAction,
|
fetchAlertManagerConfigAction,
|
||||||
|
fetchAmAlertsAction,
|
||||||
fetchExistingRuleAction,
|
fetchExistingRuleAction,
|
||||||
fetchGrafanaNotifiersAction,
|
fetchGrafanaNotifiersAction,
|
||||||
fetchPromRulesAction,
|
fetchPromRulesAction,
|
||||||
@@ -27,6 +28,8 @@ export const reducer = combineReducers({
|
|||||||
}),
|
}),
|
||||||
grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer,
|
grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer,
|
||||||
saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer,
|
saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer,
|
||||||
|
amAlerts: createAsyncMapSlice('amAlerts', fetchAmAlertsAction, (alertManagerSourceName) => alertManagerSourceName)
|
||||||
|
.reducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UnifiedAlertingState = ReturnType<typeof 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_QUERY_KEY = 'alertmanager';
|
||||||
export const ALERTMANAGER_NAME_LOCAL_STORAGE_KEY = 'alerting-alertmanager';
|
export const ALERTMANAGER_NAME_LOCAL_STORAGE_KEY = 'alerting-alertmanager';
|
||||||
|
export const SILENCES_POLL_INTERVAL_MS = 20000;
|
||||||
|
|||||||
Reference in New Issue
Block a user