mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Split out components for notification policies (#97191)
This commit is contained in:
parent
89774f3c8d
commit
6df7d1fbc0
@ -1406,12 +1406,9 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"]
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"]
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/NotificationPolicies.tsx:5381": [
|
"public/app/features/alerting/unified/NotificationPoliciesPage.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/PanelAlertTabContent.tsx:5381": [
|
"public/app/features/alerting/unified/PanelAlertTabContent.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
@ -1711,10 +1708,8 @@ exports[`better eslint`] = {
|
|||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx:5381": [
|
"public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx:5381": [
|
"public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
@ -1760,18 +1755,18 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "18"]
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "18"]
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/components/notification-policies/Filters.tsx:5381": [
|
"public/app/features/alerting/unified/components/notification-policies/Filters.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
|
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/components/notification-policies/Modals.tsx:5381": [
|
"public/app/features/alerting/unified/components/notification-policies/Modals.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
|
||||||
],
|
],
|
||||||
|
"public/app/features/alerting/unified/components/notification-policies/NotificationPoliciesList.tsx:5381": [
|
||||||
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||||
|
],
|
||||||
"public/app/features/alerting/unified/components/notification-policies/Policy.tsx:5381": [
|
"public/app/features/alerting/unified/components/notification-policies/Policy.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||||
@ -2579,9 +2574,7 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"]
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
|
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/alerting/unified/components/silences/SilencesTable.tsx:5381": [
|
"public/app/features/alerting/unified/components/silences/SilencesTable.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
|
@ -47,7 +47,10 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
|
|||||||
...PERMISSIONS_TIME_INTERVALS_MODIFY,
|
...PERMISSIONS_TIME_INTERVALS_MODIFY,
|
||||||
]),
|
]),
|
||||||
component: importAlertingComponent(
|
component: importAlertingComponent(
|
||||||
() => import(/* webpackChunkName: "AlertAmRoutes" */ 'app/features/alerting/unified/NotificationPolicies')
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "NotificationPoliciesPage" */ 'app/features/alerting/unified/NotificationPoliciesPage'
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,8 @@ import {
|
|||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies';
|
import NotificationPolicies from './NotificationPoliciesPage';
|
||||||
|
import { findRoutesMatchingFilters } from './components/notification-policies/NotificationPoliciesList';
|
||||||
import {
|
import {
|
||||||
grantUserPermissions,
|
grantUserPermissions,
|
||||||
mockDataSource,
|
mockDataSource,
|
@ -0,0 +1,113 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
|
||||||
|
import { Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui';
|
||||||
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
|
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
||||||
|
import { NotificationPoliciesList } from 'app/features/alerting/unified/components/notification-policies/NotificationPoliciesList';
|
||||||
|
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
|
||||||
|
|
||||||
|
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
||||||
|
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
||||||
|
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
|
||||||
|
import { useAlertmanager } from './state/AlertmanagerContext';
|
||||||
|
|
||||||
|
enum ActiveTab {
|
||||||
|
NotificationPolicies = 'notification_policies',
|
||||||
|
MuteTimings = 'mute_timings',
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotificationPoliciesTabs = () => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
// Alertmanager logic and data hooks
|
||||||
|
const { selectedAlertmanager = '' } = useAlertmanager();
|
||||||
|
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
|
||||||
|
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
|
||||||
|
const availableTabs = [
|
||||||
|
canSeePoliciesTab && ActiveTab.NotificationPolicies,
|
||||||
|
canSeeTimingsTab && ActiveTab.MuteTimings,
|
||||||
|
].filter((tab) => !!tab);
|
||||||
|
const { data: muteTimings = [] } = useMuteTimings({
|
||||||
|
alertmanager: selectedAlertmanager,
|
||||||
|
skip: !canSeeTimingsTab,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab state management
|
||||||
|
const [queryParams, setQueryParams] = useQueryParams();
|
||||||
|
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
|
||||||
|
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
|
||||||
|
|
||||||
|
const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings;
|
||||||
|
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
|
||||||
|
|
||||||
|
const numberOfMuteTimings = muteTimings.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager} />
|
||||||
|
<TabsBar>
|
||||||
|
{policiesSupported && canSeePoliciesTab && (
|
||||||
|
<Tab
|
||||||
|
label={'Notification Policies'}
|
||||||
|
active={policyTreeTabActive}
|
||||||
|
onChangeTab={() => {
|
||||||
|
setActiveTab(ActiveTab.NotificationPolicies);
|
||||||
|
setQueryParams({ tab: ActiveTab.NotificationPolicies });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{timingsSupported && canSeeTimingsTab && (
|
||||||
|
<Tab
|
||||||
|
label={'Mute Timings'}
|
||||||
|
active={muteTimingsTabActive}
|
||||||
|
counter={numberOfMuteTimings}
|
||||||
|
onChangeTab={() => {
|
||||||
|
setActiveTab(ActiveTab.MuteTimings);
|
||||||
|
setQueryParams({ tab: ActiveTab.MuteTimings });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TabsBar>
|
||||||
|
<TabContent className={styles.tabContent}>
|
||||||
|
{policyTreeTabActive && <NotificationPoliciesList />}
|
||||||
|
{muteTimingsTabActive && <MuteTimingsTable />}
|
||||||
|
</TabContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
tabContent: css({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface QueryParamValues {
|
||||||
|
tab: ActiveTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): QueryParamValues {
|
||||||
|
let tab = defaultTab;
|
||||||
|
|
||||||
|
if (queryParams.tab === ActiveTab.NotificationPolicies) {
|
||||||
|
tab = ActiveTab.NotificationPolicies;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.tab === ActiveTab.MuteTimings) {
|
||||||
|
tab = ActiveTab.MuteTimings;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotificationPoliciesPage = () => (
|
||||||
|
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
|
||||||
|
<NotificationPoliciesTabs />
|
||||||
|
</AlertmanagerPageWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' });
|
@ -19,7 +19,7 @@ import { MuteTimingsTable } from './MuteTimingsTable';
|
|||||||
const renderWithProvider = (alertManagerSource?: string) => {
|
const renderWithProvider = (alertManagerSource?: string) => {
|
||||||
return render(
|
return render(
|
||||||
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={alertManagerSource}>
|
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={alertManagerSource}>
|
||||||
<MuteTimingsTable alertManagerSourceName={alertManagerSource ?? GRAFANA_RULES_SOURCE_NAME} />
|
<MuteTimingsTable />
|
||||||
</AlertmanagerProvider>
|
</AlertmanagerProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
ALL_MUTE_TIMINGS,
|
ALL_MUTE_TIMINGS,
|
||||||
useExportMuteTimingsDrawer,
|
useExportMuteTimingsDrawer,
|
||||||
} from 'app/features/alerting/unified/components/mute-timings/useExportMuteTimingsDrawer';
|
} from 'app/features/alerting/unified/components/mute-timings/useExportMuteTimingsDrawer';
|
||||||
|
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||||
import { PROVENANCE_ANNOTATION } from 'app/features/alerting/unified/utils/k8s/constants';
|
import { PROVENANCE_ANNOTATION } from 'app/features/alerting/unified/utils/k8s/constants';
|
||||||
|
|
||||||
import { Authorize } from '../../components/Authorize';
|
import { Authorize } from '../../components/Authorize';
|
||||||
@ -22,21 +23,18 @@ import { Spacer } from '../Spacer';
|
|||||||
import { MuteTiming, useMuteTimings } from './useMuteTimings';
|
import { MuteTiming, useMuteTimings } from './useMuteTimings';
|
||||||
import { renderTimeIntervals } from './util';
|
import { renderTimeIntervals } from './util';
|
||||||
|
|
||||||
interface MuteTimingsTableProps {
|
|
||||||
alertManagerSourceName: string;
|
|
||||||
hideActions?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type TableItem = {
|
type TableItem = {
|
||||||
id: string;
|
id: string;
|
||||||
data: MuteTiming;
|
data: MuteTiming;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTimingsTableProps) => {
|
export const MuteTimingsTable = () => {
|
||||||
|
const { selectedAlertmanager: alertManagerSourceName = '', hasConfigurationAPI } = useAlertmanager();
|
||||||
|
const hideActions = !hasConfigurationAPI;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const [ExportAllDrawer, showExportAllDrawer] = useExportMuteTimingsDrawer();
|
const [ExportAllDrawer, showExportAllDrawer] = useExportMuteTimingsDrawer();
|
||||||
|
|
||||||
const { data, isLoading, error } = useMuteTimings({ alertmanager: alertManagerSourceName });
|
const { data, isLoading, error } = useMuteTimings({ alertmanager: alertManagerSourceName ?? '' });
|
||||||
|
|
||||||
const items = useMemo((): TableItem[] => {
|
const items = useMemo((): TableItem[] => {
|
||||||
const muteTimings = data || [];
|
const muteTimings = data || [];
|
||||||
@ -73,10 +71,10 @@ export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTi
|
|||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
<span>
|
<Trans i18nKey="alerting.mute-timings.description">
|
||||||
Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of
|
Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of
|
||||||
time.
|
time.
|
||||||
</span>
|
</Trans>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{!hideActions && items.length > 0 && (
|
{!hideActions && items.length > 0 && (
|
||||||
<Authorize actions={[AlertmanagerAction.CreateMuteTiming]}>
|
<Authorize actions={[AlertmanagerAction.CreateMuteTiming]}>
|
||||||
@ -86,7 +84,7 @@ export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTi
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
|
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
|
||||||
>
|
>
|
||||||
Add mute timing
|
<Trans i18nKey="alerting.mute-timings.add-mute-timing">Add mute timing</Trans>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</Authorize>
|
</Authorize>
|
||||||
)}
|
)}
|
||||||
|
@ -3,7 +3,9 @@ import { debounce, isEqual } from 'lodash';
|
|||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { Button, Field, Icon, Input, Label, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
|
import { Button, Field, Icon, Input, Label, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
|
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
|
||||||
|
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
|
||||||
import { ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
import { ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
|
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
|
||||||
@ -25,6 +27,7 @@ const NotificationPoliciesFilter = ({
|
|||||||
onChangeMatchers,
|
onChangeMatchers,
|
||||||
matchingCount,
|
matchingCount,
|
||||||
}: NotificationPoliciesFilterProps) => {
|
}: NotificationPoliciesFilterProps) => {
|
||||||
|
const [contactPointsSupported, canSeeContactPoints] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
|
||||||
const [searchParams, setSearchParams] = useURLSearchParams();
|
const [searchParams, setSearchParams] = useURLSearchParams();
|
||||||
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const { queryString, contactPoint } = getNotificationPoliciesFilters(searchParams);
|
const { queryString, contactPoint } = getNotificationPoliciesFilters(searchParams);
|
||||||
@ -68,13 +71,13 @@ const NotificationPoliciesFilter = ({
|
|||||||
label={
|
label={
|
||||||
<Label>
|
<Label>
|
||||||
<Stack gap={0.5}>
|
<Stack gap={0.5}>
|
||||||
<span>Search by matchers</span>
|
<Trans i18nKey="alerting.common.search-by-matchers">Search by matchers</Trans>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
<div>
|
<Trans i18nKey="alerting.policies.filter-description">
|
||||||
Filter notification policies by using a comma separated list of matchers, e.g.:
|
Filter notification policies by using a comma separated list of matchers, e.g.:
|
||||||
<pre>severity=critical, region=EMEA</pre>
|
<pre>severity=critical, region=EMEA</pre>
|
||||||
</div>
|
</Trans>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon name="info-circle" size="sm" />
|
<Icon name="info-circle" size="sm" />
|
||||||
@ -97,6 +100,7 @@ const NotificationPoliciesFilter = ({
|
|||||||
defaultValue={queryString}
|
defaultValue={queryString}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
{contactPointsSupported && canSeeContactPoints && (
|
||||||
<Field label="Search by contact point" style={{ marginBottom: 0 }}>
|
<Field label="Search by contact point" style={{ marginBottom: 0 }}>
|
||||||
<ContactPointSelector
|
<ContactPointSelector
|
||||||
selectProps={{
|
selectProps={{
|
||||||
@ -111,10 +115,11 @@ const NotificationPoliciesFilter = ({
|
|||||||
selectedContactPointName={searchParams.get('contactPoint') ?? undefined}
|
selectedContactPointName={searchParams.get('contactPoint') ?? undefined}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
)}
|
||||||
{hasFilters && (
|
{hasFilters && (
|
||||||
<Stack alignItems="center">
|
<Stack alignItems="center">
|
||||||
<Button variant="secondary" icon="times" onClick={clearFilters}>
|
<Button variant="secondary" icon="times" onClick={clearFilters}>
|
||||||
Clear filters
|
<Trans i18nKey="alerting.common.clear-filters">Clear filters</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Text variant="bodySmall" color="secondary">
|
<Text variant="bodySmall" color="secondary">
|
||||||
{matchingCount === 0 && 'No policies matching filters.'}
|
{matchingCount === 0 && 'No policies matching filters.'}
|
||||||
|
@ -1,102 +1,50 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useAsyncFn } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
|
import { Alert, Button, Stack } from '@grafana/ui';
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
LoadingPlaceholder,
|
|
||||||
Stack,
|
|
||||||
Tab,
|
|
||||||
TabContent,
|
|
||||||
TabsBar,
|
|
||||||
useStyles2,
|
|
||||||
withErrorBoundary,
|
|
||||||
} from '@grafana/ui';
|
|
||||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
|
||||||
import { Trans } from 'app/core/internationalization';
|
import { Trans } from 'app/core/internationalization';
|
||||||
import { useContactPointsWithStatus } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
|
import { useContactPointsWithStatus } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
|
||||||
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
|
||||||
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
|
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
|
||||||
|
import { FormAmRoute } from 'app/features/alerting/unified/types/amroutes';
|
||||||
|
import { addUniqueIdentifierToRoute } from 'app/features/alerting/unified/utils/amroutes';
|
||||||
|
import { ERROR_NEWER_CONFIGURATION } from 'app/features/alerting/unified/utils/k8s/errors';
|
||||||
|
import { isErrorMatchingCode, stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
|
||||||
|
import { computeInheritedTree } from 'app/features/alerting/unified/utils/notification-policies';
|
||||||
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
import { useAlertmanager } from '../../state/AlertmanagerContext';
|
||||||
import { useGetContactPointsState } from './api/receiversApi';
|
|
||||||
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
import { alertmanagerApi } from './../../api/alertmanagerApi';
|
||||||
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
|
import { useGetContactPointsState } from './../../api/receiversApi';
|
||||||
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
|
import { isLoading as isPending, useAsync } from './../../hooks/useAsync';
|
||||||
import {
|
import { useRouteGroupsMatcher } from './../../useRouteGroupsMatcher';
|
||||||
NotificationPoliciesFilter,
|
|
||||||
findRoutesByMatchers,
|
|
||||||
findRoutesMatchingPredicate,
|
|
||||||
} from './components/notification-policies/Filters';
|
|
||||||
import {
|
|
||||||
useAddPolicyModal,
|
|
||||||
useAlertGroupsModal,
|
|
||||||
useDeletePolicyModal,
|
|
||||||
useEditPolicyModal,
|
|
||||||
} from './components/notification-policies/Modals';
|
|
||||||
import { Policy } from './components/notification-policies/Policy';
|
|
||||||
import {
|
|
||||||
useNotificationPolicyRoute,
|
|
||||||
useUpdateNotificationPolicyRoute,
|
|
||||||
} from './components/notification-policies/useNotificationPolicyRoute';
|
|
||||||
import { isLoading as isPending, useAsync } from './hooks/useAsync';
|
|
||||||
import { useAlertmanager } from './state/AlertmanagerContext';
|
|
||||||
import { FormAmRoute } from './types/amroutes';
|
|
||||||
import { useRouteGroupsMatcher } from './useRouteGroupsMatcher';
|
|
||||||
import { addUniqueIdentifierToRoute } from './utils/amroutes';
|
|
||||||
import { ERROR_NEWER_CONFIGURATION } from './utils/k8s/errors';
|
|
||||||
import { isErrorMatchingCode, stringifyErrorLike } from './utils/misc';
|
|
||||||
import { computeInheritedTree } from './utils/notification-policies';
|
|
||||||
import {
|
import {
|
||||||
InsertPosition,
|
InsertPosition,
|
||||||
addRouteToReferenceRoute,
|
addRouteToReferenceRoute,
|
||||||
cleanRouteIDs,
|
cleanRouteIDs,
|
||||||
mergePartialAmRouteWithRouteTree,
|
mergePartialAmRouteWithRouteTree,
|
||||||
omitRouteFromRouteTree,
|
omitRouteFromRouteTree,
|
||||||
} from './utils/routeTree';
|
} from './../../utils/routeTree';
|
||||||
|
import { NotificationPoliciesFilter, findRoutesByMatchers, findRoutesMatchingPredicate } from './Filters';
|
||||||
|
import { useAddPolicyModal, useAlertGroupsModal, useDeletePolicyModal, useEditPolicyModal } from './Modals';
|
||||||
|
import { Policy } from './Policy';
|
||||||
|
import { useNotificationPolicyRoute, useUpdateNotificationPolicyRoute } from './useNotificationPolicyRoute';
|
||||||
|
|
||||||
enum ActiveTab {
|
export const NotificationPoliciesList = () => {
|
||||||
NotificationPolicies = 'notification_policies',
|
|
||||||
MuteTimings = 'mute_timings',
|
|
||||||
}
|
|
||||||
|
|
||||||
const AmRoutes = () => {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
const appNotification = useAppNotification();
|
const appNotification = useAppNotification();
|
||||||
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
|
const [contactPointsSupported, canSeeContactPoints] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
|
||||||
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
|
|
||||||
const [contactPointsSupported, canSeeContactPointsStatus] = useAlertmanagerAbility(
|
|
||||||
AlertmanagerAction.ViewContactPoint
|
|
||||||
);
|
|
||||||
const availableTabs = [
|
|
||||||
canSeePoliciesTab && ActiveTab.NotificationPolicies,
|
|
||||||
canSeeTimingsTab && ActiveTab.MuteTimings,
|
|
||||||
].filter((tab) => !!tab);
|
|
||||||
const [_, canSeeAlertGroups] = useAlertmanagerAbility(AlertmanagerAction.ViewAlertGroups);
|
const [_, canSeeAlertGroups] = useAlertmanagerAbility(AlertmanagerAction.ViewAlertGroups);
|
||||||
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
|
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
|
||||||
|
|
||||||
const [queryParams, setQueryParams] = useQueryParams();
|
|
||||||
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
|
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
|
|
||||||
|
|
||||||
const [contactPointFilter, setContactPointFilter] = useState<string | undefined>();
|
const [contactPointFilter, setContactPointFilter] = useState<string | undefined>();
|
||||||
const [labelMatchersFilter, setLabelMatchersFilter] = useState<ObjectMatcher[]>([]);
|
const [labelMatchersFilter, setLabelMatchersFilter] = useState<ObjectMatcher[]>([]);
|
||||||
|
|
||||||
const { selectedAlertmanager, hasConfigurationAPI, isGrafanaAlertmanager } = useAlertmanager();
|
const { selectedAlertmanager, hasConfigurationAPI, isGrafanaAlertmanager } = useAlertmanager();
|
||||||
const { getRouteGroupsMap } = useRouteGroupsMatcher();
|
const { getRouteGroupsMap } = useRouteGroupsMatcher();
|
||||||
const { data: muteTimings = [] } = useMuteTimings({
|
|
||||||
alertmanager: selectedAlertmanager ?? '',
|
|
||||||
skip: !canSeeTimingsTab,
|
|
||||||
});
|
|
||||||
|
|
||||||
const shouldFetchContactPoints =
|
|
||||||
policiesSupported && canSeePoliciesTab && contactPointsSupported && canSeeContactPointsStatus;
|
|
||||||
|
|
||||||
|
const shouldFetchContactPoints = contactPointsSupported && canSeeContactPoints;
|
||||||
const contactPointsState = useGetContactPointsState(
|
const contactPointsState = useGetContactPointsState(
|
||||||
// Workaround to not try and call this API when we don't have access to the policies tab
|
// Workaround to not try and call this API when we don't have access to the policies tab
|
||||||
shouldFetchContactPoints ? (selectedAlertmanager ?? '') : ''
|
shouldFetchContactPoints ? (selectedAlertmanager ?? '') : ''
|
||||||
@ -107,7 +55,7 @@ const AmRoutes = () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error: resultError,
|
error: resultError,
|
||||||
refetch: refetchNotificationPolicyRoute,
|
refetch: refetchNotificationPolicyRoute,
|
||||||
} = useNotificationPolicyRoute({ alertmanager: selectedAlertmanager ?? '' }, { skip: !canSeePoliciesTab });
|
} = useNotificationPolicyRoute({ alertmanager: selectedAlertmanager ?? '' });
|
||||||
|
|
||||||
// We make the assumption that the first policy is the default one
|
// We make the assumption that the first policy is the default one
|
||||||
// At the time of writing, this will be always the case for the AM config response, and the K8S API
|
// At the time of writing, this will be always the case for the AM config response, and the K8S API
|
||||||
@ -118,14 +66,14 @@ const AmRoutes = () => {
|
|||||||
|
|
||||||
const { currentData: alertGroups, refetch: refetchAlertGroups } = useGetAlertmanagerAlertGroupsQuery(
|
const { currentData: alertGroups, refetch: refetchAlertGroups } = useGetAlertmanagerAlertGroupsQuery(
|
||||||
{ amSourceName: selectedAlertmanager ?? '' },
|
{ amSourceName: selectedAlertmanager ?? '' },
|
||||||
{ skip: !canSeePoliciesTab || !canSeeAlertGroups || !selectedAlertmanager }
|
{ skip: !canSeeAlertGroups || !selectedAlertmanager }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { contactPoints: receivers } = useContactPointsWithStatus({
|
const { contactPoints: receivers } = useContactPointsWithStatus({
|
||||||
alertmanager: selectedAlertmanager ?? '',
|
alertmanager: selectedAlertmanager ?? '',
|
||||||
fetchPolicies: false,
|
fetchPolicies: false,
|
||||||
fetchStatuses: canSeeContactPointsStatus,
|
fetchStatuses: true,
|
||||||
skip: !canSeePoliciesTab,
|
skip: !shouldFetchContactPoints,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rootRoute = useMemo(() => {
|
const rootRoute = useMemo(() => {
|
||||||
@ -244,43 +192,10 @@ const AmRoutes = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numberOfMuteTimings = muteTimings.length;
|
|
||||||
const hasPoliciesData = rootRoute && !resultError && !isLoading;
|
const hasPoliciesData = rootRoute && !resultError && !isLoading;
|
||||||
const hasPoliciesError = !!resultError && !isLoading;
|
const hasPoliciesError = !!resultError && !isLoading;
|
||||||
|
|
||||||
const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings;
|
|
||||||
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager} />
|
|
||||||
<TabsBar>
|
|
||||||
{policiesSupported && canSeePoliciesTab && (
|
|
||||||
<Tab
|
|
||||||
label={'Notification Policies'}
|
|
||||||
active={policyTreeTabActive}
|
|
||||||
onChangeTab={() => {
|
|
||||||
setActiveTab(ActiveTab.NotificationPolicies);
|
|
||||||
setQueryParams({ tab: ActiveTab.NotificationPolicies });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{timingsSupported && canSeeTimingsTab && (
|
|
||||||
<Tab
|
|
||||||
label={'Mute Timings'}
|
|
||||||
active={muteTimingsTabActive}
|
|
||||||
counter={numberOfMuteTimings}
|
|
||||||
onChangeTab={() => {
|
|
||||||
setActiveTab(ActiveTab.MuteTimings);
|
|
||||||
setQueryParams({ tab: ActiveTab.MuteTimings });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TabsBar>
|
|
||||||
<TabContent className={styles.tabContent}>
|
|
||||||
{isLoading && <LoadingPlaceholder text="Loading notification policies..." />}
|
|
||||||
|
|
||||||
{policyTreeTabActive && (
|
|
||||||
<>
|
<>
|
||||||
{hasPoliciesError && (
|
{hasPoliciesError && (
|
||||||
<Alert severity="error" title="Error loading Alertmanager config">
|
<Alert severity="error" title="Error loading Alertmanager config">
|
||||||
@ -337,12 +252,6 @@ const AmRoutes = () => {
|
|||||||
{deleteModal}
|
{deleteModal}
|
||||||
{alertInstancesModal}
|
{alertInstancesModal}
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
{muteTimingsTabActive && (
|
|
||||||
<MuteTimingsTable alertManagerSourceName={selectedAlertmanager} hideActions={!hasConfigurationAPI} />
|
|
||||||
)}
|
|
||||||
</TabContent>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -429,37 +338,3 @@ function findMapIntersection(...matchingRoutes: FilterResult[]): FilterResult {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
tabContent: css({
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface QueryParamValues {
|
|
||||||
tab: ActiveTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): QueryParamValues {
|
|
||||||
let tab = defaultTab;
|
|
||||||
|
|
||||||
if (queryParams.tab === ActiveTab.NotificationPolicies) {
|
|
||||||
tab = ActiveTab.NotificationPolicies;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryParams.tab === ActiveTab.MuteTimings) {
|
|
||||||
tab = ActiveTab.MuteTimings;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tab,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const NotificationPoliciesPage = () => (
|
|
||||||
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
|
|
||||||
<AmRoutes />
|
|
||||||
</AlertmanagerPageWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' });
|
|
@ -35,7 +35,6 @@ import {
|
|||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { ReceiversState } from 'app/types';
|
import { ReceiversState } from 'app/types';
|
||||||
|
|
||||||
import { RoutesMatchingFilters } from '../../NotificationPolicies';
|
|
||||||
import { AlertmanagerAction, useAlertmanagerAbilities, useAlertmanagerAbility } from '../../hooks/useAbilities';
|
import { AlertmanagerAction, useAlertmanagerAbilities, useAlertmanagerAbility } from '../../hooks/useAbilities';
|
||||||
import { getAmMatcherFormatter } from '../../utils/alertmanager';
|
import { getAmMatcherFormatter } from '../../utils/alertmanager';
|
||||||
import { MatcherFormatter, normalizeMatchers } from '../../utils/matchers';
|
import { MatcherFormatter, normalizeMatchers } from '../../utils/matchers';
|
||||||
@ -51,6 +50,7 @@ import { Spacer } from '../Spacer';
|
|||||||
import { GrafanaPoliciesExporter } from '../export/GrafanaPoliciesExporter';
|
import { GrafanaPoliciesExporter } from '../export/GrafanaPoliciesExporter';
|
||||||
|
|
||||||
import { Matchers } from './Matchers';
|
import { Matchers } from './Matchers';
|
||||||
|
import { RoutesMatchingFilters } from './NotificationPoliciesList';
|
||||||
import { TIMING_OPTIONS_DEFAULTS, TimingOptions } from './timingOptions';
|
import { TIMING_OPTIONS_DEFAULTS, TimingOptions } from './timingOptions';
|
||||||
|
|
||||||
interface PolicyComponentProps {
|
interface PolicyComponentProps {
|
||||||
@ -742,7 +742,7 @@ const TimeIntervals: FC<{ timings: string[]; alertManagerSourceName: string }> =
|
|||||||
href={createMuteTimingLink(timing, alertManagerSourceName)}
|
href={createMuteTimingLink(timing, alertManagerSourceName)}
|
||||||
color={canSeeMuteTimings ? 'primary' : 'secondary'}
|
color={canSeeMuteTimings ? 'primary' : 'secondary'}
|
||||||
variant="bodySmall"
|
variant="bodySmall"
|
||||||
inline={false}
|
inline={canSeeMuteTimings ? false : undefined}
|
||||||
>
|
>
|
||||||
{timing}
|
{timing}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -5,6 +5,7 @@ import { FormEvent, useState } from 'react';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Button, Field, Icon, Input, Label, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
import { Button, Field, Icon, Input, Label, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
|
|
||||||
import { parsePromQLStyleMatcherLoose } from '../../utils/matchers';
|
import { parsePromQLStyleMatcherLoose } from '../../utils/matchers';
|
||||||
import { getSilenceFiltersFromUrlParams } from '../../utils/misc';
|
import { getSilenceFiltersFromUrlParams } from '../../utils/misc';
|
||||||
@ -48,7 +49,7 @@ export const SilencesFilter = () => {
|
|||||||
label={
|
label={
|
||||||
<Label>
|
<Label>
|
||||||
<Stack gap={0.5}>
|
<Stack gap={0.5}>
|
||||||
<span>Search by matchers</span>
|
<Trans i18nKey="alerting.common.search-by-matchers">Search by matchers</Trans>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
<div>
|
<div>
|
||||||
@ -79,7 +80,7 @@ export const SilencesFilter = () => {
|
|||||||
{queryString && (
|
{queryString && (
|
||||||
<div className={styles.rowChild}>
|
<div className={styles.rowChild}>
|
||||||
<Button variant="secondary" icon="times" onClick={clearFilters}>
|
<Button variant="secondary" icon="times" onClick={clearFilters}>
|
||||||
Clear filters
|
<Trans i18nKey="alerting.common.clear-filters">Clear filters</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -241,11 +241,13 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"clear-filters": "Clear filters",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"export-all": "Export all",
|
"export-all": "Export all",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
|
"search-by-matchers": "Search by matchers",
|
||||||
"view": "View"
|
"view": "View"
|
||||||
},
|
},
|
||||||
"contact-points": {
|
"contact-points": {
|
||||||
@ -327,6 +329,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute-timings": {
|
"mute-timings": {
|
||||||
|
"add-mute-timing": "Add mute timing",
|
||||||
|
"description": "Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of time.",
|
||||||
"save": "Save mute timing",
|
"save": "Save mute timing",
|
||||||
"saving": "Saving mute timing"
|
"saving": "Saving mute timing"
|
||||||
},
|
},
|
||||||
@ -349,6 +353,7 @@
|
|||||||
"warning-1": "Deleting this notification policy will permanently remove it.",
|
"warning-1": "Deleting this notification policy will permanently remove it.",
|
||||||
"warning-2": "Are you sure you want to delete this policy?"
|
"warning-2": "Are you sure you want to delete this policy?"
|
||||||
},
|
},
|
||||||
|
"filter-description": "Filter notification policies by using a comma separated list of matchers, e.g.:<1>severity=critical, region=EMEA</1>",
|
||||||
"generated-policies": "Auto-generated policies",
|
"generated-policies": "Auto-generated policies",
|
||||||
"matchers": "Matchers",
|
"matchers": "Matchers",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
@ -241,11 +241,13 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cäʼnčęľ",
|
"cancel": "Cäʼnčęľ",
|
||||||
|
"clear-filters": "Cľęäř ƒįľŧęřş",
|
||||||
"delete": "Đęľęŧę",
|
"delete": "Đęľęŧę",
|
||||||
"edit": "Ēđįŧ",
|
"edit": "Ēđįŧ",
|
||||||
"export": "Ēχpőřŧ",
|
"export": "Ēχpőřŧ",
|
||||||
"export-all": "Ēχpőřŧ äľľ",
|
"export-all": "Ēχpőřŧ äľľ",
|
||||||
"loading": "Ŀőäđįʼnģ...",
|
"loading": "Ŀőäđįʼnģ...",
|
||||||
|
"search-by-matchers": "Ŝęäřčĥ þy mäŧčĥęřş",
|
||||||
"view": "Vįęŵ"
|
"view": "Vįęŵ"
|
||||||
},
|
},
|
||||||
"contact-points": {
|
"contact-points": {
|
||||||
@ -327,6 +329,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mute-timings": {
|
"mute-timings": {
|
||||||
|
"add-mute-timing": "Åđđ mūŧę ŧįmįʼnģ",
|
||||||
|
"description": "Ēʼnŧęř şpęčįƒįč ŧįmę įʼnŧęřväľş ŵĥęʼn ʼnőŧ ŧő şęʼnđ ʼnőŧįƒįčäŧįőʼnş őř ƒřęęžę ʼnőŧįƒįčäŧįőʼnş ƒőř řęčūřřįʼnģ pęřįőđş őƒ ŧįmę.",
|
||||||
"save": "Ŝävę mūŧę ŧįmįʼnģ",
|
"save": "Ŝävę mūŧę ŧįmįʼnģ",
|
||||||
"saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ"
|
"saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ"
|
||||||
},
|
},
|
||||||
@ -349,6 +353,7 @@
|
|||||||
"warning-1": "Đęľęŧįʼnģ ŧĥįş ʼnőŧįƒįčäŧįőʼn pőľįčy ŵįľľ pęřmäʼnęʼnŧľy řęmővę įŧ.",
|
"warning-1": "Đęľęŧįʼnģ ŧĥįş ʼnőŧįƒįčäŧįőʼn pőľįčy ŵįľľ pęřmäʼnęʼnŧľy řęmővę įŧ.",
|
||||||
"warning-2": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő đęľęŧę ŧĥįş pőľįčy?"
|
"warning-2": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő đęľęŧę ŧĥįş pőľįčy?"
|
||||||
},
|
},
|
||||||
|
"filter-description": "Fįľŧęř ʼnőŧįƒįčäŧįőʼn pőľįčįęş þy ūşįʼnģ ä čőmmä şępäřäŧęđ ľįşŧ őƒ mäŧčĥęřş, ę.ģ.:<1>şęvęřįŧy=čřįŧįčäľ, řęģįőʼn=ĒMĒÅ</1>",
|
||||||
"generated-policies": "Åūŧő-ģęʼnęřäŧęđ pőľįčįęş",
|
"generated-policies": "Åūŧő-ģęʼnęřäŧęđ pőľįčįęş",
|
||||||
"matchers": "Mäŧčĥęřş",
|
"matchers": "Mäŧčĥęřş",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
Loading…
Reference in New Issue
Block a user