Files
grafana/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx
Nathan Rodman 49505b9a3b Alerting: fgac for notification policies and contact points (#46939)
* add FGAC actions for silences table

* redirect users without permissions

* add permissions checks to routes

* add fgac to notifications and contact points

* fgac for notification policies

* fix mute timing authorization

* use consistent naming for checking grafana alertmanager

* tests for fgac in contact points and notification policies

* bump up timeout on rule editor test

* use new permissions util

* break out route evaluation into util

* Remove test timeout

* Change permissions for the alert-notifiers endpoint

* Use signed in handler for alert-notifiers when unified alerting enabled

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2022-04-06 18:24:33 +02:00

184 lines
6.9 KiB
TypeScript

import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui';
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
import React, { FC, useMemo, useState } from 'react';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { getAlertTableStyles } from '../../styles/table';
import { extractNotifierTypeCounts } from '../../utils/receivers';
import { ActionIcon } from '../rules/ActionIcon';
import { ReceiversSection } from './ReceiversSection';
import { makeAMLink } from '../../utils/misc';
import { GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
import { isReceiverUsed } from '../../utils/alertmanager';
import { useDispatch } from 'react-redux';
import { deleteReceiverAction } from '../../state/actions';
import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource';
import { Authorize } from '../../components/Authorize';
import { contextSrv } from 'app/core/services/context_srv';
import { getNotificationsPermissions } from '../../utils/access-control';
interface Props {
config: AlertManagerCortexConfig;
alertManagerName: string;
}
export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
const dispatch = useDispatch();
const tableStyles = useStyles2(getAlertTableStyles);
const styles = useStyles2(getStyles);
const isVanillaAM = isVanillaPrometheusAlertManagerDataSource(alertManagerName);
const permissions = getNotificationsPermissions(alertManagerName);
const grafanaNotifiers = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
// receiver name slated for deletion. If this is set, a confirmation modal is shown. If user approves, this receiver is deleted
const [receiverToDelete, setReceiverToDelete] = useState<string>();
const [showCannotDeleteReceiverModal, setShowCannotDeleteReceiverModal] = useState(false);
const onClickDeleteReceiver = (receiverName: string): void => {
if (isReceiverUsed(receiverName, config)) {
setShowCannotDeleteReceiverModal(true);
} else {
setReceiverToDelete(receiverName);
}
};
const deleteReceiver = () => {
if (receiverToDelete) {
dispatch(deleteReceiverAction(receiverToDelete, alertManagerName));
}
setReceiverToDelete(undefined);
};
const rows = useMemo(
() =>
config.alertmanager_config.receivers?.map((receiver) => ({
name: receiver.name,
types: Object.entries(extractNotifierTypeCounts(receiver, grafanaNotifiers.result ?? [])).map(
([type, count]) => {
if (count > 1) {
return `${type} (${count})`;
}
return type;
}
),
})) ?? [],
[config, grafanaNotifiers.result]
);
return (
<ReceiversSection
className={styles.section}
title="Contact points"
description="Define where the notifications will be sent to, for example email or Slack."
showButton={!isVanillaAM && contextSrv.hasPermission(permissions.create)}
addButtonLabel="New contact point"
addButtonTo={makeAMLink('/alerting/notifications/receivers/new', alertManagerName)}
>
<table className={tableStyles.table} data-testid="receivers-table">
<colgroup>
<col />
<col />
<Authorize actions={[permissions.update, permissions.delete]}>
<col />
</Authorize>
</colgroup>
<thead>
<tr>
<th>Contact point name</th>
<th>Type</th>
<Authorize actions={[permissions.update, permissions.delete]}>
<th>Actions</th>
</Authorize>
</tr>
</thead>
<tbody>
{!rows.length && (
<tr className={tableStyles.evenRow}>
<td colSpan={3}>No receivers defined.</td>
</tr>
)}
{rows.map((receiver, idx) => (
<tr key={receiver.name} className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
<td>{receiver.name}</td>
<td>{receiver.types.join(', ')}</td>
<Authorize actions={[permissions.update, permissions.delete]}>
<td className={tableStyles.actionsCell}>
{!isVanillaAM && (
<>
<Authorize actions={[permissions.update]}>
<ActionIcon
aria-label="Edit"
data-testid="edit"
to={makeAMLink(
`/alerting/notifications/receivers/${encodeURIComponent(receiver.name)}/edit`,
alertManagerName
)}
tooltip="Edit contact point"
icon="pen"
/>
</Authorize>
<Authorize actions={[permissions.delete]}>
<ActionIcon
onClick={() => onClickDeleteReceiver(receiver.name)}
tooltip="Delete contact point"
icon="trash-alt"
/>
</Authorize>
</>
)}
{isVanillaAM && (
<Authorize actions={[permissions.update]}>
<ActionIcon
data-testid="view"
to={makeAMLink(
`/alerting/notifications/receivers/${encodeURIComponent(receiver.name)}/edit`,
alertManagerName
)}
tooltip="View contact point"
icon="file-alt"
/>
</Authorize>
)}
</td>
</Authorize>
</tr>
))}
</tbody>
</table>
{!!showCannotDeleteReceiverModal && (
<Modal
isOpen={true}
title="Cannot delete contact point"
onDismiss={() => setShowCannotDeleteReceiverModal(false)}
>
<p>
Contact point cannot be deleted because it is used in more policies. Please update or delete these policies
first.
</p>
<Modal.ButtonRow>
<Button variant="secondary" onClick={() => setShowCannotDeleteReceiverModal(false)} fill="outline">
Close
</Button>
</Modal.ButtonRow>
</Modal>
)}
{!!receiverToDelete && (
<ConfirmModal
isOpen={true}
title="Delete contact point"
body={`Are you sure you want to delete contact point "${receiverToDelete}"?`}
confirmText="Yes, delete"
onConfirm={deleteReceiver}
onDismiss={() => setReceiverToDelete(undefined)}
/>
)}
</ReceiversSection>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
section: css`
margin-top: ${theme.spacing(4)};
`,
});