diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go
index 4441b75d658..2d64d9c6ade 100644
--- a/pkg/services/navtree/navtreeimpl/navtree.go
+++ b/pkg/services/navtree/navtreeimpl/navtree.go
@@ -435,12 +435,16 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
ac.EvalPermission(ac.ActionAlertingReceiversRead),
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
ac.EvalPermission(ac.ActionAlertingReceiversCreate),
+
+ ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesRead),
+ ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesWrite),
+ ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesDelete),
)
}
if hasAccess(ac.EvalAny(contactPointsPerms...)) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
- Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
+ Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
Icon: "comment-alt-share",
})
}
diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx
index bc41d7056c9..ef88f26d6c6 100644
--- a/public/app/features/alerting/routes.tsx
+++ b/public/app/features/alerting/routes.tsx
@@ -4,6 +4,7 @@ import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/type
import { AccessControlAction } from 'app/types';
import { PERMISSIONS_CONTACT_POINTS } from './unified/components/contact-points/permissions';
+import { PERMISSIONS_TEMPLATES } from './unified/components/templates/permissions';
import { evaluateAccess } from './unified/utils/access-control';
export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
@@ -104,6 +105,7 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
AccessControlAction.AlertingNotificationsRead,
AccessControlAction.AlertingNotificationsExternalRead,
...PERMISSIONS_CONTACT_POINTS,
+ ...PERMISSIONS_TEMPLATES,
]),
component: importAlertingComponent(
() =>
@@ -150,6 +152,7 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
roles: evaluateAccess([
AccessControlAction.AlertingNotificationsRead,
AccessControlAction.AlertingNotificationsExternalRead,
+ ...PERMISSIONS_TEMPLATES,
]),
component: importAlertingComponent(
() => import(/* webpackChunkName: "Templates" */ 'app/features/alerting/unified/Templates')
diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
index 7f89081d217..6366c421d9a 100644
--- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
+++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
@@ -164,31 +164,39 @@ const NotificationTemplatesTab = () => {
);
};
-const useTabQueryParam = () => {
+const useTabQueryParam = (defaultTab: ActiveTab) => {
const [queryParams, setQueryParams] = useURLSearchParams();
const param = useMemo(() => {
const queryParam = queryParams.get('tab');
if (!queryParam || !Object.values(ActiveTab).map(String).includes(queryParam)) {
- return ActiveTab.ContactPoints;
+ return defaultTab;
}
- return queryParam || ActiveTab.ContactPoints;
- }, [queryParams]);
+ return queryParam || defaultTab;
+ }, [defaultTab, queryParams]);
const setParam = (tab: ActiveTab) => setQueryParams({ tab });
-
return [param, setParam] as const;
};
export const ContactPointsPageContents = () => {
const { selectedAlertmanager } = useAlertmanager();
- const [activeTab, setActiveTab] = useTabQueryParam();
+ const [, showContactPointsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
+ const [, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
+
+ // Depending on permissions, user may not have access to all tabs,
+ // but we can default to picking the first one that they definitely _do_ have access to
+ const defaultTab = [
+ showContactPointsTab && ActiveTab.ContactPoints,
+ showTemplatesTab && ActiveTab.NotificationTemplates,
+ ].filter((tab) => !!tab)[0];
+
+ const [activeTab, setActiveTab] = useTabQueryParam(defaultTab);
const { contactPoints } = useContactPointsWithStatus({
alertmanager: selectedAlertmanager!,
});
- const [_, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
const showingContactPoints = activeTab === ActiveTab.ContactPoints;
const showNotificationTemplates = activeTab === ActiveTab.NotificationTemplates;
@@ -198,12 +206,14 @@ export const ContactPointsPageContents = () => {
- setActiveTab(ActiveTab.ContactPoints)}
- />
+ {showContactPointsTab && (
+ setActiveTab(ActiveTab.ContactPoints)}
+ />
+ )}
{showTemplatesTab && (
)}
-
+
>
+) =>
+ render(
+
+ {children}
+ ,
+ { historyOptions }
+ );
+
setupMswServer();
const ui = {
diff --git a/public/app/features/alerting/unified/components/templates/permissions.ts b/public/app/features/alerting/unified/components/templates/permissions.ts
new file mode 100644
index 00000000000..a92758f1a1d
--- /dev/null
+++ b/public/app/features/alerting/unified/components/templates/permissions.ts
@@ -0,0 +1,11 @@
+import { AccessControlAction } from 'app/types';
+
+/**
+ * List of all permissions that allow templates read/write functionality
+ */
+
+export const PERMISSIONS_TEMPLATES = [
+ AccessControlAction.AlertingTemplatesRead,
+ AccessControlAction.AlertingTemplatesWrite,
+ AccessControlAction.AlertingTemplatesDelete,
+];
diff --git a/public/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts
index dac04eb248a..c60acd5e7f2 100644
--- a/public/app/features/alerting/unified/hooks/useAbilities.ts
+++ b/public/app/features/alerting/unified/hooks/useAbilities.ts
@@ -231,17 +231,41 @@ export function useAllAlertmanagerAbilities(): Abilities {
// TODO: Move this into the permissions config and generalise that code to allow for an array of permissions
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversCreate : null
),
- [AlertmanagerAction.ViewContactPoint]: toAbility(AlwaysSupported, notificationsPermissions.read),
- [AlertmanagerAction.UpdateContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.update),
- [AlertmanagerAction.DeleteContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.delete),
+ [AlertmanagerAction.ViewContactPoint]: toAbility(
+ AlwaysSupported,
+ notificationsPermissions.read,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversRead : null
+ ),
+ [AlertmanagerAction.UpdateContactPoint]: toAbility(
+ hasConfigurationAPI,
+ notificationsPermissions.update,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversWrite : null
+ ),
+ [AlertmanagerAction.DeleteContactPoint]: toAbility(
+ hasConfigurationAPI,
+ notificationsPermissions.delete,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversWrite : null
+ ),
// At the time of writing, only Grafana flavored alertmanager supports exporting,
// and if a user can view the contact point, then they can also export it
// So the only check we make is if the alertmanager is Grafana flavored
[AlertmanagerAction.ExportContactPoint]: [isGrafanaFlavoredAlertmanager, isGrafanaFlavoredAlertmanager],
// -- notification templates --
- [AlertmanagerAction.CreateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.create),
- [AlertmanagerAction.ViewNotificationTemplate]: toAbility(AlwaysSupported, notificationsPermissions.read),
- [AlertmanagerAction.UpdateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.update),
+ [AlertmanagerAction.CreateNotificationTemplate]: toAbility(
+ hasConfigurationAPI,
+ notificationsPermissions.create,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesWrite : null
+ ),
+ [AlertmanagerAction.ViewNotificationTemplate]: toAbility(
+ AlwaysSupported,
+ notificationsPermissions.read,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesRead : null
+ ),
+ [AlertmanagerAction.UpdateNotificationTemplate]: toAbility(
+ hasConfigurationAPI,
+ notificationsPermissions.update,
+ isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesWrite : null
+ ),
[AlertmanagerAction.DeleteNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.delete),
// -- notification policies --
[AlertmanagerAction.CreateNotificationPolicy]: toAbility(hasConfigurationAPI, notificationsPermissions.create),
diff --git a/public/app/features/alerting/unified/utils/datasource.ts b/public/app/features/alerting/unified/utils/datasource.ts
index 764de417bfb..64275f04169 100644
--- a/public/app/features/alerting/unified/utils/datasource.ts
+++ b/public/app/features/alerting/unified/utils/datasource.ts
@@ -12,6 +12,7 @@ import { PromApplication, RulesSourceApplication } from 'app/types/unified-alert
import { alertmanagerApi } from '../api/alertmanagerApi';
import { PERMISSIONS_CONTACT_POINTS } from '../components/contact-points/permissions';
+import { PERMISSIONS_TEMPLATES } from '../components/templates/permissions';
import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
import { isAlertManagerWithConfigAPI } from '../state/AlertmanagerContext';
@@ -151,6 +152,7 @@ export function getAlertManagerDataSourcesByPermission(permission: 'instance' |
const builtinAlertmanagerPermissions = [
...Object.values(permissions).flatMap((permissions) => permissions.grafana),
...PERMISSIONS_CONTACT_POINTS,
+ ...PERMISSIONS_TEMPLATES,
];
const hasPermissionsForInternalAlertmanager = builtinAlertmanagerPermissions.some((permission) =>
diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts
index de2c79b0362..01f5b4e1f6b 100644
--- a/public/app/types/accessControl.ts
+++ b/public/app/types/accessControl.ts
@@ -134,6 +134,11 @@ export enum AccessControlAction {
AlertingReceiversWrite = 'alert.notifications.receivers:write',
AlertingReceiversRead = 'alert.notifications.receivers:read',
+ // Alerting templates actions
+ AlertingTemplatesRead = 'alert.notifications.templates:read',
+ AlertingTemplatesWrite = 'alert.notifications.templates:write',
+ AlertingTemplatesDelete = 'alert.notifications.templates:delete',
+
ActionAPIKeysRead = 'apikeys:read',
ActionAPIKeysCreate = 'apikeys:create',
ActionAPIKeysDelete = 'apikeys:delete',