diff --git a/go.sum b/go.sum index f298ec1c333..08d73187305 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= @@ -479,6 +480,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -542,8 +544,10 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -1180,6 +1184,7 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1518,6 +1523,7 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 00146c7afba..ddf062bd35e 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -14,8 +14,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -// getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation. -func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]interface{}, error) { +func getFSDataSources(c *models.ReqContext, enabledPlugins *plugins.EnabledPlugins) (map[string]interface{}, error) { orgDataSources := make([]*models.DataSource, 0) if c.OrgId != 0 { @@ -42,21 +41,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i } } - datasources := make(map[string]interface{}) - var defaultDatasource string - - enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) - if err != nil { - return nil, err - } - - pluginsToPreload := []string{} - - for _, app := range enabledPlugins.Apps { - if app.Preload { - pluginsToPreload = append(pluginsToPreload, app.Module) - } - } + dataSources := make(map[string]interface{}) for _, ds := range orgDataSources { url := ds.Url @@ -65,12 +50,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10) } - var dsMap = map[string]interface{}{ - "id": ds.Id, - "uid": ds.Uid, - "type": ds.Type, - "name": ds.Name, - "url": url, + dsMap := map[string]interface{}{ + "id": ds.Id, + "uid": ds.Uid, + "type": ds.Type, + "name": ds.Name, + "url": url, + "isDefault": ds.IsDefault, } meta, exists := enabledPlugins.DataSources[ds.Type] @@ -78,17 +64,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i log.Errorf(3, "Could not find plugin definition for data source: %v", ds.Type) continue } - - if meta.Preload { - pluginsToPreload = append(pluginsToPreload, meta.Module) - } - dsMap["meta"] = meta - if ds.IsDefault { - defaultDatasource = ds.Name - } - jsonData := ds.JsonData if jsonData == nil { jsonData = simplejson.New() @@ -126,13 +103,14 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i jsonData.Set("directUrl", ds.Url) } - datasources[ds.Name] = dsMap + dataSources[ds.Name] = dsMap } - // add datasources that are built in (meaning they are not added via data sources page, nor have any entry in datasource table) + // add data sources that are built in (meaning they are not added via data sources page, nor have any entry in + // the datasource table) for _, ds := range plugins.DataSources { if ds.BuiltIn { - datasources[ds.Name] = map[string]interface{}{ + dataSources[ds.Name] = map[string]interface{}{ "type": ds.Type, "name": ds.Name, "meta": plugins.DataSources[ds.Id], @@ -140,8 +118,39 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i } } - if defaultDatasource == "" { - defaultDatasource = "-- Grafana --" + return dataSources, nil +} + +// getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation. +func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]interface{}, error) { + enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) + if err != nil { + return nil, err + } + pluginsToPreload := []string{} + for _, app := range enabledPlugins.Apps { + if app.Preload { + pluginsToPreload = append(pluginsToPreload, app.Module) + } + } + + dataSources, err := getFSDataSources(c, enabledPlugins) + if err != nil { + return nil, err + } + + defaultDS := "-- Grafana --" + for n, ds := range dataSources { + dsM := ds.(map[string]interface{}) + if isDefault, _ := dsM["isDefault"].(bool); isDefault { + defaultDS = n + } + delete(dsM, "isDefault") + + meta := dsM["meta"].(*plugins.DataSourcePlugin) + if meta.Preload { + pluginsToPreload = append(pluginsToPreload, meta.Module) + } } panels := map[string]interface{}{} @@ -179,8 +188,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i } jsonObj := map[string]interface{}{ - "defaultDatasource": defaultDatasource, - "datasources": datasources, + "defaultDatasource": defaultDS, + "datasources": dataSources, "minRefreshInterval": setting.MinRefreshInterval, "panels": panels, "appUrl": setting.AppUrl, diff --git a/pkg/api/index.go b/pkg/api/index.go index 66c38fab3c5..3fdd6152068 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -18,109 +18,133 @@ const ( darkName = "dark" ) -func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewData, error) { - settings, err := hs.getFrontendSettingsMap(c) +func getProfileNode(c *models.ReqContext) *dtos.NavLink { + // Only set login if it's different from the name + var login string + if c.SignedInUser.Login != c.SignedInUser.NameOrFallback() { + login = c.SignedInUser.Login + } + gravatarURL := dtos.GetGravatarUrl(c.Email) + + children := []*dtos.NavLink{ + { + Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "sliders-v-alt", + }, + { + Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", + Icon: "lock", HideFromMenu: true, + }, + } + if !setting.DisableSignoutMenu { + // add sign out first + children = append(children, &dtos.NavLink{ + Text: "Sign out", + Id: "sign-out", + Url: setting.AppSubUrl + "/logout", + Icon: "arrow-from-right", + Target: "_self", + HideFromTabs: true, + }) + } + + return &dtos.NavLink{ + Text: c.SignedInUser.NameOrFallback(), + SubTitle: login, + Id: "profile", + Img: gravatarURL, + Url: setting.AppSubUrl + "/profile", + HideFromMenu: true, + SortWeight: dtos.WeightProfile, + Children: children, + } +} + +func getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error) { + enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) if err != nil { return nil, err } - prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser} - if err := bus.Dispatch(&prefsQuery); err != nil { - return nil, err - } - prefs := prefsQuery.Result + appLinks := []*dtos.NavLink{} + for _, plugin := range enabledPlugins.Apps { + if !plugin.Pinned { + continue + } - // Read locale from accept-language - acceptLang := c.Req.Header.Get("Accept-Language") - locale := "en-US" + appLink := &dtos.NavLink{ + Text: plugin.Name, + Id: "plugin-page-" + plugin.Id, + Url: plugin.DefaultNavUrl, + Img: plugin.Info.Logos.Small, + SortWeight: dtos.WeightPlugin, + } - if len(acceptLang) > 0 { - parts := strings.Split(acceptLang, ",") - locale = parts[0] + for _, include := range plugin.Includes { + if !c.HasUserRole(include.Role) { + continue + } + + if include.Type == "page" && include.AddToNav { + var link *dtos.NavLink + if len(include.Path) > 0 { + link = &dtos.NavLink{ + Url: setting.AppSubUrl + include.Path, + Text: include.Name, + } + if include.DefaultNav { + appLink.Url = link.Url // Overwrite the hardcoded page logic + } + } else { + link = &dtos.NavLink{ + Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug, + Text: include.Name, + } + } + appLink.Children = append(appLink.Children, link) + } + + if include.Type == "dashboard" && include.AddToNav { + link := &dtos.NavLink{ + Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug, + Text: include.Name, + } + appLink.Children = append(appLink.Children, link) + } + } + + if len(appLink.Children) > 0 && c.OrgRole == models.ROLE_ADMIN { + appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true}) + appLink.Children = append(appLink.Children, &dtos.NavLink{ + Text: "Plugin Config", Icon: "cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/", + }) + } + + if len(appLink.Children) > 0 { + appLinks = append(appLinks, appLink) + } } - appURL := setting.AppUrl - appSubURL := setting.AppSubUrl + return appLinks, nil +} - // special case when doing localhost call from image renderer - if c.IsRenderCall && !hs.Cfg.ServeFromSubPath { - appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort) - appSubURL = "" - settings["appSubUrl"] = "" - } +func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dtos.NavLink, error) { + navTree := []*dtos.NavLink{} - hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} - if err := bus.Dispatch(&hasEditPermissionInFoldersQuery); err != nil { - return nil, err - } - - settings["dateFormats"] = hs.Cfg.DateFormats - - var data = dtos.IndexViewData{ - User: &dtos.CurrentUser{ - Id: c.UserId, - IsSignedIn: c.IsSignedIn, - Login: c.Login, - Email: c.Email, - Name: c.Name, - OrgCount: c.OrgCount, - OrgId: c.OrgId, - OrgName: c.OrgName, - OrgRole: c.OrgRole, - GravatarUrl: dtos.GetGravatarUrl(c.Email), - IsGrafanaAdmin: c.IsGrafanaAdmin, - LightTheme: prefs.Theme == lightName, - Timezone: prefs.Timezone, - Locale: locale, - HelpFlags1: c.HelpFlags1, - HasEditPermissionInFolders: hasEditPermissionInFoldersQuery.Result, - }, - Settings: settings, - Theme: prefs.Theme, - AppUrl: appURL, - AppSubUrl: appSubURL, - GoogleAnalyticsId: setting.GoogleAnalyticsId, - GoogleTagManagerId: setting.GoogleTagManagerId, - BuildVersion: setting.BuildVersion, - BuildCommit: setting.BuildCommit, - NewGrafanaVersion: plugins.GrafanaLatestVersion, - NewGrafanaVersionExists: plugins.GrafanaHasUpdate, - AppName: setting.ApplicationName, - AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()), - FavIcon: "public/img/fav32.png", - AppleTouchIcon: "public/img/apple-touch-icon.png", - AppTitle: "Grafana", - } - - if setting.DisableGravatar { - data.User.GravatarUrl = setting.AppSubUrl + "/public/img/user_profile.png" - } - - if len(data.User.Name) == 0 { - data.User.Name = data.User.Login - } - - themeURLParam := c.Query("theme") - if themeURLParam == lightName { - data.User.LightTheme = true - data.Theme = lightName - } else if themeURLParam == darkName { - data.User.LightTheme = false - data.Theme = darkName - } - - if hasEditPermissionInFoldersQuery.Result { + if hasEditPerm { children := []*dtos.NavLink{ {Text: "Dashboard", Icon: "apps", Url: setting.AppSubUrl + "/dashboard/new"}, } - if c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR { - children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "folder-plus", Url: setting.AppSubUrl + "/dashboards/folder/new"}) + children = append(children, &dtos.NavLink{ + Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", + Icon: "folder-plus", Url: setting.AppSubUrl + "/dashboards/folder/new", + }) } - - children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "import", Url: setting.AppSubUrl + "/dashboard/import"}) - - data.NavTree = append(data.NavTree, &dtos.NavLink{ + children = append(children, &dtos.NavLink{ + Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "import", + Url: setting.AppSubUrl + "/dashboard/import", + }) + navTree = append(navTree, &dtos.NavLink{ Text: "Create", Id: "create", Icon: "plus", @@ -138,7 +162,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat {Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "camera"}, } - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Text: "Dashboards", Id: "dashboards", SubTitle: "Manage dashboards & folders", @@ -149,7 +173,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat }) if setting.ExploreEnabled && (c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR || setting.ViewersCanEdit) { - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Text: "Explore", Id: "explore", SubTitle: "Explore your data", @@ -160,47 +184,19 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat } if c.IsSignedIn { - // Only set login if it's different from the name - var login string - if c.SignedInUser.Login != c.SignedInUser.NameOrFallback() { - login = c.SignedInUser.Login - } - profileNode := &dtos.NavLink{ - Text: c.SignedInUser.NameOrFallback(), - SubTitle: login, - Id: "profile", - Img: data.User.GravatarUrl, - Url: setting.AppSubUrl + "/profile", - HideFromMenu: true, - SortWeight: dtos.WeightProfile, - Children: []*dtos.NavLink{ - {Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "sliders-v-alt"}, - {Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", Icon: "lock", HideFromMenu: true}, - }, - } - - if !setting.DisableSignoutMenu { - // add sign out first - profileNode.Children = append(profileNode.Children, &dtos.NavLink{ - Text: "Sign out", - Id: "sign-out", - Url: setting.AppSubUrl + "/logout", - Icon: "arrow-from-right", - Target: "_self", - HideFromTabs: true, - }) - } - - data.NavTree = append(data.NavTree, profileNode) + navTree = append(navTree, getProfileNode(c)) } if setting.AlertingEnabled && (c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR) { alertChildNavs := []*dtos.NavLink{ {Text: "Alert Rules", Id: "alert-list", Url: setting.AppSubUrl + "/alerting/list", Icon: "list-ul"}, - {Text: "Notification channels", Id: "channels", Url: setting.AppSubUrl + "/alerting/notifications", Icon: "comment-alt-share"}, + { + Text: "Notification channels", Id: "channels", Url: setting.AppSubUrl + "/alerting/notifications", + Icon: "comment-alt-share", + }, } - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Text: "Alerting", SubTitle: "Alert rules & notifications", Id: "alerting", @@ -211,64 +207,11 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat }) } - enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId) + appLinks, err := getAppLinks(c) if err != nil { return nil, err } - - for _, plugin := range enabledPlugins.Apps { - if plugin.Pinned { - appLink := &dtos.NavLink{ - Text: plugin.Name, - Id: "plugin-page-" + plugin.Id, - Url: plugin.DefaultNavUrl, - Img: plugin.Info.Logos.Small, - SortWeight: dtos.WeightPlugin, - } - - for _, include := range plugin.Includes { - if !c.HasUserRole(include.Role) { - continue - } - - if include.Type == "page" && include.AddToNav { - var link *dtos.NavLink - if len(include.Path) > 0 { - link = &dtos.NavLink{ - Url: setting.AppSubUrl + include.Path, - Text: include.Name, - } - if include.DefaultNav { - appLink.Url = link.Url // Overwrite the hardcoded page logic - } - } else { - link = &dtos.NavLink{ - Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug, - Text: include.Name, - } - } - appLink.Children = append(appLink.Children, link) - } - - if include.Type == "dashboard" && include.AddToNav { - link := &dtos.NavLink{ - Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug, - Text: include.Name, - } - appLink.Children = append(appLink.Children, link) - } - } - - if len(appLink.Children) > 0 && c.OrgRole == models.ROLE_ADMIN { - appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true}) - appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/"}) - } - - if len(appLink.Children) > 0 { - data.NavTree = append(data.NavTree, appLink) - } - } - } + navTree = append(navTree, appLinks...) configNodes := []*dtos.NavLink{} @@ -325,7 +268,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat } if len(configNodes) > 0 { - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Id: "cfg", Text: "Configuration", SubTitle: "Organization: " + c.OrgName, @@ -356,7 +299,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat }) } - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Text: "Server Admin", SubTitle: "Manage all users & orgs", HideFromTabs: true, @@ -373,7 +316,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat helpVersion = setting.ApplicationName } - data.NavTree = append(data.NavTree, &dtos.NavLink{ + navTree = append(navTree, &dtos.NavLink{ Text: "Help", SubTitle: helpVersion, Id: "help", @@ -384,11 +327,113 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat Children: []*dtos.NavLink{}, }) + sort.SliceStable(navTree, func(i, j int) bool { + return navTree[i].SortWeight < navTree[j].SortWeight + }) + + return navTree, nil +} + +func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewData, error) { + hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} + if err := bus.Dispatch(&hasEditPermissionInFoldersQuery); err != nil { + return nil, err + } + hasEditPerm := hasEditPermissionInFoldersQuery.Result + + settings, err := hs.getFrontendSettingsMap(c) + if err != nil { + return nil, err + } + + settings["dateFormats"] = hs.Cfg.DateFormats + + prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser} + if err := bus.Dispatch(&prefsQuery); err != nil { + return nil, err + } + prefs := prefsQuery.Result + + // Read locale from accept-language + acceptLang := c.Req.Header.Get("Accept-Language") + locale := "en-US" + + if len(acceptLang) > 0 { + parts := strings.Split(acceptLang, ",") + locale = parts[0] + } + + appURL := setting.AppUrl + appSubURL := setting.AppSubUrl + + // special case when doing localhost call from image renderer + if c.IsRenderCall && !hs.Cfg.ServeFromSubPath { + appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort) + appSubURL = "" + settings["appSubUrl"] = "" + } + + navTree, err := hs.getNavTree(c, hasEditPerm) + if err != nil { + return nil, err + } + + data := dtos.IndexViewData{ + User: &dtos.CurrentUser{ + Id: c.UserId, + IsSignedIn: c.IsSignedIn, + Login: c.Login, + Email: c.Email, + Name: c.Name, + OrgCount: c.OrgCount, + OrgId: c.OrgId, + OrgName: c.OrgName, + OrgRole: c.OrgRole, + GravatarUrl: dtos.GetGravatarUrl(c.Email), + IsGrafanaAdmin: c.IsGrafanaAdmin, + LightTheme: prefs.Theme == lightName, + Timezone: prefs.Timezone, + Locale: locale, + HelpFlags1: c.HelpFlags1, + HasEditPermissionInFolders: hasEditPerm, + }, + Settings: settings, + Theme: prefs.Theme, + AppUrl: appURL, + AppSubUrl: appSubURL, + GoogleAnalyticsId: setting.GoogleAnalyticsId, + GoogleTagManagerId: setting.GoogleTagManagerId, + BuildVersion: setting.BuildVersion, + BuildCommit: setting.BuildCommit, + NewGrafanaVersion: plugins.GrafanaLatestVersion, + NewGrafanaVersionExists: plugins.GrafanaHasUpdate, + AppName: setting.ApplicationName, + AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()), + FavIcon: "public/img/fav32.png", + AppleTouchIcon: "public/img/apple-touch-icon.png", + AppTitle: "Grafana", + NavTree: navTree, + } + + if setting.DisableGravatar { + data.User.GravatarUrl = setting.AppSubUrl + "/public/img/user_profile.png" + } + + if len(data.User.Name) == 0 { + data.User.Name = data.User.Login + } + + themeURLParam := c.Query("theme") + if themeURLParam == lightName { + data.User.LightTheme = true + data.Theme = lightName + } else if themeURLParam == darkName { + data.User.LightTheme = false + data.Theme = darkName + } + hs.HooksService.RunIndexDataHooks(&data, c) - sort.SliceStable(data.NavTree, func(i, j int) bool { - return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight - }) return &data, nil } diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index 88ab994836e..87096187132 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -119,6 +119,7 @@ func sanitizeMethod(m string) string { // If the wrapped http.Handler has not set a status code, i.e. the value is // currently 0, sanitizeCode will return 200, for consistency with behavior in // the stdlib. +//nolint: gocyclo func sanitizeCode(s int) string { switch s { case 100: diff --git a/pkg/services/alerting/conditions/reducer.go b/pkg/services/alerting/conditions/reducer.go index 4ad28b3056f..6a23e6f9e48 100644 --- a/pkg/services/alerting/conditions/reducer.go +++ b/pkg/services/alerting/conditions/reducer.go @@ -17,6 +17,7 @@ type queryReducer struct { Type string } +//nolint: gocyclo func (s *queryReducer) Reduce(series *tsdb.TimeSeries) null.Float { if len(series.Points) == 0 { return null.FloatFromPtr(nil) diff --git a/pkg/tsdb/interval.go b/pkg/tsdb/interval.go index 52ec37863a7..dd6eab539c7 100644 --- a/pkg/tsdb/interval.go +++ b/pkg/tsdb/interval.go @@ -118,6 +118,7 @@ func FormatDuration(inter time.Duration) string { return "1ms" } +//nolint: gocyclo func roundInterval(interval time.Duration) time.Duration { switch { // 0.015s diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index fca904d9d9c..55745855b60 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -70,6 +70,7 @@ func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.Tim return sql, nil } +//nolint: gocyclo func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, error) { switch name { case "__time": diff --git a/scripts/go/configs/.golangci.toml b/scripts/go/configs/.golangci.toml index 433f4769d40..cb288d92cc7 100644 --- a/scripts/go/configs/.golangci.toml +++ b/scripts/go/configs/.golangci.toml @@ -38,10 +38,10 @@ enable = [ "unused", "varcheck", "whitespace", + "gocyclo", ] # Disabled linters (might want them later) -# "gocyclo", # "unparam" [[issues.exclude-rules]]