From a37b868da74a7c2506a64bf9cb1c2e5f63c344a3 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 20 Jun 2022 16:55:12 +0200 Subject: [PATCH] Alerting: Provisioning UI (#50776) Co-authored-by: Konrad Lalik --- .../features/alerting/unified/AmRoutes.tsx | 11 +- .../features/alerting/unified/MuteTimings.tsx | 17 ++- .../features/alerting/unified/RuleViewer.tsx | 5 +- .../alerting/unified/api/alertmanager.ts | 1 + .../unified/components/Provisioning.tsx | 28 ++++ .../components/amroutes/AmRootRoute.tsx | 6 +- .../components/amroutes/MuteTimingForm.tsx | 7 +- .../components/amroutes/MuteTimingsTable.tsx | 34 ++++- .../components/receivers/EditReceiverView.tsx | 8 +- .../components/receivers/EditTemplateView.tsx | 3 + .../components/receivers/ReceiversTable.tsx | 10 +- .../components/receivers/TemplateForm.tsx | 141 +++++++++--------- .../components/receivers/TemplatesTable.tsx | 71 ++++++--- .../receivers/form/CloudReceiverForm.tsx | 3 + .../form/GrafanaCommonChannelSettings.tsx | 7 +- .../receivers/form/GrafanaReceiverForm.tsx | 13 +- .../receivers/form/ReceiverForm.tsx | 5 +- .../rules/RuleDetailsActionButtons.tsx | 15 +- .../unified/components/rules/RulesTable.tsx | 19 +++ .../alerting/unified/state/actions.ts | 1 + .../alerting/unified/types/receiver-form.ts | 1 + .../alerting/unified/utils/receiver-form.ts | 1 + .../plugins/datasource/alertmanager/types.ts | 8 + public/app/types/unified-alerting-dto.ts | 1 + 24 files changed, 289 insertions(+), 127 deletions(-) create mode 100644 public/app/features/alerting/unified/components/Provisioning.tsx diff --git a/public/app/features/alerting/unified/AmRoutes.tsx b/public/app/features/alerting/unified/AmRoutes.tsx index eddb4012644..9cefb26546e 100644 --- a/public/app/features/alerting/unified/AmRoutes.tsx +++ b/public/app/features/alerting/unified/AmRoutes.tsx @@ -11,6 +11,7 @@ import { useCleanup } from '../../../core/hooks/useCleanup'; import { AlertManagerPicker } from './components/AlertManagerPicker'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { NoAlertManagerWarning } from './components/NoAlertManagerWarning'; +import { ProvisionedResource, ProvisioningAlert } from './components/Provisioning'; import { AmRootRoute } from './components/amroutes/AmRootRoute'; import { AmSpecificRouting } from './components/amroutes/AmSpecificRouting'; import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable'; @@ -30,8 +31,6 @@ const AmRoutes: FC = () => { const alertManagers = useAlertManagersByPermission('notification'); const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers); - const readOnly = alertManagerSourceName ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) : true; - const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs); const fetchConfig = useCallback(() => { @@ -57,6 +56,8 @@ const AmRoutes: FC = () => { (config?.receivers ?? []).map((receiver: Receiver) => receiver.name) ) as AmRouteReceiver[]; + const isProvisioned = useMemo(() => Boolean(config?.route?.provenance), [config?.route]); + const enterRootRouteEditMode = () => { setIsRootRouteEditMode(true); }; @@ -109,6 +110,10 @@ const AmRoutes: FC = () => { ); } + const readOnly = alertManagerSourceName + ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) || isProvisioned + : true; + return ( { {resultError.message || 'Unknown error.'} )} + {isProvisioned && } {resultLoading && } {result && !resultLoading && !resultError && ( <> { const getMuteTimingByName = useCallback( (id: string): MuteTimeInterval | undefined => { - return config?.mute_time_intervals?.find(({ name }: MuteTimeInterval) => name === id); + const timing = config?.mute_time_intervals?.find(({ name }: MuteTimeInterval) => name === id); + + if (timing) { + const provenance = (config?.muteTimeProvenances ?? {})[timing.name]; + + return { + ...timing, + provenance, + }; + } + + return timing; }, [config] ); @@ -60,7 +71,9 @@ const MuteTimings = () => { {() => { if (queryParams['muteName']) { const muteTiming = getMuteTimingByName(String(queryParams['muteName'])); - return ; + const provenance = muteTiming?.provenance; + + return ; } return ; }} diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 92893842427..2d35ee2c6ce 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -19,6 +19,7 @@ import { AlertQuery } from '../../../types/unified-alerting-dto'; import { AlertLabels } from './components/AlertLabels'; import { DetailsField } from './components/DetailsField'; +import { ProvisionedResource, ProvisioningAlert } from './components/Provisioning'; import { RuleViewerLayout, RuleViewerLayoutContent } from './components/rule-viewer/RuleViewerLayout'; import { RuleViewerVisualization } from './components/rule-viewer/RuleViewerVisualization'; import { RuleDetailsActionButtons } from './components/rules/RuleDetailsActionButtons'; @@ -35,7 +36,7 @@ import { AlertingQueryRunner } from './state/AlertingQueryRunner'; import { getRulesSourceByName } from './utils/datasource'; import { alertRuleToQueries } from './utils/query'; import * as ruleId from './utils/rule-id'; -import { isFederatedRuleGroup } from './utils/rules'; +import { isFederatedRuleGroup, isGrafanaRulerRule } from './utils/rules'; type RuleViewerProps = GrafanaRouteComponentProps<{ id?: string; sourceName?: string }>; @@ -131,6 +132,7 @@ export function RuleViewer({ match }: RuleViewerProps) { const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim()); const isFederatedRule = isFederatedRuleGroup(rule.group); + const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); return ( @@ -146,6 +148,7 @@ export function RuleViewer({ match }: RuleViewerProps) { )} + {isProvisioned && }

diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 78a4385bdc1..6921c8ea527 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -32,6 +32,7 @@ export async function fetchAlertManagerConfig(alertManagerSourceName: string): P ); return { template_files: result.data.template_files ?? {}, + template_file_provenances: result.data.template_file_provenances ?? {}, alertmanager_config: result.data.alertmanager_config ?? {}, }; } catch (e) { diff --git a/public/app/features/alerting/unified/components/Provisioning.tsx b/public/app/features/alerting/unified/components/Provisioning.tsx new file mode 100644 index 00000000000..a5b66f73bc6 --- /dev/null +++ b/public/app/features/alerting/unified/components/Provisioning.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import { Alert, Badge } from '@grafana/ui'; + +export enum ProvisionedResource { + ContactPoint = 'contact point', + Template = 'template', + MuteTiming = 'mute timing', + AlertRule = 'alert rule', + RootNotificationPolicy = 'root notification policy', +} + +interface ProvisioningAlertProps { + resource: ProvisionedResource; +} + +export const ProvisioningAlert = ({ resource }: ProvisioningAlertProps) => { + return ( + + This {resource} has been provisioned, that means it was created by config. Please contact your server admin to + update this {resource}. + + ); +}; + +export const ProvisioningBadge = () => { + return ; +}; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx b/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx index 9b12db3a6d5..a88c517e647 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx @@ -7,7 +7,6 @@ import { Button, useStyles2 } from '@grafana/ui'; import { Authorize } from '../../components/Authorize'; import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; import { getNotificationsPermissions } from '../../utils/access-control'; -import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; import { AmRootRouteForm } from './AmRootRouteForm'; import { AmRootRouteRead } from './AmRootRouteRead'; @@ -20,6 +19,7 @@ export interface AmRootRouteProps { receivers: AmRouteReceiver[]; routes: FormAmRoute; alertManagerSourceName: string; + readOnly?: boolean; } export const AmRootRoute: FC = ({ @@ -30,11 +30,11 @@ export const AmRootRoute: FC = ({ receivers, routes, alertManagerSourceName, + readOnly = false, }) => { const styles = useStyles2(getStyles); const permissions = getNotificationsPermissions(alertManagerSourceName); - const isReadOnly = isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName); return (
@@ -42,7 +42,7 @@ export const AmRootRoute: FC = ({
Root policy - default for all alerts
- {!isEditMode && !isReadOnly && ( + {!isEditMode && !readOnly && (