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,
|
TestReceiversAlert,
|
||||||
TestReceiversPayload,
|
TestReceiversPayload,
|
||||||
TestReceiversResult,
|
TestReceiversResult,
|
||||||
|
ExternalAlertmanagerConfig,
|
||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||||
@ -222,11 +223,11 @@ function getReceiverResultError(receiversResult: TestReceiversResult) {
|
|||||||
.join('; ');
|
.join('; ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addAlertManagers(alertManagers: string[]): Promise<void> {
|
export async function addAlertManagers(alertManagerConfig: ExternalAlertmanagerConfig): Promise<void> {
|
||||||
await lastValueFrom(
|
await lastValueFrom(
|
||||||
getBackendSrv().fetch({
|
getBackendSrv().fetch({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { alertmanagers: alertManagers },
|
data: alertManagerConfig,
|
||||||
url: '/api/v1/ngalert/admin_config',
|
url: '/api/v1/ngalert/admin_config',
|
||||||
showErrorAlert: false,
|
showErrorAlert: false,
|
||||||
showSuccessAlert: false,
|
showSuccessAlert: false,
|
||||||
@ -247,9 +248,9 @@ export async function fetchExternalAlertmanagers(): Promise<ExternalAlertmanager
|
|||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchExternalAlertmanagerConfig(): Promise<{ alertmanagers: string[] }> {
|
export async function fetchExternalAlertmanagerConfig(): Promise<ExternalAlertmanagerConfig> {
|
||||||
const result = await lastValueFrom(
|
const result = await lastValueFrom(
|
||||||
getBackendSrv().fetch<{ alertmanagers: string[] }>({
|
getBackendSrv().fetch<ExternalAlertmanagerConfig>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/v1/ngalert/admin_config',
|
url: '/api/v1/ngalert/admin_config',
|
||||||
showErrorAlert: false,
|
showErrorAlert: false,
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Button, Field, FieldArray, Form, Icon, Input, Modal, useStyles2 } from '@grafana/ui';
|
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';
|
import { AlertmanagerUrl } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
alertmanagers: AlertmanagerUrl[];
|
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 styles = useStyles2(getStyles);
|
||||||
const dispatch = useDispatch();
|
|
||||||
const defaultValues: Record<string, AlertmanagerUrl[]> = useMemo(
|
const defaultValues: Record<string, AlertmanagerUrl[]> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
alertmanagers: alertmanagers,
|
alertmanagers: alertmanagers,
|
||||||
@ -29,7 +27,7 @@ export const AddAlertManagerModal: FC<Props> = ({ alertmanagers, onClose }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = (values: Record<string, AlertmanagerUrl[]>) => {
|
const onSubmit = (values: Record<string, AlertmanagerUrl[]>) => {
|
||||||
dispatch(addExternalAlertmanagersAction(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url))));
|
onChangeAlertmanagerConfig(values.alertmanagers.map((am) => cleanAlertmanagerUrl(am.url)));
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
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 EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import { AddAlertManagerModal } from './AddAlertManagerModal';
|
import { AddAlertManagerModal } from './AddAlertManagerModal';
|
||||||
import {
|
import {
|
||||||
@ -11,13 +21,25 @@ import {
|
|||||||
fetchExternalAlertmanagersConfigAction,
|
fetchExternalAlertmanagersConfigAction,
|
||||||
} from '../../state/actions';
|
} from '../../state/actions';
|
||||||
import { useExternalAmSelector } from '../../hooks/useExternalAmSelector';
|
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 = () => {
|
export const ExternalAlertmanagers = () => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
|
const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
|
||||||
const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
|
const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
|
||||||
|
|
||||||
const externalAlertManagers = useExternalAmSelector();
|
const externalAlertManagers = useExternalAmSelector();
|
||||||
|
const alertmanagersChoice = useSelector(
|
||||||
|
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
|
||||||
|
);
|
||||||
|
const theme = useTheme2();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchExternalAlertmanagersAction());
|
dispatch(fetchExternalAlertmanagersAction());
|
||||||
@ -37,10 +59,12 @@ export const ExternalAlertmanagers = () => {
|
|||||||
.map((am) => {
|
.map((am) => {
|
||||||
return am.url;
|
return am.url;
|
||||||
});
|
});
|
||||||
dispatch(addExternalAlertmanagersAction(newList));
|
dispatch(
|
||||||
|
addExternalAlertmanagersAction({ alertmanagers: newList, alertmanagersChoice: alertmanagersChoice ?? 'all' })
|
||||||
|
);
|
||||||
setDeleteModalState({ open: false, index: 0 });
|
setDeleteModalState({ open: false, index: 0 });
|
||||||
},
|
},
|
||||||
[externalAlertManagers, dispatch]
|
[externalAlertManagers, dispatch, alertmanagersChoice]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEdit = useCallback(() => {
|
const onEdit = useCallback(() => {
|
||||||
@ -70,16 +94,26 @@ export const ExternalAlertmanagers = () => {
|
|||||||
}));
|
}));
|
||||||
}, [setModalState]);
|
}, [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) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'active':
|
case 'active':
|
||||||
return 'green';
|
return theme.colors.success.main;
|
||||||
|
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return 'yellow';
|
return theme.colors.warning.main;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 'red';
|
return theme.colors.error.main;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,49 +141,63 @@ export const ExternalAlertmanagers = () => {
|
|||||||
buttonIcon="bell-slash"
|
buttonIcon="bell-slash"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<table className="filter-table form-inline filter-table--hover">
|
<>
|
||||||
<thead>
|
<table className={cx('filter-table form-inline filter-table--hover', styles.table)}>
|
||||||
<tr>
|
<thead>
|
||||||
<th>Url</th>
|
<tr>
|
||||||
<th>Status</th>
|
<th>Url</th>
|
||||||
<th style={{ width: '2%' }}>Action</th>
|
<th>Status</th>
|
||||||
</tr>
|
<th style={{ width: '2%' }}>Action</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{externalAlertManagers?.map((am, index) => {
|
<tbody>
|
||||||
return (
|
{externalAlertManagers?.map((am, index) => {
|
||||||
<tr key={index}>
|
return (
|
||||||
<td>
|
<tr key={index}>
|
||||||
<span className={styles.url}>{am.url}</span>
|
<td>
|
||||||
{am.actualUrl ? (
|
<span className={styles.url}>{am.url}</span>
|
||||||
<Tooltip content={`Discovered ${am.actualUrl} from ${am.url}`} theme="info">
|
{am.actualUrl ? (
|
||||||
<Icon name="info-circle" />
|
<Tooltip content={`Discovered ${am.actualUrl} from ${am.url}`} theme="info">
|
||||||
</Tooltip>
|
<Icon name="info-circle" />
|
||||||
) : null}
|
</Tooltip>
|
||||||
</td>
|
) : null}
|
||||||
<td>
|
</td>
|
||||||
<Icon name="heart" style={{ color: getStatusColor(am.status) }} title={am.status} />
|
<td>
|
||||||
</td>
|
<Icon name="heart" style={{ color: getStatusColor(am.status) }} title={am.status} />
|
||||||
<td>
|
</td>
|
||||||
<HorizontalGroup>
|
<td>
|
||||||
<Button variant="secondary" type="button" onClick={onEdit} aria-label="Edit alertmanager">
|
<HorizontalGroup>
|
||||||
<Icon name="pen" />
|
<Button variant="secondary" type="button" onClick={onEdit} aria-label="Edit alertmanager">
|
||||||
</Button>
|
<Icon name="pen" />
|
||||||
<Button
|
</Button>
|
||||||
variant="destructive"
|
<Button
|
||||||
aria-label="Remove alertmanager"
|
variant="destructive"
|
||||||
type="button"
|
aria-label="Remove alertmanager"
|
||||||
onClick={() => setDeleteModalState({ open: true, index })}
|
type="button"
|
||||||
>
|
onClick={() => setDeleteModalState({ open: true, index })}
|
||||||
<Icon name="trash-alt" />
|
>
|
||||||
</Button>
|
<Icon name="trash-alt" />
|
||||||
</HorizontalGroup>
|
</Button>
|
||||||
</td>
|
</HorizontalGroup>
|
||||||
</tr>
|
</td>
|
||||||
);
|
</tr>
|
||||||
})}
|
);
|
||||||
</tbody>
|
})}
|
||||||
</table>
|
</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
|
<ConfirmModal
|
||||||
isOpen={deleteModalState.open}
|
isOpen={deleteModalState.open}
|
||||||
@ -159,7 +207,13 @@ export const ExternalAlertmanagers = () => {
|
|||||||
onConfirm={() => onDelete(deleteModalState.index)}
|
onConfirm={() => onDelete(deleteModalState.index)}
|
||||||
onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -176,5 +230,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
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;
|
const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i;
|
||||||
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
|
type AlertmanagerConfig = { url: string; status: string; actualUrl: string };
|
||||||
|
|
||||||
export function useExternalAmSelector(): AlertmanagerConfig[] | undefined {
|
export function useExternalAmSelector(): AlertmanagerConfig[] | [] {
|
||||||
const discoveredAlertmanagers = useSelector(
|
const discoveredAlertmanagers = useSelector(
|
||||||
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result?.data
|
(state: StoreState) => state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result?.data
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ import { getBackendSrv, locationService } from '@grafana/runtime';
|
|||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import {
|
import {
|
||||||
AlertmanagerAlert,
|
AlertmanagerAlert,
|
||||||
|
ExternalAlertmanagerConfig,
|
||||||
AlertManagerCortexConfig,
|
AlertManagerCortexConfig,
|
||||||
AlertmanagerGroup,
|
AlertmanagerGroup,
|
||||||
ExternalAlertmanagersResponse,
|
ExternalAlertmanagersResponse,
|
||||||
@ -122,7 +123,7 @@ export const fetchExternalAlertmanagersAction = createAsyncThunk(
|
|||||||
|
|
||||||
export const fetchExternalAlertmanagersConfigAction = createAsyncThunk(
|
export const fetchExternalAlertmanagersConfigAction = createAsyncThunk(
|
||||||
'unifiedAlerting/fetchExternAlertmanagersConfig',
|
'unifiedAlerting/fetchExternAlertmanagersConfig',
|
||||||
(): Promise<{ alertmanagers: string[] }> => {
|
(): Promise<ExternalAlertmanagerConfig> => {
|
||||||
return withSerializedError(fetchExternalAlertmanagerConfig());
|
return withSerializedError(fetchExternalAlertmanagerConfig());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -808,11 +809,11 @@ export const updateLotexNamespaceAndGroupAction = createAsyncThunk(
|
|||||||
|
|
||||||
export const addExternalAlertmanagersAction = createAsyncThunk(
|
export const addExternalAlertmanagersAction = createAsyncThunk(
|
||||||
'unifiedAlerting/addExternalAlertmanagers',
|
'unifiedAlerting/addExternalAlertmanagers',
|
||||||
async (alertManagerUrls: string[], thunkAPI): Promise<void> => {
|
async (alertmanagerConfig: ExternalAlertmanagerConfig, thunkAPI): Promise<void> => {
|
||||||
return withAppEvents(
|
return withAppEvents(
|
||||||
withSerializedError(
|
withSerializedError(
|
||||||
(async () => {
|
(async () => {
|
||||||
await addAlertManagers(alertManagerUrls);
|
await addAlertManagers(alertmanagerConfig);
|
||||||
thunkAPI.dispatch(fetchExternalAlertmanagersConfigAction());
|
thunkAPI.dispatch(fetchExternalAlertmanagersConfigAction());
|
||||||
})()
|
})()
|
||||||
),
|
),
|
||||||
|
@ -272,6 +272,12 @@ export interface ExternalAlertmanagersResponse {
|
|||||||
data: ExternalAlertmanagers;
|
data: ExternalAlertmanagers;
|
||||||
status: 'string';
|
status: 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExternalAlertmanagerConfig {
|
||||||
|
alertmanagers: string[];
|
||||||
|
alertmanagersChoice: string;
|
||||||
|
}
|
||||||
|
|
||||||
export enum AlertManagerImplementation {
|
export enum AlertManagerImplementation {
|
||||||
cortex = 'cortex',
|
cortex = 'cortex',
|
||||||
prometheus = 'prometheus',
|
prometheus = 'prometheus',
|
||||||
|
Loading…
Reference in New Issue
Block a user