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