The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/services/navtree/navtreeimpl/navtree.go

589 lines
20 KiB

package navtreeimpl
import (
"sort"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/navtree"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
"github.com/grafana/grafana/pkg/setting"
"github.com/open-feature/go-sdk/openfeature"
)
type ServiceImpl struct {
cfg *setting.Cfg
log log.Logger
accessControl ac.AccessControl
authnService authn.Service
pluginStore pluginstore.Store
pluginSettings pluginsettings.Service
starService star.Service
features featuremgmt.FeatureToggles
openFeature *featuremgmt.OpenFeatureService
dashboardService dashboards.DashboardService
accesscontrolService ac.Service
kvStore kvstore.KVStore
apiKeyService apikey.Service
license licensing.Licensing
// Navigation
navigationAppConfig map[string]NavigationAppConfig
navigationAppPathConfig map[string]NavigationAppConfig
}
type NavigationAppConfig struct {
SectionID string
SortWeight int64
Text string
Icon string
SubTitle string
IsNew bool
}
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service,
features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service,
license licensing.Licensing, authnService authn.Service, openFeature *featuremgmt.OpenFeatureService) navtree.Service {
service := &ServiceImpl{
cfg: cfg,
log: log.New("navtree service"),
accessControl: accessControl,
authnService: authnService,
pluginStore: pluginStore,
pluginSettings: pluginSettings,
starService: starService,
features: features,
openFeature: openFeature,
dashboardService: dashboardService,
accesscontrolService: accesscontrolService,
kvStore: kvStore,
apiKeyService: apiKeyService,
license: license,
}
service.readNavigationSettings()
return service
}
//nolint:gocyclo
func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Preference) (*navtree.NavTreeRoot, error) {
hasAccess := ac.HasAccess(s.accessControl, c)
treeRoot := &navtree.NavTreeRoot{}
ctx := c.Req.Context()
treeRoot.AddSection(s.getHomeNode(c, prefs))
if hasAccess(ac.EvalPermission(dashboards.ActionDashboardsRead)) {
starredItemsLinks, err := s.buildStarredItemsNavLinks(c)
if err != nil {
return nil, err
}
treeRoot.AddSection(&navtree.NavLink{
Text: "Starred",
Id: "starred",
Icon: "star",
SortWeight: navtree.WeightSavedItems,
Children: starredItemsLinks,
EmptyMessageId: "starred-empty",
Url: s.cfg.AppSubURL + "/dashboards?starred",
})
}
if c.IsPublicDashboardView() || hasAccess(ac.EvalAny(
ac.EvalPermission(dashboards.ActionFoldersRead), ac.EvalPermission(dashboards.ActionFoldersCreate),
ac.EvalPermission(dashboards.ActionDashboardsRead), ac.EvalPermission(dashboards.ActionDashboardsCreate)),
) {
dashboardChildLinks := s.buildDashboardNavLinks(c)
dashboardLink := &navtree.NavLink{
Text: "Dashboards",
Id: navtree.NavIDDashboards,
SubTitle: "Create and manage dashboards to visualize your data",
Icon: "apps",
Url: s.cfg.AppSubURL + "/dashboards",
SortWeight: navtree.WeightDashboard,
Children: dashboardChildLinks,
}
treeRoot.AddSection(dashboardLink)
}
if s.cfg.ExploreEnabled && hasAccess(ac.EvalPermission(ac.ActionDatasourcesExplore)) {
treeRoot.AddSection(&navtree.NavLink{
Text: "Explore",
Id: navtree.NavIDExplore,
SubTitle: "Explore your data",
Icon: "compass",
SortWeight: navtree.WeightExplore,
Url: s.cfg.AppSubURL + "/explore",
})
}
if hasAccess(ac.EvalPermission(ac.ActionDatasourcesExplore)) {
drilldownChildNavLinks := s.buildDrilldownNavLinks(c)
treeRoot.AddSection(&navtree.NavLink{
Text: "Drilldown",
Id: navtree.NavIDDrilldown,
SubTitle: "Drill down into your data using Grafana's powerful queryless apps",
Icon: "drilldown",
IsNew: true,
SortWeight: navtree.WeightDrilldown,
Url: s.cfg.AppSubURL + "/drilldown",
Children: drilldownChildNavLinks,
})
}
if s.cfg.ProfileEnabled && c.IsSignedIn {
treeRoot.AddSection(s.getProfileNode(c))
}
_, uaIsDisabledForOrg := s.cfg.UnifiedAlerting.DisabledOrgs[c.GetOrgID()]
uaVisibleForOrg := s.cfg.UnifiedAlerting.IsEnabled() && !uaIsDisabledForOrg
if uaVisibleForOrg {
if alertingSection := s.buildAlertNavLinks(c); alertingSection != nil {
treeRoot.AddSection(alertingSection)
}
}
if connectionsSection := s.buildDataConnectionsNavLink(c); connectionsSection != nil {
treeRoot.AddSection(connectionsSection)
}
orgAdminNode, err := s.getAdminNode(c)
if orgAdminNode != nil && len(orgAdminNode.Children) > 0 {
treeRoot.AddSection(orgAdminNode)
} else if err != nil {
return nil, err
}
s.addHelpLinks(treeRoot, c)
if err := s.addAppLinks(treeRoot, c); err != nil {
return nil, err
}
// remove user access if empty. Happens if grafana-auth-app is not injected
if sec := treeRoot.FindById(navtree.NavIDCfgAccess); sec != nil && len(sec.Children) == 0 {
treeRoot.RemoveSectionByID(navtree.NavIDCfgAccess)
}
// double-check and remove admin menu if empty
if sec := treeRoot.FindById(navtree.NavIDCfg); sec != nil && len(sec.Children) == 0 {
treeRoot.RemoveSectionByID(navtree.NavIDCfg)
}
enabled := s.openFeature.Client.Boolean(ctx, featuremgmt.FlagPinNavItems, true, openfeature.TransactionContext(ctx))
if enabled && c.IsSignedIn {
treeRoot.AddSection(&navtree.NavLink{
Text: "Bookmarks",
Id: navtree.NavIDBookmarks,
Icon: "bookmark",
SortWeight: navtree.WeightBookmarks,
Children: []*navtree.NavLink{},
EmptyMessageId: "bookmarks-empty",
Url: s.cfg.AppSubURL + "/bookmarks",
})
}
return treeRoot, nil
}
func (s *ServiceImpl) getHomeNode(c *contextmodel.ReqContext, prefs *pref.Preference) *navtree.NavLink {
homeUrl := s.cfg.AppSubURL + "/"
if !c.IsSignedIn && !s.cfg.Anonymous.Enabled {
homeUrl = s.cfg.AppSubURL + "/login"
} else {
homePage := s.cfg.HomePage
if prefs.HomeDashboardID == 0 && len(homePage) > 0 {
homeUrl = homePage
}
}
homeNode := &navtree.NavLink{
Text: "Home",
Id: "home",
Url: homeUrl,
Icon: "home-alt",
SortWeight: navtree.WeightHome,
}
ctx := c.Req.Context()
if s.features.IsEnabled(ctx, featuremgmt.FlagHomeSetupGuide) {
var children []*navtree.NavLink
// setup guide (a submenu item under Home)
children = append(children, &navtree.NavLink{
Id: "home-setup-guide",
Text: "Setup guide",
Url: homeUrl + "/setup-guide",
SortWeight: navtree.WeightHome,
})
homeNode.Children = children
}
return homeNode
}
func isSupportBundlesEnabled(s *ServiceImpl) bool {
return s.cfg.SectionWithEnvOverrides("support_bundles").Key("enabled").MustBool(true)
}
func (s *ServiceImpl) addHelpLinks(treeRoot *navtree.NavTreeRoot, c *contextmodel.ReqContext) {
if s.cfg.HelpEnabled {
// The version subtitle is set later by NavTree.ApplyHelpVersion
helpNode := &navtree.NavLink{
Text: "Help",
Id: "help",
Url: "#",
Icon: "question-circle",
SortWeight: navtree.WeightHelp,
Children: []*navtree.NavLink{},
}
treeRoot.AddSection(helpNode)
hasAccess := ac.HasAccess(s.accessControl, c)
supportBundleAccess := ac.EvalAny(
ac.EvalPermission(supportbundlesimpl.ActionRead),
ac.EvalPermission(supportbundlesimpl.ActionCreate),
)
if isSupportBundlesEnabled(s) && hasAccess(supportBundleAccess) {
supportBundleNode := &navtree.NavLink{
Text: "Support bundles",
Id: "support-bundles",
Url: "/support-bundles",
Icon: "wrench",
SortWeight: navtree.WeightHelp,
}
helpNode.Children = append(helpNode.Children, supportBundleNode)
}
}
}
func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLink {
// Only set login if it's different from the name
var login string
if c.GetLogin() != c.GetName() {
login = c.GetLogin()
}
gravatarURL := dtos.GetGravatarUrl(s.cfg, c.GetEmail())
children := []*navtree.NavLink{
{
Text: "Profile", Id: "profile/settings", Url: s.cfg.AppSubURL + "/profile", Icon: "sliders-v-alt",
},
}
children = append(children, &navtree.NavLink{
Text: "Notification history", Id: "profile/notifications", Url: s.cfg.AppSubURL + "/profile/notifications", Icon: "bell",
})
if s.cfg.AddChangePasswordLink() {
children = append(children, &navtree.NavLink{
Text: "Change password", Id: "profile/password", Url: s.cfg.AppSubURL + "/profile/password",
Icon: "lock",
})
}
return &navtree.NavLink{
Text: c.GetName(),
SubTitle: login,
Id: "profile",
Img: gravatarURL,
Url: s.cfg.AppSubURL + "/profile",
SortWeight: navtree.WeightProfile,
Children: children,
RoundIcon: true,
}
}
func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*navtree.NavLink, error) {
starredItemsChildNavs := []*navtree.NavLink{}
userID, _ := identity.UserIdentifier(c.GetID())
query := star.GetUserStarsQuery{
UserID: userID,
}
starredDashboardResult, err := s.starService.GetByUser(c.Req.Context(), &query)
if err != nil {
return nil, err
}
if len(starredDashboardResult.UserStars) > 0 {
var uids []string
for uid := range starredDashboardResult.UserStars {
uids = append(uids, uid)
}
starredDashboards, err := s.dashboardService.SearchDashboards(c.Req.Context(), &dashboards.FindPersistedDashboardsQuery{
DashboardUIDs: uids,
Type: searchstore.TypeDashboard,
OrgId: c.GetOrgID(),
SignedInUser: c.SignedInUser,
})
if err != nil {
return nil, err
}
// Set a loose limit to the first 50 starred dashboards found
if len(starredDashboards) > 50 {
starredDashboards = starredDashboards[:50]
}
sort.Slice(starredDashboards, func(i, j int) bool {
return starredDashboards[i].Title < starredDashboards[j].Title
})
for _, starredItem := range starredDashboards {
starredItemsChildNavs = append(starredItemsChildNavs, &navtree.NavLink{
Id: "starred/" + starredItem.UID,
Text: starredItem.Title,
Url: starredItem.URL,
})
}
}
return starredItemsChildNavs, nil
}
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
hasAccess := ac.HasAccess(s.accessControl, c)
dashboardChildNavs := []*navtree.NavLink{}
if c.IsSignedIn {
if c.HasRole(org.RoleViewer) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Playlists", SubTitle: "Groups of dashboards that are displayed in a sequence", Id: "dashboards/playlists", Url: s.cfg.AppSubURL + "/playlists", Icon: "presentation-play",
})
}
if s.cfg.SnapshotEnabled && hasAccess(ac.EvalPermission(dashboards.ActionSnapshotsRead)) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Snapshots",
SubTitle: "Interactive, publicly available, point-in-time representations of dashboards",
Id: "dashboards/snapshots",
Url: s.cfg.AppSubURL + "/dashboard/snapshots",
Icon: "camera",
})
}
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Library panels",
SubTitle: "Reusable panels that can be added to multiple dashboards",
Id: "dashboards/library-panels",
Url: s.cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
})
if s.cfg.PublicDashboardsEnabled {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Public dashboards",
Id: "dashboards/public",
Url: s.cfg.AppSubURL + "/dashboard/public",
Icon: "library-panel",
})
}
}
if hasAccess(ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "New dashboard", Icon: "plus", Url: s.cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", IsCreateAction: true,
})
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Import dashboard", SubTitle: "Import dashboard from file or Grafana.com", Id: "dashboards/import", Icon: "plus",
Url: s.cfg.AppSubURL + "/dashboard/import", HideFromTabs: true, IsCreateAction: true,
})
}
return dashboardChildNavs
}
func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink {
hasAccess := ac.HasAccess(s.accessControl, c)
var alertChildNavs []*navtree.NavLink
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
})
}
Alerting: Add manage permissions UI logic for Contact Points (#92885) * Add showPolicies prop * Add manage permissions component for easier reuse within alerting * Add method for checking whether to show access control within alerting * Remove accidental console.log from main * Tweak styling for contact point width and add manage permissions drawer * Improve typing for access control type response * Add basic test for manage permissions on contact points list * Only show manage permissions if grafana AM and alertingApiServer is enabled * Update i18n * Add test utils for turning features on and back off * Add access control handlers * Update tests with new util * Pass AM in and add tests * Receiver OSS resource permissions There is a complication that is not fully addressed: Viewer defaults to read:* and Editor defaults to read+write+delete:* This is different to other resource permissions where non-admin are not granted any global permissions and instead access is handled solely by resource-specific permissions that are populated on create and removed on delete. This allows them to easily remove permission to view or edit a single resource from basic roles. The reason this is tricky here is that we have multiple APIs that can create/delete receivers: config api, provisioning api, and k8s receivers api. Config api in particular is not well-equipped to determine when creates/deletes are happening and thus ensuring that the proper resource-specific permissions are created/deleted is finicky. We would also have to create a migration to populate resource-specific permissions for all current receivers. This migration would need to be reset so it can run again if the flag is disabled. * Add access control permissions * Pass in contact point ID to receivers form * Temporarily remove access control check for contact points * Include access control metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/accessControl` * Include new permissions for contact points navbar * Fix receiver creator fixed role to not give global read * Include in-use metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/inUse` * Add receiver creator permission to receiver writer * Add receiver creator permission to navbar * Always allow listing receivers, don't return 403 * Remove receiver read precondition from receiver create Otherwise, Creator role will not be able to create their first receiver * Update routes permissions * Add further support for RBAC in contact points * Update routes permissions * Update contact points header logic * Back out test feature toggle refactor Not working atm, not sure why * Tidy up imports * Update mock permissions * Revert more test changes * Update i18n * Sync inuse metadata pr * Add back canAdmin permissions after main merge * Split out check for policies navtree item * Tidy up utils and imports and fix rules in use * Fix contact point tests and act warnings * Add missing ReceiverPermissionAdmin after merge conflict * Move contact points permissions * Only show contact points filter when permissions are correct * Move to constants * Fallback to empty array and remove labelSelectors (not needed) * Allow `toAbility` to take multiple actions * Show builtin alertmanager if contact points permission * Add empty state and hide templates if missing permissions * Translations * Tidy up mock data * Fix tests and templates permission * Update message for unused contact points * Don't return 403 when user lists receivers and has access to none * Fix receiver create not adding empty uid permissions * Move SetDefaultPermissions to ReceiverPermissionService * Have SetDefaultPermissions use uid from string Fixes circular dependency * Add FakeReceiverPermissionsService and fix test wiring * Implement resource permission handling in provisioning API and renames Create: Sets to default permissions Delete: Removes permissions Update: If receiver name is modified and the new name doesn't exist, it copies the permissions from the old receiver to the newly created one. If old receiver is now empty, it removes the old permissions as well. * Split contact point permissions checks for read/modify * Generalise getting annotation values from k8s entities * Proxy RouteDeleteAlertingConfig through MultiOrgAlertmanager * Cleanup permissions on config api reset and restore * Cleanup permissions on config api POST note this is still not available with feature flag enabled * Gate the permission manager behind FF until initial migration is added * Sync changes from config api PR * Switch to named export * Revert unnecessary changes * Revert Filter auth change and implement in k8s api only * Don't allow new scoped permissions to give access without FF Prevents complications around mixed support for the scoped permissions causing oddities in the UI. * Fix integration tests to account for list permission change * Move to `permissions` file * Add additional tests for contact points * Fix redirect for viewer on edit page * Combine alerting test utils and move to new file location * Allow new permissions to access provisioning export paths with FF * Always allow exporting if its grafana flavoured * Fix logic for showing auto generated policies * Fix delete logic for contact point only referenced by a rule * Suppress warning message when renaming a contact point * Clear team and role perm cache on receiver rename Prevents temporarily broken UI permissions after rename when a user's source of elevated permissions comes from a cached team or basic role permission. * Debug log failed cache clear on CopyPermissions --------- Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
9 months ago
contactPointsPerms := []ac.Evaluator{
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead),
}
// With the new alerting API, we have other permissions to consider. We don't want to consider these with the old
// alerting API to maintain backwards compatibility.
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingApiServer) {
contactPointsPerms = append(contactPointsPerms,
ac.EvalPermission(ac.ActionAlertingReceiversRead),
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
ac.EvalPermission(ac.ActionAlertingReceiversCreate),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesRead),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesWrite),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesDelete),
Alerting: Add manage permissions UI logic for Contact Points (#92885) * Add showPolicies prop * Add manage permissions component for easier reuse within alerting * Add method for checking whether to show access control within alerting * Remove accidental console.log from main * Tweak styling for contact point width and add manage permissions drawer * Improve typing for access control type response * Add basic test for manage permissions on contact points list * Only show manage permissions if grafana AM and alertingApiServer is enabled * Update i18n * Add test utils for turning features on and back off * Add access control handlers * Update tests with new util * Pass AM in and add tests * Receiver OSS resource permissions There is a complication that is not fully addressed: Viewer defaults to read:* and Editor defaults to read+write+delete:* This is different to other resource permissions where non-admin are not granted any global permissions and instead access is handled solely by resource-specific permissions that are populated on create and removed on delete. This allows them to easily remove permission to view or edit a single resource from basic roles. The reason this is tricky here is that we have multiple APIs that can create/delete receivers: config api, provisioning api, and k8s receivers api. Config api in particular is not well-equipped to determine when creates/deletes are happening and thus ensuring that the proper resource-specific permissions are created/deleted is finicky. We would also have to create a migration to populate resource-specific permissions for all current receivers. This migration would need to be reset so it can run again if the flag is disabled. * Add access control permissions * Pass in contact point ID to receivers form * Temporarily remove access control check for contact points * Include access control metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/accessControl` * Include new permissions for contact points navbar * Fix receiver creator fixed role to not give global read * Include in-use metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/inUse` * Add receiver creator permission to receiver writer * Add receiver creator permission to navbar * Always allow listing receivers, don't return 403 * Remove receiver read precondition from receiver create Otherwise, Creator role will not be able to create their first receiver * Update routes permissions * Add further support for RBAC in contact points * Update routes permissions * Update contact points header logic * Back out test feature toggle refactor Not working atm, not sure why * Tidy up imports * Update mock permissions * Revert more test changes * Update i18n * Sync inuse metadata pr * Add back canAdmin permissions after main merge * Split out check for policies navtree item * Tidy up utils and imports and fix rules in use * Fix contact point tests and act warnings * Add missing ReceiverPermissionAdmin after merge conflict * Move contact points permissions * Only show contact points filter when permissions are correct * Move to constants * Fallback to empty array and remove labelSelectors (not needed) * Allow `toAbility` to take multiple actions * Show builtin alertmanager if contact points permission * Add empty state and hide templates if missing permissions * Translations * Tidy up mock data * Fix tests and templates permission * Update message for unused contact points * Don't return 403 when user lists receivers and has access to none * Fix receiver create not adding empty uid permissions * Move SetDefaultPermissions to ReceiverPermissionService * Have SetDefaultPermissions use uid from string Fixes circular dependency * Add FakeReceiverPermissionsService and fix test wiring * Implement resource permission handling in provisioning API and renames Create: Sets to default permissions Delete: Removes permissions Update: If receiver name is modified and the new name doesn't exist, it copies the permissions from the old receiver to the newly created one. If old receiver is now empty, it removes the old permissions as well. * Split contact point permissions checks for read/modify * Generalise getting annotation values from k8s entities * Proxy RouteDeleteAlertingConfig through MultiOrgAlertmanager * Cleanup permissions on config api reset and restore * Cleanup permissions on config api POST note this is still not available with feature flag enabled * Gate the permission manager behind FF until initial migration is added * Sync changes from config api PR * Switch to named export * Revert unnecessary changes * Revert Filter auth change and implement in k8s api only * Don't allow new scoped permissions to give access without FF Prevents complications around mixed support for the scoped permissions causing oddities in the UI. * Fix integration tests to account for list permission change * Move to `permissions` file * Add additional tests for contact points * Fix redirect for viewer on edit page * Combine alerting test utils and move to new file location * Allow new permissions to access provisioning export paths with FF * Always allow exporting if its grafana flavoured * Fix logic for showing auto generated policies * Fix delete logic for contact point only referenced by a rule * Suppress warning message when renaming a contact point * Clear team and role perm cache on receiver rename Prevents temporarily broken UI permissions after rename when a user's source of elevated permissions comes from a cached team or basic role permission. * Debug log failed cache clear on CopyPermissions --------- Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
9 months ago
)
}
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",
Icon: "comment-alt-share",
})
Alerting: Add manage permissions UI logic for Contact Points (#92885) * Add showPolicies prop * Add manage permissions component for easier reuse within alerting * Add method for checking whether to show access control within alerting * Remove accidental console.log from main * Tweak styling for contact point width and add manage permissions drawer * Improve typing for access control type response * Add basic test for manage permissions on contact points list * Only show manage permissions if grafana AM and alertingApiServer is enabled * Update i18n * Add test utils for turning features on and back off * Add access control handlers * Update tests with new util * Pass AM in and add tests * Receiver OSS resource permissions There is a complication that is not fully addressed: Viewer defaults to read:* and Editor defaults to read+write+delete:* This is different to other resource permissions where non-admin are not granted any global permissions and instead access is handled solely by resource-specific permissions that are populated on create and removed on delete. This allows them to easily remove permission to view or edit a single resource from basic roles. The reason this is tricky here is that we have multiple APIs that can create/delete receivers: config api, provisioning api, and k8s receivers api. Config api in particular is not well-equipped to determine when creates/deletes are happening and thus ensuring that the proper resource-specific permissions are created/deleted is finicky. We would also have to create a migration to populate resource-specific permissions for all current receivers. This migration would need to be reset so it can run again if the flag is disabled. * Add access control permissions * Pass in contact point ID to receivers form * Temporarily remove access control check for contact points * Include access control metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/accessControl` * Include new permissions for contact points navbar * Fix receiver creator fixed role to not give global read * Include in-use metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/inUse` * Add receiver creator permission to receiver writer * Add receiver creator permission to navbar * Always allow listing receivers, don't return 403 * Remove receiver read precondition from receiver create Otherwise, Creator role will not be able to create their first receiver * Update routes permissions * Add further support for RBAC in contact points * Update routes permissions * Update contact points header logic * Back out test feature toggle refactor Not working atm, not sure why * Tidy up imports * Update mock permissions * Revert more test changes * Update i18n * Sync inuse metadata pr * Add back canAdmin permissions after main merge * Split out check for policies navtree item * Tidy up utils and imports and fix rules in use * Fix contact point tests and act warnings * Add missing ReceiverPermissionAdmin after merge conflict * Move contact points permissions * Only show contact points filter when permissions are correct * Move to constants * Fallback to empty array and remove labelSelectors (not needed) * Allow `toAbility` to take multiple actions * Show builtin alertmanager if contact points permission * Add empty state and hide templates if missing permissions * Translations * Tidy up mock data * Fix tests and templates permission * Update message for unused contact points * Don't return 403 when user lists receivers and has access to none * Fix receiver create not adding empty uid permissions * Move SetDefaultPermissions to ReceiverPermissionService * Have SetDefaultPermissions use uid from string Fixes circular dependency * Add FakeReceiverPermissionsService and fix test wiring * Implement resource permission handling in provisioning API and renames Create: Sets to default permissions Delete: Removes permissions Update: If receiver name is modified and the new name doesn't exist, it copies the permissions from the old receiver to the newly created one. If old receiver is now empty, it removes the old permissions as well. * Split contact point permissions checks for read/modify * Generalise getting annotation values from k8s entities * Proxy RouteDeleteAlertingConfig through MultiOrgAlertmanager * Cleanup permissions on config api reset and restore * Cleanup permissions on config api POST note this is still not available with feature flag enabled * Gate the permission manager behind FF until initial migration is added * Sync changes from config api PR * Switch to named export * Revert unnecessary changes * Revert Filter auth change and implement in k8s api only * Don't allow new scoped permissions to give access without FF Prevents complications around mixed support for the scoped permissions causing oddities in the UI. * Fix integration tests to account for list permission change * Move to `permissions` file * Add additional tests for contact points * Fix redirect for viewer on edit page * Combine alerting test utils and move to new file location * Allow new permissions to access provisioning export paths with FF * Always allow exporting if its grafana flavoured * Fix logic for showing auto generated policies * Fix delete logic for contact point only referenced by a rule * Suppress warning message when renaming a contact point * Clear team and role perm cache on receiver rename Prevents temporarily broken UI permissions after rename when a user's source of elevated permissions comes from a cached team or basic role permission. * Debug log failed cache clear on CopyPermissions --------- Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
9 months ago
}
if hasAccess(ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead),
Alerting: Consume k8s API for notification policies tree (#96147) * Add basic usage of K8s API for notification policies * Add permissions checks for navtree for routes * Add and update permissions for routing tree logic * Add capability to skip calling contact points hook * Conditionally show list of mute timings depending on permissions * Conditionally link to mute timings if user can see at least one * Add work in progress k8s handlers for routing tree * Update notification policy hooks * Wire up policies to permissions better (conditionally calling APIs) * Add additional checks for whether to show grafana AM * Add permission checks to access control * Remove accidental permissions after rebase * Update types and const for k8s routes * Improve statefulness and reset routing tree in tests * Update notif policy tests to check k8s and config API * Fix type assertion * Move non-grafana test out of .each * Make failure case safer * Override tag invalidation for notification policies API * Pass in error and add new error alert component * Add basic mock server conflict check * Add test to check user can save after a conflict * Add logic to allow reloading policies if changed by another user * Fix test * Update translations in Modals * Add ViewAlertGroups ability * Tweak provisioning logic and memoize AM config response * Update snapshots for useAbilities * Update result destructure * Use enums for provenance in routingtrees * Use consistent memoisation * Fix _metadata for vanilla AM * useAsync for error / update state * move k8s api error handling to separate file * use cause for error codes * Use `supported` bools from Alertmanager abilities and clarify default policy --------- Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com> Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
7 months ago
ac.EvalPermission(ac.ActionAlertingRoutesRead),
ac.EvalPermission(ac.ActionAlertingRoutesWrite),
ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsRead),
ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsWrite),
Alerting: Add manage permissions UI logic for Contact Points (#92885) * Add showPolicies prop * Add manage permissions component for easier reuse within alerting * Add method for checking whether to show access control within alerting * Remove accidental console.log from main * Tweak styling for contact point width and add manage permissions drawer * Improve typing for access control type response * Add basic test for manage permissions on contact points list * Only show manage permissions if grafana AM and alertingApiServer is enabled * Update i18n * Add test utils for turning features on and back off * Add access control handlers * Update tests with new util * Pass AM in and add tests * Receiver OSS resource permissions There is a complication that is not fully addressed: Viewer defaults to read:* and Editor defaults to read+write+delete:* This is different to other resource permissions where non-admin are not granted any global permissions and instead access is handled solely by resource-specific permissions that are populated on create and removed on delete. This allows them to easily remove permission to view or edit a single resource from basic roles. The reason this is tricky here is that we have multiple APIs that can create/delete receivers: config api, provisioning api, and k8s receivers api. Config api in particular is not well-equipped to determine when creates/deletes are happening and thus ensuring that the proper resource-specific permissions are created/deleted is finicky. We would also have to create a migration to populate resource-specific permissions for all current receivers. This migration would need to be reset so it can run again if the flag is disabled. * Add access control permissions * Pass in contact point ID to receivers form * Temporarily remove access control check for contact points * Include access control metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/accessControl` * Include new permissions for contact points navbar * Fix receiver creator fixed role to not give global read * Include in-use metadata in k8s receiver List & Get GET: Always included. LIST: Included by adding a label selector with value `grafana.com/inUse` * Add receiver creator permission to receiver writer * Add receiver creator permission to navbar * Always allow listing receivers, don't return 403 * Remove receiver read precondition from receiver create Otherwise, Creator role will not be able to create their first receiver * Update routes permissions * Add further support for RBAC in contact points * Update routes permissions * Update contact points header logic * Back out test feature toggle refactor Not working atm, not sure why * Tidy up imports * Update mock permissions * Revert more test changes * Update i18n * Sync inuse metadata pr * Add back canAdmin permissions after main merge * Split out check for policies navtree item * Tidy up utils and imports and fix rules in use * Fix contact point tests and act warnings * Add missing ReceiverPermissionAdmin after merge conflict * Move contact points permissions * Only show contact points filter when permissions are correct * Move to constants * Fallback to empty array and remove labelSelectors (not needed) * Allow `toAbility` to take multiple actions * Show builtin alertmanager if contact points permission * Add empty state and hide templates if missing permissions * Translations * Tidy up mock data * Fix tests and templates permission * Update message for unused contact points * Don't return 403 when user lists receivers and has access to none * Fix receiver create not adding empty uid permissions * Move SetDefaultPermissions to ReceiverPermissionService * Have SetDefaultPermissions use uid from string Fixes circular dependency * Add FakeReceiverPermissionsService and fix test wiring * Implement resource permission handling in provisioning API and renames Create: Sets to default permissions Delete: Removes permissions Update: If receiver name is modified and the new name doesn't exist, it copies the permissions from the old receiver to the newly created one. If old receiver is now empty, it removes the old permissions as well. * Split contact point permissions checks for read/modify * Generalise getting annotation values from k8s entities * Proxy RouteDeleteAlertingConfig through MultiOrgAlertmanager * Cleanup permissions on config api reset and restore * Cleanup permissions on config api POST note this is still not available with feature flag enabled * Gate the permission manager behind FF until initial migration is added * Sync changes from config api PR * Switch to named export * Revert unnecessary changes * Revert Filter auth change and implement in k8s api only * Don't allow new scoped permissions to give access without FF Prevents complications around mixed support for the scoped permissions causing oddities in the UI. * Fix integration tests to account for list permission change * Move to `permissions` file * Add additional tests for contact points * Fix redirect for viewer on edit page * Combine alerting test utils and move to new file location * Allow new permissions to access provisioning export paths with FF * Always allow exporting if its grafana flavoured * Fix logic for showing auto generated policies * Fix delete logic for contact point only referenced by a rule * Suppress warning message when renaming a contact point * Clear team and role perm cache on receiver rename Prevents temporarily broken UI permissions after rename when a user's source of elevated permissions comes from a cached team or basic role permission. * Debug log failed cache clear on CopyPermissions --------- Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
9 months ago
)) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
}
if hasAccess(ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingInstanceRead),
ac.EvalPermission(ac.ActionAlertingInstancesExternalRead),
ac.EvalPermission(ac.ActionAlertingSilencesRead),
)) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
}
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts with active notifications", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingCentralAlertHistory) {
Alerting: Central alert history part4 (#90088) * Implement EventDetails for expanded rows and pagination on the events list * Add test for getPanelDataForRule function * prettier * refactor EventState component * create interfaces for props * Add missing translations * Update some comments * Add plus button in alertrulename , to add it into the filter * Add plus button to add filters from the list labels and alert name * Add clear labels filter button * run prettier * fix RBAC checks * Update AlertLabels onLabelClick functionality * add limit=0 in useCombinedRule call * Add filter by state * remove plus button in labels * Fix state filter * Add filter by previous state * fix some errors after solving conflicts * Add comments and remove some type assertions * Update the number of transitions calculation to be for each instance * Add tests for state filters * remove type assertion * Address review comments * Update returnTo prop in alert list view url * Update translations * address review comments * prettier * update cursor to pointer * Address Deyan review comments * address review pr comments from Deyan * fix label styles * Visualize expanded row as a state graph and address some pr review comments * Add warning when limit of events is reached and rename onClickLabel * Update texts * Fix translations * Update some Labels in the expanded states visualization * move getPanelDataForRule to a separate file * Add header to the list of events * Move HistoryErrorMessage to a separate file * remove getPanelDataForRule function and test * add comment * fitler by instance label results shown inthe state chart * remove defaults.ini changes * fix having single event on time state chart --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
12 months ago
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "History",
SubTitle: "View a history of all alert events generated by your Grafana-managed alert rules. All alert events are displayed regardless of whether silences or mute timings are set.",
Id: "alerts-history",
Url: s.cfg.AppSubURL + "/alerting/history",
Icon: "history",
})
}
}
if c.GetOrgRole() == org.RoleAdmin && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertRuleRestore) && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingRuleRecoverDeleted) {
Alerting: Recover deleted alert rules (#101869) * add column guid to alert rule table and rule_guid to rule version table + populate the new field with UUID * update storage and domain models * patch GUID * ignore GUID in fingerprint tests * get alert rule versions by GUID * update rule delete to reset uid in history instead of deleting rows * add test * Update delete rule to require UserUID, remove all versions and create "delete" version that holds information about who and when deleted the rule * update usages of the delete method * add list deleted rules to store * list api to support deleted query parameter * add list deleted rules to store ^ Conflicts: ^ pkg/services/ngalert/store/alert_rule_test.go * list api to support deleted query parameter * Restore deleted rules * fix recovering manually recording rules * update role control for the trash route * add alertingRuleRecoverDeleted feature toggle enabled by default * fix access control for trash menu item in the navtree * Add alertingRuleRecoverDeleted feature toggle, add Delete permanently alert rule menu option * hide remove permanently functionality in the UI until we have the api available for it * discard wrong changes after merging * Reverted changes in pkg/apis/dashboard_manifest.go after fixing conflicts * Reverted changes in go.work.sum after fixing conflicts * Reverted changes in pkg/apis/dashboard_manifest.go after fixing conflicts * Reverted changes in pkg/services/ngalert/models/alert_rule.go after fixing conflicts * Reverted changes in pkg/apis/dashboard_manifest.go after fixing conflicts * restore delete permanently and add the queryparam for it * update snapshots * fix translations * protect trash route by the feature toggles * use the new rulerRuleType for checks * revert ability to delete permanently and alert rule from the ui * remove unnecessary update after reverting * fix ff * add tracking * use recently deleted instead of trash * create isRecoverDeletedRulesEnabled for checking ffs * address pr feedback 1 * include alert rule uid in getRowId * use RulerGrafanaRulesConfigDTO for deleted rules response * use isLoading in the AlertingPageWrapper * fix wrong check for recording rules type * add test for restoring manually through the alert rule form * add test for restoring deleted rule * use importAlertingComponent * udpate mock * address pr feedback * update translations * address feedback * address feedback 2 * address feedback 3 * address feedback 4 * move transformation of the response to the endpoint * fix typo * refactor grafanaRuleDtoToFormValues * update translations * use guid for identifying rows * prettier * use ONLY the guid for identifying rows * Add provides/invalidates tags for deleted rules --------- Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com> Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
3 months ago
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Recently deleted",
SubTitle: "Any items listed here for more than 30 days will be automatically deleted.",
Id: "alerts/recently-deleted",
Url: s.cfg.AppSubURL + "/alerting/recently-deleted",
})
}
if c.GetOrgRole() == org.RoleAdmin {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Settings", Id: "alerting-admin", Url: s.cfg.AppSubURL + "/alerting/admin",
Icon: "cog",
})
}
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Create alert rule", SubTitle: "Create an alert rule", Id: "alert",
Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, IsCreateAction: true,
})
}
if len(alertChildNavs) > 0 {
var alertNav = navtree.NavLink{
Text: "Alerting",
SubTitle: "Learn about problems in your systems moments after they occur",
Id: navtree.NavIDAlerting,
Icon: "bell",
Children: alertChildNavs,
SortWeight: navtree.WeightAlerting,
Url: s.cfg.AppSubURL + "/alerting",
}
return &alertNav
}
return nil
}
func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *navtree.NavLink {
hasAccess := ac.HasAccess(s.accessControl, c)
var children []*navtree.NavLink
var navLink *navtree.NavLink
baseUrl := s.cfg.AppSubURL + "/connections"
if hasAccess(datasources.ConfigurationPageAccess) {
// Add new connection
children = append(children, &navtree.NavLink{
Id: "connections-add-new-connection",
Text: "Add new connection",
SubTitle: "Browse and create new connections",
Url: baseUrl + "/add-new-connection",
Children: []*navtree.NavLink{},
Keywords: []string{"csv", "graphite", "json", "loki", "prometheus", "sql", "tempo"},
})
// Data sources
children = append(children, &navtree.NavLink{
Id: "connections-datasources",
Text: "Data sources",
SubTitle: "View and manage your connected data source connections",
Url: baseUrl + "/datasources",
Children: []*navtree.NavLink{},
})
}
if len(children) > 0 {
// Connections (main)
navLink = &navtree.NavLink{
Text: "Connections",
Icon: "adjust-circle",
Id: "connections",
Url: baseUrl,
Children: children,
SortWeight: navtree.WeightDataConnections,
}
return navLink
}
return nil
}
func (s *ServiceImpl) buildDrilldownNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
drilldownChildNavs := []*navtree.NavLink{}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetrics) && !s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetricsUseExternalAppPlugin) {
drilldownChildNavs = append(drilldownChildNavs, &navtree.NavLink{
Text: "Metrics",
SubTitle: "Queryless exploration of your metrics",
Id: "explore/metrics",
Url: s.cfg.AppSubURL + "/explore/metrics",
Icon: "code-branch",
})
}
return drilldownChildNavs
}