|
|
|
@ -1,16 +1,25 @@ |
|
|
|
|
import { useMemo } from 'react'; |
|
|
|
|
|
|
|
|
|
import { contextSrv as ctx } from 'app/core/services/context_srv'; |
|
|
|
|
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; |
|
|
|
|
import { AccessControlAction } from 'app/types'; |
|
|
|
|
import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; |
|
|
|
|
|
|
|
|
|
import { alertmanagerApi } from '../api/alertmanagerApi'; |
|
|
|
|
import { useAlertmanager } from '../state/AlertmanagerContext'; |
|
|
|
|
import { getInstancesPermissions, getNotificationsPermissions } from '../utils/access-control'; |
|
|
|
|
import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissions } from '../utils/access-control'; |
|
|
|
|
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; |
|
|
|
|
import { isFederatedRuleGroup, isGrafanaRulerRule } from '../utils/rules'; |
|
|
|
|
|
|
|
|
|
import { useIsRuleEditable } from './useIsRuleEditable'; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* These hooks will determine if |
|
|
|
|
* 1. the action is supported in the current alertmanager or data source context |
|
|
|
|
* 1. the action is supported in the current context (alertmanager, alert rule or general context) |
|
|
|
|
* 2. user is allowed to perform actions based on their set of permissions / assigned role |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
// this enum lists all of the available actions we can perform within the context of an alertmanager
|
|
|
|
|
export enum AlertmanagerAction { |
|
|
|
|
// configuration
|
|
|
|
|
ViewExternalConfiguration = 'view-external-configuration', |
|
|
|
@ -49,12 +58,27 @@ export enum AlertmanagerAction { |
|
|
|
|
DeleteMuteTiming = 'delete-mute-timing', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export enum AlertSourceAction { |
|
|
|
|
// this enum lists all of the available actions we can take on a single alert rule
|
|
|
|
|
export enum AlertRuleAction { |
|
|
|
|
Duplicate = 'duplicate-alert-rule', |
|
|
|
|
View = 'view-alert-rule', |
|
|
|
|
Update = 'update-alert-rule', |
|
|
|
|
Delete = 'delete-alert-rule', |
|
|
|
|
Explore = 'explore-alert-rule', |
|
|
|
|
Silence = 'silence-alert-rule', |
|
|
|
|
ModifyExport = 'modify-export-rule', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// this enum lists all of the actions we can perform within alerting in general, not linked to a specific
|
|
|
|
|
// alert source, rule or alertmanager
|
|
|
|
|
export enum AlertingAction { |
|
|
|
|
// internal (Grafana managed)
|
|
|
|
|
CreateAlertRule = 'create-alert-rule', |
|
|
|
|
ViewAlertRule = 'view-alert-rule', |
|
|
|
|
UpdateAlertRule = 'update-alert-rule', |
|
|
|
|
DeleteAlertRule = 'delete-alert-rule', |
|
|
|
|
ExportGrafanaManagedRules = 'export-grafana-managed-rules', |
|
|
|
|
|
|
|
|
|
// external (any compatible alerting data source)
|
|
|
|
|
CreateExternalAlertRule = 'create-external-alert-rule', |
|
|
|
|
ViewExternalAlertRule = 'view-external-alert-rule', |
|
|
|
@ -62,39 +86,92 @@ export enum AlertSourceAction { |
|
|
|
|
DeleteExternalAlertRule = 'delete-external-alert-rule', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const AlwaysSupported = true; // this just makes it easier to understand the code
|
|
|
|
|
export type Action = AlertmanagerAction | AlertSourceAction; |
|
|
|
|
// these just makes it easier to read the code :)
|
|
|
|
|
const AlwaysSupported = true; |
|
|
|
|
const NotSupported = false; |
|
|
|
|
|
|
|
|
|
export type Action = AlertmanagerAction | AlertingAction | AlertRuleAction; |
|
|
|
|
export type Ability = [actionSupported: boolean, actionAllowed: boolean]; |
|
|
|
|
export type Abilities<T extends Action> = Record<T, Ability>; |
|
|
|
|
|
|
|
|
|
export function useAlertSourceAbilities(): Abilities<AlertSourceAction> { |
|
|
|
|
// TODO add the "supported" booleans here, we currently only do authorization
|
|
|
|
|
|
|
|
|
|
const abilities: Abilities<AlertSourceAction> = { |
|
|
|
|
// -- Grafana managed alert rules --
|
|
|
|
|
[AlertSourceAction.CreateAlertRule]: [AlwaysSupported, ctx.hasPermission(AccessControlAction.AlertingRuleCreate)], |
|
|
|
|
[AlertSourceAction.ViewAlertRule]: [AlwaysSupported, ctx.hasPermission(AccessControlAction.AlertingRuleRead)], |
|
|
|
|
[AlertSourceAction.UpdateAlertRule]: [AlwaysSupported, ctx.hasPermission(AccessControlAction.AlertingRuleUpdate)], |
|
|
|
|
[AlertSourceAction.DeleteAlertRule]: [AlwaysSupported, ctx.hasPermission(AccessControlAction.AlertingRuleDelete)], |
|
|
|
|
// -- External alert rules (Mimir / Loki / etc) --
|
|
|
|
|
// for these we only have "read" and "write" permissions
|
|
|
|
|
[AlertSourceAction.CreateExternalAlertRule]: [ |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
], |
|
|
|
|
[AlertSourceAction.ViewExternalAlertRule]: [ |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingRuleExternalRead), |
|
|
|
|
], |
|
|
|
|
[AlertSourceAction.UpdateExternalAlertRule]: [ |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
], |
|
|
|
|
[AlertSourceAction.DeleteExternalAlertRule]: [ |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
], |
|
|
|
|
/** |
|
|
|
|
* This one will check for alerting abilities that don't apply to any particular alert source or alert rule |
|
|
|
|
*/ |
|
|
|
|
export const useAlertingAbilities = (): Abilities<AlertingAction> => { |
|
|
|
|
return { |
|
|
|
|
// internal (Grafana managed)
|
|
|
|
|
[AlertingAction.CreateAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleCreate), |
|
|
|
|
[AlertingAction.ViewAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleRead), |
|
|
|
|
[AlertingAction.UpdateAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleUpdate), |
|
|
|
|
[AlertingAction.DeleteAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleDelete), |
|
|
|
|
[AlertingAction.ExportGrafanaManagedRules]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleRead), |
|
|
|
|
|
|
|
|
|
// external
|
|
|
|
|
[AlertingAction.CreateExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
[AlertingAction.ViewExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalRead), |
|
|
|
|
[AlertingAction.UpdateExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
[AlertingAction.DeleteExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalWrite), |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const useAlertingAbility = (action: AlertingAction): Ability => { |
|
|
|
|
const allAbilities = useAlertingAbilities(); |
|
|
|
|
return allAbilities[action]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* This hook will check if we support the action and have sufficient permissions for it on a single alert rule |
|
|
|
|
*/ |
|
|
|
|
export function useAlertRuleAbility(rule: CombinedRule, action: AlertRuleAction): Ability { |
|
|
|
|
const abilities = useAllAlertRuleAbilities(rule); |
|
|
|
|
|
|
|
|
|
return useMemo(() => { |
|
|
|
|
return abilities[action]; |
|
|
|
|
}, [abilities, action]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function useAlertRuleAbilities(rule: CombinedRule, actions: AlertRuleAction[]): Ability[] { |
|
|
|
|
const abilities = useAllAlertRuleAbilities(rule); |
|
|
|
|
|
|
|
|
|
return useMemo(() => { |
|
|
|
|
return actions.map((action) => abilities[action]); |
|
|
|
|
}, [abilities, actions]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities<AlertRuleAction> { |
|
|
|
|
const rulesSource = rule.namespace.rulesSource; |
|
|
|
|
const rulesSourceName = typeof rulesSource === 'string' ? rulesSource : rulesSource.name; |
|
|
|
|
|
|
|
|
|
const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); |
|
|
|
|
const isFederated = isFederatedRuleGroup(rule.group); |
|
|
|
|
|
|
|
|
|
// if a rule is either provisioned or a federated rule, we don't allow it to be removed or edited
|
|
|
|
|
const immutableRule = isProvisioned || isFederated; |
|
|
|
|
|
|
|
|
|
// TODO refactor this hook maybe
|
|
|
|
|
const { |
|
|
|
|
isEditable, |
|
|
|
|
isRemovable, |
|
|
|
|
isRulerAvailable = false, |
|
|
|
|
loading, |
|
|
|
|
} = useIsRuleEditable(rulesSourceName, rule.rulerRule); |
|
|
|
|
const [_, exportAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules); |
|
|
|
|
|
|
|
|
|
// while we gather info, pretend it's not supported
|
|
|
|
|
const MaybeSupported = loading ? NotSupported : isRulerAvailable; |
|
|
|
|
const MaybeSupportedUnlessImmutable = immutableRule ? NotSupported : MaybeSupported; |
|
|
|
|
|
|
|
|
|
const rulesPermissions = getRulesPermissions(rulesSourceName); |
|
|
|
|
const canSilence = useCanSilence(rulesSource); |
|
|
|
|
|
|
|
|
|
const abilities: Abilities<AlertRuleAction> = { |
|
|
|
|
[AlertRuleAction.Duplicate]: toAbility(MaybeSupported, rulesPermissions.create), |
|
|
|
|
[AlertRuleAction.View]: toAbility(AlwaysSupported, rulesPermissions.read), |
|
|
|
|
[AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false], |
|
|
|
|
[AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false], |
|
|
|
|
[AlertRuleAction.Explore]: toAbility(AlwaysSupported, AccessControlAction.DataSourcesExplore), |
|
|
|
|
[AlertRuleAction.Silence]: canSilence, |
|
|
|
|
[AlertRuleAction.ModifyExport]: [MaybeSupported, exportAllowed], |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return abilities; |
|
|
|
@ -115,72 +192,48 @@ export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> { |
|
|
|
|
// list out all of the abilities, and if the user has permissions to perform them
|
|
|
|
|
const abilities: Abilities<AlertmanagerAction> = { |
|
|
|
|
// -- configuration --
|
|
|
|
|
[AlertmanagerAction.ViewExternalConfiguration]: [ |
|
|
|
|
[AlertmanagerAction.ViewExternalConfiguration]: toAbility( |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingNotificationsExternalRead), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.UpdateExternalConfiguration]: [ |
|
|
|
|
AccessControlAction.AlertingNotificationsExternalRead |
|
|
|
|
), |
|
|
|
|
[AlertmanagerAction.UpdateExternalConfiguration]: toAbility( |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(AccessControlAction.AlertingNotificationsExternalWrite), |
|
|
|
|
], |
|
|
|
|
AccessControlAction.AlertingNotificationsExternalWrite |
|
|
|
|
), |
|
|
|
|
// -- contact points --
|
|
|
|
|
[AlertmanagerAction.CreateContactPoint]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.create)], |
|
|
|
|
[AlertmanagerAction.ViewContactPoint]: [AlwaysSupported, ctx.hasPermission(notificationsPermissions.read)], |
|
|
|
|
[AlertmanagerAction.UpdateContactPoint]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.update)], |
|
|
|
|
[AlertmanagerAction.DeleteContactPoint]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.delete)], |
|
|
|
|
[AlertmanagerAction.CreateContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.create), |
|
|
|
|
[AlertmanagerAction.ViewContactPoint]: toAbility(AlwaysSupported, notificationsPermissions.read), |
|
|
|
|
[AlertmanagerAction.UpdateContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.update), |
|
|
|
|
[AlertmanagerAction.DeleteContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.delete), |
|
|
|
|
// only Grafana flavored alertmanager supports exporting
|
|
|
|
|
[AlertmanagerAction.ExportContactPoint]: [ |
|
|
|
|
isGrafanaFlavoredAlertmanager, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.read), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.ExportContactPoint]: toAbility(isGrafanaFlavoredAlertmanager, notificationsPermissions.read), |
|
|
|
|
// -- notification templates --
|
|
|
|
|
[AlertmanagerAction.CreateNotificationTemplate]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.create), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.ViewNotificationTemplate]: [AlwaysSupported, ctx.hasPermission(notificationsPermissions.read)], |
|
|
|
|
[AlertmanagerAction.UpdateNotificationTemplate]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.update), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.DeleteNotificationTemplate]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.delete), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.CreateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.create), |
|
|
|
|
[AlertmanagerAction.ViewNotificationTemplate]: toAbility(AlwaysSupported, notificationsPermissions.read), |
|
|
|
|
[AlertmanagerAction.UpdateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.update), |
|
|
|
|
[AlertmanagerAction.DeleteNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.delete), |
|
|
|
|
// -- notification policies --
|
|
|
|
|
[AlertmanagerAction.CreateNotificationPolicy]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.create), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.ViewNotificationPolicyTree]: [ |
|
|
|
|
AlwaysSupported, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.read), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.UpdateNotificationPolicyTree]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.update), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.DeleteNotificationPolicy]: [ |
|
|
|
|
hasConfigurationAPI, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.delete), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.ExportNotificationPolicies]: [ |
|
|
|
|
[AlertmanagerAction.CreateNotificationPolicy]: toAbility(hasConfigurationAPI, notificationsPermissions.create), |
|
|
|
|
[AlertmanagerAction.ViewNotificationPolicyTree]: toAbility(AlwaysSupported, notificationsPermissions.read), |
|
|
|
|
[AlertmanagerAction.UpdateNotificationPolicyTree]: toAbility(hasConfigurationAPI, notificationsPermissions.update), |
|
|
|
|
[AlertmanagerAction.DeleteNotificationPolicy]: toAbility(hasConfigurationAPI, notificationsPermissions.delete), |
|
|
|
|
[AlertmanagerAction.ExportNotificationPolicies]: toAbility( |
|
|
|
|
isGrafanaFlavoredAlertmanager, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.read), |
|
|
|
|
], |
|
|
|
|
[AlertmanagerAction.DecryptSecrets]: [ |
|
|
|
|
notificationsPermissions.read |
|
|
|
|
), |
|
|
|
|
[AlertmanagerAction.DecryptSecrets]: toAbility( |
|
|
|
|
isGrafanaFlavoredAlertmanager, |
|
|
|
|
ctx.hasPermission(notificationsPermissions.provisioning.readSecrets), |
|
|
|
|
], |
|
|
|
|
notificationsPermissions.provisioning.readSecrets |
|
|
|
|
), |
|
|
|
|
// -- silences --
|
|
|
|
|
[AlertmanagerAction.CreateSilence]: [hasConfigurationAPI, ctx.hasPermission(instancePermissions.create)], |
|
|
|
|
[AlertmanagerAction.ViewSilence]: [AlwaysSupported, ctx.hasPermission(instancePermissions.read)], |
|
|
|
|
[AlertmanagerAction.UpdateSilence]: [hasConfigurationAPI, ctx.hasPermission(instancePermissions.update)], |
|
|
|
|
[AlertmanagerAction.CreateSilence]: toAbility(hasConfigurationAPI, instancePermissions.create), |
|
|
|
|
[AlertmanagerAction.ViewSilence]: toAbility(AlwaysSupported, instancePermissions.read), |
|
|
|
|
[AlertmanagerAction.UpdateSilence]: toAbility(hasConfigurationAPI, instancePermissions.update), |
|
|
|
|
// -- mute timtings --
|
|
|
|
|
[AlertmanagerAction.CreateMuteTiming]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.create)], |
|
|
|
|
[AlertmanagerAction.ViewMuteTiming]: [AlwaysSupported, ctx.hasPermission(notificationsPermissions.read)], |
|
|
|
|
[AlertmanagerAction.UpdateMuteTiming]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.update)], |
|
|
|
|
[AlertmanagerAction.DeleteMuteTiming]: [hasConfigurationAPI, ctx.hasPermission(notificationsPermissions.delete)], |
|
|
|
|
[AlertmanagerAction.CreateMuteTiming]: toAbility(hasConfigurationAPI, notificationsPermissions.create), |
|
|
|
|
[AlertmanagerAction.ViewMuteTiming]: toAbility(AlwaysSupported, notificationsPermissions.read), |
|
|
|
|
[AlertmanagerAction.UpdateMuteTiming]: toAbility(hasConfigurationAPI, notificationsPermissions.update), |
|
|
|
|
[AlertmanagerAction.DeleteMuteTiming]: toAbility(hasConfigurationAPI, notificationsPermissions.delete), |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return abilities; |
|
|
|
@ -202,7 +255,31 @@ export function useAlertmanagerAbilities(actions: AlertmanagerAction[]): Ability |
|
|
|
|
}, [abilities, actions]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function useAlertSourceAbility(action: AlertSourceAction): Ability { |
|
|
|
|
const abilities = useAlertSourceAbilities(); |
|
|
|
|
return useMemo(() => abilities[action], [abilities, action]); |
|
|
|
|
/** |
|
|
|
|
* We don't want to show the silence button if either |
|
|
|
|
* 1. the user has no permissions to create silences |
|
|
|
|
* 2. the admin has configured to only send instances to external AMs |
|
|
|
|
*/ |
|
|
|
|
function useCanSilence(rulesSource: RulesSource): [boolean, boolean] { |
|
|
|
|
const isGrafanaManagedRule = rulesSource === GRAFANA_RULES_SOURCE_NAME; |
|
|
|
|
|
|
|
|
|
const { useGetAlertmanagerChoiceStatusQuery } = alertmanagerApi; |
|
|
|
|
const { currentData: amConfigStatus, isLoading } = useGetAlertmanagerChoiceStatusQuery(undefined, { |
|
|
|
|
skip: !isGrafanaManagedRule, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// we don't support silencing when the rule is not a Grafana managed rule
|
|
|
|
|
// we simply don't know what Alertmanager the ruler is sending alerts to
|
|
|
|
|
if (!isGrafanaManagedRule || isLoading) { |
|
|
|
|
return [false, false]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const interactsOnlyWithExternalAMs = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.External; |
|
|
|
|
const interactsWithAll = amConfigStatus?.alertmanagersChoice === AlertmanagerChoice.All; |
|
|
|
|
const silenceSupported = !interactsOnlyWithExternalAMs || interactsWithAll; |
|
|
|
|
|
|
|
|
|
return toAbility(silenceSupported, AccessControlAction.AlertingInstanceCreate); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// just a convenient function
|
|
|
|
|
const toAbility = (supported: boolean, action: AccessControlAction): Ability => [supported, ctx.hasPermission(action)]; |
|
|
|
|