Files
Konrad Lalik 54f2c056f5 Alerting: Configure alert manager data source as an external AM (#52081)
Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
Co-authored-by: gotjosh <josue.abreu@gmail.com>
Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2022-08-01 10:20:43 +02:00

285 lines
9.3 KiB
TypeScript

import { css, cx } from '@emotion/css';
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import {
Alert,
Button,
ConfirmModal,
Field,
HorizontalGroup,
Icon,
RadioButtonGroup,
Tooltip,
useStyles2,
useTheme2,
} from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { loadDataSources } from 'app/features/datasources/state/actions';
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { StoreState } from 'app/types/store';
import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from '../../hooks/useExternalAmSelector';
import {
addExternalAlertmanagersAction,
fetchExternalAlertmanagersAction,
fetchExternalAlertmanagersConfigAction,
} from '../../state/actions';
import { AddAlertManagerModal } from './AddAlertManagerModal';
import { ExternalAlertmanagerDataSources } from './ExternalAlertmanagerDataSources';
const alertmanagerChoices: Array<SelectableValue<AlertmanagerChoice>> = [
{ value: AlertmanagerChoice.Internal, label: 'Only Internal' },
{ value: AlertmanagerChoice.External, label: 'Only External' },
{ value: AlertmanagerChoice.All, label: 'Both internal and external' },
];
export const ExternalAlertmanagers = () => {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
const externalAlertManagers = useExternalAmSelector();
const externalDsAlertManagers = useExternalDataSourceAlertmanagers();
const alertmanagersChoice = useSelector(
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
);
const theme = useTheme2();
useEffect(() => {
dispatch(fetchExternalAlertmanagersAction());
dispatch(fetchExternalAlertmanagersConfigAction());
dispatch(loadDataSources());
const interval = setInterval(() => dispatch(fetchExternalAlertmanagersAction()), 5000);
return () => {
clearInterval(interval);
};
}, [dispatch]);
const onDelete = useCallback(
(index: number) => {
// to delete we need to filter the alertmanager from the list and repost
const newList = (externalAlertManagers ?? [])
.filter((am, i) => i !== index)
.map((am) => {
return am.url;
});
dispatch(
addExternalAlertmanagersAction({
alertmanagers: newList,
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
})
);
setDeleteModalState({ open: false, index: 0 });
},
[externalAlertManagers, dispatch, alertmanagersChoice]
);
const onEdit = useCallback(() => {
const ams = externalAlertManagers ? [...externalAlertManagers] : [{ url: '' }];
setModalState((state) => ({
...state,
open: true,
payload: ams,
}));
}, [setModalState, externalAlertManagers]);
const onOpenModal = useCallback(() => {
setModalState((state) => {
const ams = externalAlertManagers ? [...externalAlertManagers, { url: '' }] : [{ url: '' }];
return {
...state,
open: true,
payload: ams,
};
});
}, [externalAlertManagers]);
const onCloseModal = useCallback(() => {
setModalState((state) => ({
...state,
open: false,
}));
}, [setModalState]);
const onChangeAlertmanagerChoice = (alertmanagersChoice: AlertmanagerChoice) => {
dispatch(
addExternalAlertmanagersAction({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice })
);
};
const onChangeAlertmanagers = (alertmanagers: string[]) => {
dispatch(
addExternalAlertmanagersAction({
alertmanagers,
alertmanagersChoice: alertmanagersChoice ?? AlertmanagerChoice.All,
})
);
};
const getStatusColor = (status: string) => {
switch (status) {
case 'active':
return theme.colors.success.main;
case 'pending':
return theme.colors.warning.main;
default:
return theme.colors.error.main;
}
};
const noAlertmanagers = externalAlertManagers?.length === 0;
const noDsAlertmanagers = externalDsAlertManagers?.length === 0;
const hasExternalAlertmanagers = !(noAlertmanagers && noDsAlertmanagers);
return (
<div>
<h4>External Alertmanagers</h4>
<Alert title="External Alertmanager changes" severity="info">
The way you configure external Alertmanagers has changed.
<br />
You can now use configured Alertmanager data sources as receivers of your Grafana-managed alerts.
<br />
For more information, refer to our documentation.
</Alert>
<ExternalAlertmanagerDataSources
alertmanagers={externalDsAlertManagers}
inactive={alertmanagersChoice === AlertmanagerChoice.Internal}
/>
{hasExternalAlertmanagers && (
<div className={styles.amChoice}>
<Field
label="Send alerts to"
description="Configures how the Grafana alert rule evaluation engine Alertmanager handles your alerts. Internal (Grafana built-in Alertmanager), External (All Alertmanagers configured above), or both."
>
<RadioButtonGroup
options={alertmanagerChoices}
value={alertmanagersChoice}
onChange={(value) => onChangeAlertmanagerChoice(value!)}
/>
</Field>
</div>
)}
<h5>Alertmanagers by URL</h5>
<Alert severity="warning" title="Deprecation Notice">
The URL-based configuration of Alertmanagers is deprecated and will be removed in Grafana 9.2.0.
<br />
Use Alertmanager data sources to configure your external Alertmanagers.
</Alert>
<div className={styles.muted}>
You can have your Grafana managed alerts be delivered to one or many external Alertmanager(s) in addition to the
internal Alertmanager by specifying their URLs below.
</div>
<div className={styles.actions}>
{!noAlertmanagers && (
<Button type="button" onClick={onOpenModal}>
Add Alertmanager
</Button>
)}
</div>
{noAlertmanagers ? (
<EmptyListCTA
title="You have not added any external alertmanagers"
onClick={onOpenModal}
buttonTitle="Add Alertmanager"
buttonIcon="bell-slash"
/>
) : (
<>
<table className={cx('filter-table form-inline filter-table--hover', styles.table)}>
<thead>
<tr>
<th>Url</th>
<th>Status</th>
<th style={{ width: '2%' }}>Action</th>
</tr>
</thead>
<tbody>
{externalAlertManagers?.map((am, index) => {
return (
<tr key={index}>
<td>
<span className={styles.url}>{am.url}</span>
{am.actualUrl ? (
<Tooltip content={`Discovered ${am.actualUrl} from ${am.url}`} theme="info">
<Icon name="info-circle" />
</Tooltip>
) : null}
</td>
<td>
<Icon name="heart" style={{ color: getStatusColor(am.status) }} title={am.status} />
</td>
<td>
<HorizontalGroup>
<Button variant="secondary" type="button" onClick={onEdit} aria-label="Edit alertmanager">
<Icon name="pen" />
</Button>
<Button
variant="destructive"
aria-label="Remove alertmanager"
type="button"
onClick={() => setDeleteModalState({ open: true, index })}
>
<Icon name="trash-alt" />
</Button>
</HorizontalGroup>
</td>
</tr>
);
})}
</tbody>
</table>
</>
)}
<ConfirmModal
isOpen={deleteModalState.open}
title="Remove Alertmanager"
body="Are you sure you want to remove this Alertmanager"
confirmText="Remove"
onConfirm={() => onDelete(deleteModalState.index)}
onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
/>
{modalState.open && (
<AddAlertManagerModal
onClose={onCloseModal}
alertmanagers={modalState.payload}
onChangeAlertmanagerConfig={onChangeAlertmanagers}
/>
)}
</div>
);
};
export const getStyles = (theme: GrafanaTheme2) => ({
url: css`
margin-right: ${theme.spacing(1)};
`,
muted: css`
color: ${theme.colors.text.secondary};
`,
actions: css`
margin-top: ${theme.spacing(2)};
display: flex;
justify-content: flex-end;
`,
table: css`
margin-bottom: ${theme.spacing(2)};
`,
amChoice: css`
margin-bottom: ${theme.spacing(4)};
`,
});