ExternalAlertmanagers.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import { css, cx } from '@emotion/css';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import { useDispatch, useSelector } from 'react-redux';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import {
  6. Button,
  7. ConfirmModal,
  8. Field,
  9. HorizontalGroup,
  10. Icon,
  11. RadioButtonGroup,
  12. Tooltip,
  13. useStyles2,
  14. useTheme2,
  15. } from '@grafana/ui';
  16. import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
  17. import { StoreState } from 'app/types/store';
  18. import { useExternalAmSelector } from '../../hooks/useExternalAmSelector';
  19. import {
  20. addExternalAlertmanagersAction,
  21. fetchExternalAlertmanagersAction,
  22. fetchExternalAlertmanagersConfigAction,
  23. } from '../../state/actions';
  24. import { AddAlertManagerModal } from './AddAlertManagerModal';
  25. const alertmanagerChoices = [
  26. { value: 'internal', label: 'Only Internal' },
  27. { value: 'external', label: 'Only External' },
  28. { value: 'all', label: 'Both internal and external' },
  29. ];
  30. export const ExternalAlertmanagers = () => {
  31. const styles = useStyles2(getStyles);
  32. const dispatch = useDispatch();
  33. const [modalState, setModalState] = useState({ open: false, payload: [{ url: '' }] });
  34. const [deleteModalState, setDeleteModalState] = useState({ open: false, index: 0 });
  35. const externalAlertManagers = useExternalAmSelector();
  36. const alertmanagersChoice = useSelector(
  37. (state: StoreState) => state.unifiedAlerting.externalAlertmanagers.alertmanagerConfig.result?.alertmanagersChoice
  38. );
  39. const theme = useTheme2();
  40. useEffect(() => {
  41. dispatch(fetchExternalAlertmanagersAction());
  42. dispatch(fetchExternalAlertmanagersConfigAction());
  43. const interval = setInterval(() => dispatch(fetchExternalAlertmanagersAction()), 5000);
  44. return () => {
  45. clearInterval(interval);
  46. };
  47. }, [dispatch]);
  48. const onDelete = useCallback(
  49. (index: number) => {
  50. // to delete we need to filter the alertmanager from the list and repost
  51. const newList = (externalAlertManagers ?? [])
  52. .filter((am, i) => i !== index)
  53. .map((am) => {
  54. return am.url;
  55. });
  56. dispatch(
  57. addExternalAlertmanagersAction({ alertmanagers: newList, alertmanagersChoice: alertmanagersChoice ?? 'all' })
  58. );
  59. setDeleteModalState({ open: false, index: 0 });
  60. },
  61. [externalAlertManagers, dispatch, alertmanagersChoice]
  62. );
  63. const onEdit = useCallback(() => {
  64. const ams = externalAlertManagers ? [...externalAlertManagers] : [{ url: '' }];
  65. setModalState((state) => ({
  66. ...state,
  67. open: true,
  68. payload: ams,
  69. }));
  70. }, [setModalState, externalAlertManagers]);
  71. const onOpenModal = useCallback(() => {
  72. setModalState((state) => {
  73. const ams = externalAlertManagers ? [...externalAlertManagers, { url: '' }] : [{ url: '' }];
  74. return {
  75. ...state,
  76. open: true,
  77. payload: ams,
  78. };
  79. });
  80. }, [externalAlertManagers]);
  81. const onCloseModal = useCallback(() => {
  82. setModalState((state) => ({
  83. ...state,
  84. open: false,
  85. }));
  86. }, [setModalState]);
  87. const onChangeAlertmanagerChoice = (alertmanagersChoice: string) => {
  88. dispatch(
  89. addExternalAlertmanagersAction({ alertmanagers: externalAlertManagers.map((am) => am.url), alertmanagersChoice })
  90. );
  91. };
  92. const onChangeAlertmanagers = (alertmanagers: string[]) => {
  93. dispatch(addExternalAlertmanagersAction({ alertmanagers, alertmanagersChoice: alertmanagersChoice ?? 'all' }));
  94. };
  95. const getStatusColor = (status: string) => {
  96. switch (status) {
  97. case 'active':
  98. return theme.colors.success.main;
  99. case 'pending':
  100. return theme.colors.warning.main;
  101. default:
  102. return theme.colors.error.main;
  103. }
  104. };
  105. const noAlertmanagers = externalAlertManagers?.length === 0;
  106. return (
  107. <div>
  108. <h4>External Alertmanagers</h4>
  109. <div className={styles.muted}>
  110. You can have your Grafana managed alerts be delivered to one or many external Alertmanager(s) in addition to the
  111. internal Alertmanager by specifying their URLs below.
  112. </div>
  113. <div className={styles.actions}>
  114. {!noAlertmanagers && (
  115. <Button type="button" onClick={onOpenModal}>
  116. Add Alertmanager
  117. </Button>
  118. )}
  119. </div>
  120. {noAlertmanagers ? (
  121. <EmptyListCTA
  122. title="You have not added any external alertmanagers"
  123. onClick={onOpenModal}
  124. buttonTitle="Add Alertmanager"
  125. buttonIcon="bell-slash"
  126. />
  127. ) : (
  128. <>
  129. <table className={cx('filter-table form-inline filter-table--hover', styles.table)}>
  130. <thead>
  131. <tr>
  132. <th>Url</th>
  133. <th>Status</th>
  134. <th style={{ width: '2%' }}>Action</th>
  135. </tr>
  136. </thead>
  137. <tbody>
  138. {externalAlertManagers?.map((am, index) => {
  139. return (
  140. <tr key={index}>
  141. <td>
  142. <span className={styles.url}>{am.url}</span>
  143. {am.actualUrl ? (
  144. <Tooltip content={`Discovered ${am.actualUrl} from ${am.url}`} theme="info">
  145. <Icon name="info-circle" />
  146. </Tooltip>
  147. ) : null}
  148. </td>
  149. <td>
  150. <Icon name="heart" style={{ color: getStatusColor(am.status) }} title={am.status} />
  151. </td>
  152. <td>
  153. <HorizontalGroup>
  154. <Button variant="secondary" type="button" onClick={onEdit} aria-label="Edit alertmanager">
  155. <Icon name="pen" />
  156. </Button>
  157. <Button
  158. variant="destructive"
  159. aria-label="Remove alertmanager"
  160. type="button"
  161. onClick={() => setDeleteModalState({ open: true, index })}
  162. >
  163. <Icon name="trash-alt" />
  164. </Button>
  165. </HorizontalGroup>
  166. </td>
  167. </tr>
  168. );
  169. })}
  170. </tbody>
  171. </table>
  172. <div>
  173. <Field
  174. label="Send alerts to"
  175. description="Sets which Alertmanager will handle your alerts. Internal (Grafana built in Alertmanager), External (All Alertmanagers configured above), or both."
  176. >
  177. <RadioButtonGroup
  178. options={alertmanagerChoices}
  179. value={alertmanagersChoice}
  180. onChange={(value) => onChangeAlertmanagerChoice(value!)}
  181. />
  182. </Field>
  183. </div>
  184. </>
  185. )}
  186. <ConfirmModal
  187. isOpen={deleteModalState.open}
  188. title="Remove Alertmanager"
  189. body="Are you sure you want to remove this Alertmanager"
  190. confirmText="Remove"
  191. onConfirm={() => onDelete(deleteModalState.index)}
  192. onDismiss={() => setDeleteModalState({ open: false, index: 0 })}
  193. />
  194. {modalState.open && (
  195. <AddAlertManagerModal
  196. onClose={onCloseModal}
  197. alertmanagers={modalState.payload}
  198. onChangeAlertmanagerConfig={onChangeAlertmanagers}
  199. />
  200. )}
  201. </div>
  202. );
  203. };
  204. const getStyles = (theme: GrafanaTheme2) => ({
  205. url: css`
  206. margin-right: ${theme.spacing(1)};
  207. `,
  208. muted: css`
  209. color: ${theme.colors.text.secondary};
  210. `,
  211. actions: css`
  212. margin-top: ${theme.spacing(2)};
  213. display: flex;
  214. justify-content: flex-end;
  215. `,
  216. table: css`
  217. margin-bottom: ${theme.spacing(2)};
  218. `,
  219. });