123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import { css } from '@emotion/css';
- import React, { FC, useMemo } from 'react';
- import { useDispatch } from 'react-redux';
- import { GrafanaTheme2, dateMath } from '@grafana/data';
- import { Stack } from '@grafana/experimental';
- import { Icon, useStyles2, Link, Button } from '@grafana/ui';
- import { useQueryParams } from 'app/core/hooks/useQueryParams';
- import { contextSrv } from 'app/core/services/context_srv';
- import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';
- import { expireSilenceAction } from '../../state/actions';
- import { getInstancesPermissions } from '../../utils/access-control';
- import { parseMatchers } from '../../utils/alertmanager';
- import { getSilenceFiltersFromUrlParams, makeAMLink } from '../../utils/misc';
- import { Authorize } from '../Authorize';
- import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
- import { ActionButton } from '../rules/ActionButton';
- import { ActionIcon } from '../rules/ActionIcon';
- import { Matchers } from './Matchers';
- import { NoSilencesSplash } from './NoSilencesCTA';
- import { SilenceDetails } from './SilenceDetails';
- import { SilenceStateTag } from './SilenceStateTag';
- import { SilencesFilter } from './SilencesFilter';
- export interface SilenceTableItem extends Silence {
- silencedAlerts: AlertmanagerAlert[];
- }
- type SilenceTableColumnProps = DynamicTableColumnProps<SilenceTableItem>;
- type SilenceTableItemProps = DynamicTableItemProps<SilenceTableItem>;
- interface Props {
- silences: Silence[];
- alertManagerAlerts: AlertmanagerAlert[];
- alertManagerSourceName: string;
- }
- const SilencesTable: FC<Props> = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
- const styles = useStyles2(getStyles);
- const [queryParams] = useQueryParams();
- const filteredSilences = useFilteredSilences(silences);
- const permissions = getInstancesPermissions(alertManagerSourceName);
- const { silenceState } = getSilenceFiltersFromUrlParams(queryParams);
- const showExpiredSilencesBanner =
- !!filteredSilences.length && (silenceState === undefined || silenceState === SilenceState.Expired);
- const columns = useColumns(alertManagerSourceName);
- const items = useMemo((): SilenceTableItemProps[] => {
- const findSilencedAlerts = (id: string) => {
- return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
- };
- return filteredSilences.map((silence) => {
- const silencedAlerts = findSilencedAlerts(silence.id);
- return {
- id: silence.id,
- data: { ...silence, silencedAlerts },
- };
- });
- }, [filteredSilences, alertManagerAlerts]);
- return (
- <div data-testid="silences-table">
- {!!silences.length && (
- <>
- <SilencesFilter />
- <Authorize actions={[permissions.create]} fallback={contextSrv.isEditor}>
- <div className={styles.topButtonContainer}>
- <Link href={makeAMLink('/alerting/silence/new', alertManagerSourceName)}>
- <Button className={styles.addNewSilence} icon="plus">
- New Silence
- </Button>
- </Link>
- </div>
- </Authorize>
- {!!items.length ? (
- <>
- <DynamicTable
- items={items}
- cols={columns}
- isExpandable
- renderExpandedContent={({ data }) => <SilenceDetails silence={data} />}
- />
- {showExpiredSilencesBanner && (
- <div className={styles.callout}>
- <Icon className={styles.calloutIcon} name="info-circle" />
- <span>Expired silences are automatically deleted after 5 days.</span>
- </div>
- )}
- </>
- ) : (
- 'No matching silences found'
- )}
- </>
- )}
- {!silences.length && <NoSilencesSplash alertManagerSourceName={alertManagerSourceName} />}
- </div>
- );
- };
- const useFilteredSilences = (silences: Silence[]) => {
- const [queryParams] = useQueryParams();
- return useMemo(() => {
- const { queryString, silenceState } = getSilenceFiltersFromUrlParams(queryParams);
- const silenceIdsString = queryParams?.silenceIds;
- return silences.filter((silence) => {
- if (typeof silenceIdsString === 'string') {
- const idsIncluded = silenceIdsString.split(',').includes(silence.id);
- if (!idsIncluded) {
- return false;
- }
- }
- if (queryString) {
- const matchers = parseMatchers(queryString);
- const matchersMatch = matchers.every((matcher) =>
- silence.matchers?.some(
- ({ name, value, isEqual, isRegex }) =>
- matcher.name === name &&
- matcher.value === value &&
- matcher.isEqual === isEqual &&
- matcher.isRegex === isRegex
- )
- );
- if (!matchersMatch) {
- return false;
- }
- }
- if (silenceState) {
- const stateMatches = silence.status.state === silenceState;
- if (!stateMatches) {
- return false;
- }
- }
- return true;
- });
- }, [queryParams, silences]);
- };
- const getStyles = (theme: GrafanaTheme2) => ({
- topButtonContainer: css`
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
- `,
- addNewSilence: css`
- margin: ${theme.spacing(2, 0)};
- `,
- callout: css`
- background-color: ${theme.colors.background.secondary};
- border-top: 3px solid ${theme.colors.info.border};
- border-radius: 2px;
- height: 62px;
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-top: ${theme.spacing(2)};
- & > * {
- margin-left: ${theme.spacing(1)};
- }
- `,
- calloutIcon: css`
- color: ${theme.colors.info.text};
- `,
- editButton: css`
- margin-left: ${theme.spacing(0.5)};
- `,
- });
- function useColumns(alertManagerSourceName: string) {
- const dispatch = useDispatch();
- const styles = useStyles2(getStyles);
- const permissions = getInstancesPermissions(alertManagerSourceName);
- return useMemo((): SilenceTableColumnProps[] => {
- const handleExpireSilenceClick = (id: string) => {
- dispatch(expireSilenceAction(alertManagerSourceName, id));
- };
- const showActions = contextSrv.hasAccess(permissions.update, contextSrv.isEditor);
- const columns: SilenceTableColumnProps[] = [
- {
- id: 'state',
- label: 'State',
- renderCell: function renderStateTag({ data: { status } }) {
- return <SilenceStateTag state={status.state} />;
- },
- size: '88px',
- },
- {
- id: 'matchers',
- label: 'Matching labels',
- renderCell: function renderMatchers({ data: { matchers } }) {
- return <Matchers matchers={matchers || []} />;
- },
- size: 9,
- },
- {
- id: 'alerts',
- label: 'Alerts',
- renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) {
- return <span data-testid="alerts">{silencedAlerts.length}</span>;
- },
- size: 1,
- },
- {
- id: 'schedule',
- label: 'Schedule',
- renderCell: function renderSchedule({ data: { startsAt, endsAt } }) {
- const startsAtDate = dateMath.parse(startsAt);
- const endsAtDate = dateMath.parse(endsAt);
- const dateDisplayFormat = 'YYYY-MM-DD HH:mm';
- return (
- <>
- {' '}
- {startsAtDate?.format(dateDisplayFormat)} {'-'}
- <br />
- {endsAtDate?.format(dateDisplayFormat)}
- </>
- );
- },
- size: '150px',
- },
- ];
- if (showActions) {
- columns.push({
- id: 'actions',
- label: 'Actions',
- renderCell: function renderActions({ data: silence }) {
- return (
- <Stack gap={0.5}>
- {silence.status.state === 'expired' ? (
- <Link href={makeAMLink(`/alerting/silence/${silence.id}/edit`, alertManagerSourceName)}>
- <ActionButton icon="sync">Recreate</ActionButton>
- </Link>
- ) : (
- <ActionButton icon="bell" onClick={() => handleExpireSilenceClick(silence.id)}>
- Unsilence
- </ActionButton>
- )}
- {silence.status.state !== 'expired' && (
- <ActionIcon
- className={styles.editButton}
- to={makeAMLink(`/alerting/silence/${silence.id}/edit`, alertManagerSourceName)}
- icon="pen"
- tooltip="edit"
- />
- )}
- </Stack>
- );
- },
- size: '147px',
- });
- }
- return columns;
- }, [alertManagerSourceName, dispatch, styles, permissions]);
- }
- export default SilencesTable;
|