import { css } from '@emotion/css'; import React, { useMemo } from 'react'; import { dateMath, GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; import { CollapsableSection, Icon, Link, LinkButton, useStyles2 } 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 { useDispatch } from 'app/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; type SilenceTableItemProps = DynamicTableItemProps; interface Props { silences: Silence[]; alertManagerAlerts: AlertmanagerAlert[]; alertManagerSourceName: string; } const SilencesTable = ({ silences, alertManagerAlerts, alertManagerSourceName }: Props) => { const styles = useStyles2(getStyles); const [queryParams] = useQueryParams(); const filteredSilencesNotExpired = useFilteredSilences(silences, false); const filteredSilencesExpired = useFilteredSilences(silences, true); const permissions = getInstancesPermissions(alertManagerSourceName); const { silenceState: silenceStateInParams } = getSilenceFiltersFromUrlParams(queryParams); const showExpiredFromUrl = silenceStateInParams === SilenceState.Expired; const itemsNotExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesNotExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); return { id: silence.id, data: { ...silence, silencedAlerts }, }; }); }, [filteredSilencesNotExpired, alertManagerAlerts]); const itemsExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); return { id: silence.id, data: { ...silence, silencedAlerts }, }; }); }, [filteredSilencesExpired, alertManagerAlerts]); return (
{!!silences.length && (
Add Silence
{itemsExpired.length > 0 && (
Expired silences are automatically deleted after 5 days.
)}
)} {!silences.length && }
); }; function SilenceList({ items, alertManagerSourceName, dataTestId, }: { items: SilenceTableItemProps[]; alertManagerSourceName: string; dataTestId: string; }) { const columns = useColumns(alertManagerSourceName); if (!!items.length) { return ( } /> ); } else { return <>No matching silences found; } } const useFilteredSilences = (silences: Silence[], expired = false) => { const [queryParams] = useQueryParams(); return useMemo(() => { const { queryString } = 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 (expired) { return silence.status.state === SilenceState.Expired; } else { return silence.status.state !== SilenceState.Expired; } }); }, [queryParams, silences, expired]); }; 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: ${theme.shape.borderRadius()}; height: 62px; display: flex; flex-direction: row; align-items: center; & > * { 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 ; }, size: 4, }, { id: 'matchers', label: 'Matching labels', renderCell: function renderMatchers({ data: { matchers } }) { return ; }, size: 10, }, { id: 'alerts', label: 'Alerts', renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) { return {silencedAlerts.length}; }, size: 4, }, { 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)} {'-'} {endsAtDate?.format(dateDisplayFormat)} ); }, size: 7, }, ]; if (showActions) { columns.push({ id: 'actions', label: 'Actions', renderCell: function renderActions({ data: silence }) { return ( {silence.status.state === 'expired' ? ( Recreate ) : ( handleExpireSilenceClick(silence.id)}> Unsilence )} {silence.status.state !== 'expired' && ( )} ); }, size: 5, }); } return columns; }, [alertManagerSourceName, dispatch, styles, permissions]); } export default SilencesTable;