Merge remote-tracking branch 'origin/master' into envelope

This commit is contained in:
Emil Tullstedt
2020-10-15 14:29:48 +02:00
1264 changed files with 24235 additions and 43996 deletions
+12 -2
View File
@@ -56,7 +56,6 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/admin/orgs", reqGrafanaAdmin, hs.Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, hs.Index)
r.Get("/admin/stats", reqGrafanaAdmin, hs.Index)
r.Get("/admin/live", reqGrafanaAdmin, hs.Index)
r.Get("/admin/ldap", reqGrafanaAdmin, hs.Index)
r.Get("/styleguide", reqSignedIn, hs.Index)
@@ -79,6 +78,7 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/import/dashboard", reqSignedIn, hs.Index)
r.Get("/dashboards/", reqSignedIn, hs.Index)
r.Get("/dashboards/*", reqSignedIn, hs.Index)
r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index)
r.Get("/explore", reqSignedIn, middleware.EnsureEditorOrViewerCanEdit, hs.Index)
@@ -352,6 +352,13 @@ func (hs *HTTPServer) registerRoutes() {
alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
})
if hs.Cfg.IsNgAlertEnabled() {
apiRoute.Group("/alert-definitions", func(alertDefinitions routing.RouteRegister) {
alertDefinitions.Get("/eval/:dashboardID/:panelID/:refID", reqEditorRole, Wrap(hs.AlertDefinitionEval))
alertDefinitions.Post("/eval", reqEditorRole, bind(dtos.EvalAlertConditionsCommand{}), Wrap(hs.ConditionsEval))
})
}
apiRoute.Get("/alert-notifiers", reqEditorRole, Wrap(GetAlertNotifiers))
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
@@ -384,6 +391,9 @@ func (hs *HTTPServer) registerRoutes() {
// error test
r.Get("/metrics/error", Wrap(GenerateError))
// short urls
apiRoute.Post("/short-urls", bind(dtos.CreateShortURLCmd{}), Wrap(hs.createShortURL))
}, reqSignedIn)
// admin api
@@ -432,7 +442,7 @@ func (hs *HTTPServer) registerRoutes() {
// Snapshots
r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
r.Get("/api/snapshot/shared-options/", reqSignedIn, GetSharingOptions)
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
r.Get("/api/snapshots/:key", Wrap(GetDashboardSnapshot))
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey))
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
+16 -11
View File
@@ -86,7 +86,7 @@ func CreateDashboardSnapshot(c *models.ReqContext, cmd models.CreateDashboardSna
response, err := createExternalDashboardSnapshot(cmd)
if err != nil {
c.JsonApiErr(500, "Failed to create external snaphost", err)
c.JsonApiErr(500, "Failed to create external snapshot", err)
return
}
@@ -123,7 +123,7 @@ func CreateDashboardSnapshot(c *models.ReqContext, cmd models.CreateDashboardSna
}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to create snaphost", err)
c.JsonApiErr(500, "Failed to create snapshot", err)
return
}
@@ -136,26 +136,29 @@ func CreateDashboardSnapshot(c *models.ReqContext, cmd models.CreateDashboardSna
}
// GET /api/snapshots/:key
func GetDashboardSnapshot(c *models.ReqContext) {
func GetDashboardSnapshot(c *models.ReqContext) Response {
key := c.Params(":key")
query := &models.GetDashboardSnapshotQuery{Key: key}
err := bus.Dispatch(query)
if err != nil {
c.JsonApiErr(500, "Failed to get dashboard snapshot", err)
return
return Error(500, "Failed to get dashboard snapshot", err)
}
snapshot := query.Result
// expired snapshots should also be removed from db
if snapshot.Expires.Before(time.Now()) {
c.JsonApiErr(404, "Dashboard snapshot not found", err)
return
return Error(404, "Dashboard snapshot not found", err)
}
dashboard, err := snapshot.DashboardJSON()
if err != nil {
return Error(500, "Failed to get dashboard data for dashboard snapshot", err)
}
dto := dtos.DashboardFullWithMeta{
Dashboard: snapshot.Dashboard,
Dashboard: dashboard,
Meta: dtos.DashboardMeta{
Type: models.DashTypeSnapshot,
IsSnapshot: true,
@@ -166,8 +169,7 @@ func GetDashboardSnapshot(c *models.ReqContext) {
metrics.MApiDashboardSnapshotGet.Inc()
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
c.JSON(200, dto)
return JSON(200, dto).Header("Cache-Control", "public, max-age=3600")
}
func deleteExternalDashboardSnapshot(externalUrl string) error {
@@ -238,7 +240,10 @@ func DeleteDashboardSnapshot(c *models.ReqContext) Response {
if query.Result == nil {
return Error(404, "Failed to get dashboard snapshot", nil)
}
dashboard := query.Result.Dashboard
dashboard, err := query.Result.DashboardJSON()
if err != nil {
return Error(500, "Failed to get dashboard data for dashboard snapshot", err)
}
dashboardID := dashboard.Get("id").MustInt64()
guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
+59
View File
@@ -7,9 +7,12 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/components/securedata"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
@@ -198,6 +201,62 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 500)
})
})
Convey("Should be able to read a snapshot's un-encrypted data", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
dashboard := respJSON.Get("dashboard")
id := dashboard.Get("id")
So(id.MustInt64(), ShouldEqual, 100)
})
})
Convey("Should be able to read a snapshot's encrypted data", func() {
origSecret := setting.SecretKey
setting.SecretKey = "dashboard_snapshot_api_test"
t.Cleanup(func() {
setting.SecretKey = origSecret
})
dashboardId := 123
jsonModel, err := simplejson.NewJson([]byte(fmt.Sprintf(`{"id":%d}`, dashboardId)))
So(err, ShouldBeNil)
jsonModelEncoded, err := jsonModel.Encode()
So(err, ShouldBeNil)
encrypted, err := securedata.Encrypt(jsonModelEncoded)
So(err, ShouldBeNil)
// mock snapshot with encrypted dashboard info
mockSnapshotResult := &models.DashboardSnapshot{
Key: "12345",
DashboardEncrypted: encrypted,
Expires: time.Now().Add(time.Duration(1000) * time.Second),
}
bus.AddHandler("test", func(query *models.GetDashboardSnapshotQuery) error {
query.Result = mockSnapshotResult
return nil
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("dashboard").Get("id").MustInt64(), ShouldEqual, dashboardId)
})
})
})
})
}
+12
View File
@@ -0,0 +1,12 @@
package dtos
import (
"time"
eval "github.com/grafana/grafana/pkg/services/ngalert"
)
type EvalAlertConditionsCommand struct {
Conditions eval.Conditions `json:"conditions"`
Now time.Time `json:"now"`
}
+10
View File
@@ -0,0 +1,10 @@
package dtos
type ShortURL struct {
UID string `json:"uid"`
URL string `json:"url"`
}
type CreateShortURLCmd struct {
Path string `json:"path"`
}
+1
View File
@@ -203,6 +203,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"alertingMinInterval": setting.AlertingMinInterval,
"autoAssignOrg": setting.AutoAssignOrg,
"verifyEmailEnabled": setting.VerifyEmailEnabled,
"sigV4AuthEnabled": setting.SigV4AuthEnabled,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
+4
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/live"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/shorturls"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
@@ -29,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/login"
eval "github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering"
@@ -70,6 +72,8 @@ type HTTPServer struct {
BackendPluginManager backendplugin.Manager `inject:""`
PluginManager *plugins.PluginManager `inject:""`
SearchService *search.SearchService `inject:""`
AlertNG *eval.AlertNG `inject:""`
ShortURLService *shorturls.ShortURLService `inject:""`
Live *live.GrafanaLive
Listener net.Listener
}
-6
View File
@@ -287,12 +287,6 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "graph-bar"},
}
if hs.Live != nil {
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
Text: "Live", Id: "live", Url: setting.AppSubUrl + "/admin/live", Icon: "water",
})
}
if setting.LDAPEnabled {
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
Text: "LDAP", Id: "ldap", Url: setting.AppSubUrl + "/admin/ldap", Icon: "book",
+5
View File
@@ -254,6 +254,11 @@ func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext)
}
func (hs *HTTPServer) Logout(c *models.ReqContext) {
if hs.Cfg.SAMLEnabled && hs.Cfg.SAMLSingleLogoutEnabled {
c.Redirect(setting.AppSubUrl + "/logout/saml")
return
}
if err := hs.AuthTokenService.RevokeToken(c.Req.Context(), c.UserToken); err != nil && err != models.ErrUserTokenNotFound {
hs.log.Error("failed to revoke auth token", "error", err)
}
+101
View File
@@ -0,0 +1,101 @@
package api
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
eval "github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util"
)
// POST /api/alert-definitions/eval
func (hs *HTTPServer) ConditionsEval(c *models.ReqContext, dto dtos.EvalAlertConditionsCommand) Response {
alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout)
defer cancelFn()
alertExecCtx := eval.AlertExecCtx{Ctx: alertCtx, SignedInUser: c.SignedInUser}
fromStr := c.Query("from")
if fromStr == "" {
fromStr = "now-3h"
}
toStr := c.Query("to")
if toStr == "" {
toStr = "now"
}
execResult, err := dto.Conditions.Execute(alertExecCtx, fromStr, toStr)
if err != nil {
return Error(400, "Failed to execute conditions", err)
}
evalResults, err := eval.EvaluateExecutionResult(execResult)
if err != nil {
return Error(400, "Failed to evaluate results", err)
}
frame := evalResults.AsDataFrame()
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
instances, err := df.Encoded()
if err != nil {
return Error(400, "Failed to encode result dataframes", err)
}
return JSON(200, util.DynMap{
"instances": instances,
})
}
// GET /api/alert-definitions/eval/:dashboardId/:panelId/:refId"
func (hs *HTTPServer) AlertDefinitionEval(c *models.ReqContext) Response {
dashboardID := c.ParamsInt64(":dashboardID")
panelID := c.ParamsInt64(":panelID")
conditionRefID := c.Params(":refID")
fromStr := c.Query("from")
if fromStr == "" {
fromStr = "now-3h"
}
toStr := c.Query("to")
if toStr == "" {
toStr = "now"
}
conditions, err := hs.AlertNG.LoadAlertConditions(dashboardID, panelID, conditionRefID, c.SignedInUser, c.SkipCache)
if err != nil {
return Error(400, "Failed to load conditions", err)
}
alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout)
defer cancelFn()
alertExecCtx := eval.AlertExecCtx{Ctx: alertCtx, SignedInUser: c.SignedInUser}
execResult, err := conditions.Execute(alertExecCtx, fromStr, toStr)
if err != nil {
return Error(400, "Failed to execute conditions", err)
}
evalResults, err := eval.EvaluateExecutionResult(execResult)
if err != nil {
return Error(400, "Failed to evaluate results", err)
}
frame := evalResults.AsDataFrame()
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
instances, err := df.Encoded()
if err != nil {
return Error(400, "Failed to encode result dataframes", err)
}
return JSON(200, util.DynMap{
"instances": instances,
})
}
+61
View File
@@ -0,0 +1,61 @@
package api
import (
"errors"
"path"
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
// createShortURL handles requests to create short URLs.
func (hs *HTTPServer) createShortURL(c *models.ReqContext, cmd dtos.CreateShortURLCmd) Response {
hs.log.Debug("Received request to create short URL", "path", cmd.Path)
cmd.Path = strings.TrimSpace(cmd.Path)
if path.IsAbs(cmd.Path) {
hs.log.Error("Invalid short URL path", "path", cmd.Path)
return Error(400, "Path should be relative", nil)
}
shortURL, err := hs.ShortURLService.CreateShortURL(c.Req.Context(), c.SignedInUser, cmd.Path)
if err != nil {
return Error(500, "Failed to create short URL", err)
}
url := path.Join(setting.AppUrl, "goto", shortURL.Uid)
c.Logger.Debug("Created short URL", "url", url)
dto := dtos.ShortURL{
UID: shortURL.Uid,
URL: url,
}
return JSON(200, dto)
}
func (hs *HTTPServer) redirectFromShortURL(c *models.ReqContext) {
shortURLUID := c.Params(":uid")
if !util.IsValidShortUID(shortURLUID) {
return
}
shortURL, err := hs.ShortURLService.GetShortURLByUID(c.Req.Context(), c.SignedInUser, shortURLUID)
if err != nil {
if errors.Is(err, models.ErrShortURLNotFound) {
hs.log.Debug("Not redirecting short URL since not found")
return
}
hs.log.Error("Short URL redirection error", "err", err)
return
}
hs.log.Debug("Redirecting short URL", "path", shortURL.Path)
c.Redirect(setting.ToAbsUrl(shortURL.Path), 302)
}