Merge remote-tracking branch 'origin/master' into bar-gauge-poc

This commit is contained in:
Torkel Ödegaard
2019-03-13 08:26:06 +01:00
433 changed files with 8822 additions and 5961 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
+14 -14
View File
@@ -33,17 +33,17 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/profile/", reqSignedIn, hs.Index)
r.Get("/profile/password", reqSignedIn, hs.Index)
r.Get("/profile/switch-org/:id", reqSignedIn, hs.ChangeActiveOrgAndRedirectToHome)
r.Get("/org/", reqSignedIn, hs.Index)
r.Get("/org/new", reqSignedIn, hs.Index)
r.Get("/datasources/", reqSignedIn, hs.Index)
r.Get("/datasources/new", reqSignedIn, hs.Index)
r.Get("/datasources/edit/*", reqSignedIn, hs.Index)
r.Get("/org/users", reqSignedIn, hs.Index)
r.Get("/org/users/new", reqSignedIn, hs.Index)
r.Get("/org/users/invite", reqSignedIn, hs.Index)
r.Get("/org/teams", reqSignedIn, hs.Index)
r.Get("/org/teams/*", reqSignedIn, hs.Index)
r.Get("/org/apikeys/", reqSignedIn, hs.Index)
r.Get("/org/", reqOrgAdmin, hs.Index)
r.Get("/org/new", reqGrafanaAdmin, hs.Index)
r.Get("/datasources/", reqOrgAdmin, hs.Index)
r.Get("/datasources/new", reqOrgAdmin, hs.Index)
r.Get("/datasources/edit/*", reqOrgAdmin, hs.Index)
r.Get("/org/users", reqOrgAdmin, hs.Index)
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
r.Get("/org/users/invite", reqOrgAdmin, hs.Index)
r.Get("/org/teams", reqOrgAdmin, hs.Index)
r.Get("/org/teams/*", reqOrgAdmin, hs.Index)
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
r.Get("/dashboard/import/", reqSignedIn, hs.Index)
r.Get("/configuration", reqGrafanaAdmin, hs.Index)
r.Get("/admin", reqGrafanaAdmin, hs.Index)
@@ -73,12 +73,12 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/dashboards/", reqSignedIn, hs.Index)
r.Get("/dashboards/*", reqSignedIn, hs.Index)
r.Get("/explore", reqEditorRole, hs.Index)
r.Get("/explore", reqSignedIn, middleware.EnsureEditorOrViewerCanEdit, hs.Index)
r.Get("/playlists/", reqSignedIn, hs.Index)
r.Get("/playlists/*", reqSignedIn, hs.Index)
r.Get("/alerting/", reqSignedIn, hs.Index)
r.Get("/alerting/*", reqSignedIn, hs.Index)
r.Get("/alerting/", reqEditorRole, hs.Index)
r.Get("/alerting/*", reqEditorRole, hs.Index)
// sign up
r.Get("/signup", hs.Index)
+2 -1
View File
@@ -13,8 +13,8 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/dashdiffs"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/guardian"
@@ -488,6 +488,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.Resto
saveCmd.Dashboard.Set("version", dash.Version)
saveCmd.Dashboard.Set("uid", dash.Uid)
saveCmd.Message = fmt.Sprintf("Restored from version %d", version.Version)
saveCmd.FolderId = dash.FolderId
return hs.PostDashboard(c, saveCmd)
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
+124
View File
@@ -810,6 +810,93 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
})
Convey("Given dashboard in folder being restored should restore to folder", t, func() {
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 2
fakeDash.FolderId = 1
fakeDash.HasAcl = false
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
return nil
})
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
query.Result = &m.DashboardVersion{
DashboardId: 2,
Version: 1,
Data: fakeDash.Data,
}
return nil
})
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: &m.Dashboard{
Id: 2,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 1,
},
}
cmd := dtos.RestoreDashboardVersionCommand{
Version: 1,
}
restoreDashboardVersionScenario("When calling POST on", "/api/dashboards/id/1/restore", "/api/dashboards/id/:dashboardId/restore", mock, cmd, func(sc *scenarioContext) {
CallRestoreDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 200)
dto := mock.SavedDashboards[0]
So(dto.Dashboard.FolderId, ShouldEqual, 1)
So(dto.Dashboard.Title, ShouldEqual, "Child dash")
So(dto.Message, ShouldEqual, "Restored from version 1")
})
})
Convey("Given dashboard in general folder being restored should restore to general folder", t, func() {
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 2
fakeDash.HasAcl = false
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
return nil
})
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
query.Result = &m.DashboardVersion{
DashboardId: 2,
Version: 1,
Data: fakeDash.Data,
}
return nil
})
mock := &dashboards.FakeDashboardService{
SaveDashboardResult: &m.Dashboard{
Id: 2,
Uid: "uid",
Title: "Dash",
Slug: "dash",
Version: 1,
},
}
cmd := dtos.RestoreDashboardVersionCommand{
Version: 1,
}
restoreDashboardVersionScenario("When calling POST on", "/api/dashboards/id/1/restore", "/api/dashboards/id/:dashboardId/restore", mock, cmd, func(sc *scenarioContext) {
CallRestoreDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 200)
dto := mock.SavedDashboards[0]
So(dto.Dashboard.FolderId, ShouldEqual, 0)
So(dto.Dashboard.Title, ShouldEqual, "Child dash")
So(dto.Message, ShouldEqual, "Restored from version 1")
})
})
}
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
@@ -871,6 +958,10 @@ func CallPostDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func CallRestoreDashboardVersion(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) {
CallPostDashboard(sc)
@@ -928,6 +1019,39 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal
})
}
func restoreDashboardVersionScenario(desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
hs := HTTPServer{
Bus: bus.GetBus(),
}
sc := setupScenarioContext(url)
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
sc.context = c
sc.context.SignedInUser = &m.SignedInUser{
OrgId: TestOrgID,
UserId: TestUserID,
}
sc.context.OrgRole = m.ROLE_ADMIN
return hs.RestoreDashboardVersion(c, cmd)
})
origNewDashboardService := dashboards.NewService
dashboards.MockDashboardService(mock)
sc.m.Post(routePattern, sc.defaultHandler)
defer func() {
dashboards.NewService = origNewDashboardService
}()
fn(sc)
})
}
func (sc *scenarioContext) ToJSON() *simplejson.Json {
var result *simplejson.Json
err := json.NewDecoder(sc.resp.Body).Decode(&result)
+1 -1
View File
@@ -2,7 +2,7 @@ package api
import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
+1
View File
@@ -167,6 +167,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"viewersCanEdit": setting.ViewersCanEdit,
"editorsCanOwn": hs.Cfg.EditorsCanOwn,
"disableSanitizeHtml": hs.Cfg.DisableSanitizeHtml,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
+17 -24
View File
@@ -307,33 +307,26 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
}
}
if c.OrgRole == m.ROLE_ADMIN && c.IsGrafanaAdmin {
cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
Divider: true, HideFromTabs: true, Id: "admin-divider", Text: "Text",
})
}
if c.IsGrafanaAdmin {
cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
Text: "Server Admin",
HideFromTabs: true,
SubTitle: "Manage all users & orgs",
Id: "admin",
Icon: "gicon gicon-shield",
Url: setting.AppSubUrl + "/admin/users",
Children: []*dtos.NavLink{
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
{Text: "Style Guide", Id: "styleguide", Url: setting.AppSubUrl + "/styleguide", Icon: "fa fa-fw fa-eyedropper"},
},
})
}
data.NavTree = append(data.NavTree, cfgNode)
}
if c.IsGrafanaAdmin {
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Server Admin",
SubTitle: "Manage all users & orgs",
HideFromTabs: true,
Id: "admin",
Icon: "gicon gicon-shield",
Url: setting.AppSubUrl + "/admin/users",
Children: []*dtos.NavLink{
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
},
})
}
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Help",
SubTitle: fmt.Sprintf(`%s v%s (%s)`, setting.ApplicationName, setting.BuildVersion, setting.BuildCommit),
+2 -1
View File
@@ -7,9 +7,9 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
@@ -36,6 +36,7 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
viewData.Settings["oauth"] = enabledOAuths
viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp
viewData.Settings["loginHint"] = setting.LoginHint
viewData.Settings["passwordHint"] = setting.PasswordHint
viewData.Settings["disableLoginForm"] = setting.DisableLoginForm
if loginError, ok := tryGetEncryptedCookie(c, LoginErrorCookieName); ok {
+2 -2
View File
@@ -16,12 +16,12 @@ import (
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/login/social"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social"
)
var (
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
+5 -5
View File
@@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -27,6 +27,10 @@ func GetPendingOrgInvites(c *m.ReqContext) Response {
}
func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
if setting.DisableLoginForm {
return Error(400, "Cannot invite when login is disabled.", nil)
}
if !inviteDto.Role.IsValid() {
return Error(400, "Invalid role specified", nil)
}
@@ -37,10 +41,6 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
if err != m.ErrUserNotFound {
return Error(500, "Failed to query db for existing user check", err)
}
if setting.DisableLoginForm {
return Error(401, "User could not be found", nil)
}
} else {
return inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
}
+2
View File
@@ -52,8 +52,10 @@ func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboar
for _, item := range searchQuery.Result {
result = append(result, dtos.PlaylistDashboard{
Id: item.Id,
Slug: item.Slug,
Title: item.Title,
Uri: item.Uri,
Url: m.GetDashboardUrl(item.Uid, item.Slug),
Order: dashboardTagOrder[tag],
})
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"strconv"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
+1 -1
View File
@@ -121,7 +121,7 @@ func GetUserTeams(c *m.ReqContext) Response {
return getUserTeamList(c.OrgId, c.ParamsInt64(":id"))
}
func getUserTeamList(userID int64, orgID int64) Response {
func getUserTeamList(orgID int64, userID int64) Response {
query := m.GetTeamsByUserQuery{OrgId: orgID, UserId: userID}
if err := bus.Dispatch(&query); err != nil {
+1 -1
View File
@@ -14,8 +14,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
+4 -3
View File
@@ -16,9 +16,9 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/social"
"golang.org/x/sync/errgroup"
@@ -28,8 +28,10 @@ import (
// self registering services
_ "github.com/grafana/grafana/pkg/extensions"
_ "github.com/grafana/grafana/pkg/infra/metrics"
_ "github.com/grafana/grafana/pkg/infra/serverlock"
_ "github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/infra/tracing"
_ "github.com/grafana/grafana/pkg/infra/usagestats"
_ "github.com/grafana/grafana/pkg/plugins"
_ "github.com/grafana/grafana/pkg/services/alerting"
_ "github.com/grafana/grafana/pkg/services/auth"
@@ -39,7 +41,6 @@ import (
_ "github.com/grafana/grafana/pkg/services/rendering"
_ "github.com/grafana/grafana/pkg/services/search"
_ "github.com/grafana/grafana/pkg/services/sqlstore"
_ "github.com/grafana/grafana/pkg/tracing"
)
func NewGrafanaServer() *GrafanaServerImpl {
@@ -3,8 +3,8 @@ package metrics
import (
"context"
"github.com/grafana/grafana/pkg/infra/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
)
@@ -5,7 +5,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/infra/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/social"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/registry"
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
+9 -2
View File
@@ -25,6 +25,7 @@ var filters map[string]log15.Lvl
func init() {
loggersToClose = make([]DisposableHandler, 0)
loggersToReload = make([]ReloadableHandler, 0)
filters = map[string]log15.Lvl{}
Root = log15.Root()
Root.SetHandler(log15.DiscardHandler())
}
@@ -197,7 +198,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
// Log level.
_, level := getLogLevelFromConfig("log."+mode, defaultLevelName, cfg)
filters := getFilters(util.SplitString(sec.Key("filters").String()))
modeFilters := getFilters(util.SplitString(sec.Key("filters").String()))
format := getLogFormat(sec.Key("format").MustString(""))
var handler log15.Handler
@@ -230,12 +231,18 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
}
for key, value := range defaultFilters {
if _, exist := modeFilters[key]; !exist {
modeFilters[key] = value
}
}
for key, value := range modeFilters {
if _, exist := filters[key]; !exist {
filters[key] = value
}
}
handler = LogFilterHandler(level, filters, handler)
handler = LogFilterHandler(level, modeFilters, handler)
handlers = append(handlers, handler)
}
+23 -2
View File
@@ -18,6 +18,7 @@ import (
type ILdapConn interface {
Bind(username, password string) error
UnauthenticatedBind(username string) error
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
StartTLS(*tls.Config) error
Close()
@@ -218,8 +219,18 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo
}
func (a *ldapAuther) serverBind() error {
bindFn := func() error {
return a.conn.Bind(a.server.BindDN, a.server.BindPassword)
}
if a.server.BindPassword == "" {
bindFn = func() error {
return a.conn.UnauthenticatedBind(a.server.BindDN)
}
}
// bind_dn and bind_password to bind
if err := a.conn.Bind(a.server.BindDN, a.server.BindPassword); err != nil {
if err := bindFn(); err != nil {
a.log.Info("LDAP initial bind failed, %v", err)
if ldapErr, ok := err.(*ldap.Error); ok {
@@ -259,7 +270,17 @@ func (a *ldapAuther) initialBind(username, userPassword string) error {
bindPath = fmt.Sprintf(a.server.BindDN, username)
}
if err := a.conn.Bind(bindPath, userPassword); err != nil {
bindFn := func() error {
return a.conn.Bind(bindPath, userPassword)
}
if userPassword == "" {
bindFn = func() error {
return a.conn.UnauthenticatedBind(bindPath)
}
}
if err := bindFn(); err != nil {
a.log.Info("Initial bind failed", "error", err)
if ldapErr, ok := err.(*ldap.Error); ok {
+144 -3
View File
@@ -13,6 +13,133 @@ import (
)
func TestLdapAuther(t *testing.T) {
Convey("initialBind", t, func() {
Convey("Given bind dn and password configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
BindPassword: "bindpwd",
},
}
err := ldapAuther.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(ldapAuther.requireSecondBind, ShouldBeTrue)
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "bindpwd")
})
Convey("Given bind dn configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
},
}
err := ldapAuther.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(ldapAuther.requireSecondBind, ShouldBeFalse)
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "pwd")
})
Convey("Given empty bind dn and password", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{},
}
err := ldapAuther.initialBind("user", "pwd")
So(err, ShouldBeNil)
So(ldapAuther.requireSecondBind, ShouldBeTrue)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldBeEmpty)
})
})
Convey("serverBind", t, func() {
Convey("Given bind dn and password configured", func() {
conn := &mockLdapConn{}
var actualUsername, actualPassword string
conn.bindProvider = func(username, password string) error {
actualUsername = username
actualPassword = password
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{
BindDN: "o=users,dc=grafana,dc=org",
BindPassword: "bindpwd",
},
}
err := ldapAuther.serverBind()
So(err, ShouldBeNil)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
So(actualPassword, ShouldEqual, "bindpwd")
})
Convey("Given bind dn configured", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{
BindDN: "o=users,dc=grafana,dc=org",
},
}
err := ldapAuther.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldEqual, "o=users,dc=grafana,dc=org")
})
Convey("Given empty bind dn and password", func() {
conn := &mockLdapConn{}
unauthenticatedBindWasCalled := false
var actualUsername string
conn.unauthenticatedBindProvider = func(username string) error {
unauthenticatedBindWasCalled = true
actualUsername = username
return nil
}
ldapAuther := &ldapAuther{
conn: conn,
server: &LdapServerConf{},
}
err := ldapAuther.serverBind()
So(err, ShouldBeNil)
So(unauthenticatedBindWasCalled, ShouldBeTrue)
So(actualUsername, ShouldBeEmpty)
})
})
Convey("When translating ldap user to grafana user", t, func() {
@@ -365,12 +492,26 @@ func TestLdapAuther(t *testing.T) {
}
type mockLdapConn struct {
result *ldap.SearchResult
searchCalled bool
searchAttributes []string
result *ldap.SearchResult
searchCalled bool
searchAttributes []string
bindProvider func(username, password string) error
unauthenticatedBindProvider func(username string) error
}
func (c *mockLdapConn) Bind(username, password string) error {
if c.bindProvider != nil {
return c.bindProvider(username, password)
}
return nil
}
func (c *mockLdapConn) UnauthenticatedBind(username string) error {
if c.unauthenticatedBindProvider != nil {
return c.unauthenticatedBindProvider(username)
}
return nil
}
+7 -1
View File
@@ -4,7 +4,7 @@ import (
"net/url"
"strings"
"gopkg.in/macaron.v1"
macaron "gopkg.in/macaron.v1"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
@@ -52,6 +52,12 @@ func notAuthorized(c *m.ReqContext) {
c.Redirect(setting.AppSubUrl + "/login")
}
func EnsureEditorOrViewerCanEdit(c *m.ReqContext) {
if !c.SignedInUser.HasRole(m.ROLE_EDITOR) && !setting.ViewersCanEdit {
accessForbidden(c)
}
}
func RoleAuth(roles ...m.RoleType) macaron.Handler {
return func(c *m.ReqContext) {
ok := false
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
"gopkg.in/macaron.v1"
)
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
)
type DefaultEvalHandler struct {
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/imguploader"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
@@ -138,7 +138,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
return err
}
renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?panelId=%d", ref.Uid, ref.Slug, context.Rule.PanelId)
renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?orgId=%d&panelId=%d", ref.Uid, ref.Slug, context.Rule.OrgId, context.Rule.PanelId)
result, err := n.renderService.Render(context.Ctx, renderOpts)
if err != nil {
+68 -19
View File
@@ -1,6 +1,10 @@
package notifiers
import (
"fmt"
"net/url"
"strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
@@ -8,19 +12,26 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
)
func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: "dingding",
Name: "DingDing",
Description: "Sends HTTP POST request to DingDing",
Factory: NewDingDingNotifier,
OptionsTemplate: `
const DefaultDingdingMsgType = "link"
const DingdingOptionsTemplate = `
<h3 class="page-heading">DingDing settings</h3>
<div class="gf-form">
<span class="gf-form-label width-10">Url</span>
<input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url" placeholder="https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx"></input>
<input type="text" required class="gf-form-input max-width-70" ng-model="ctrl.model.settings.url" placeholder="https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx"></input>
</div>
`,
<div class="gf-form">
<span class="gf-form-label width-10">MessageType</span>
<select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.msgType" ng-options="s for s in ['link','actionCard']" ng-init="ctrl.model.settings.msgType=ctrl.model.settings.msgType || '` + DefaultDingdingMsgType + `'"></select>
</div>
`
func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: "dingding",
Name: "DingDing",
Description: "Sends HTTP POST request to DingDing",
Factory: NewDingDingNotifier,
OptionsTemplate: DingdingOptionsTemplate,
})
}
@@ -31,8 +42,11 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
}
msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType)
return &DingDingNotifier{
NotifierBase: NewNotifierBase(model),
MsgType: msgType,
Url: url,
log: log.New("alerting.notifier.dingding"),
}, nil
@@ -40,8 +54,9 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
type DingDingNotifier struct {
NotifierBase
Url string
log log.Logger
MsgType string
Url string
log log.Logger
}
func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -52,6 +67,16 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Error("Failed to get messageUrl", "error", err, "dingding", this.Name)
messageUrl = ""
}
q := url.Values{
"pc_slide": {"false"},
"url": {messageUrl},
}
// Use special link to auto open the message url outside of Dingding
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode()
this.log.Info("messageUrl:" + messageUrl)
message := evalContext.Rule.Message
@@ -61,15 +86,39 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
message = title
}
bodyJSON, err := simplejson.NewJson([]byte(`{
"msgtype": "link",
"link": {
"text": "` + message + `",
"title": "` + title + `",
"picUrl": "` + picUrl + `",
"messageUrl": "` + messageUrl + `"
for i, match := range evalContext.EvalMatches {
message += fmt.Sprintf("\\n%2d. %s: %s", i+1, match.Metric, match.Value)
}
var bodyStr string
if this.MsgType == "actionCard" {
// Embed the pic into the markdown directly because actionCard doesn't have a picUrl field
if picUrl != "" {
message = "![](" + picUrl + ")\\n\\n" + message
}
}`))
bodyStr = `{
"msgtype": "actionCard",
"actionCard": {
"text": "` + strings.Replace(message, `"`, "'", -1) + `",
"title": "` + strings.Replace(title, `"`, "'", -1) + `",
"singleTitle": "More",
"singleURL": "` + messageUrl + `"
}
}`
} else {
bodyStr = `{
"msgtype": "link",
"link": {
"text": "` + message + `",
"title": "` + title + `",
"picUrl": "` + picUrl + `",
"messageUrl": "` + messageUrl + `"
}
}`
}
bodyJSON, err := simplejson.NewJson([]byte(bodyStr))
if err != nil {
this.log.Error("Failed to create Json data", "error", err, "dingding", this.Name)
+53 -48
View File
@@ -111,57 +111,20 @@ func (this *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
json, _ := bodyJSON.MarshalJSON()
content_type := "application/json"
var body []byte
if embeddedImage {
var b bytes.Buffer
w := multipart.NewWriter(&b)
f, err := os.Open(evalContext.ImageOnDiskPath)
if err != nil {
this.log.Error("Can't open graph file", err)
return err
}
defer f.Close()
fw, err := w.CreateFormField("payload_json")
if err != nil {
return err
}
if _, err = fw.Write([]byte(string(json))); err != nil {
return err
}
fw, err = w.CreateFormFile("file", "graph.png")
if err != nil {
return err
}
if _, err = io.Copy(fw, f); err != nil {
return err
}
w.Close()
body = b.Bytes()
content_type = w.FormDataContentType()
} else {
body = json
}
cmd := &m.SendWebhookSync{
Url: this.WebhookURL,
Body: string(body),
HttpMethod: "POST",
ContentType: content_type,
ContentType: "application/json",
}
if !embeddedImage {
cmd.Body = string(json)
} else {
err := this.embedImage(cmd, evalContext.ImageOnDiskPath, json)
if err != nil {
this.log.Error("failed to embed image", "error", err)
return err
}
}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
@@ -171,3 +134,45 @@ func (this *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
return nil
}
func (this *DiscordNotifier) embedImage(cmd *m.SendWebhookSync, imagePath string, existingJSONBody []byte) error {
f, err := os.Open(imagePath)
defer f.Close()
if err != nil {
if os.IsNotExist(err) {
cmd.Body = string(existingJSONBody)
return nil
}
if !os.IsNotExist(err) {
return err
}
}
var b bytes.Buffer
w := multipart.NewWriter(&b)
fw, err := w.CreateFormField("payload_json")
if err != nil {
return err
}
if _, err = fw.Write([]byte(string(existingJSONBody))); err != nil {
return err
}
fw, err = w.CreateFormFile("file", "graph.png")
if err != nil {
return err
}
if _, err = io.Copy(fw, f); err != nil {
return err
}
w.Close()
cmd.Body = string(b.Bytes())
cmd.ContentType = w.FormDataContentType()
return nil
}
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
)
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/rendering"
+1 -1
View File
@@ -56,7 +56,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext {
ctx := NewEvalContext(context.Background(), testRule)
if cmd.Settings.Get("uploadImage").MustBool(true) {
ctx.ImagePublicUrl = "http://grafana.org/assets/img/blog/mixed_styles.png"
ctx.ImagePublicUrl = "https://grafana.com/assets/img/blog/mixed_styles.png"
}
ctx.IsTestRun = true
ctx.Firing = true
+15 -4
View File
@@ -36,7 +36,7 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
defer middleware.RemoveRenderAuthKey(renderKey)
phantomDebugArg := "--debug=false"
if log.GetLogLevelFor("renderer") >= log.LvlDebug {
if log.GetLogLevelFor("rendering") >= log.LvlDebug {
phantomDebugArg = "--debug=true"
}
@@ -64,13 +64,26 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
cmd := exec.CommandContext(commandCtx, binPath, cmdArgs...)
cmd.Stderr = cmd.Stdout
timezone := ""
if opts.Timezone != "" {
timezone = isoTimeOffsetToPosixTz(opts.Timezone)
baseEnviron := os.Environ()
cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(opts.Timezone))
cmd.Env = appendEnviron(baseEnviron, "TZ", timezone)
}
rs.log.Debug("executing Phantomjs", "binPath", binPath, "cmdArgs", cmdArgs, "timezone", timezone)
out, err := cmd.Output()
if out != nil {
rs.log.Debug("Phantomjs output", "out", string(out))
}
if err != nil {
rs.log.Debug("Phantomjs error", "error", err)
}
// check for timeout first
if commandCtx.Err() == context.DeadlineExceeded {
rs.log.Info("Rendering timed out")
@@ -82,8 +95,6 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
return nil, err
}
rs.log.Debug("Phantomjs output", "out", string(out))
rs.log.Debug("Image rendered", "path", pngPath)
return &RenderResult{FilePath: pngPath}, nil
}
+1
View File
@@ -17,6 +17,7 @@ type Hit struct {
Title string `json:"title"`
Uri string `json:"uri"`
Url string `json:"url"`
Slug string `json:"slug"`
Type HitType `json:"type"`
Tags []string `json:"tags"`
IsStarred bool `json:"isStarred"`
+3 -1
View File
@@ -309,7 +309,9 @@ func PauseAlert(cmd *m.PauseAlertCommand) error {
params = append(params, v)
}
res, err := sess.Exec(buffer.String(), params...)
sqlOrArgs := append([]interface{}{buffer.String()}, params...)
res, err := sess.Exec(sqlOrArgs...)
if err != nil {
return err
}
+6 -2
View File
@@ -258,11 +258,15 @@ func (r *SqlAnnotationRepo) Delete(params *annotations.DeleteParams) error {
queryParams = []interface{}{params.DashboardId, params.PanelId, params.OrgId}
}
if _, err := sess.Exec(annoTagSql, queryParams...); err != nil {
sqlOrArgs := append([]interface{}{annoTagSql}, queryParams...)
if _, err := sess.Exec(sqlOrArgs...); err != nil {
return err
}
if _, err := sess.Exec(sql, queryParams...); err != nil {
sqlOrArgs = append([]interface{}{sql}, queryParams...)
if _, err := sess.Exec(sqlOrArgs...); err != nil {
return err
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util"
+3 -2
View File
@@ -51,7 +51,7 @@ func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
dashboard_version.message,
dashboard_version.data,`+
dialect.Quote("user")+`.login as created_by`).
Join("LEFT", "user", `dashboard_version.created_by = `+dialect.Quote("user")+`.id`).
Join("LEFT", dialect.Quote("user"), `dashboard_version.created_by = `+dialect.Quote("user")+`.id`).
Join("LEFT", "dashboard", `dashboard.id = dashboard_version.dashboard_id`).
Where("dashboard_version.dashboard_id=? AND dashboard.org_id=?", query.DashboardId, query.OrgId).
OrderBy("dashboard_version.version DESC").
@@ -102,7 +102,8 @@ func DeleteExpiredVersions(cmd *m.DeleteExpiredVersionsCommand) error {
if len(versionIdsToDelete) > 0 {
deleteExpiredSql := `DELETE FROM dashboard_version WHERE id IN (?` + strings.Repeat(",?", len(versionIdsToDelete)-1) + `)`
expiredResponse, err := sess.Exec(deleteExpiredSql, versionIdsToDelete...)
sqlOrArgs := append([]interface{}{deleteExpiredSql}, versionIdsToDelete...)
expiredResponse, err := sess.Exec(sqlOrArgs...)
if err != nil {
return err
}
+11 -1
View File
@@ -3,11 +3,13 @@ package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models"
)
@@ -95,6 +97,10 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
return m.ErrDataSourceNameExists
}
if cmd.JsonData == nil {
cmd.JsonData = simplejson.New()
}
ds := &m.DataSource{
OrgId: cmd.OrgId,
Name: cmd.Name,
@@ -142,6 +148,10 @@ func updateIsDefaultFlag(ds *m.DataSource, sess *DBSession) error {
func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
return inTransaction(func(sess *DBSession) error {
if cmd.JsonData == nil {
cmd.JsonData = simplejson.New()
}
ds := &m.DataSource{
Id: cmd.Id,
OrgId: cmd.OrgId,
+4
View File
@@ -44,6 +44,10 @@ func DeleteOldLoginAttempts(cmd *m.DeleteOldLoginAttemptsCommand) error {
return err
}
if result == nil || len(result) == 0 || result[0] == nil {
return nil
}
maxId = toInt64(result[0]["id"])
if maxId == 0 {
@@ -130,4 +130,7 @@ func addDataSourceMigration(mg *Migrator) {
const migrateLoggingToLoki = `UPDATE data_source SET type = 'loki' WHERE type = 'logging'`
mg.AddMigration("Migrate logging ds to loki ds", NewRawSqlMigration(migrateLoggingToLoki))
const setEmptyJSONWhereNullJSON = `UPDATE data_source SET json_data = '{}' WHERE json_data is null`
mg.AddMigration("Update json_data with nulls", NewRawSqlMigration(setEmptyJSONWhereNullJSON))
}
+1 -1
View File
@@ -98,7 +98,7 @@ func GetOrgUsers(query *m.GetOrgUsersQuery) error {
query.Result = make([]*m.OrgUserDTO, 0)
sess := x.Table("org_user")
sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
whereConditions := make([]string, 0)
whereParams := make([]interface{}, 0)
+1 -1
View File
@@ -280,7 +280,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
func GetTeamMembers(query *m.GetTeamMembersQuery) error {
query.Result = make([]*m.TeamMemberDTO, 0)
sess := x.Table("team_member")
sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
if query.OrgId != 0 {
sess.Where("team_member.org_id=?", query.OrgId)
}
+1 -1
View File
@@ -208,7 +208,7 @@ func TestUserDataAccess(t *testing.T) {
func GetOrgUsersForTest(query *m.GetOrgUsersQuery) error {
query.Result = make([]*m.OrgUserDTO, 0)
sess := x.Table("org_user")
sess.Join("LEFT ", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Join("LEFT ", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("org_user.org_id=?", query.OrgId)
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role")
+7
View File
@@ -109,6 +109,7 @@ var (
AutoAssignOrgRole string
VerifyEmailEnabled bool
LoginHint string
PasswordHint string
DefaultTheme string
DisableLoginForm bool
DisableSignoutMenu bool
@@ -237,6 +238,9 @@ type Cfg struct {
LoginMaxInactiveLifetimeDays int
LoginMaxLifetimeDays int
TokenRotationIntervalMinutes int
// User
EditorsCanOwn bool
}
type CommandLineArgs struct {
@@ -644,6 +648,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
AdminUser = security.Key("admin_user").String()
AdminPassword = security.Key("admin_password").String()
// users
users := iniFile.Section("users")
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
@@ -652,11 +657,13 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String()
PasswordHint = users.Key("password_hint").String()
DefaultTheme = users.Key("default_theme").String()
ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
ExternalUserMngLinkName = users.Key("external_manage_link_name").String()
ExternalUserMngInfo = users.Key("external_manage_info").String()
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
cfg.EditorsCanOwn = users.Key("editors_can_own").MustBool(false)
// auth
auth := iniFile.Section("auth")
+6 -5
View File
@@ -24,7 +24,7 @@ import (
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
)
type CloudWatchExecutor struct {
@@ -496,9 +496,6 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
}
alias := model.Get("alias").MustString()
if alias == "" {
alias = "{{metric}}_{{stat}}"
}
returnData := model.Get("returnData").MustBool(false)
highResolution := model.Get("highResolution").MustBool(false)
@@ -521,7 +518,11 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
func formatAlias(query *CloudWatchQuery, stat string, dimensions map[string]string) string {
if len(query.Id) > 0 && len(query.Expression) > 0 {
return query.Id
if len(query.Alias) > 0 {
return query.Alias
} else {
return query.Id
}
}
data := map[string]string{}
+2 -2
View File
@@ -17,7 +17,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/tsdb"
)
@@ -101,7 +101,7 @@ func init() {
"AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"},
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "ServerlessDatabaseCapacity", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "MaximumUsedTransactionIDs", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "ServerlessDatabaseCapacity", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
"AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"},
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"},
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send", "Reputation.BounceRate", "Reputation.ComplaintRate"},
@@ -279,7 +279,7 @@ func formatDate(t time.Time, pattern string) string {
isoYearShort := fmt.Sprintf("%d", isoYear)[2:4]
formatted = strings.Replace(formatted, "<stdIsoYear>", fmt.Sprintf("%d", isoYear), -1)
formatted = strings.Replace(formatted, "<stdIsoYearShort>", isoYearShort, -1)
formatted = strings.Replace(formatted, "<stdWeekOfYear>", fmt.Sprintf("%d", isoWeek), -1)
formatted = strings.Replace(formatted, "<stdWeekOfYear>", fmt.Sprintf("%02d", isoWeek), -1)
formatted = strings.Replace(formatted, "<stdUnix>", fmt.Sprintf("%d", t.Unix()), -1)
@@ -76,6 +76,15 @@ func TestIndexPattern(t *testing.T) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018-data")
})
Convey("Should return 01 week", func() {
from = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 50, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
to = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 55, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", tsdb.NewTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.03")
})
})
})
Convey("Hourly interval", t, func() {
+3 -1
View File
@@ -336,6 +336,8 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver
return StackdriverResponse{}, err
}
// slog.Info("stackdriver", "response", string(body))
if res.StatusCode/100 != 2 {
slog.Error("Request failed", "status", res.Status, "body", string(body))
return StackdriverResponse{}, fmt.Errorf(string(body))
@@ -559,7 +561,7 @@ func calcBucketBound(bucketOptions StackdriverBucketOptions, n int) string {
} else if bucketOptions.ExponentialBuckets != nil {
bucketBound = strconv.FormatInt(int64(bucketOptions.ExponentialBuckets.Scale*math.Pow(bucketOptions.ExponentialBuckets.GrowthFactor, float64(n-1))), 10)
} else if bucketOptions.ExplicitBuckets != nil {
bucketBound = strconv.FormatInt(bucketOptions.ExplicitBuckets.Bounds[(n-1)], 10)
bucketBound = fmt.Sprintf("%g", bucketOptions.ExplicitBuckets.Bounds[n])
}
return bucketBound
}
+49 -2
View File
@@ -344,8 +344,8 @@ func TestStackdriver(t *testing.T) {
})
})
Convey("when data from query is distribution", func() {
data, err := loadTestFile("./test-data/3-series-response-distribution.json")
Convey("when data from query is distribution with exponential bounds", func() {
data, err := loadTestFile("./test-data/3-series-response-distribution-exponential.json")
So(err, ShouldBeNil)
So(len(data.TimeSeries), ShouldEqual, 1)
@@ -370,6 +370,14 @@ func TestStackdriver(t *testing.T) {
So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1536669060000)
})
Convey("bucket bounds should be correct", func() {
So(res.Series[0].Name, ShouldEqual, "0")
So(res.Series[1].Name, ShouldEqual, "1")
So(res.Series[2].Name, ShouldEqual, "2")
So(res.Series[3].Name, ShouldEqual, "4")
So(res.Series[4].Name, ShouldEqual, "8")
})
Convey("value should be correct", func() {
So(res.Series[8].Points[0][0].Float64, ShouldEqual, 1)
So(res.Series[9].Points[0][0].Float64, ShouldEqual, 1)
@@ -383,6 +391,45 @@ func TestStackdriver(t *testing.T) {
})
})
Convey("when data from query is distribution with explicit bounds", func() {
data, err := loadTestFile("./test-data/4-series-response-distribution-explicit.json")
So(err, ShouldBeNil)
So(len(data.TimeSeries), ShouldEqual, 1)
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
query := &StackdriverQuery{AliasBy: "{{bucket}}"}
err = executor.parseResponse(res, data, query)
So(err, ShouldBeNil)
So(len(res.Series), ShouldEqual, 33)
for i := 0; i < 33; i++ {
if i == 0 {
So(res.Series[i].Name, ShouldEqual, "0")
}
So(len(res.Series[i].Points), ShouldEqual, 2)
}
Convey("timestamps should be in ascending order", func() {
So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1550859086000)
So(res.Series[0].Points[1][1].Float64, ShouldEqual, 1550859146000)
})
Convey("bucket bounds should be correct", func() {
So(res.Series[0].Name, ShouldEqual, "0")
So(res.Series[1].Name, ShouldEqual, "0.01")
So(res.Series[2].Name, ShouldEqual, "0.05")
So(res.Series[3].Name, ShouldEqual, "0.1")
})
Convey("value should be correct", func() {
So(res.Series[8].Points[0][0].Float64, ShouldEqual, 381)
So(res.Series[9].Points[0][0].Float64, ShouldEqual, 212)
So(res.Series[10].Points[0][0].Float64, ShouldEqual, 56)
So(res.Series[8].Points[1][0].Float64, ShouldEqual, 375)
So(res.Series[9].Points[1][0].Float64, ShouldEqual, 213)
So(res.Series[10].Points[1][0].Float64, ShouldEqual, 56)
})
})
})
Convey("when interpolating filter wildcards", func() {
@@ -0,0 +1,209 @@
{
"timeSeries": [
{
"metric": {
"type": "custom.googleapis.com\/opencensus\/grpc.io\/client\/roundtrip_latency"
},
"resource": {
"type": "global",
"labels": {
"project_id": "grafana-demo"
}
},
"metricKind": "DELTA",
"valueType": "DISTRIBUTION",
"points": [
{
"interval": {
"startTime": "2019-02-22T18:11:26Z",
"endTime": "2019-02-22T18:12:26Z"
},
"value": {
"distributionValue": {
"count": "1878",
"mean": 17.813718392255,
"sumOfSquaredDeviation": 7141630.651914,
"bucketOptions": {
"explicitBuckets": {
"bounds": [
0,
0.01,
0.05,
0.1,
0.3,
0.6,
0.8,
1,
2,
3,
4,
5,
6,
8,
10,
13,
16,
20,
25,
30,
40,
50,
65,
80,
100,
130,
160,
200,
250,
300,
400,
500,
650,
800,
1000,
2000,
5000,
10000,
20000,
50000,
100000
]
}
},
"bucketCounts": [
"0",
"0",
"0",
"0",
"8",
"403",
"297",
"184",
"375",
"213",
"56",
"31",
"15",
"13",
"4",
"1",
"5",
"2",
"8",
"13",
"26",
"13",
"45",
"48",
"61",
"10",
"3",
"6",
"7",
"4",
"7",
"12",
"8"
]
}
}
},
{
"interval": {
"startTime": "2019-02-22T18:10:26Z",
"endTime": "2019-02-22T18:11:26Z"
},
"value": {
"distributionValue": {
"count": "1887",
"mean": 17.654277577766,
"sumOfSquaredDeviation": 7082587.2133073,
"bucketOptions": {
"explicitBuckets": {
"bounds": [
0,
0.01,
0.05,
0.1,
0.3,
0.6,
0.8,
1,
2,
3,
4,
5,
6,
8,
10,
13,
16,
20,
25,
30,
40,
50,
65,
80,
100,
130,
160,
200,
250,
300,
400,
500,
650,
800,
1000,
2000,
5000,
10000,
20000,
50000,
100000
]
}
},
"bucketCounts": [
"0",
"0",
"0",
"0",
"8",
"404",
"298",
"187",
"381",
"212",
"56",
"31",
"15",
"14",
"4",
"1",
"4",
"2",
"9",
"13",
"24",
"13",
"46",
"46",
"61",
"11",
"3",
"6",
"7",
"5",
"7",
"11",
"8"
]
}
}
}
]
}
]
}
+1 -1
View File
@@ -26,7 +26,7 @@ type StackdriverBucketOptions struct {
Scale float64 `json:"scale"`
} `json:"exponentialBuckets"`
ExplicitBuckets *struct {
Bounds []int64 `json:"bounds"`
Bounds []float64 `json:"bounds"`
} `json:"explicitBuckets"`
}