mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add choice to external alertmanagers (#45157)
* implement alertmanagersChoice * return empty array and remove non null assertion
This commit is contained in:
parent
52d4e2ddcc
commit
0502a84922
@ -13,6 +13,7 @@ import {
|
||||
TestReceiversAlert,
|
||||
TestReceiversPayload,
|
||||
TestReceiversResult,
|
||||
ExternalAlertmanagerConfig,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||
@ -222,11 +223,11 @@ function getReceiverResultError(receiversResult: TestReceiversResult) {
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
export async function addAlertManagers(alertManagers: string[]): Promise<void> {
|
||||
export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise<void> {
|
||||
await lastValueFrom(
|
||||
getBackendSrv().fetch({
|
||||
method: 'POST',
|
||||
data: { alertmanagers: alertManagers },
|
||||
data: alertManagerConfig,
|
||||
url: '/api/v1/ngalert/admin_config',
|
||||
showErrorAlert: false,
|
||||
showSuccessAlert: false,
|
||||
@ -247,9 +248,9 @@ export async function fetchExternalAlertmanagers(): Promise<ExternalAlertmanager
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function fetchExternalAlertmanagerConfig(): Promise<{ alertmanagers: string[] }> {
|
||||
export async function fetchExternalAlertmanagerConfig(): Promise<ExternalAlertmanagerConfig> {
|
||||
const result = await lastValueFrom(
|
||||
getBackendSrv().fetch<{ alertmanagers: string[] }>({
|
||||
getBackendSrv().fetch<ExternalAlertmanagerConfig>({
|
||||
method: 'GET',
|
||||
url: '/api/v1/ngalert/admin_config',
|
||||
showErrorAlert: false,
|
||||
|
@ -1,19 +1,17 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, Field, FieldArray, Form, Icon, Input, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { addExternalAlertmanagersAction } from '../../state/actions';
|
||||
import { AlertmanagerUrl } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
alertmanagers: AlertmanagerUrl[];
|
||||
onChangeAlertmanagerConfig: (alertmanagers: string[]) => void;
|
||||
}
|
||||
|
||||
export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onClose }) => {
|
||||
export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onChangeAlertmanagerConfig, onClose }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const dispatch = useDispatch();
|
||||
const defaultValues: Record<string, AlertmanagerUrl[]> = useMemo(
|
||||
() => ({
|
||||
alertmanagers: alertmanagers,
|
||||
@ -29,7 +27,7 @@ export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onClose }) => {
|
||||
);
|
||||
|
||||
const onSubmit = (values: Record<string, AlertmanagerUrl[]>) => {
|
||||
dispatch(addExternalAlertmanagersAction(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url))));
|
||||
onChangeAlertmanagerConfig(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url)));
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,18 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { css } from '@emotion/css';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, ConfirmModal, HorizontalGroup, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import {
|
||||
Button,
|
||||
ConfirmModal,
|
||||
Field,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
RadioButtonGroup,
|
||||
Tooltip,
|
||||
useStyles2,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { AddAlertManagerModal } from './AddAlertManagerModal';
|
||||
import {
|
||||
@ -11,13 +21,25 @@ import {
|
||||
fetchExternalAlertmanagersConfigAction,
|
||||
} from '../../state/actions';
|
||||
import { useExternalAmSelector } from '../../hooks/useExternalAmSelector';
|
||||
import { StoreState } from 'app/types/store';
|
||||
|
||||
const alertmanagerChoices = [
|
||||
{ value: 'internal', label: 'Only Internal' },
|
||||
{ value: 'external', label: 'Only External' },
|
||||
{ value: '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 alertmanagersChoice = useSelector(
|
||||
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
|
||||
);
|
||||
const theme = useTheme2();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchExternalAlertmanagersAction());
|
||||
@ -37,10 +59,12 @@ export const ExternalAlertmanagers = () => {
|
||||
.map((am) => {
|
||||
return am.url;
|
||||
});
|
||||
dispatch(addExternalAlertmanagersAction(newList));
|
||||
dispatch(
|
||||
addExternalAlertmanagersAction({ alertmanagers: newList, alertmanagersChoice: alertmanagersChoice ?? 'all' })
|
||||
);
|
||||
setDeleteModalState({ open: false, index: 0 });
|
||||
},
|
||||
[externalAlertManagers, dispatch]
|
||||
[externalAlertManagers, dispatch, alertmanagersChoice]
|
||||
);
|
||||
|
||||
const onEdit = useCallback(() => {
|
||||
@ -70,16 +94,26 @@ export const ExternalAlertmanagers = () => {
|
||||
}));
|
||||
}, [setModalState]);
|
||||
|
||||
const onChangeAlertmanagerChoice = (alertmanagersChoice: string) => {
|
||||
dispatch(
|
||||
addExternalAlertmanagersAction({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice })
|
||||
);
|
||||
};
|
||||
|
||||
const onChangeAlertmanagers = (alertmanagers: string[]) => {
|
||||
dispatch(addExternalAlertmanagersAction({ alertmanagers, alertmanagersChoice: alertmanagersChoice ?? 'all' }));
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'green';
|
||||
return theme.colors.success.main;
|
||||
|
||||
case 'pending':
|
||||
return 'yellow';
|
||||
return theme.colors.warning.main;
|
||||
|
||||
default:
|
||||
return 'red';
|
||||
return theme.colors.error.main;
|
||||
}
|
||||
};
|
||||
|
||||
@ -107,49 +141,63 @@ export const ExternalAlertmanagers = () => {
|
||||
buttonIcon="bell-slash"
|
||||
/>
|
||||
) : (
|
||||
<table className="filter-table form-inline filter-table--hover">
|
||||
<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>
|
||||
<>
|
||||
<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>
|
||||
<div>
|
||||
<Field
|
||||
label="Send alerts to"
|
||||
description="Sets which Alertmanager will handle 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>
|
||||
</>
|
||||
)}
|
||||
<ConfirmModal
|
||||
isOpen={deleteModalState.open}
|
||||
@ -159,7 +207,13 @@ export const ExternalAlertmanagers = () => {
|
||||
onConfirm={() => onDelete(deleteModalState.index)}
|
||||
onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
|
||||
/>
|
||||
{modalState.open && <AddAlertManagerModal onClose={onCloseModal} alertmanagers={modalState.payload} />}
|
||||
{modalState.open && (
|
||||
<AddAlertManagerModal
|
||||
onClose={onCloseModal}
|
||||
alertmanagers={modalState.payload}
|
||||
onChangeAlertmanagerConfig={onChangeAlertmanagers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -176,5 +230,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`,
|
||||
table: css``,
|
||||
table: css`
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import { StoreState } from '../../../../types';
|
||||
const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i;
|
||||
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
|
||||
|
||||
export function useExternalAmSelector(): AlertmanagerConfig[] | undefined {
|
||||
export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
|
||||
const discoveredAlertmanagers = useSelector(
|
||||
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result?.data
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import {
|
||||
AlertmanagerAlert,
|
||||
ExternalAlertmanagerConfig,
|
||||
AlertManagerCortexConfig,
|
||||
AlertmanagerGroup,
|
||||
ExternalAlertmanagersResponse,
|
||||
@ -122,7 +123,7 @@ export const fetchExternalAlertmanagersAction = createAsyncThunk(
|
||||
|
||||
export const fetchExternalAlertmanagersConfigAction = createAsyncThunk(
|
||||
'unifiedAlerting/fetchExternAlertmanagersConfig',
|
||||
(): Promise<{ alertmanagers: string[] }> => {
|
||||
(): Promise<ExternalAlertmanagerConfig> => {
|
||||
return withSerializedError(fetchExternalAlertmanagerConfig());
|
||||
}
|
||||
);
|
||||
@ -808,11 +809,11 @@ export const updateLotexNamespaceAndGroupAction = createAsyncThunk(
|
||||
|
||||
export const addExternalAlertmanagersAction = createAsyncThunk(
|
||||
'unifiedAlerting/addExternalAlertmanagers',
|
||||
async (alertManagerUrls: string[], thunkAPI): Promise<void> => {
|
||||
async (alertmanagerConfig: ExternalAlertmanagerConfig, thunkAPI): Promise<void> => {
|
||||
return withAppEvents(
|
||||
withSerializedError(
|
||||
(async () => {
|
||||
await addAlertManagers(alertManagerUrls);
|
||||
await addAlertManagers(alertmanagerConfig);
|
||||
thunkAPI.dispatch(fetchExternalAlertmanagersConfigAction());
|
||||
})()
|
||||
),
|
||||
|
@ -272,6 +272,12 @@ export interface ExternalAlertmanagersResponse {
|
||||
data: ExternalAlertmanagers;
|
||||
status: 'string';
|
||||
}
|
||||
|
||||
export interface ExternalAlertmanagerConfig {
|
||||
alertmanagers: string[];
|
||||
alertmanagersChoice: string;
|
||||
}
|
||||
|
||||
export enum AlertManagerImplementation {
|
||||
cortex = 'cortex',
|
||||
prometheus = 'prometheus',
|
||||
|
Loading…
Reference in New Issue
Block a user