= ({ alert, className }) => {
|
|
- for {alertDuration} seconds |
+ for {duration} seconds |
{alertName} |
diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
index c49ac6fe094..a2bb9c45aa5 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
@@ -208,10 +208,7 @@ export const SilencesEditor: FC = ({ silence, alertManagerSourceName }) =
error={formState.errors.createdBy?.message}
invalid={!!formState.errors.createdBy}
>
-
+
diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
index e8fafe6f1c8..74422e8e38b 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
@@ -1,16 +1,29 @@
import React, { FC, useMemo } from 'react';
-import { GrafanaTheme2 } from '@grafana/data';
+import { GrafanaTheme2, dateMath } from '@grafana/data';
import { Icon, useStyles2, Link, Button } from '@grafana/ui';
import { css } from '@emotion/css';
import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';
-import SilenceTableRow from './SilenceTableRow';
-import { getAlertTableStyles } from '../../styles/table';
import { NoSilencesSplash } from './NoSilencesCTA';
-import { getFiltersFromUrlParams, makeAMLink } from '../../utils/misc';
+import { getSilenceFiltersFromUrlParams, makeAMLink } from '../../utils/misc';
import { contextSrv } from 'app/core/services/context_srv';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { SilencesFilter } from './SilencesFilter';
import { parseMatchers } from '../../utils/alertmanager';
+import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
+import { SilenceStateTag } from './SilenceStateTag';
+import { Matchers } from './Matchers';
+import { ActionButton } from '../rules/ActionButton';
+import { ActionIcon } from '../rules/ActionIcon';
+import { useDispatch } from 'react-redux';
+import { expireSilenceAction } from '../../state/actions';
+import { SilenceDetails } from './SilenceDetails';
+
+export interface SilenceTableItem extends Silence {
+ silencedAlerts: AlertmanagerAlert[];
+}
+
+type SilenceTableColumnProps = DynamicTableColumnProps ;
+type SilenceTableItemProps = DynamicTableItemProps;
interface Props {
silences: Silence[];
alertManagerAlerts: AlertmanagerAlert[];
@@ -19,18 +32,28 @@ interface Props {
const SilencesTable: FC = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
const styles = useStyles2(getStyles);
- const tableStyles = useStyles2(getAlertTableStyles);
const [queryParams] = useQueryParams();
const filteredSilences = useFilteredSilences(silences);
- const { silenceState } = getFiltersFromUrlParams(queryParams);
+ const { silenceState } = getSilenceFiltersFromUrlParams(queryParams);
const showExpiredSilencesBanner =
!!filteredSilences.length && (silenceState === undefined || silenceState === SilenceState.Expired);
- const findSilencedAlerts = (id: string) => {
- return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
- };
+ 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 (
@@ -46,53 +69,23 @@ const SilencesTable: FC = ({ silences, alertManagerAlerts, alertManagerSo
)}
- {!!filteredSilences.length ? (
-
-
-
-
-
-
-
- {contextSrv.isEditor && }
-
-
-
- |
- State |
- Matching labels |
- Alerts |
- Schedule |
- {contextSrv.isEditor && Action | }
-
-
-
- {filteredSilences.map((silence, index) => {
- const silencedAlerts = findSilencedAlerts(silence.id);
- return (
-
- );
- })}
-
-
+ {!!items.length ? (
+ <>
+ }
+ />
+ {showExpiredSilencesBanner && (
+
+
+ Expired silences are automatically deleted after 5 days.
+
+ )}
+ >
) : (
-
-
- No silences match your filters
-
- )}
-
- {showExpiredSilencesBanner && (
-
-
- Expired silences are automatically deleted after 5 days.
-
+ 'No matching silences found'
)}
>
)}
@@ -104,7 +97,7 @@ const SilencesTable: FC = ({ silences, alertManagerAlerts, alertManagerSo
const useFilteredSilences = (silences: Silence[]) => {
const [queryParams] = useQueryParams();
return useMemo(() => {
- const { queryString, silenceState } = getFiltersFromUrlParams(queryParams);
+ const { queryString, silenceState } = getSilenceFiltersFromUrlParams(queryParams);
const silenceIdsString = queryParams?.silenceIds;
return silences.filter((silence) => {
if (typeof silenceIdsString === 'string') {
@@ -148,12 +141,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
addNewSilence: css`
margin: ${theme.spacing(2, 0)};
`,
- colState: css`
- width: 110px;
- `,
- colMatchers: css`
- width: 50%;
- `,
callout: css`
background-color: ${theme.colors.background.secondary};
border-top: 3px solid ${theme.colors.info.border};
@@ -171,6 +158,95 @@ const getStyles = (theme: GrafanaTheme2) => ({
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);
+ return useMemo((): SilenceTableColumnProps[] => {
+ const handleExpireSilenceClick = (id: string) => {
+ dispatch(expireSilenceAction(alertManagerSourceName, id));
+ };
+ const showActions = contextSrv.isEditor;
+ const columns: SilenceTableColumnProps[] = [
+ {
+ id: 'state',
+ label: 'State',
+ renderCell: function renderStateTag({ data: { status } }) {
+ return ;
+ },
+ size: '88px',
+ },
+ {
+ id: 'matchers',
+ label: 'Matching labels',
+ renderCell: function renderMatchers({ data: { matchers } }) {
+ return ;
+ },
+ size: 9,
+ },
+ {
+ id: 'alerts',
+ label: 'Alerts',
+ renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) {
+ return {silencedAlerts.length};
+ },
+ 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)} {'-'}
+
+ {endsAtDate?.format(dateDisplayFormat)}
+ >
+ );
+ },
+ size: '150px',
+ },
+ ];
+ 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: '140px',
+ });
+ }
+ return columns;
+ }, [alertManagerSourceName, dispatch, styles]);
+}
+
export default SilencesTable;
diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts
index 7bc61a97fc0..b8be1dd1edb 100644
--- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts
+++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts
@@ -14,9 +14,6 @@ export const useFilteredRules = (namespaces: CombinedRuleNamespace[]) => {
const filters = getFiltersFromUrlParams(queryParams);
return useMemo(() => {
- if (!filters.queryString && !filters.dataSource && !filters.alertState) {
- return namespaces;
- }
const filteredNamespaces = namespaces
// Filter by data source
// TODO: filter by multiple data sources for grafana-managed alerts
@@ -48,6 +45,9 @@ const reduceNamespaces = (filters: FilterState) => {
const reduceGroups = (filters: FilterState) => {
return (groupAcc: CombinedRuleGroup[], group: CombinedRuleGroup) => {
const rules = group.rules.filter((rule) => {
+ if (filters.ruleType && filters.ruleType !== rule.promRule?.type) {
+ return false;
+ }
if (filters.dataSource && isGrafanaRulerRule(rule.rulerRule) && !isQueryingDataSource(rule.rulerRule, filters)) {
return false;
}
diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts
index 803d34c1348..946f064e9fa 100644
--- a/public/app/features/alerting/unified/utils/misc.ts
+++ b/public/app/features/alerting/unified/utils/misc.ts
@@ -37,9 +37,9 @@ export const getFiltersFromUrlParams = (queryParams: UrlQueryMap): FilterState =
const queryString = queryParams['queryString'] === undefined ? undefined : String(queryParams['queryString']);
const alertState = queryParams['alertState'] === undefined ? undefined : String(queryParams['alertState']);
const dataSource = queryParams['dataSource'] === undefined ? undefined : String(queryParams['dataSource']);
+ const ruleType = queryParams['ruleType'] === undefined ? undefined : String(queryParams['ruleType']);
const groupBy = queryParams['groupBy'] === undefined ? undefined : String(queryParams['groupBy']).split(',');
- const silenceState = queryParams['silenceState'] === undefined ? undefined : String(queryParams['silenceState']);
- return { queryString, alertState, dataSource, groupBy, silenceState };
+ return { queryString, alertState, dataSource, groupBy, ruleType };
};
export const getSilenceFiltersFromUrlParams = (queryParams: UrlQueryMap): SilenceFilterState => {
diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts
index c937ff4b8f7..c6861069254 100644
--- a/public/app/types/unified-alerting.ts
+++ b/public/app/types/unified-alerting.ts
@@ -133,7 +133,7 @@ export interface FilterState {
dataSource?: string;
alertState?: string;
groupBy?: string[];
- silenceState?: string;
+ ruleType?: string;
}
export interface SilenceFilterState {
|