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