ReceiversTable.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { css } from '@emotion/css';
  2. import React, { FC, useMemo, useState } from 'react';
  3. import { useDispatch } from 'react-redux';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui';
  6. import { contextSrv } from 'app/core/services/context_srv';
  7. import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
  8. import { Authorize } from '../../components/Authorize';
  9. import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
  10. import { deleteReceiverAction } from '../../state/actions';
  11. import { getAlertTableStyles } from '../../styles/table';
  12. import { getNotificationsPermissions } from '../../utils/access-control';
  13. import { isReceiverUsed } from '../../utils/alertmanager';
  14. import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
  15. import { makeAMLink } from '../../utils/misc';
  16. import { extractNotifierTypeCounts } from '../../utils/receivers';
  17. import { ActionIcon } from '../rules/ActionIcon';
  18. import { ReceiversSection } from './ReceiversSection';
  19. interface Props {
  20. config: AlertManagerCortexConfig;
  21. alertManagerName: string;
  22. }
  23. export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
  24. const dispatch = useDispatch();
  25. const tableStyles = useStyles2(getAlertTableStyles);
  26. const styles = useStyles2(getStyles);
  27. const isVanillaAM = isVanillaPrometheusAlertManagerDataSource(alertManagerName);
  28. const permissions = getNotificationsPermissions(alertManagerName);
  29. const grafanaNotifiers = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
  30. // receiver name slated for deletion. If this is set, a confirmation modal is shown. If user approves, this receiver is deleted
  31. const [receiverToDelete, setReceiverToDelete] = useState<string>();
  32. const [showCannotDeleteReceiverModal, setShowCannotDeleteReceiverModal] = useState(false);
  33. const onClickDeleteReceiver = (receiverName: string): void => {
  34. if (isReceiverUsed(receiverName, config)) {
  35. setShowCannotDeleteReceiverModal(true);
  36. } else {
  37. setReceiverToDelete(receiverName);
  38. }
  39. };
  40. const deleteReceiver = () => {
  41. if (receiverToDelete) {
  42. dispatch(deleteReceiverAction(receiverToDelete, alertManagerName));
  43. }
  44. setReceiverToDelete(undefined);
  45. };
  46. const rows = useMemo(
  47. () =>
  48. config.alertmanager_config.receivers?.map((receiver) => ({
  49. name: receiver.name,
  50. types: Object.entries(extractNotifierTypeCounts(receiver, grafanaNotifiers.result ?? [])).map(
  51. ([type, count]) => {
  52. if (count > 1) {
  53. return `${type} (${count})`;
  54. }
  55. return type;
  56. }
  57. ),
  58. })) ?? [],
  59. [config, grafanaNotifiers.result]
  60. );
  61. return (
  62. <ReceiversSection
  63. className={styles.section}
  64. title="Contact points"
  65. description="Define where the notifications will be sent to, for example email or Slack."
  66. showButton={!isVanillaAM && contextSrv.hasPermission(permissions.create)}
  67. addButtonLabel="New contact point"
  68. addButtonTo={makeAMLink('/alerting/notifications/receivers/new', alertManagerName)}
  69. >
  70. <table className={tableStyles.table} data-testid="receivers-table">
  71. <colgroup>
  72. <col />
  73. <col />
  74. <Authorize actions={[permissions.update, permissions.delete]}>
  75. <col />
  76. </Authorize>
  77. </colgroup>
  78. <thead>
  79. <tr>
  80. <th>Contact point name</th>
  81. <th>Type</th>
  82. <Authorize actions={[permissions.update, permissions.delete]}>
  83. <th>Actions</th>
  84. </Authorize>
  85. </tr>
  86. </thead>
  87. <tbody>
  88. {!rows.length && (
  89. <tr className={tableStyles.evenRow}>
  90. <td colSpan={3}>No receivers defined.</td>
  91. </tr>
  92. )}
  93. {rows.map((receiver, idx) => (
  94. <tr key={receiver.name} className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
  95. <td>{receiver.name}</td>
  96. <td>{receiver.types.join(', ')}</td>
  97. <Authorize actions={[permissions.update, permissions.delete]}>
  98. <td className={tableStyles.actionsCell}>
  99. {!isVanillaAM && (
  100. <>
  101. <Authorize actions={[permissions.update]}>
  102. <ActionIcon
  103. aria-label="Edit"
  104. data-testid="edit"
  105. to={makeAMLink(
  106. `/alerting/notifications/receivers/${encodeURIComponent(receiver.name)}/edit`,
  107. alertManagerName
  108. )}
  109. tooltip="Edit contact point"
  110. icon="pen"
  111. />
  112. </Authorize>
  113. <Authorize actions={[permissions.delete]}>
  114. <ActionIcon
  115. onClick={() => onClickDeleteReceiver(receiver.name)}
  116. tooltip="Delete contact point"
  117. icon="trash-alt"
  118. />
  119. </Authorize>
  120. </>
  121. )}
  122. {isVanillaAM && (
  123. <Authorize actions={[permissions.update]}>
  124. <ActionIcon
  125. data-testid="view"
  126. to={makeAMLink(
  127. `/alerting/notifications/receivers/${encodeURIComponent(receiver.name)}/edit`,
  128. alertManagerName
  129. )}
  130. tooltip="View contact point"
  131. icon="file-alt"
  132. />
  133. </Authorize>
  134. )}
  135. </td>
  136. </Authorize>
  137. </tr>
  138. ))}
  139. </tbody>
  140. </table>
  141. {!!showCannotDeleteReceiverModal && (
  142. <Modal
  143. isOpen={true}
  144. title="Cannot delete contact point"
  145. onDismiss={() => setShowCannotDeleteReceiverModal(false)}
  146. >
  147. <p>
  148. Contact point cannot be deleted because it is used in more policies. Please update or delete these policies
  149. first.
  150. </p>
  151. <Modal.ButtonRow>
  152. <Button variant="secondary" onClick={() => setShowCannotDeleteReceiverModal(false)} fill="outline">
  153. Close
  154. </Button>
  155. </Modal.ButtonRow>
  156. </Modal>
  157. )}
  158. {!!receiverToDelete && (
  159. <ConfirmModal
  160. isOpen={true}
  161. title="Delete contact point"
  162. body={`Are you sure you want to delete contact point "${receiverToDelete}"?`}
  163. confirmText="Yes, delete"
  164. onConfirm={deleteReceiver}
  165. onDismiss={() => setReceiverToDelete(undefined)}
  166. />
  167. )}
  168. </ReceiversSection>
  169. );
  170. };
  171. const getStyles = (theme: GrafanaTheme2) => ({
  172. section: css`
  173. margin-top: ${theme.spacing(4)};
  174. `,
  175. });