mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into add_google_hangouts_chat_notifier
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
||||
func AdminGetSettings(c *m.ReqContext) {
|
||||
settings := make(map[string]interface{})
|
||||
|
||||
for _, section := range setting.Cfg.Sections() {
|
||||
for _, section := range setting.Raw.Sections() {
|
||||
jsonSec := make(map[string]interface{})
|
||||
settings[section.Name()] = jsonSec
|
||||
|
||||
|
||||
@@ -47,14 +47,14 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) {
|
||||
}
|
||||
|
||||
func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
userID := c.ParamsInt64(":id")
|
||||
|
||||
if len(form.Password) < 4 {
|
||||
c.JsonApiErr(400, "New password too short", nil)
|
||||
return
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByIdQuery{Id: userId}
|
||||
userQuery := m.GetUserByIdQuery{Id: userID}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
c.JsonApiErr(500, "Could not read user from database", err)
|
||||
@@ -64,7 +64,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
|
||||
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
|
||||
|
||||
cmd := m.ChangeUserPasswordCommand{
|
||||
UserId: userId,
|
||||
UserId: userID,
|
||||
NewPassword: passwordHashed,
|
||||
}
|
||||
|
||||
@@ -77,10 +77,10 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
|
||||
}
|
||||
|
||||
func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
userID := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.UpdateUserPermissionsCommand{
|
||||
UserId: userId,
|
||||
UserId: userID,
|
||||
IsGrafanaAdmin: form.IsGrafanaAdmin,
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis
|
||||
}
|
||||
|
||||
func AdminDeleteUser(c *m.ReqContext) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
userID := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.DeleteUserCommand{UserId: userId}
|
||||
cmd := m.DeleteUserCommand{UserId: userID}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to delete user", err)
|
||||
|
||||
@@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
func ValidateOrgAlert(c *m.ReqContext) {
|
||||
@@ -26,10 +28,10 @@ func ValidateOrgAlert(c *m.ReqContext) {
|
||||
}
|
||||
|
||||
func GetAlertStatesForDashboard(c *m.ReqContext) Response {
|
||||
dashboardId := c.QueryInt64("dashboardId")
|
||||
dashboardID := c.QueryInt64("dashboardId")
|
||||
|
||||
if dashboardId == 0 {
|
||||
return ApiError(400, "Missing query parameter dashboardId", nil)
|
||||
if dashboardID == 0 {
|
||||
return Error(400, "Missing query parameter dashboardId", nil)
|
||||
}
|
||||
|
||||
query := m.GetAlertStatesForDashboardQuery{
|
||||
@@ -38,20 +40,72 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to fetch alert states", err)
|
||||
return Error(500, "Failed to fetch alert states", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// GET /api/alerts
|
||||
func GetAlerts(c *m.ReqContext) Response {
|
||||
dashboardQuery := c.Query("dashboardQuery")
|
||||
dashboardTags := c.QueryStrings("dashboardTag")
|
||||
stringDashboardIDs := c.QueryStrings("dashboardId")
|
||||
stringFolderIDs := c.QueryStrings("folderId")
|
||||
|
||||
dashboardIDs := make([]int64, 0)
|
||||
for _, id := range stringDashboardIDs {
|
||||
dashboardID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
dashboardIDs = append(dashboardIDs, dashboardID)
|
||||
}
|
||||
}
|
||||
|
||||
if dashboardQuery != "" || len(dashboardTags) > 0 || len(stringFolderIDs) > 0 {
|
||||
folderIDs := make([]int64, 0)
|
||||
for _, id := range stringFolderIDs {
|
||||
folderID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
folderIDs = append(folderIDs, folderID)
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery := search.Query{
|
||||
Title: dashboardQuery,
|
||||
Tags: dashboardTags,
|
||||
SignedInUser: c.SignedInUser,
|
||||
Limit: 1000,
|
||||
OrgId: c.OrgId,
|
||||
DashboardIds: dashboardIDs,
|
||||
Type: string(search.DashHitDB),
|
||||
FolderIds: folderIDs,
|
||||
Permission: m.PERMISSION_VIEW,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
if err != nil {
|
||||
return Error(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
for _, d := range searchQuery.Result {
|
||||
if d.Type == search.DashHitDB && d.Id > 0 {
|
||||
dashboardIDs = append(dashboardIDs, d.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find any dashboards, return empty result
|
||||
if len(dashboardIDs) == 0 {
|
||||
return JSON(200, []*m.AlertListItemDTO{})
|
||||
}
|
||||
}
|
||||
|
||||
query := m.GetAlertsQuery{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: c.QueryInt64("dashboardId"),
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
User: c.SignedInUser,
|
||||
OrgId: c.OrgId,
|
||||
DashboardIDs: dashboardIDs,
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
User: c.SignedInUser,
|
||||
Query: c.Query("query"),
|
||||
}
|
||||
|
||||
states := c.QueryStrings("state")
|
||||
@@ -60,33 +114,37 @@ func GetAlerts(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "List alerts failed", err)
|
||||
return Error(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
for _, alert := range query.Result {
|
||||
alert.Url = m.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// POST /api/alerts/test
|
||||
func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
|
||||
if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
|
||||
return ApiError(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
|
||||
return Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
|
||||
}
|
||||
|
||||
backendCmd := alerting.AlertTestCommand{
|
||||
OrgId: c.OrgId,
|
||||
Dashboard: dto.Dashboard,
|
||||
PanelId: dto.PanelId,
|
||||
User: c.SignedInUser,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&backendCmd); err != nil {
|
||||
if validationErr, ok := err.(alerting.ValidationError); ok {
|
||||
return ApiError(422, validationErr.Error(), nil)
|
||||
return Error(422, validationErr.Error(), nil)
|
||||
}
|
||||
return ApiError(500, "Failed to test rule", err)
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
return Error(403, "Access denied to datasource", err)
|
||||
}
|
||||
return Error(500, "Failed to test rule", err)
|
||||
}
|
||||
|
||||
res := backendCmd.Result
|
||||
@@ -109,7 +167,7 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
|
||||
|
||||
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
|
||||
|
||||
return Json(200, dtoRes)
|
||||
return JSON(200, dtoRes)
|
||||
}
|
||||
|
||||
// GET /api/alerts/:id
|
||||
@@ -118,70 +176,63 @@ func GetAlert(c *m.ReqContext) Response {
|
||||
query := m.GetAlertByIdQuery{Id: id}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "List alerts failed", err)
|
||||
return Error(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
return Json(200, &query.Result)
|
||||
return JSON(200, &query.Result)
|
||||
}
|
||||
|
||||
func GetAlertNotifiers(c *m.ReqContext) Response {
|
||||
return Json(200, alerting.GetNotifiers())
|
||||
return JSON(200, alerting.GetNotifiers())
|
||||
}
|
||||
|
||||
func GetAlertNotifications(c *m.ReqContext) Response {
|
||||
query := &m.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return ApiError(500, "Failed to get alert notifications", err)
|
||||
return Error(500, "Failed to get alert notifications", err)
|
||||
}
|
||||
|
||||
result := make([]*dtos.AlertNotification, 0)
|
||||
|
||||
for _, notification := range query.Result {
|
||||
result = append(result, &dtos.AlertNotification{
|
||||
Id: notification.Id,
|
||||
Name: notification.Name,
|
||||
Type: notification.Type,
|
||||
IsDefault: notification.IsDefault,
|
||||
Created: notification.Created,
|
||||
Updated: notification.Updated,
|
||||
})
|
||||
result = append(result, dtos.NewAlertNotification(notification))
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func GetAlertNotificationById(c *m.ReqContext) Response {
|
||||
func GetAlertNotificationByID(c *m.ReqContext) Response {
|
||||
query := &m.GetAlertNotificationsQuery{
|
||||
OrgId: c.OrgId,
|
||||
Id: c.ParamsInt64("notificationId"),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return ApiError(500, "Failed to get alert notifications", err)
|
||||
return Error(500, "Failed to get alert notifications", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, dtos.NewAlertNotification(query.Result))
|
||||
}
|
||||
|
||||
func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to create alert notification", err)
|
||||
return Error(500, "Failed to create alert notification", err)
|
||||
}
|
||||
|
||||
return Json(200, cmd.Result)
|
||||
return JSON(200, dtos.NewAlertNotification(cmd.Result))
|
||||
}
|
||||
|
||||
func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update alert notification", err)
|
||||
return Error(500, "Failed to update alert notification", err)
|
||||
}
|
||||
|
||||
return Json(200, cmd.Result)
|
||||
return JSON(200, dtos.NewAlertNotification(cmd.Result))
|
||||
}
|
||||
|
||||
func DeleteAlertNotification(c *m.ReqContext) Response {
|
||||
@@ -191,10 +242,10 @@ func DeleteAlertNotification(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete alert notification", err)
|
||||
return Error(500, "Failed to delete alert notification", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Notification deleted")
|
||||
return Success("Notification deleted")
|
||||
}
|
||||
|
||||
//POST /api/alert-notifications/test
|
||||
@@ -207,41 +258,41 @@ func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Respons
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
if err == m.ErrSmtpNotEnabled {
|
||||
return ApiError(412, err.Error(), err)
|
||||
return Error(412, err.Error(), err)
|
||||
}
|
||||
return ApiError(500, "Failed to send alert notifications", err)
|
||||
return Error(500, "Failed to send alert notifications", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Test notification sent")
|
||||
return Success("Test notification sent")
|
||||
}
|
||||
|
||||
//POST /api/alerts/:alertId/pause
|
||||
func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
|
||||
alertId := c.ParamsInt64("alertId")
|
||||
alertID := c.ParamsInt64("alertId")
|
||||
|
||||
query := m.GetAlertByIdQuery{Id: alertId}
|
||||
query := m.GetAlertByIdQuery{Id: alertID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Get Alert failed", err)
|
||||
return Error(500, "Get Alert failed", err)
|
||||
}
|
||||
|
||||
guardian := guardian.New(query.Result.DashboardId, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking permissions for Alert", err)
|
||||
return Error(500, "Error while checking permissions for Alert", err)
|
||||
}
|
||||
|
||||
return ApiError(403, "Access denied to this dashboard and alert", nil)
|
||||
return Error(403, "Access denied to this dashboard and alert", nil)
|
||||
}
|
||||
|
||||
cmd := m.PauseAlertCommand{
|
||||
OrgId: c.OrgId,
|
||||
AlertIds: []int64{alertId},
|
||||
AlertIds: []int64{alertID},
|
||||
Paused: dto.Paused,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "", err)
|
||||
return Error(500, "", err)
|
||||
}
|
||||
|
||||
var response m.AlertStateType = m.AlertStatePending
|
||||
@@ -252,12 +303,12 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"alertId": alertId,
|
||||
"alertId": alertID,
|
||||
"state": response,
|
||||
"message": "Alert " + pausedState,
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
//POST /api/admin/pause-all-alerts
|
||||
@@ -267,7 +318,7 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&updateCmd); err != nil {
|
||||
return ApiError(500, "Failed to pause alerts", err)
|
||||
return Error(500, "Failed to pause alerts", err)
|
||||
}
|
||||
|
||||
var response m.AlertStateType = m.AlertStatePending
|
||||
@@ -283,5 +334,5 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
|
||||
"alertsAffected": updateCmd.ResultCount,
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
@@ -30,7 +31,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -64,6 +65,60 @@ func TestAlertingApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
var searchQuery *search.Query
|
||||
bus.AddHandler("test", func(query *search.Query) error {
|
||||
searchQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
var getAlertsQuery *m.GetAlertsQuery
|
||||
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
|
||||
getAlertsQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = GetAlerts
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(searchQuery, ShouldBeNil)
|
||||
So(getAlertsQuery, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
var searchQuery *search.Query
|
||||
bus.AddHandler("test", func(query *search.Query) error {
|
||||
searchQuery = query
|
||||
query.Result = search.HitList{
|
||||
&search.Hit{Id: 1},
|
||||
&search.Hit{Id: 2},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
var getAlertsQuery *m.GetAlertsQuery
|
||||
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
|
||||
getAlertsQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = GetAlerts
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(searchQuery, ShouldNotBeNil)
|
||||
So(searchQuery.DashboardIds[0], ShouldEqual, 1)
|
||||
So(searchQuery.DashboardIds[1], ShouldEqual, 2)
|
||||
So(searchQuery.FolderIds[0], ShouldEqual, 3)
|
||||
So(searchQuery.Tags[0], ShouldEqual, "abc")
|
||||
So(searchQuery.Title, ShouldEqual, "dbQuery")
|
||||
|
||||
So(getAlertsQuery, ShouldNotBeNil)
|
||||
So(getAlertsQuery.DashboardIDs[0], ShouldEqual, 1)
|
||||
So(getAlertsQuery.DashboardIDs[1], ShouldEqual, 2)
|
||||
So(getAlertsQuery.Limit, ShouldEqual, 5)
|
||||
So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +135,7 @@ func postAlertScenario(desc string, url string, routePattern string, role m.Role
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -15,32 +14,33 @@ import (
|
||||
func GetAnnotations(c *m.ReqContext) Response {
|
||||
|
||||
query := &annotations.ItemQuery{
|
||||
From: c.QueryInt64("from") / 1000,
|
||||
To: c.QueryInt64("to") / 1000,
|
||||
From: c.QueryInt64("from"),
|
||||
To: c.QueryInt64("to"),
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.QueryInt64("userId"),
|
||||
AlertId: c.QueryInt64("alertId"),
|
||||
DashboardId: c.QueryInt64("dashboardId"),
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
Tags: c.QueryStrings("tags"),
|
||||
Type: c.Query("type"),
|
||||
MatchAny: c.QueryBool("matchAny"),
|
||||
}
|
||||
|
||||
repo := annotations.GetRepository()
|
||||
|
||||
items, err := repo.Find(query)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to get annotations", err)
|
||||
return Error(500, "Failed to get annotations", err)
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if item.Email != "" {
|
||||
item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
|
||||
}
|
||||
item.Time = item.Time * 1000
|
||||
}
|
||||
|
||||
return Json(200, items)
|
||||
return JSON(200, items)
|
||||
}
|
||||
|
||||
type CreateAnnotationError struct {
|
||||
@@ -52,7 +52,7 @@ func (e *CreateAnnotationError) Error() string {
|
||||
}
|
||||
|
||||
func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
if canSave, err := canSaveByDashboardId(c, cmd.DashboardId); err != nil || !canSave {
|
||||
if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
|
||||
if cmd.Text == "" {
|
||||
err := &CreateAnnotationError{"text field should not be empty"}
|
||||
return ApiError(500, "Failed to save annotation", err)
|
||||
return Error(500, "Failed to save annotation", err)
|
||||
}
|
||||
|
||||
item := annotations.Item{
|
||||
@@ -68,18 +68,14 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
UserId: c.UserId,
|
||||
DashboardId: cmd.DashboardId,
|
||||
PanelId: cmd.PanelId,
|
||||
Epoch: cmd.Time / 1000,
|
||||
Epoch: cmd.Time,
|
||||
Text: cmd.Text,
|
||||
Data: cmd.Data,
|
||||
Tags: cmd.Tags,
|
||||
}
|
||||
|
||||
if item.Epoch == 0 {
|
||||
item.Epoch = time.Now().Unix()
|
||||
}
|
||||
|
||||
if err := repo.Save(&item); err != nil {
|
||||
return ApiError(500, "Failed to save annotation", err)
|
||||
return Error(500, "Failed to save annotation", err)
|
||||
}
|
||||
|
||||
startID := item.Id
|
||||
@@ -93,24 +89,24 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
|
||||
}
|
||||
|
||||
if err := repo.Update(&item); err != nil {
|
||||
return ApiError(500, "Failed set regionId on annotation", err)
|
||||
return Error(500, "Failed set regionId on annotation", err)
|
||||
}
|
||||
|
||||
item.Id = 0
|
||||
item.Epoch = cmd.TimeEnd / 1000
|
||||
item.Epoch = cmd.TimeEnd
|
||||
|
||||
if err := repo.Save(&item); err != nil {
|
||||
return ApiError(500, "Failed save annotation for region end time", err)
|
||||
return Error(500, "Failed save annotation for region end time", err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"message": "Annotation added",
|
||||
"id": startID,
|
||||
"endId": item.Id,
|
||||
})
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"message": "Annotation added",
|
||||
"id": startID,
|
||||
})
|
||||
@@ -129,12 +125,9 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
|
||||
|
||||
if cmd.What == "" {
|
||||
err := &CreateAnnotationError{"what field should not be empty"}
|
||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||
return Error(500, "Failed to save Graphite annotation", err)
|
||||
}
|
||||
|
||||
if cmd.When == 0 {
|
||||
cmd.When = time.Now().Unix()
|
||||
}
|
||||
text := formatGraphiteAnnotation(cmd.What, cmd.Data)
|
||||
|
||||
// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
|
||||
@@ -152,133 +145,137 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
|
||||
tagsArray = append(tagsArray, tagStr)
|
||||
} else {
|
||||
err := &CreateAnnotationError{"tag should be a string"}
|
||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||
return Error(500, "Failed to save Graphite annotation", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
err := &CreateAnnotationError{"unsupported tags format"}
|
||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||
return Error(500, "Failed to save Graphite annotation", err)
|
||||
}
|
||||
|
||||
item := annotations.Item{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
Epoch: cmd.When,
|
||||
Epoch: cmd.When * 1000,
|
||||
Text: text,
|
||||
Tags: tagsArray,
|
||||
}
|
||||
|
||||
if err := repo.Save(&item); err != nil {
|
||||
return ApiError(500, "Failed to save Graphite annotation", err)
|
||||
return Error(500, "Failed to save Graphite annotation", err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"message": "Graphite annotation added",
|
||||
"id": item.Id,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
|
||||
annotationId := c.ParamsInt64(":annotationId")
|
||||
annotationID := c.ParamsInt64(":annotationId")
|
||||
|
||||
repo := annotations.GetRepository()
|
||||
|
||||
if resp := canSave(c, repo, annotationId); resp != nil {
|
||||
if resp := canSave(c, repo, annotationID); resp != nil {
|
||||
return resp
|
||||
}
|
||||
|
||||
item := annotations.Item{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
Id: annotationId,
|
||||
Epoch: cmd.Time / 1000,
|
||||
Id: annotationID,
|
||||
Epoch: cmd.Time,
|
||||
Text: cmd.Text,
|
||||
Tags: cmd.Tags,
|
||||
}
|
||||
|
||||
if err := repo.Update(&item); err != nil {
|
||||
return ApiError(500, "Failed to update annotation", err)
|
||||
return Error(500, "Failed to update annotation", err)
|
||||
}
|
||||
|
||||
if cmd.IsRegion {
|
||||
itemRight := item
|
||||
itemRight.RegionId = item.Id
|
||||
itemRight.Epoch = cmd.TimeEnd / 1000
|
||||
itemRight.Epoch = cmd.TimeEnd
|
||||
|
||||
// We don't know id of region right event, so set it to 0 and find then using query like
|
||||
// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
|
||||
itemRight.Id = 0
|
||||
|
||||
if err := repo.Update(&itemRight); err != nil {
|
||||
return ApiError(500, "Failed to update annotation for region end time", err)
|
||||
return Error(500, "Failed to update annotation for region end time", err)
|
||||
}
|
||||
}
|
||||
|
||||
return ApiSuccess("Annotation updated")
|
||||
return Success("Annotation updated")
|
||||
}
|
||||
|
||||
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
|
||||
repo := annotations.GetRepository()
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
AlertId: cmd.PanelId,
|
||||
OrgId: c.OrgId,
|
||||
Id: cmd.AnnotationId,
|
||||
RegionId: cmd.RegionId,
|
||||
DashboardId: cmd.DashboardId,
|
||||
PanelId: cmd.PanelId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete annotations", err)
|
||||
return Error(500, "Failed to delete annotations", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Annotations deleted")
|
||||
return Success("Annotations deleted")
|
||||
}
|
||||
|
||||
func DeleteAnnotationById(c *m.ReqContext) Response {
|
||||
func DeleteAnnotationByID(c *m.ReqContext) Response {
|
||||
repo := annotations.GetRepository()
|
||||
annotationId := c.ParamsInt64(":annotationId")
|
||||
annotationID := c.ParamsInt64(":annotationId")
|
||||
|
||||
if resp := canSave(c, repo, annotationId); resp != nil {
|
||||
if resp := canSave(c, repo, annotationID); resp != nil {
|
||||
return resp
|
||||
}
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
Id: annotationId,
|
||||
OrgId: c.OrgId,
|
||||
Id: annotationID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete annotation", err)
|
||||
return Error(500, "Failed to delete annotation", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Annotation deleted")
|
||||
return Success("Annotation deleted")
|
||||
}
|
||||
|
||||
func DeleteAnnotationRegion(c *m.ReqContext) Response {
|
||||
repo := annotations.GetRepository()
|
||||
regionId := c.ParamsInt64(":regionId")
|
||||
regionID := c.ParamsInt64(":regionId")
|
||||
|
||||
if resp := canSave(c, repo, regionId); resp != nil {
|
||||
if resp := canSave(c, repo, regionID); resp != nil {
|
||||
return resp
|
||||
}
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
RegionId: regionId,
|
||||
OrgId: c.OrgId,
|
||||
RegionId: regionID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete annotation region", err)
|
||||
return Error(500, "Failed to delete annotation region", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Annotation region deleted")
|
||||
return Success("Annotation region deleted")
|
||||
}
|
||||
|
||||
func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
|
||||
if dashboardId == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
|
||||
func canSaveByDashboardID(c *m.ReqContext, dashboardID int64) (bool, error) {
|
||||
if dashboardID == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if dashboardId > 0 {
|
||||
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
||||
if dashboardID != 0 {
|
||||
guard := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@@ -286,32 +283,16 @@ func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func canSave(c *m.ReqContext, repo annotations.Repository, annotationId int64) Response {
|
||||
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationId, OrgId: c.OrgId})
|
||||
func canSave(c *m.ReqContext, repo annotations.Repository, annotationID int64) Response {
|
||||
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
|
||||
|
||||
if err != nil || len(items) == 0 {
|
||||
return ApiError(500, "Could not find annotation to update", err)
|
||||
return Error(500, "Could not find annotation to update", err)
|
||||
}
|
||||
|
||||
dashboardId := items[0].DashboardId
|
||||
dashboardID := items[0].DashboardId
|
||||
|
||||
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func canSaveByRegionId(c *m.ReqContext, repo annotations.Repository, regionId int64) Response {
|
||||
items, err := repo.Find(&annotations.ItemQuery{RegionId: regionId, OrgId: c.OrgId})
|
||||
|
||||
if err != nil || len(items) == 0 {
|
||||
return ApiError(500, "Could not find annotation to update", err)
|
||||
}
|
||||
|
||||
dashboardId := items[0].DashboardId
|
||||
|
||||
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
|
||||
if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteAnnotationById
|
||||
sc.handlerFunc = DeleteAnnotationByID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
@@ -68,7 +68,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteAnnotationById
|
||||
sc.handlerFunc = DeleteAnnotationByID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
@@ -100,6 +100,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
Id: 1,
|
||||
}
|
||||
|
||||
deleteCmd := dtos.DeleteAnnotationsCmd{
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
}
|
||||
|
||||
viewerRole := m.ROLE_VIEWER
|
||||
editorRole := m.ROLE_EDITOR
|
||||
|
||||
@@ -114,7 +119,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -132,7 +137,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteAnnotationById
|
||||
sc.handlerFunc = DeleteAnnotationByID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
@@ -159,7 +164,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteAnnotationById
|
||||
sc.handlerFunc = DeleteAnnotationByID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
@@ -171,6 +176,25 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Admin", func() {
|
||||
role := m.ROLE_ADMIN
|
||||
Convey("Should be able to do anything", func() {
|
||||
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -199,7 +223,7 @@ func postAnnotationScenario(desc string, url string, routePattern string, role m
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@@ -222,7 +246,7 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@@ -239,3 +263,26 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func deleteAnnotationsScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.OrgRole = role
|
||||
|
||||
return DeleteAnnotations(c, cmd)
|
||||
})
|
||||
|
||||
fakeAnnoRepo = &fakeAnnotationsRepo{}
|
||||
annotations.SetRepository(fakeAnnoRepo)
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
468
pkg/api/api.go
468
pkg/api/api.go
@@ -4,369 +4,379 @@ import (
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// Register adds http routes
|
||||
func (hs *HttpServer) registerRoutes() {
|
||||
macaronR := hs.macaron
|
||||
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
|
||||
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardUrl()
|
||||
redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl()
|
||||
func (hs *HTTPServer) registerRoutes() {
|
||||
reqSignedIn := middleware.ReqSignedIn
|
||||
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
|
||||
reqEditorRole := middleware.ReqEditorRole
|
||||
reqOrgAdmin := middleware.ReqOrgAdmin
|
||||
redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL()
|
||||
redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL()
|
||||
quota := middleware.Quota
|
||||
bind := binding.Bind
|
||||
|
||||
// automatically set HEAD for every GET
|
||||
macaronR.SetAutoHead(true)
|
||||
|
||||
r := newRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)
|
||||
r := hs.RouteRegister
|
||||
|
||||
// not logged in views
|
||||
r.Get("/", reqSignedIn, Index)
|
||||
r.Get("/", reqSignedIn, hs.Index)
|
||||
r.Get("/logout", Logout)
|
||||
r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost))
|
||||
r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), Wrap(LoginPost))
|
||||
r.Get("/login/:name", quota("session"), OAuthLogin)
|
||||
r.Get("/login", LoginView)
|
||||
r.Get("/invite/:code", Index)
|
||||
r.Get("/login", hs.LoginView)
|
||||
r.Get("/invite/:code", hs.Index)
|
||||
|
||||
// authed views
|
||||
r.Get("/profile/", reqSignedIn, Index)
|
||||
r.Get("/profile/password", reqSignedIn, Index)
|
||||
r.Get("/profile/switch-org/:id", reqSignedIn, ChangeActiveOrgAndRedirectToHome)
|
||||
r.Get("/org/", reqSignedIn, Index)
|
||||
r.Get("/org/new", reqSignedIn, Index)
|
||||
r.Get("/datasources/", reqSignedIn, Index)
|
||||
r.Get("/datasources/new", reqSignedIn, Index)
|
||||
r.Get("/datasources/edit/*", reqSignedIn, Index)
|
||||
r.Get("/org/users", reqSignedIn, Index)
|
||||
r.Get("/org/users/new", reqSignedIn, Index)
|
||||
r.Get("/org/users/invite", reqSignedIn, Index)
|
||||
r.Get("/org/teams", reqSignedIn, Index)
|
||||
r.Get("/org/teams/*", reqSignedIn, Index)
|
||||
r.Get("/org/apikeys/", reqSignedIn, Index)
|
||||
r.Get("/dashboard/import/", reqSignedIn, Index)
|
||||
r.Get("/configuration", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/settings", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users/create", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/stats", reqGrafanaAdmin, Index)
|
||||
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("/dashboard/import/", reqSignedIn, hs.Index)
|
||||
r.Get("/configuration", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/settings", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/users", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/users/create", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, hs.Index)
|
||||
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("/styleguide", reqSignedIn, Index)
|
||||
r.Get("/styleguide", reqSignedIn, hs.Index)
|
||||
|
||||
r.Get("/plugins", reqSignedIn, Index)
|
||||
r.Get("/plugins/:id/edit", reqSignedIn, Index)
|
||||
r.Get("/plugins/:id/page/:page", reqSignedIn, Index)
|
||||
r.Get("/plugins", reqSignedIn, hs.Index)
|
||||
r.Get("/plugins/:id/edit", reqSignedIn, hs.Index)
|
||||
r.Get("/plugins/:id/page/:page", reqSignedIn, hs.Index)
|
||||
|
||||
r.Get("/d/:uid/:slug", reqSignedIn, Index)
|
||||
r.Get("/d/:uid", reqSignedIn, Index)
|
||||
r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardUrl, Index)
|
||||
r.Get("/dashboard/script/*", reqSignedIn, Index)
|
||||
r.Get("/dashboard-solo/snapshot/*", Index)
|
||||
r.Get("/d-solo/:uid/:slug", reqSignedIn, Index)
|
||||
r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloUrl, Index)
|
||||
r.Get("/dashboard-solo/script/*", reqSignedIn, Index)
|
||||
r.Get("/import/dashboard", reqSignedIn, Index)
|
||||
r.Get("/dashboards/", reqSignedIn, Index)
|
||||
r.Get("/dashboards/*", reqSignedIn, Index)
|
||||
r.Get("/d/:uid/:slug", reqSignedIn, hs.Index)
|
||||
r.Get("/d/:uid", reqSignedIn, hs.Index)
|
||||
r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardURL, hs.Index)
|
||||
r.Get("/dashboard/script/*", reqSignedIn, hs.Index)
|
||||
r.Get("/dashboard-solo/snapshot/*", hs.Index)
|
||||
r.Get("/d-solo/:uid/:slug", reqSignedIn, hs.Index)
|
||||
r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloURL, hs.Index)
|
||||
r.Get("/dashboard-solo/script/*", reqSignedIn, hs.Index)
|
||||
r.Get("/import/dashboard", reqSignedIn, hs.Index)
|
||||
r.Get("/dashboards/", reqSignedIn, hs.Index)
|
||||
r.Get("/dashboards/*", reqSignedIn, hs.Index)
|
||||
|
||||
r.Get("/playlists/", reqSignedIn, Index)
|
||||
r.Get("/playlists/*", reqSignedIn, Index)
|
||||
r.Get("/alerting/", reqSignedIn, Index)
|
||||
r.Get("/alerting/*", reqSignedIn, Index)
|
||||
r.Get("/explore", reqEditorRole, 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)
|
||||
|
||||
// sign up
|
||||
r.Get("/signup", Index)
|
||||
r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
|
||||
r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp))
|
||||
r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))
|
||||
r.Get("/signup", hs.Index)
|
||||
r.Get("/api/user/signup/options", Wrap(GetSignUpOptions))
|
||||
r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), Wrap(SignUp))
|
||||
r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), Wrap(SignUpStep2))
|
||||
|
||||
// invited
|
||||
r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
|
||||
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))
|
||||
r.Get("/api/user/invite/:code", Wrap(GetInviteInfoByCode))
|
||||
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), Wrap(CompleteInvite))
|
||||
|
||||
// reset password
|
||||
r.Get("/user/password/send-reset-email", Index)
|
||||
r.Get("/user/password/reset", Index)
|
||||
r.Get("/user/password/send-reset-email", hs.Index)
|
||||
r.Get("/user/password/reset", hs.Index)
|
||||
|
||||
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
|
||||
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), Wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), Wrap(ResetPassword))
|
||||
|
||||
// dashboard snapshots
|
||||
r.Get("/dashboard/snapshot/*", Index)
|
||||
r.Get("/dashboard/snapshots/", reqSignedIn, Index)
|
||||
r.Get("/dashboard/snapshot/*", hs.Index)
|
||||
r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index)
|
||||
|
||||
// api for dashboard snapshots
|
||||
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
|
||||
r.Get("/api/snapshot/shared-options/", GetSharingOptions)
|
||||
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
|
||||
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
|
||||
r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey))
|
||||
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
||||
|
||||
// api renew session based on remember cookie
|
||||
r.Get("/api/login/ping", quota("session"), LoginApiPing)
|
||||
r.Get("/api/login/ping", quota("session"), LoginAPIPing)
|
||||
|
||||
// authed api
|
||||
r.Group("/api", func(apiRoute RouteRegister) {
|
||||
r.Group("/api", func(apiRoute routing.RouteRegister) {
|
||||
|
||||
// user (signed in)
|
||||
apiRoute.Group("/user", func(userRoute RouteRegister) {
|
||||
userRoute.Get("/", wrap(GetSignedInUser))
|
||||
userRoute.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||
userRoute.Post("/using/:id", wrap(UserSetUsingOrg))
|
||||
userRoute.Get("/orgs", wrap(GetSignedInUserOrgList))
|
||||
apiRoute.Group("/user", func(userRoute routing.RouteRegister) {
|
||||
userRoute.Get("/", Wrap(GetSignedInUser))
|
||||
userRoute.Put("/", bind(m.UpdateUserCommand{}), Wrap(UpdateSignedInUser))
|
||||
userRoute.Post("/using/:id", Wrap(UserSetUsingOrg))
|
||||
userRoute.Get("/orgs", Wrap(GetSignedInUserOrgList))
|
||||
userRoute.Get("/teams", Wrap(GetSignedInUserTeamList))
|
||||
|
||||
userRoute.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
||||
userRoute.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
||||
userRoute.Post("/stars/dashboard/:id", Wrap(StarDashboard))
|
||||
userRoute.Delete("/stars/dashboard/:id", Wrap(UnstarDashboard))
|
||||
|
||||
userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
||||
userRoute.Get("/quotas", wrap(GetUserQuotas))
|
||||
userRoute.Put("/helpflags/:id", wrap(SetHelpFlag))
|
||||
userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), Wrap(ChangeUserPassword))
|
||||
userRoute.Get("/quotas", Wrap(GetUserQuotas))
|
||||
userRoute.Put("/helpflags/:id", Wrap(SetHelpFlag))
|
||||
// For dev purpose
|
||||
userRoute.Get("/helpflags/clear", wrap(ClearHelpFlags))
|
||||
userRoute.Get("/helpflags/clear", Wrap(ClearHelpFlags))
|
||||
|
||||
userRoute.Get("/preferences", wrap(GetUserPreferences))
|
||||
userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
|
||||
userRoute.Get("/preferences", Wrap(GetUserPreferences))
|
||||
userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateUserPreferences))
|
||||
})
|
||||
|
||||
// users (admin permission required)
|
||||
apiRoute.Group("/users", func(usersRoute RouteRegister) {
|
||||
usersRoute.Get("/", wrap(SearchUsers))
|
||||
usersRoute.Get("/search", wrap(SearchUsersWithPaging))
|
||||
usersRoute.Get("/:id", wrap(GetUserById))
|
||||
usersRoute.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
|
||||
usersRoute.Get("/", Wrap(SearchUsers))
|
||||
usersRoute.Get("/search", Wrap(SearchUsersWithPaging))
|
||||
usersRoute.Get("/:id", Wrap(GetUserByID))
|
||||
usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList))
|
||||
// query parameters /users/lookup?loginOrEmail=admin@example.com
|
||||
usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail))
|
||||
usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg))
|
||||
usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail))
|
||||
usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), Wrap(UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", Wrap(UpdateUserActiveOrg))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// team (admin permission required)
|
||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", wrap(GetTeamById))
|
||||
teamsRoute.Get("/search", wrap(SearchTeams))
|
||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
|
||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", wrap(DeleteTeamById))
|
||||
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), Wrap(CreateTeam))
|
||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), Wrap(UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", Wrap(DeleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", Wrap(GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), Wrap(AddTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", Wrap(RemoveTeamMember))
|
||||
teamsRoute.Get("/:teamId/preferences", Wrap(GetTeamPreferences))
|
||||
teamsRoute.Put("/:teamId/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateTeamPreferences))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// team without requirement of user to be org admin
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", Wrap(GetTeamByID))
|
||||
teamsRoute.Get("/search", Wrap(SearchTeams))
|
||||
})
|
||||
|
||||
// org information available to all users.
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Get("/", wrap(GetOrgCurrent))
|
||||
orgRoute.Get("/quotas", wrap(GetOrgQuotas))
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/", Wrap(GetOrgCurrent))
|
||||
orgRoute.Get("/quotas", Wrap(GetOrgQuotas))
|
||||
})
|
||||
|
||||
// current org
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
|
||||
|
||||
// invites
|
||||
orgRoute.Get("/invites", wrap(GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||
orgRoute.Get("/invites", Wrap(GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), Wrap(AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", Wrap(RevokeInvite))
|
||||
|
||||
// prefs
|
||||
orgRoute.Get("/preferences", wrap(GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||
orgRoute.Get("/preferences", Wrap(GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateOrgPreferences))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// current org without requirement of user to be org admin
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
|
||||
})
|
||||
|
||||
// create new org
|
||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), Wrap(CreateOrg))
|
||||
|
||||
// search all orgs
|
||||
apiRoute.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))
|
||||
apiRoute.Get("/orgs", reqGrafanaAdmin, Wrap(SearchOrgs))
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) {
|
||||
orgsRoute.Get("/", wrap(GetOrgById))
|
||||
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", wrap(DeleteOrgById))
|
||||
orgsRoute.Get("/users", wrap(GetOrgUsers))
|
||||
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", wrap(RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", wrap(GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota))
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
|
||||
orgsRoute.Get("/", Wrap(GetOrgByID))
|
||||
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", Wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", Wrap(GetOrgUsers))
|
||||
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), Wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", Wrap(RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", Wrap(GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), Wrap(UpdateOrgQuota))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/name/:name", func(orgsRoute RouteRegister) {
|
||||
orgsRoute.Get("/", wrap(GetOrgByName))
|
||||
apiRoute.Group("/orgs/name/:name", func(orgsRoute routing.RouteRegister) {
|
||||
orgsRoute.Get("/", Wrap(GetOrgByName))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// auth api keys
|
||||
apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) {
|
||||
keysRoute.Get("/", wrap(GetApiKeys))
|
||||
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
|
||||
keysRoute.Delete("/:id", wrap(DeleteApiKey))
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
keysRoute.Get("/", Wrap(GetAPIKeys))
|
||||
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), Wrap(AddAPIKey))
|
||||
keysRoute.Delete("/:id", Wrap(DeleteAPIKey))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// Preferences
|
||||
apiRoute.Group("/preferences", func(prefRoute RouteRegister) {
|
||||
prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
|
||||
apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) {
|
||||
prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), Wrap(SetHomeDashboard))
|
||||
})
|
||||
|
||||
// Data sources
|
||||
apiRoute.Group("/datasources", func(datasourceRoute RouteRegister) {
|
||||
datasourceRoute.Get("/", wrap(GetDataSources))
|
||||
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource))
|
||||
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
|
||||
datasourceRoute.Delete("/:id", wrap(DeleteDataSourceById))
|
||||
datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName))
|
||||
datasourceRoute.Get("/:id", wrap(GetDataSourceById))
|
||||
datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName))
|
||||
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
|
||||
datasourceRoute.Get("/", Wrap(GetDataSources))
|
||||
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource))
|
||||
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource))
|
||||
datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceById))
|
||||
datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName))
|
||||
datasourceRoute.Get("/:id", Wrap(GetDataSourceById))
|
||||
datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
|
||||
apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIdByName), reqSignedIn)
|
||||
|
||||
apiRoute.Get("/plugins", wrap(GetPluginList))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
|
||||
apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
|
||||
apiRoute.Get("/plugins", Wrap(hs.GetPluginList))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID))
|
||||
apiRoute.Get("/plugins/:pluginId/markdown/:name", Wrap(GetPluginMarkdown))
|
||||
|
||||
apiRoute.Group("/plugins", func(pluginRoute RouteRegister) {
|
||||
pluginRoute.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
|
||||
pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
|
||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||
pluginRoute.Get("/:pluginId/dashboards/", Wrap(GetPluginDashboards))
|
||||
pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), Wrap(UpdatePluginSetting))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
apiRoute.Get("/frontend/settings/", GetFrontendSettings)
|
||||
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
|
||||
apiRoute.Any("/datasources/proxy/:id/*", reqSignedIn, hs.ProxyDataSourceRequest)
|
||||
apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest)
|
||||
|
||||
// Folders
|
||||
apiRoute.Group("/folders", func(folderRoute RouteRegister) {
|
||||
folderRoute.Get("/", wrap(GetFolders))
|
||||
folderRoute.Get("/id/:id", wrap(GetFolderById))
|
||||
folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder))
|
||||
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
|
||||
folderRoute.Get("/", Wrap(GetFolders))
|
||||
folderRoute.Get("/id/:id", Wrap(GetFolderByID))
|
||||
folderRoute.Post("/", bind(m.CreateFolderCommand{}), Wrap(CreateFolder))
|
||||
|
||||
folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) {
|
||||
folderUidRoute.Get("/", wrap(GetFolderByUid))
|
||||
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder))
|
||||
folderUidRoute.Delete("/", wrap(DeleteFolder))
|
||||
folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) {
|
||||
folderUidRoute.Get("/", Wrap(GetFolderByUID))
|
||||
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), Wrap(UpdateFolder))
|
||||
folderUidRoute.Delete("/", Wrap(DeleteFolder))
|
||||
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute RouteRegister) {
|
||||
folderPermissionRoute.Get("/", wrap(GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateFolderPermissions))
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
|
||||
folderPermissionRoute.Get("/", Wrap(GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateFolderPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Dashboard
|
||||
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
|
||||
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUid))
|
||||
apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) {
|
||||
dashboardRoute.Get("/uid/:uid", Wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/uid/:uid", Wrap(DeleteDashboardByUID))
|
||||
|
||||
dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
|
||||
dashboardRoute.Get("/db/:slug", Wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/db/:slug", Wrap(DeleteDashboard))
|
||||
|
||||
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), wrap(CalculateDashboardDiff))
|
||||
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), Wrap(CalculateDashboardDiff))
|
||||
|
||||
dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), wrap(PostDashboard))
|
||||
dashboardRoute.Get("/home", wrap(GetHomeDashboard))
|
||||
dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), Wrap(PostDashboard))
|
||||
dashboardRoute.Get("/home", Wrap(GetHomeDashboard))
|
||||
dashboardRoute.Get("/tags", GetDashboardTags)
|
||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), Wrap(ImportDashboard))
|
||||
|
||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
|
||||
dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
|
||||
dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
|
||||
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), wrap(RestoreDashboardVersion))
|
||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) {
|
||||
dashIdRoute.Get("/versions", Wrap(GetDashboardVersions))
|
||||
dashIdRoute.Get("/versions/:id", Wrap(GetDashboardVersion))
|
||||
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(RestoreDashboardVersion))
|
||||
|
||||
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute RouteRegister) {
|
||||
dashboardPermissionRoute.Get("/", wrap(GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateDashboardPermissions))
|
||||
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) {
|
||||
dashboardPermissionRoute.Get("/", Wrap(GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateDashboardPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Dashboard snapshots
|
||||
apiRoute.Group("/dashboard/snapshots", func(dashboardRoute RouteRegister) {
|
||||
dashboardRoute.Get("/", wrap(SearchDashboardSnapshots))
|
||||
apiRoute.Group("/dashboard/snapshots", func(dashboardRoute routing.RouteRegister) {
|
||||
dashboardRoute.Get("/", Wrap(SearchDashboardSnapshots))
|
||||
})
|
||||
|
||||
// Playlist
|
||||
apiRoute.Group("/playlists", func(playlistRoute RouteRegister) {
|
||||
playlistRoute.Get("/", wrap(SearchPlaylists))
|
||||
playlistRoute.Get("/:id", ValidateOrgPlaylist, wrap(GetPlaylist))
|
||||
playlistRoute.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems))
|
||||
playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards))
|
||||
playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist))
|
||||
playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
|
||||
playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist))
|
||||
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
|
||||
playlistRoute.Get("/", Wrap(SearchPlaylists))
|
||||
playlistRoute.Get("/:id", ValidateOrgPlaylist, Wrap(GetPlaylist))
|
||||
playlistRoute.Get("/:id/items", ValidateOrgPlaylist, Wrap(GetPlaylistItems))
|
||||
playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, Wrap(GetPlaylistDashboards))
|
||||
playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, Wrap(DeletePlaylist))
|
||||
playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, Wrap(UpdatePlaylist))
|
||||
playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), Wrap(CreatePlaylist))
|
||||
})
|
||||
|
||||
// Search
|
||||
apiRoute.Get("/search/", Search)
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(hs.QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/scenarios", Wrap(GetTestDataScenarios))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, Wrap(GenerateSQLTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", Wrap(GetTestDataRandomWalk))
|
||||
|
||||
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest))
|
||||
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), wrap(PauseAlert))
|
||||
alertsRoute.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
||||
alertsRoute.Get("/", wrap(GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard))
|
||||
apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), Wrap(AlertTest))
|
||||
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), Wrap(PauseAlert))
|
||||
alertsRoute.Get("/:alertId", ValidateOrgAlert, Wrap(GetAlert))
|
||||
alertsRoute.Get("/", Wrap(GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
|
||||
})
|
||||
|
||||
apiRoute.Get("/alert-notifications", wrap(GetAlertNotifications))
|
||||
apiRoute.Get("/alert-notifiers", wrap(GetAlertNotifiers))
|
||||
apiRoute.Get("/alert-notifications", Wrap(GetAlertNotifications))
|
||||
apiRoute.Get("/alert-notifiers", Wrap(GetAlertNotifiers))
|
||||
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications RouteRegister) {
|
||||
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
|
||||
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification))
|
||||
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
|
||||
alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationById))
|
||||
alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification))
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
|
||||
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest))
|
||||
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification))
|
||||
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification))
|
||||
alertNotifications.Get("/:notificationId", Wrap(GetAlertNotificationByID))
|
||||
alertNotifications.Delete("/:notificationId", Wrap(DeleteAlertNotification))
|
||||
}, reqEditorRole)
|
||||
|
||||
apiRoute.Get("/annotations", wrap(GetAnnotations))
|
||||
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations))
|
||||
apiRoute.Get("/annotations", Wrap(GetAnnotations))
|
||||
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations))
|
||||
|
||||
apiRoute.Group("/annotations", func(annotationsRoute RouteRegister) {
|
||||
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation))
|
||||
annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationById))
|
||||
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), wrap(UpdateAnnotation))
|
||||
annotationsRoute.Delete("/region/:regionId", wrap(DeleteAnnotationRegion))
|
||||
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), wrap(PostGraphiteAnnotation))
|
||||
apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) {
|
||||
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
|
||||
annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
|
||||
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
|
||||
annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
|
||||
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
|
||||
})
|
||||
|
||||
// error test
|
||||
r.Get("/metrics/error", wrap(GenerateError))
|
||||
r.Get("/metrics/error", Wrap(GenerateError))
|
||||
|
||||
}, reqSignedIn)
|
||||
|
||||
// admin api
|
||||
r.Group("/api/admin", func(adminRoute RouteRegister) {
|
||||
r.Group("/api/admin", func(adminRoute routing.RouteRegister) {
|
||||
adminRoute.Get("/settings", AdminGetSettings)
|
||||
adminRoute.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
||||
adminRoute.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
||||
adminRoute.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
||||
adminRoute.Delete("/users/:id", AdminDeleteUser)
|
||||
adminRoute.Get("/users/:id/quotas", wrap(GetUserQuotas))
|
||||
adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
|
||||
adminRoute.Get("/users/:id/quotas", Wrap(GetUserQuotas))
|
||||
adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), Wrap(UpdateUserQuota))
|
||||
adminRoute.Get("/stats", AdminGetStats)
|
||||
adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), wrap(PauseAllAlerts))
|
||||
adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), Wrap(PauseAllAlerts))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// rendering
|
||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||
r.Get("/render/*", reqSignedIn, hs.RenderToPng)
|
||||
|
||||
// grafana.net proxy
|
||||
r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
|
||||
@@ -380,10 +390,4 @@ func (hs *HttpServer) registerRoutes() {
|
||||
|
||||
// streams
|
||||
//r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)
|
||||
|
||||
r.Register(macaronR)
|
||||
|
||||
InitAppPluginRoutes(macaronR)
|
||||
|
||||
macaronR.NotFound(NotFoundHandler)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func GetApiKeys(c *m.ReqContext) Response {
|
||||
func GetAPIKeys(c *m.ReqContext) Response {
|
||||
query := m.GetApiKeysQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to list api keys", err)
|
||||
return Error(500, "Failed to list api keys", err)
|
||||
}
|
||||
|
||||
result := make([]*m.ApiKeyDTO, len(query.Result))
|
||||
@@ -23,25 +23,25 @@ func GetApiKeys(c *m.ReqContext) Response {
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func DeleteApiKey(c *m.ReqContext) Response {
|
||||
func DeleteAPIKey(c *m.ReqContext) Response {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
|
||||
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete API key", err)
|
||||
return Error(500, "Failed to delete API key", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("API key deleted")
|
||||
return Success("API key deleted")
|
||||
}
|
||||
|
||||
func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
|
||||
func AddAPIKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
return Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
cmd.OrgId = c.OrgId
|
||||
@@ -50,12 +50,12 @@ func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
|
||||
cmd.Key = newKeyInfo.HashedKey
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to add API key", err)
|
||||
return Error(500, "Failed to add API key", err)
|
||||
}
|
||||
|
||||
result := &dtos.NewApiKeyResult{
|
||||
Name: cmd.Result.Name,
|
||||
Key: newKeyInfo.ClientSecret}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
var pluginProxyTransport *http.Transport
|
||||
|
||||
func InitAppPluginRoutes(r *macaron.Macaron) {
|
||||
func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
|
||||
pluginProxyTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: setting.PluginAppsSkipVerifyTLS,
|
||||
@@ -55,11 +55,11 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
|
||||
}
|
||||
}
|
||||
|
||||
func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler {
|
||||
func AppPluginRoute(route *plugins.AppPluginRoute, appID string) macaron.Handler {
|
||||
return func(c *m.ReqContext) {
|
||||
path := c.Params("*")
|
||||
|
||||
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId)
|
||||
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID)
|
||||
proxy.Transport = pluginProxyTransport
|
||||
proxy.ServeHTTP(c.Resp, c.Req.Request)
|
||||
}
|
||||
|
||||
@@ -97,15 +97,6 @@ type CacheServer struct {
|
||||
cache *gocache.Cache
|
||||
}
|
||||
|
||||
func (this *CacheServer) mustInt(r *http.Request, defaultValue int, keys ...string) (v int) {
|
||||
for _, k := range keys {
|
||||
if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil {
|
||||
defaultValue = v
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (this *CacheServer) Handler(ctx *macaron.Context) {
|
||||
urlPath := ctx.Req.URL.Path
|
||||
hash := urlPath[strings.LastIndex(urlPath, "/")+1:]
|
||||
@@ -226,7 +217,7 @@ func (this *thunderTask) Fetch() {
|
||||
this.Done()
|
||||
}
|
||||
|
||||
var client *http.Client = &http.Client{
|
||||
var client = &http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
}
|
||||
@@ -258,9 +249,6 @@ func (this *thunderTask) fetch() error {
|
||||
this.Avatar.data = &bytes.Buffer{}
|
||||
writer := bufio.NewWriter(this.Avatar.data)
|
||||
|
||||
if _, err = io.Copy(writer, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
|
||||
var (
|
||||
NotFound = func() Response {
|
||||
return ApiError(404, "Not found", nil)
|
||||
return Error(404, "Not found", nil)
|
||||
}
|
||||
ServerError = func(err error) Response {
|
||||
return ApiError(500, "Server error", err)
|
||||
return Error(500, "Server error", err)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ type NormalResponse struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func wrap(action interface{}) macaron.Handler {
|
||||
func Wrap(action interface{}) macaron.Handler {
|
||||
|
||||
return func(c *m.ReqContext) {
|
||||
var res Response
|
||||
@@ -67,22 +67,25 @@ func (r *NormalResponse) Header(key, value string) *NormalResponse {
|
||||
return r
|
||||
}
|
||||
|
||||
// functions to create responses
|
||||
// Empty create an empty response
|
||||
func Empty(status int) *NormalResponse {
|
||||
return Respond(status, nil)
|
||||
}
|
||||
|
||||
func Json(status int, body interface{}) *NormalResponse {
|
||||
// JSON create a JSON response
|
||||
func JSON(status int, body interface{}) *NormalResponse {
|
||||
return Respond(status, body).Header("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
func ApiSuccess(message string) *NormalResponse {
|
||||
// Success create a successful response
|
||||
func Success(message string) *NormalResponse {
|
||||
resp := make(map[string]interface{})
|
||||
resp["message"] = message
|
||||
return Json(200, resp)
|
||||
return JSON(200, resp)
|
||||
}
|
||||
|
||||
func ApiError(status int, message string, err error) *NormalResponse {
|
||||
// Error create a erroneous response
|
||||
func Error(status int, message string, err error) *NormalResponse {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
switch status {
|
||||
@@ -102,7 +105,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
|
||||
}
|
||||
}
|
||||
|
||||
resp := Json(status, data)
|
||||
resp := JSON(status, data)
|
||||
|
||||
if err != nil {
|
||||
resp.errMessage = message
|
||||
@@ -112,6 +115,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
|
||||
return resp
|
||||
}
|
||||
|
||||
// Respond create a response
|
||||
func Respond(status int, body interface{}) *NormalResponse {
|
||||
var b []byte
|
||||
var err error
|
||||
@@ -122,7 +126,7 @@ func Respond(status int, body interface{}) *NormalResponse {
|
||||
b = []byte(t)
|
||||
default:
|
||||
if b, err = json.Marshal(body); err != nil {
|
||||
return ApiError(500, "body json marshal", err)
|
||||
return Error(500, "body json marshal", err)
|
||||
}
|
||||
}
|
||||
return &NormalResponse{
|
||||
|
||||
@@ -23,7 +23,7 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@@ -46,6 +46,31 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
|
||||
})
|
||||
}
|
||||
|
||||
func anonymousUserScenario(desc string, method string, url string, routePattern string, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
if sc.handlerFunc != nil {
|
||||
return sc.handlerFunc(sc.context)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
sc.m.Get(routePattern, sc.defaultHandler)
|
||||
case "DELETE":
|
||||
sc.m.Delete(routePattern, sc.defaultHandler)
|
||||
}
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
|
||||
sc.resp = httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
@@ -99,7 +124,7 @@ func setupScenarioContext(url string) *scenarioContext {
|
||||
}))
|
||||
|
||||
sc.m.Use(middleware.GetContextHandler())
|
||||
sc.m.Use(middleware.Sessioner(&session.Options{}))
|
||||
sc.m.Use(middleware.Sessioner(&session.Options{}, 0))
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
@@ -22,12 +23,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
|
||||
const (
|
||||
anonString = "Anonymous"
|
||||
)
|
||||
|
||||
func isDashboardStarredByUser(c *m.ReqContext, dashID int64) (bool, error) {
|
||||
if !c.IsSignedIn {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashId}
|
||||
query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashID}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -37,10 +42,10 @@ func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
|
||||
|
||||
func dashboardGuardianResponse(err error) Response {
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking dashboard permissions", err)
|
||||
return Error(500, "Error while checking dashboard permissions", err)
|
||||
}
|
||||
|
||||
return ApiError(403, "Access denied to this dashboard", nil)
|
||||
return Error(403, "Access denied to this dashboard", nil)
|
||||
}
|
||||
|
||||
func GetDashboard(c *m.ReqContext) Response {
|
||||
@@ -60,11 +65,11 @@ func GetDashboard(c *m.ReqContext) Response {
|
||||
|
||||
isStarred, err := isDashboardStarredByUser(c, dash.Id)
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking if dashboard was starred by user", err)
|
||||
return Error(500, "Error while checking if dashboard was starred by user", err)
|
||||
}
|
||||
|
||||
// Finding creator and last updater of the dashboard
|
||||
updater, creator := "Anonymous", "Anonymous"
|
||||
updater, creator := anonString, anonString
|
||||
if dash.UpdatedBy > 0 {
|
||||
updater = getUserLogin(dash.UpdatedBy)
|
||||
}
|
||||
@@ -96,12 +101,22 @@ func GetDashboard(c *m.ReqContext) Response {
|
||||
if dash.FolderId > 0 {
|
||||
query := m.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Dashboard folder could not be read", err)
|
||||
return Error(500, "Dashboard folder could not be read", err)
|
||||
}
|
||||
meta.FolderTitle = query.Result.Title
|
||||
meta.FolderUrl = query.Result.GetUrl()
|
||||
}
|
||||
|
||||
isDashboardProvisioned := &m.IsDashboardProvisionedQuery{DashboardId: dash.Id}
|
||||
err = bus.Dispatch(isDashboardProvisioned)
|
||||
if err != nil {
|
||||
return Error(500, "Error while checking if dashboard is provisioned", err)
|
||||
}
|
||||
|
||||
if isDashboardProvisioned.Result {
|
||||
meta.Provisioned = true
|
||||
}
|
||||
|
||||
// make sure db version is in sync with json model version
|
||||
dash.Data.Set("version", dash.Version)
|
||||
|
||||
@@ -111,31 +126,29 @@ func GetDashboard(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
c.TimeRequest(metrics.M_Api_Dashboard_Get)
|
||||
return Json(200, dto)
|
||||
return JSON(200, dto)
|
||||
}
|
||||
|
||||
func getUserLogin(userId int64) string {
|
||||
query := m.GetUserByIdQuery{Id: userId}
|
||||
func getUserLogin(userID int64) string {
|
||||
query := m.GetUserByIdQuery{Id: userID}
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
return "Anonymous"
|
||||
} else {
|
||||
user := query.Result
|
||||
return user.Login
|
||||
return anonString
|
||||
}
|
||||
return query.Result.Login
|
||||
}
|
||||
|
||||
func getDashboardHelper(orgId int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
|
||||
func getDashboardHelper(orgID int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
|
||||
var query m.GetDashboardQuery
|
||||
|
||||
if len(uid) > 0 {
|
||||
query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgId}
|
||||
query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgID}
|
||||
} else {
|
||||
query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgId}
|
||||
query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgID}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, ApiError(404, "Dashboard not found", err)
|
||||
return nil, Error(404, "Dashboard not found", err)
|
||||
}
|
||||
|
||||
return query.Result, nil
|
||||
@@ -145,11 +158,11 @@ func DeleteDashboard(c *m.ReqContext) Response {
|
||||
query := m.GetDashboardsBySlugQuery{OrgId: c.OrgId, Slug: c.Params(":slug")}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to retrieve dashboards by slug", err)
|
||||
return Error(500, "Failed to retrieve dashboards by slug", err)
|
||||
}
|
||||
|
||||
if len(query.Result) > 1 {
|
||||
return Json(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
|
||||
return JSON(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
|
||||
}
|
||||
|
||||
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, "")
|
||||
@@ -164,16 +177,16 @@ func DeleteDashboard(c *m.ReqContext) Response {
|
||||
|
||||
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete dashboard", err)
|
||||
return Error(500, "Failed to delete dashboard", err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"title": dash.Title,
|
||||
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDashboardByUid(c *m.ReqContext) Response {
|
||||
func DeleteDashboardByUID(c *m.ReqContext) Response {
|
||||
dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid"))
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
@@ -186,10 +199,10 @@ func DeleteDashboardByUid(c *m.ReqContext) Response {
|
||||
|
||||
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete dashboard", err)
|
||||
return Error(500, "Failed to delete dashboard", err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"title": dash.Title,
|
||||
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
|
||||
})
|
||||
@@ -204,10 +217,10 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
||||
if dash.Id == 0 && dash.Uid == "" {
|
||||
limitReached, err := quota.QuotaReached(c, "dashboard")
|
||||
if err != nil {
|
||||
return ApiError(500, "failed to get quota", err)
|
||||
return Error(500, "failed to get quota", err)
|
||||
}
|
||||
if limitReached {
|
||||
return ApiError(403, "Quota reached", nil)
|
||||
return Error(403, "Quota reached", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,24 +243,25 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
||||
err == m.ErrDashboardWithSameUIDExists ||
|
||||
err == m.ErrFolderNotFound ||
|
||||
err == m.ErrDashboardFolderCannotHaveParent ||
|
||||
err == m.ErrDashboardFolderNameExists {
|
||||
return ApiError(400, err.Error(), nil)
|
||||
err == m.ErrDashboardFolderNameExists ||
|
||||
err == m.ErrDashboardCannotSaveProvisionedDashboard {
|
||||
return Error(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardUpdateAccessDenied {
|
||||
return ApiError(403, err.Error(), err)
|
||||
return Error(403, err.Error(), err)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardContainsInvalidAlertData {
|
||||
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
if validationErr, ok := err.(alerting.ValidationError); ok {
|
||||
return Error(422, validationErr.Error(), nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == m.ErrDashboardWithSameNameInFolderExists {
|
||||
return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
|
||||
return JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()})
|
||||
}
|
||||
if err == m.ErrDashboardVersionMismatch {
|
||||
return Json(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
|
||||
return JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
|
||||
}
|
||||
if pluginErr, ok := err.(m.UpdatePluginDashboardError); ok {
|
||||
message := "The dashboard belongs to plugin " + pluginErr.PluginId + "."
|
||||
@@ -255,20 +269,20 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
||||
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
|
||||
message = "The dashboard belongs to plugin " + pluginDef.Name + "."
|
||||
}
|
||||
return Json(412, util.DynMap{"status": "plugin-dashboard", "message": message})
|
||||
return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
|
||||
}
|
||||
if err == m.ErrDashboardNotFound {
|
||||
return Json(404, util.DynMap{"status": "not-found", "message": err.Error()})
|
||||
return JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
|
||||
}
|
||||
return ApiError(500, "Failed to save dashboard", err)
|
||||
return Error(500, "Failed to save dashboard", err)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardFailedToUpdateAlertData {
|
||||
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
return Error(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
}
|
||||
|
||||
c.TimeRequest(metrics.M_Api_Dashboard_Save)
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"status": "success",
|
||||
"slug": dashboard.Slug,
|
||||
"version": dashboard.Version,
|
||||
@@ -279,9 +293,9 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
||||
}
|
||||
|
||||
func GetHomeDashboard(c *m.ReqContext) Response {
|
||||
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||
prefsQuery := m.GetPreferencesWithDefaultsQuery{User: c.SignedInUser}
|
||||
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||
return ApiError(500, "Failed to get preferences", err)
|
||||
return Error(500, "Failed to get preferences", err)
|
||||
}
|
||||
|
||||
if prefsQuery.Result.HomeDashboardId != 0 {
|
||||
@@ -290,16 +304,15 @@ func GetHomeDashboard(c *m.ReqContext) Response {
|
||||
if err == nil {
|
||||
url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
|
||||
dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
|
||||
return Json(200, &dashRedirect)
|
||||
} else {
|
||||
log.Warn("Failed to get slug from database, %s", err.Error())
|
||||
return JSON(200, &dashRedirect)
|
||||
}
|
||||
log.Warn("Failed to get slug from database, %s", err.Error())
|
||||
}
|
||||
|
||||
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to load home dashboard", err)
|
||||
return Error(500, "Failed to load home dashboard", err)
|
||||
}
|
||||
|
||||
dash := dtos.DashboardFullWithMeta{}
|
||||
@@ -309,14 +322,14 @@ func GetHomeDashboard(c *m.ReqContext) Response {
|
||||
|
||||
jsonParser := json.NewDecoder(file)
|
||||
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
|
||||
return ApiError(500, "Failed to load home dashboard", err)
|
||||
return Error(500, "Failed to load home dashboard", err)
|
||||
}
|
||||
|
||||
if c.HasUserRole(m.ROLE_ADMIN) && !c.HasHelpFlag(m.HelpFlagGettingStartedPanelDismissed) {
|
||||
addGettingStartedPanelToHomeDashboard(dash.Dashboard)
|
||||
}
|
||||
|
||||
return Json(200, &dash)
|
||||
return JSON(200, &dash)
|
||||
}
|
||||
|
||||
func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
|
||||
@@ -339,22 +352,22 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
|
||||
|
||||
// GetDashboardVersions returns all dashboard versions as JSON
|
||||
func GetDashboardVersions(c *m.ReqContext) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
dashID := c.ParamsInt64(":dashboardId")
|
||||
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
query := m.GetDashboardVersionsQuery{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: dashId,
|
||||
DashboardId: dashID,
|
||||
Limit: c.QueryInt("limit"),
|
||||
Start: c.QueryInt("start"),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(404, fmt.Sprintf("No versions found for dashboardId %d", dashId), err)
|
||||
return Error(404, fmt.Sprintf("No versions found for dashboardId %d", dashID), err)
|
||||
}
|
||||
|
||||
for _, version := range query.Result {
|
||||
@@ -373,29 +386,29 @@ func GetDashboardVersions(c *m.ReqContext) Response {
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// GetDashboardVersion returns the dashboard version with the given ID.
|
||||
func GetDashboardVersion(c *m.ReqContext) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
dashID := c.ParamsInt64(":dashboardId")
|
||||
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
query := m.GetDashboardVersionQuery{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: dashId,
|
||||
DashboardId: dashID,
|
||||
Version: c.ParamsInt(":id"),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashId), err)
|
||||
return Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashID), err)
|
||||
}
|
||||
|
||||
creator := "Anonymous"
|
||||
creator := anonString
|
||||
if query.Result.CreatedBy > 0 {
|
||||
creator = getUserLogin(query.Result.CreatedBy)
|
||||
}
|
||||
@@ -405,7 +418,7 @@ func GetDashboardVersion(c *m.ReqContext) Response {
|
||||
CreatedBy: creator,
|
||||
}
|
||||
|
||||
return Json(200, dashVersionMeta)
|
||||
return JSON(200, dashVersionMeta)
|
||||
}
|
||||
|
||||
// POST /api/dashboards/calculate-diff performs diffs on two dashboards
|
||||
@@ -441,9 +454,9 @@ func CalculateDashboardDiff(c *m.ReqContext, apiOptions dtos.CalculateDiffOption
|
||||
result, err := dashdiffs.CalculateDiff(&options)
|
||||
if err != nil {
|
||||
if err == m.ErrDashboardVersionNotFound {
|
||||
return ApiError(404, "Dashboard version not found", err)
|
||||
return Error(404, "Dashboard version not found", err)
|
||||
}
|
||||
return ApiError(500, "Unable to compute diff", err)
|
||||
return Error(500, "Unable to compute diff", err)
|
||||
}
|
||||
|
||||
if options.DiffType == dashdiffs.DiffDelta {
|
||||
@@ -467,7 +480,7 @@ func RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersio
|
||||
|
||||
versionQuery := m.GetDashboardVersionQuery{DashboardId: dash.Id, Version: apiCmd.Version, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&versionQuery); err != nil {
|
||||
return ApiError(404, "Dashboard version not found", nil)
|
||||
return Error(404, "Dashboard version not found", nil)
|
||||
}
|
||||
|
||||
version := versionQuery.Result
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func GetDashboardPermissionList(c *m.ReqContext) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
dashID := c.ParamsInt64(":dashboardId")
|
||||
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
|
||||
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
g := guardian.New(dashID, c.OrgId, c.SignedInUser)
|
||||
|
||||
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
|
||||
return dashboardGuardianResponse(err)
|
||||
@@ -25,38 +25,43 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
|
||||
|
||||
acl, err := g.GetAcl()
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to get dashboard permissions", err)
|
||||
return Error(500, "Failed to get dashboard permissions", err)
|
||||
}
|
||||
|
||||
for _, perm := range acl {
|
||||
perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail)
|
||||
|
||||
if perm.TeamId > 0 {
|
||||
perm.TeamAvatarUrl = dtos.GetGravatarUrlWithDefault(perm.TeamEmail, perm.Team)
|
||||
}
|
||||
if perm.Slug != "" {
|
||||
perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, acl)
|
||||
return JSON(200, acl)
|
||||
}
|
||||
|
||||
func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
dashID := c.ParamsInt64(":dashboardId")
|
||||
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
|
||||
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
g := guardian.New(dashID, c.OrgId, c.SignedInUser)
|
||||
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
cmd := m.UpdateDashboardAclCommand{}
|
||||
cmd.DashboardId = dashId
|
||||
cmd.DashboardId = dashID
|
||||
|
||||
for _, item := range apiCmd.Items {
|
||||
cmd.Items = append(cmd.Items, &m.DashboardAcl{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: dashId,
|
||||
DashboardId: dashID,
|
||||
UserId: item.UserId,
|
||||
TeamId: item.TeamId,
|
||||
Role: item.Role,
|
||||
@@ -70,21 +75,21 @@ func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclC
|
||||
if err != nil {
|
||||
if err == guardian.ErrGuardianPermissionExists ||
|
||||
err == guardian.ErrGuardianOverride {
|
||||
return ApiError(400, err.Error(), err)
|
||||
return Error(400, err.Error(), err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Error while checking dashboard permissions", err)
|
||||
return Error(500, "Error while checking dashboard permissions", err)
|
||||
}
|
||||
|
||||
return ApiError(403, "Cannot remove own admin permission for a folder", nil)
|
||||
return Error(403, "Cannot remove own admin permission for a folder", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrDashboardAclInfoMissing || err == m.ErrDashboardPermissionDashboardEmpty {
|
||||
return ApiError(409, err.Error(), err)
|
||||
return Error(409, err.Error(), err)
|
||||
}
|
||||
return ApiError(500, "Failed to create permission", err)
|
||||
return Error(500, "Failed to create permission", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Dashboard permissions updated")
|
||||
return Success("Dashboard permissions updated")
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to override inherited permissions with lower presedence", func() {
|
||||
Convey("When trying to override inherited permissions with lower precedence", func() {
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanAdminValue: true,
|
||||
@@ -194,7 +194,7 @@ func updateDashboardPermissionScenario(desc string, url string, routePattern str
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.UserId = TestUserID
|
||||
|
||||
@@ -91,40 +91,60 @@ func GetDashboardSnapshot(c *m.ReqContext) {
|
||||
c.JSON(200, dto)
|
||||
}
|
||||
|
||||
// GET /api/snapshots-delete/:key
|
||||
func DeleteDashboardSnapshot(c *m.ReqContext) Response {
|
||||
key := c.Params(":key")
|
||||
// GET /api/snapshots-delete/:deleteKey
|
||||
func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response {
|
||||
key := c.Params(":deleteKey")
|
||||
|
||||
query := &m.GetDashboardSnapshotQuery{DeleteKey: key}
|
||||
|
||||
err := bus.Dispatch(query)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to get dashboard snapshot", err)
|
||||
return Error(500, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return Error(500, "Failed to delete dashboard snapshot", err)
|
||||
}
|
||||
|
||||
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
|
||||
}
|
||||
|
||||
// DELETE /api/snapshots/:key
|
||||
func DeleteDashboardSnapshot(c *m.ReqContext) Response {
|
||||
key := c.Params(":key")
|
||||
|
||||
query := &m.GetDashboardSnapshotQuery{Key: key}
|
||||
|
||||
err := bus.Dispatch(query)
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
|
||||
if query.Result == nil {
|
||||
return ApiError(404, "Failed to get dashboard snapshot", nil)
|
||||
return Error(404, "Failed to get dashboard snapshot", nil)
|
||||
}
|
||||
dashboard := query.Result.Dashboard
|
||||
dashboardId := dashboard.Get("id").MustInt64()
|
||||
dashboardID := dashboard.Get("id").MustInt64()
|
||||
|
||||
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
|
||||
canEdit, err := guardian.CanEdit()
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking permissions for snapshot", err)
|
||||
return Error(500, "Error while checking permissions for snapshot", err)
|
||||
}
|
||||
|
||||
if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
|
||||
return ApiError(403, "Access denied to this snapshot", nil)
|
||||
return Error(403, "Access denied to this snapshot", nil)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: key}
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete dashboard snapshot", err)
|
||||
return Error(500, "Failed to delete dashboard snapshot", err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
|
||||
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
|
||||
}
|
||||
|
||||
// GET /api/dashboard/snapshots
|
||||
@@ -145,7 +165,7 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
if err != nil {
|
||||
return ApiError(500, "Search failed", err)
|
||||
return Error(500, "Search failed", err)
|
||||
}
|
||||
|
||||
dtos := make([]*m.DashboardSnapshotDTO, len(searchQuery.Result))
|
||||
@@ -154,7 +174,6 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
|
||||
Id: snapshot.Id,
|
||||
Name: snapshot.Name,
|
||||
Key: snapshot.Key,
|
||||
DeleteKey: snapshot.DeleteKey,
|
||||
OrgId: snapshot.OrgId,
|
||||
UserId: snapshot.UserId,
|
||||
External: snapshot.External,
|
||||
@@ -165,5 +184,5 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, dtos)
|
||||
return JSON(200, dtos)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
teamResp := []*m.Team{}
|
||||
teamResp := []*m.TeamDTO{}
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = teamResp
|
||||
return nil
|
||||
@@ -47,15 +47,30 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
|
||||
Convey("When user has editor role and is not in the ACL", func() {
|
||||
Convey("Should not be able to delete snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is anonymous", func() {
|
||||
Convey("Should be able to delete snapshot by deleteKey", func() {
|
||||
anonymousUserScenario("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is editor and dashboard has default ACL", func() {
|
||||
aclMockResp = []*m.DashboardAclInfoDTO{
|
||||
{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
|
||||
@@ -63,9 +78,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("Should be able to delete a snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
@@ -81,9 +96,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
mockSnapshotResult.UserId = TestUserID
|
||||
|
||||
Convey("Should be able to delete a snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
@@ -42,6 +43,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
|
||||
viewerRole := m.ROLE_VIEWER
|
||||
editorRole := m.ROLE_EDITOR
|
||||
|
||||
@@ -56,7 +62,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -105,7 +111,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -165,7 +171,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -192,6 +198,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
fakeDash.HasAcl = true
|
||||
setting.ViewersCanEdit = false
|
||||
|
||||
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
||||
dashboards := []*m.Dashboard{fakeDash}
|
||||
query.Result = dashboards
|
||||
@@ -220,7 +231,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -271,7 +282,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -329,7 +340,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -398,7 +409,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -468,7 +479,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -527,7 +538,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -594,7 +605,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
|
||||
CallDeleteDashboardByUid(sc)
|
||||
CallDeleteDashboardByUID(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
|
||||
Convey("Should lookup dashboard by uid", func() {
|
||||
@@ -625,6 +636,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
dashTwo.FolderId = 3
|
||||
dashTwo.HasAcl = false
|
||||
|
||||
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
|
||||
dashboards := []*m.Dashboard{dashOne, dashTwo}
|
||||
query.Result = dashboards
|
||||
@@ -638,7 +654,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
|
||||
Convey("Should result in 412 Precondition failed", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 412)
|
||||
result := sc.ToJson()
|
||||
result := sc.ToJSON()
|
||||
So(result.Get("status").MustString(), ShouldEqual, "multiple-slugs-exists")
|
||||
So(result.Get("message").MustString(), ShouldEqual, m.ErrDashboardsWithSameSlugExists.Error())
|
||||
})
|
||||
@@ -686,7 +702,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("It should return correct response data", func() {
|
||||
result := sc.ToJson()
|
||||
result := sc.ToJSON()
|
||||
So(result.Get("status").MustString(), ShouldEqual, "success")
|
||||
So(result.Get("id").MustInt64(), ShouldEqual, 2)
|
||||
So(result.Get("uid").MustString(), ShouldEqual, "uid")
|
||||
@@ -710,7 +726,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
{SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
|
||||
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardContainsInvalidAlertData, ExpectedStatusCode: 500},
|
||||
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
|
||||
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
|
||||
@@ -720,6 +736,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
{SaveError: m.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
||||
{SaveError: m.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400},
|
||||
{SaveError: m.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
||||
}
|
||||
|
||||
@@ -750,6 +767,11 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.IsDashboardProvisionedQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
|
||||
query.Result = &m.DashboardVersion{
|
||||
Data: simplejson.NewFromAny(map[string]interface{}{
|
||||
@@ -837,12 +859,12 @@ func CallDeleteDashboard(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func CallDeleteDashboardByUid(sc *scenarioContext) {
|
||||
func CallDeleteDashboardByUID(sc *scenarioContext) {
|
||||
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = DeleteDashboardByUid
|
||||
sc.handlerFunc = DeleteDashboardByUID
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
@@ -861,7 +883,7 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.SignedInUser = &m.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
|
||||
|
||||
@@ -886,7 +908,7 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.SignedInUser = &m.SignedInUser{
|
||||
OrgId: TestOrgID,
|
||||
@@ -903,7 +925,7 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal
|
||||
})
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) ToJson() *simplejson.Json {
|
||||
func (sc *scenarioContext) ToJSON() *simplejson.Json {
|
||||
var result *simplejson.Json
|
||||
err := json.NewDecoder(sc.resp.Body).Decode(&result)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -1,47 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
|
||||
|
||||
func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m.DataSource, error) {
|
||||
cacheKey := fmt.Sprintf("ds-%d", id)
|
||||
|
||||
if !nocache {
|
||||
if cached, found := hs.cache.Get(cacheKey); found {
|
||||
ds := cached.(*m.DataSource)
|
||||
if ds.OrgId == orgId {
|
||||
return ds, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hs.cache.Set(cacheKey, query.Result, time.Second*5)
|
||||
return query.Result, nil
|
||||
}
|
||||
|
||||
func (hs *HttpServer) ProxyDataSourceRequest(c *m.ReqContext) {
|
||||
func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
|
||||
c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer)
|
||||
|
||||
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
|
||||
|
||||
ds, err := hs.getDatasourceById(c.ParamsInt64(":id"), c.OrgId, nocache)
|
||||
|
||||
dsId := c.ParamsInt64(":id")
|
||||
ds, err := hs.DatasourceCache.GetDatasource(dsId, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
c.JsonApiErr(403, "Access denied to datasource", err)
|
||||
return
|
||||
}
|
||||
c.JsonApiErr(500, "Unable to load datasource meta data", err)
|
||||
return
|
||||
}
|
||||
@@ -53,7 +28,21 @@ func (hs *HttpServer) ProxyDataSourceRequest(c *m.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
proxyPath := c.Params("*")
|
||||
// macaron does not include trailing slashes when resolving a wildcard path
|
||||
proxyPath := ensureProxyPathTrailingSlash(c.Req.URL.Path, c.Params("*"))
|
||||
|
||||
proxy := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath)
|
||||
proxy.HandleRequest()
|
||||
}
|
||||
|
||||
// ensureProxyPathTrailingSlash Check for a trailing slash in original path and makes
|
||||
// sure that a trailing slash is added to proxy path, if not already exists.
|
||||
func ensureProxyPathTrailingSlash(originalPath, proxyPath string) string {
|
||||
if len(proxyPath) > 1 {
|
||||
if originalPath[len(originalPath)-1] == '/' && proxyPath[len(proxyPath)-1] != '/' {
|
||||
return proxyPath + "/"
|
||||
}
|
||||
}
|
||||
|
||||
return proxyPath
|
||||
}
|
||||
|
||||
19
pkg/api/dataproxy_test.go
Normal file
19
pkg/api/dataproxy_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDataProxy(t *testing.T) {
|
||||
Convey("Data proxy test", t, func() {
|
||||
Convey("Should append trailing slash to proxy path if original path has a trailing slash", func() {
|
||||
So(ensureProxyPathTrailingSlash("/api/datasources/proxy/6/api/v1/query_range/", "api/v1/query_range/"), ShouldEqual, "api/v1/query_range/")
|
||||
})
|
||||
|
||||
Convey("Should not append trailing slash to proxy path if original path doesn't have a trailing slash", func() {
|
||||
So(ensureProxyPathTrailingSlash("/api/datasources/proxy/6/api/v1/query_range", "api/v1/query_range"), ShouldEqual, "api/v1/query_range")
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -14,14 +14,14 @@ func GetDataSources(c *m.ReqContext) Response {
|
||||
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to query datasources", err)
|
||||
return Error(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
result := make(dtos.DataSourceList, 0)
|
||||
for _, ds := range query.Result {
|
||||
dsItem := dtos.DataSourceListItemDTO{
|
||||
Id: ds.Id,
|
||||
OrgId: ds.OrgId,
|
||||
Id: ds.Id,
|
||||
Name: ds.Name,
|
||||
Url: ds.Url,
|
||||
Type: ds.Type,
|
||||
@@ -46,7 +46,7 @@ func GetDataSources(c *m.ReqContext) Response {
|
||||
|
||||
sort.Sort(result)
|
||||
|
||||
return Json(200, &result)
|
||||
return JSON(200, &result)
|
||||
}
|
||||
|
||||
func GetDataSourceById(c *m.ReqContext) Response {
|
||||
@@ -57,66 +57,69 @@ func GetDataSourceById(c *m.ReqContext) Response {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrDataSourceNotFound {
|
||||
return ApiError(404, "Data source not found", nil)
|
||||
return Error(404, "Data source not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to query datasources", err)
|
||||
return Error(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
ds := query.Result
|
||||
dtos := convertModelToDtos(ds)
|
||||
|
||||
return Json(200, &dtos)
|
||||
return JSON(200, &dtos)
|
||||
}
|
||||
|
||||
func DeleteDataSourceById(c *m.ReqContext) Response {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
if id <= 0 {
|
||||
return ApiError(400, "Missing valid datasource id", nil)
|
||||
return Error(400, "Missing valid datasource id", nil)
|
||||
}
|
||||
|
||||
ds, err := getRawDataSourceById(id, c.OrgId)
|
||||
if err != nil {
|
||||
return ApiError(400, "Failed to delete datasource", nil)
|
||||
return Error(400, "Failed to delete datasource", nil)
|
||||
}
|
||||
|
||||
if ds.ReadOnly {
|
||||
return ApiError(403, "Cannot delete read-only data source", nil)
|
||||
return Error(403, "Cannot delete read-only data source", nil)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDataSourceByIdCommand{Id: id, OrgId: c.OrgId}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete datasource", err)
|
||||
return Error(500, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Data source deleted")
|
||||
return Success("Data source deleted")
|
||||
}
|
||||
|
||||
func DeleteDataSourceByName(c *m.ReqContext) Response {
|
||||
name := c.Params(":name")
|
||||
|
||||
if name == "" {
|
||||
return ApiError(400, "Missing valid datasource name", nil)
|
||||
return Error(400, "Missing valid datasource name", nil)
|
||||
}
|
||||
|
||||
getCmd := &m.GetDataSourceByNameQuery{Name: name, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(getCmd); err != nil {
|
||||
return ApiError(500, "Failed to delete datasource", err)
|
||||
if err == m.ErrDataSourceNotFound {
|
||||
return Error(404, "Data source not found", nil)
|
||||
}
|
||||
return Error(500, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
if getCmd.Result.ReadOnly {
|
||||
return ApiError(403, "Cannot delete read-only data source", nil)
|
||||
return Error(403, "Cannot delete read-only data source", nil)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDataSourceByNameCommand{Name: name, OrgId: c.OrgId}
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to delete datasource", err)
|
||||
return Error(500, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Data source deleted")
|
||||
return Success("Data source deleted")
|
||||
}
|
||||
|
||||
func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
|
||||
@@ -124,14 +127,14 @@ func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrDataSourceNameExists {
|
||||
return ApiError(409, err.Error(), err)
|
||||
return Error(409, err.Error(), err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to add datasource", err)
|
||||
return Error(500, "Failed to add datasource", err)
|
||||
}
|
||||
|
||||
ds := convertModelToDtos(cmd.Result)
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"message": "Datasource added",
|
||||
"id": cmd.Result.Id,
|
||||
"name": cmd.Result.Name,
|
||||
@@ -143,29 +146,42 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.Id = c.ParamsInt64(":id")
|
||||
|
||||
err := fillWithSecureJsonData(&cmd)
|
||||
err := fillWithSecureJSONData(&cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to update datasource", err)
|
||||
return Error(500, "Failed to update datasource", err)
|
||||
}
|
||||
|
||||
err = bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceUpdatingOldVersion {
|
||||
return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
|
||||
} else {
|
||||
return ApiError(500, "Failed to update datasource", err)
|
||||
return Error(500, "Failed to update datasource. Reload new version and try again", err)
|
||||
}
|
||||
return Error(500, "Failed to update datasource", err)
|
||||
}
|
||||
ds := convertModelToDtos(cmd.Result)
|
||||
return Json(200, util.DynMap{
|
||||
|
||||
query := m.GetDataSourceByIdQuery{
|
||||
Id: cmd.Id,
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrDataSourceNotFound {
|
||||
return Error(404, "Data source not found", nil)
|
||||
}
|
||||
return Error(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
dtos := convertModelToDtos(query.Result)
|
||||
|
||||
return JSON(200, util.DynMap{
|
||||
"message": "Datasource updated",
|
||||
"id": cmd.Id,
|
||||
"name": cmd.Name,
|
||||
"datasource": ds,
|
||||
"datasource": dtos,
|
||||
})
|
||||
}
|
||||
|
||||
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
|
||||
func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
|
||||
if len(cmd.SecureJsonData) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -179,8 +195,8 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
|
||||
return m.ErrDatasourceIsReadOnly
|
||||
}
|
||||
|
||||
secureJsonData := ds.SecureJsonData.Decrypt()
|
||||
for k, v := range secureJsonData {
|
||||
secureJSONData := ds.SecureJsonData.Decrypt()
|
||||
for k, v := range secureJSONData {
|
||||
|
||||
if _, ok := cmd.SecureJsonData[k]; !ok {
|
||||
cmd.SecureJsonData[k] = v
|
||||
@@ -190,10 +206,10 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRawDataSourceById(id int64, orgId int64) (*m.DataSource, error) {
|
||||
func getRawDataSourceById(id int64, orgID int64) (*m.DataSource, error) {
|
||||
query := m.GetDataSourceByIdQuery{
|
||||
Id: id,
|
||||
OrgId: orgId,
|
||||
OrgId: orgID,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
@@ -209,14 +225,14 @@ func GetDataSourceByName(c *m.ReqContext) Response {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrDataSourceNotFound {
|
||||
return ApiError(404, "Data source not found", nil)
|
||||
return Error(404, "Data source not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to query datasources", err)
|
||||
return Error(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
dtos := convertModelToDtos(query.Result)
|
||||
dtos.ReadOnly = true
|
||||
return Json(200, &dtos)
|
||||
return JSON(200, &dtos)
|
||||
}
|
||||
|
||||
// Get /api/datasources/id/:name
|
||||
@@ -225,9 +241,9 @@ func GetDataSourceIdByName(c *m.ReqContext) Response {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrDataSourceNotFound {
|
||||
return ApiError(404, "Data source not found", nil)
|
||||
return Error(404, "Data source not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to query datasources", err)
|
||||
return Error(500, "Failed to query datasources", err)
|
||||
}
|
||||
|
||||
ds := query.Result
|
||||
@@ -235,7 +251,7 @@ func GetDataSourceIdByName(c *m.ReqContext) Response {
|
||||
Id: ds.Id,
|
||||
}
|
||||
|
||||
return Json(200, &dtos)
|
||||
return JSON(200, &dtos)
|
||||
}
|
||||
|
||||
func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
|
||||
|
||||
@@ -46,5 +46,13 @@ func TestDataSourcesProxy(t *testing.T) {
|
||||
So(respJSON[3]["name"], ShouldEqual, "ZZZ")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to save a data source", func() {
|
||||
loggedInUserScenario("When calling DELETE on non-existing", "/api/datasources/name/12345", func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDataSourceByName
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 404)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,35 +1,78 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type AlertRule struct {
|
||||
Id int64 `json:"id"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
PanelId int64 `json:"panelId"`
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
State m.AlertStateType `json:"state"`
|
||||
NewStateDate time.Time `json:"newStateDate"`
|
||||
EvalDate time.Time `json:"evalDate"`
|
||||
EvalData *simplejson.Json `json:"evalData"`
|
||||
ExecutionError string `json:"executionError"`
|
||||
Url string `json:"url"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
Id int64 `json:"id"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
PanelId int64 `json:"panelId"`
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
State models.AlertStateType `json:"state"`
|
||||
NewStateDate time.Time `json:"newStateDate"`
|
||||
EvalDate time.Time `json:"evalDate"`
|
||||
EvalData *simplejson.Json `json:"evalData"`
|
||||
ExecutionError string `json:"executionError"`
|
||||
Url string `json:"url"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
}
|
||||
|
||||
func formatShort(interval time.Duration) string {
|
||||
var result string
|
||||
|
||||
hours := interval / time.Hour
|
||||
if hours > 0 {
|
||||
result += fmt.Sprintf("%dh", hours)
|
||||
}
|
||||
|
||||
remaining := interval - (hours * time.Hour)
|
||||
mins := remaining / time.Minute
|
||||
if mins > 0 {
|
||||
result += fmt.Sprintf("%dm", mins)
|
||||
}
|
||||
|
||||
remaining = remaining - (mins * time.Minute)
|
||||
seconds := remaining / time.Second
|
||||
if seconds > 0 {
|
||||
result += fmt.Sprintf("%ds", seconds)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func NewAlertNotification(notification *models.AlertNotification) *AlertNotification {
|
||||
return &AlertNotification{
|
||||
Id: notification.Id,
|
||||
Name: notification.Name,
|
||||
Type: notification.Type,
|
||||
IsDefault: notification.IsDefault,
|
||||
Created: notification.Created,
|
||||
Updated: notification.Updated,
|
||||
Frequency: formatShort(notification.Frequency),
|
||||
SendReminder: notification.SendReminder,
|
||||
DisableResolveMessage: notification.DisableResolveMessage,
|
||||
Settings: notification.Settings,
|
||||
}
|
||||
}
|
||||
|
||||
type AlertNotification struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
SendReminder bool `json:"sendReminder"`
|
||||
DisableResolveMessage bool `json:"disableResolveMessage"`
|
||||
Frequency string `json:"frequency"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Settings *simplejson.Json `json:"settings"`
|
||||
}
|
||||
|
||||
type AlertTestCommand struct {
|
||||
@@ -39,7 +82,7 @@ type AlertTestCommand struct {
|
||||
|
||||
type AlertTestResult struct {
|
||||
Firing bool `json:"firing"`
|
||||
State m.AlertStateType `json:"state"`
|
||||
State models.AlertStateType `json:"state"`
|
||||
ConditionEvals string `json:"conditionEvals"`
|
||||
TimeMs string `json:"timeMs"`
|
||||
Error string `json:"error,omitempty"`
|
||||
@@ -59,9 +102,12 @@ type EvalMatch struct {
|
||||
}
|
||||
|
||||
type NotificationTestCommand struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Settings *simplejson.Json `json:"settings"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SendReminder bool `json:"sendReminder"`
|
||||
DisableResolveMessage bool `json:"disableResolveMessage"`
|
||||
Frequency string `json:"frequency"`
|
||||
Settings *simplejson.Json `json:"settings"`
|
||||
}
|
||||
|
||||
type PauseAlertCommand struct {
|
||||
|
||||
35
pkg/api/dtos/alerting_test.go
Normal file
35
pkg/api/dtos/alerting_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFormatShort(t *testing.T) {
|
||||
tcs := []struct {
|
||||
interval time.Duration
|
||||
expected string
|
||||
}{
|
||||
{interval: time.Hour, expected: "1h"},
|
||||
{interval: time.Hour + time.Minute, expected: "1h1m"},
|
||||
{interval: (time.Hour * 10) + time.Minute, expected: "10h1m"},
|
||||
{interval: (time.Hour * 10) + (time.Minute * 10) + time.Second, expected: "10h10m1s"},
|
||||
{interval: time.Minute * 10, expected: "10m"},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
got := formatShort(tc.interval)
|
||||
if got != tc.expected {
|
||||
t.Errorf("expected %s got %s interval: %v", tc.expected, got, tc.interval)
|
||||
}
|
||||
|
||||
parsed, err := time.ParseDuration(tc.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse expected duration")
|
||||
}
|
||||
|
||||
if parsed != tc.interval {
|
||||
t.Errorf("expects the parsed duration to equal the interval. Got %v expected: %v", parsed, tc.interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ type DashboardMeta struct {
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
}
|
||||
|
||||
type DashboardFullWithMeta struct {
|
||||
|
||||
@@ -13,6 +13,8 @@ type IndexViewData struct {
|
||||
Theme string
|
||||
NewGrafanaVersionExists bool
|
||||
NewGrafanaVersion string
|
||||
AppName string
|
||||
AppNameBodyClass string
|
||||
}
|
||||
|
||||
type PluginCss struct {
|
||||
|
||||
@@ -22,21 +22,22 @@ type LoginCommand struct {
|
||||
}
|
||||
|
||||
type CurrentUser struct {
|
||||
IsSignedIn bool `json:"isSignedIn"`
|
||||
Id int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgCount int `json:"orgCount"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
Timezone string `json:"timezone"`
|
||||
Locale string `json:"locale"`
|
||||
HelpFlags1 m.HelpFlags1 `json:"helpFlags1"`
|
||||
IsSignedIn bool `json:"isSignedIn"`
|
||||
Id int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
OrgCount int `json:"orgCount"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
Timezone string `json:"timezone"`
|
||||
Locale string `json:"locale"`
|
||||
HelpFlags1 m.HelpFlags1 `json:"helpFlags1"`
|
||||
HasEditPermissionInFolders bool `json:"hasEditPermissionInFolders"`
|
||||
}
|
||||
|
||||
type MetricRequest struct {
|
||||
@@ -50,6 +51,10 @@ type UserStars struct {
|
||||
}
|
||||
|
||||
func GetGravatarUrl(text string) string {
|
||||
if setting.DisableGravatar {
|
||||
return setting.AppSubUrl + "/public/img/user_profile.png"
|
||||
}
|
||||
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ type PluginSetting struct {
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
DefaultNavUrl string `json:"defaultNavUrl"`
|
||||
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
State string `json:"state"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
State plugins.PluginState `json:"state"`
|
||||
}
|
||||
|
||||
type PluginListItem struct {
|
||||
@@ -34,7 +34,7 @@ type PluginListItem struct {
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
DefaultNavUrl string `json:"defaultNavUrl"`
|
||||
State string `json:"state"`
|
||||
State plugins.PluginState `json:"state"`
|
||||
}
|
||||
|
||||
type PluginList []PluginListItem
|
||||
@@ -57,4 +57,5 @@ type ImportDashboardCommand struct {
|
||||
Overwrite bool `json:"overwrite"`
|
||||
Dashboard *simplejson.Json `json:"dashboard"`
|
||||
Inputs []plugins.ImportDashboardInput `json:"inputs"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package dtos
|
||||
|
||||
type Prefs struct {
|
||||
Theme string `json:"theme"`
|
||||
HomeDashboardId int64 `json:"homeDashboardId"`
|
||||
HomeDashboardID int64 `json:"homeDashboardId"`
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
type UpdatePrefsCmd struct {
|
||||
Theme string `json:"theme"`
|
||||
HomeDashboardId int64 `json:"homeDashboardId"`
|
||||
HomeDashboardID int64 `json:"homeDashboardId"`
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
@@ -28,30 +28,30 @@ func GetFolders(c *m.ReqContext) Response {
|
||||
})
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func GetFolderByUid(c *m.ReqContext) Response {
|
||||
func GetFolderByUID(c *m.ReqContext) Response {
|
||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
|
||||
folder, err := s.GetFolderByUid(c.Params(":uid"))
|
||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||
|
||||
if err != nil {
|
||||
return toFolderError(err)
|
||||
}
|
||||
|
||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||
return Json(200, toFolderDto(g, folder))
|
||||
return JSON(200, toFolderDto(g, folder))
|
||||
}
|
||||
|
||||
func GetFolderById(c *m.ReqContext) Response {
|
||||
func GetFolderByID(c *m.ReqContext) Response {
|
||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
|
||||
folder, err := s.GetFolderById(c.ParamsInt64(":id"))
|
||||
folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
return toFolderError(err)
|
||||
}
|
||||
|
||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||
return Json(200, toFolderDto(g, folder))
|
||||
return JSON(200, toFolderDto(g, folder))
|
||||
}
|
||||
|
||||
func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
|
||||
@@ -62,7 +62,7 @@ func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
|
||||
}
|
||||
|
||||
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
|
||||
return Json(200, toFolderDto(g, cmd.Result))
|
||||
return JSON(200, toFolderDto(g, cmd.Result))
|
||||
}
|
||||
|
||||
func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
|
||||
@@ -73,7 +73,7 @@ func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
|
||||
}
|
||||
|
||||
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
|
||||
return Json(200, toFolderDto(g, cmd.Result))
|
||||
return JSON(200, toFolderDto(g, cmd.Result))
|
||||
}
|
||||
|
||||
func DeleteFolder(c *m.ReqContext) Response {
|
||||
@@ -83,7 +83,7 @@ func DeleteFolder(c *m.ReqContext) Response {
|
||||
return toFolderError(err)
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"title": f.Title,
|
||||
"message": fmt.Sprintf("Folder %s deleted", f.Title),
|
||||
})
|
||||
@@ -95,7 +95,7 @@ func toFolderDto(g guardian.DashboardGuardian, folder *m.Folder) dtos.Folder {
|
||||
canAdmin, _ := g.CanAdmin()
|
||||
|
||||
// Finding creator and last updater of the folder
|
||||
updater, creator := "Anonymous", "Anonymous"
|
||||
updater, creator := anonString, anonString
|
||||
if folder.CreatedBy > 0 {
|
||||
creator = getUserLogin(folder.CreatedBy)
|
||||
}
|
||||
@@ -127,20 +127,20 @@ func toFolderError(err error) Response {
|
||||
err == m.ErrDashboardTypeMismatch ||
|
||||
err == m.ErrDashboardInvalidUid ||
|
||||
err == m.ErrDashboardUidToLong {
|
||||
return ApiError(400, err.Error(), nil)
|
||||
return Error(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
if err == m.ErrFolderAccessDenied {
|
||||
return ApiError(403, "Access denied", err)
|
||||
return Error(403, "Access denied", err)
|
||||
}
|
||||
|
||||
if err == m.ErrFolderNotFound {
|
||||
return Json(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
|
||||
return JSON(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
|
||||
}
|
||||
|
||||
if err == m.ErrFolderVersionMismatch {
|
||||
return Json(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
|
||||
return JSON(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
|
||||
}
|
||||
|
||||
return ApiError(500, "Folder API error", err)
|
||||
return Error(500, "Folder API error", err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func GetFolderPermissionList(c *m.ReqContext) Response {
|
||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
|
||||
folder, err := s.GetFolderByUid(c.Params(":uid"))
|
||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||
|
||||
if err != nil {
|
||||
return toFolderError(err)
|
||||
@@ -26,24 +26,30 @@ func GetFolderPermissionList(c *m.ReqContext) Response {
|
||||
|
||||
acl, err := g.GetAcl()
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to get folder permissions", err)
|
||||
return Error(500, "Failed to get folder permissions", err)
|
||||
}
|
||||
|
||||
for _, perm := range acl {
|
||||
perm.FolderId = folder.Id
|
||||
perm.DashboardId = 0
|
||||
|
||||
perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail)
|
||||
|
||||
if perm.TeamId > 0 {
|
||||
perm.TeamAvatarUrl = dtos.GetGravatarUrlWithDefault(perm.TeamEmail, perm.Team)
|
||||
}
|
||||
|
||||
if perm.Slug != "" {
|
||||
perm.Url = m.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, acl)
|
||||
return JSON(200, acl)
|
||||
}
|
||||
|
||||
func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
|
||||
folder, err := s.GetFolderByUid(c.Params(":uid"))
|
||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||
|
||||
if err != nil {
|
||||
return toFolderError(err)
|
||||
@@ -79,13 +85,13 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
|
||||
if err != nil {
|
||||
if err == guardian.ErrGuardianPermissionExists ||
|
||||
err == guardian.ErrGuardianOverride {
|
||||
return ApiError(400, err.Error(), err)
|
||||
return Error(400, err.Error(), err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Error while checking folder permissions", err)
|
||||
return Error(500, "Error while checking folder permissions", err)
|
||||
}
|
||||
|
||||
return ApiError(403, "Cannot remove own admin permission for a folder", nil)
|
||||
return Error(403, "Cannot remove own admin permission for a folder", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
@@ -97,11 +103,11 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
|
||||
}
|
||||
|
||||
if err == m.ErrFolderAclInfoMissing || err == m.ErrFolderPermissionFolderEmpty {
|
||||
return ApiError(409, err.Error(), err)
|
||||
return Error(409, err.Error(), err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to create permission", err)
|
||||
return Error(500, "Failed to create permission", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Folder permissions updated")
|
||||
return Success("Folder permissions updated")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
|
||||
Convey("Folder permissions test", t, func() {
|
||||
Convey("Given folder not exists", func() {
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUidError: m.ErrFolderNotFound,
|
||||
GetFolderByUIDError: m.ErrFolderNotFound,
|
||||
}
|
||||
|
||||
origNewFolderService := dashboards.NewFolderService
|
||||
@@ -49,7 +49,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
|
||||
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUidResult: &m.Folder{
|
||||
GetFolderByUIDResult: &m.Folder{
|
||||
Id: 1,
|
||||
Uid: "uid",
|
||||
Title: "Folder",
|
||||
@@ -96,7 +96,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUidResult: &m.Folder{
|
||||
GetFolderByUIDResult: &m.Folder{
|
||||
Id: 1,
|
||||
Uid: "uid",
|
||||
Title: "Folder",
|
||||
@@ -142,7 +142,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUidResult: &m.Folder{
|
||||
GetFolderByUIDResult: &m.Folder{
|
||||
Id: 1,
|
||||
Uid: "uid",
|
||||
Title: "Folder",
|
||||
@@ -178,7 +178,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
|
||||
)
|
||||
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUidResult: &m.Folder{
|
||||
GetFolderByUIDResult: &m.Folder{
|
||||
Id: 1,
|
||||
Uid: "uid",
|
||||
Title: "Folder",
|
||||
@@ -226,7 +226,7 @@ func updateFolderPermissionScenario(desc string, url string, routePattern string
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.UserId = TestUserID
|
||||
|
||||
@@ -133,16 +133,6 @@ func TestFoldersApiEndpoint(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func callGetFolderByUid(sc *scenarioContext) {
|
||||
sc.handlerFunc = GetFolderByUid
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func callDeleteFolder(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteFolder
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func callCreateFolder(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
@@ -152,7 +142,7 @@ func createFolderScenario(desc string, url string, routePattern string, mock *fa
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.SignedInUser = &m.SignedInUser{OrgId: TestOrgID, UserId: TestUserID}
|
||||
|
||||
@@ -181,7 +171,7 @@ func updateFolderScenario(desc string, url string, routePattern string, mock *fa
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.SignedInUser = &m.SignedInUser{OrgId: TestOrgID, UserId: TestUserID}
|
||||
|
||||
@@ -204,10 +194,10 @@ func updateFolderScenario(desc string, url string, routePattern string, mock *fa
|
||||
type fakeFolderService struct {
|
||||
GetFoldersResult []*m.Folder
|
||||
GetFoldersError error
|
||||
GetFolderByUidResult *m.Folder
|
||||
GetFolderByUidError error
|
||||
GetFolderByIdResult *m.Folder
|
||||
GetFolderByIdError error
|
||||
GetFolderByUIDResult *m.Folder
|
||||
GetFolderByUIDError error
|
||||
GetFolderByIDResult *m.Folder
|
||||
GetFolderByIDError error
|
||||
CreateFolderResult *m.Folder
|
||||
CreateFolderError error
|
||||
UpdateFolderResult *m.Folder
|
||||
@@ -221,12 +211,12 @@ func (s *fakeFolderService) GetFolders(limit int) ([]*m.Folder, error) {
|
||||
return s.GetFoldersResult, s.GetFoldersError
|
||||
}
|
||||
|
||||
func (s *fakeFolderService) GetFolderById(id int64) (*m.Folder, error) {
|
||||
return s.GetFolderByIdResult, s.GetFolderByIdError
|
||||
func (s *fakeFolderService) GetFolderByID(id int64) (*m.Folder, error) {
|
||||
return s.GetFolderByIDResult, s.GetFolderByIDError
|
||||
}
|
||||
|
||||
func (s *fakeFolderService) GetFolderByUid(uid string) (*m.Folder, error) {
|
||||
return s.GetFolderByUidResult, s.GetFolderByUidError
|
||||
func (s *fakeFolderService) GetFolderByUID(uid string) (*m.Folder, error) {
|
||||
return s.GetFolderByUIDResult, s.GetFolderByUIDError
|
||||
}
|
||||
|
||||
func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
|
||||
@@ -234,7 +224,7 @@ func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
|
||||
return s.CreateFolderError
|
||||
}
|
||||
|
||||
func (s *fakeFolderService) UpdateFolder(existingUid string, cmd *m.UpdateFolderCommand) error {
|
||||
func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *m.UpdateFolderCommand) error {
|
||||
cmd.Result = s.UpdateFolderResult
|
||||
return s.UpdateFolderError
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
orgDataSources := make([]*m.DataSource, 0)
|
||||
|
||||
if c.OrgId != 0 {
|
||||
@@ -22,7 +22,20 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgDataSources = query.Result
|
||||
dsFilterQuery := m.DatasourcesPermissionFilterQuery{
|
||||
User: c.SignedInUser,
|
||||
Datasources: query.Result,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&dsFilterQuery); err != nil {
|
||||
if err != bus.ErrHandlerNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgDataSources = query.Result
|
||||
} else {
|
||||
orgDataSources = dsFilterQuery.Result
|
||||
}
|
||||
}
|
||||
|
||||
datasources := make(map[string]interface{})
|
||||
@@ -120,6 +133,10 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
|
||||
panels := map[string]interface{}{}
|
||||
for _, panel := range enabledPlugins.Panels {
|
||||
if panel.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
|
||||
continue
|
||||
}
|
||||
|
||||
panels[panel.Id] = map[string]interface{}{
|
||||
"module": panel.Module,
|
||||
"baseUrl": panel.BaseUrl,
|
||||
@@ -132,19 +149,22 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
jsonObj := map[string]interface{}{
|
||||
"defaultDatasource": defaultDatasource,
|
||||
"datasources": datasources,
|
||||
"panels": panels,
|
||||
"appSubUrl": setting.AppSubUrl,
|
||||
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
"authProxyEnabled": setting.AuthProxyEnabled,
|
||||
"ldapEnabled": setting.LdapEnabled,
|
||||
"alertingEnabled": setting.AlertingEnabled,
|
||||
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
||||
"disableLoginForm": setting.DisableLoginForm,
|
||||
"externalUserMngInfo": setting.ExternalUserMngInfo,
|
||||
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
|
||||
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
|
||||
"defaultDatasource": defaultDatasource,
|
||||
"datasources": datasources,
|
||||
"panels": panels,
|
||||
"appSubUrl": setting.AppSubUrl,
|
||||
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
"authProxyEnabled": setting.AuthProxyEnabled,
|
||||
"ldapEnabled": setting.LdapEnabled,
|
||||
"alertingEnabled": setting.AlertingEnabled,
|
||||
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
|
||||
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
|
||||
"exploreEnabled": setting.ExploreEnabled,
|
||||
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
||||
"disableLoginForm": setting.DisableLoginForm,
|
||||
"externalUserMngInfo": setting.ExternalUserMngInfo,
|
||||
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
|
||||
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
|
||||
"buildInfo": map[string]interface{}{
|
||||
"version": setting.BuildVersion,
|
||||
"commit": setting.BuildCommit,
|
||||
@@ -152,6 +172,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
|
||||
"latestVersion": plugins.GrafanaLatestVersion,
|
||||
"hasUpdate": plugins.GrafanaHasUpdate,
|
||||
"env": setting.Env,
|
||||
"isEnterprise": setting.IsEnterprise,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -179,8 +200,8 @@ func getPanelSort(id string) int {
|
||||
return sort
|
||||
}
|
||||
|
||||
func GetFrontendSettings(c *m.ReqContext) {
|
||||
settings, err := getFrontendSettingsMap(c)
|
||||
func (hs *HTTPServer) GetFrontendSettings(c *m.ReqContext) {
|
||||
settings, err := hs.getFrontendSettingsMap(c)
|
||||
if err != nil {
|
||||
c.JsonApiErr(400, "Failed to get frontend settings", err)
|
||||
return
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/live"
|
||||
@@ -26,40 +26,71 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/cache"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
func init() {
|
||||
registry.Register(®istry.Descriptor{
|
||||
Name: "HTTPServer",
|
||||
Instance: &HTTPServer{},
|
||||
InitPriority: registry.High,
|
||||
})
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
log log.Logger
|
||||
macaron *macaron.Macaron
|
||||
context context.Context
|
||||
streamManager *live.StreamManager
|
||||
cache *gocache.Cache
|
||||
httpSrv *http.Server
|
||||
|
||||
httpSrv *http.Server
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
Bus bus.Bus `inject:""`
|
||||
RenderService rendering.Service `inject:""`
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
HooksService *hooks.HooksService `inject:""`
|
||||
CacheService *cache.CacheService `inject:""`
|
||||
DatasourceCache datasources.CacheService `inject:""`
|
||||
}
|
||||
|
||||
func NewHttpServer() *HttpServer {
|
||||
return &HttpServer{
|
||||
log: log.New("http.server"),
|
||||
cache: gocache.New(5*time.Minute, 10*time.Minute),
|
||||
}
|
||||
}
|
||||
func (hs *HTTPServer) Init() error {
|
||||
hs.log = log.New("http.server")
|
||||
|
||||
func (hs *HttpServer) Start(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
hs.context = ctx
|
||||
hs.streamManager = live.NewStreamManager()
|
||||
hs.macaron = hs.newMacaron()
|
||||
hs.registerRoutes()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) Run(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
hs.context = ctx
|
||||
|
||||
hs.applyRoutes()
|
||||
hs.streamManager.Run(ctx)
|
||||
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
hs.log.Info("Initializing HTTP Server", "address", listenAddr, "protocol", setting.Protocol, "subUrl", setting.AppSubUrl, "socket", setting.SocketPath)
|
||||
hs.log.Info("HTTP Server Listen", "address", listenAddr, "protocol", setting.Protocol, "subUrl", setting.AppSubUrl, "socket", setting.SocketPath)
|
||||
|
||||
hs.httpSrv = &http.Server{Addr: listenAddr, Handler: hs.macaron}
|
||||
|
||||
// handle http shutdown on server context done
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
// Hacky fix for race condition between ListenAndServe and Shutdown
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if err := hs.httpSrv.Shutdown(context.Background()); err != nil {
|
||||
hs.log.Error("Failed to shutdown server", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
switch setting.Protocol {
|
||||
case setting.HTTP:
|
||||
err = hs.httpSrv.ListenAndServe()
|
||||
@@ -74,12 +105,15 @@ func (hs *HttpServer) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
case setting.SOCKET:
|
||||
ln, err := net.Listen("unix", setting.SocketPath)
|
||||
ln, err := net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"})
|
||||
if err != nil {
|
||||
hs.log.Debug("server was shutdown gracefully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make socket writable by group
|
||||
os.Chmod(setting.SocketPath, 0660)
|
||||
|
||||
err = hs.httpSrv.Serve(ln)
|
||||
if err != nil {
|
||||
hs.log.Debug("server was shutdown gracefully")
|
||||
@@ -93,13 +127,7 @@ func (hs *HttpServer) Start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (hs *HttpServer) Shutdown(ctx context.Context) error {
|
||||
err := hs.httpSrv.Shutdown(ctx)
|
||||
hs.log.Info("Stopped HTTP server")
|
||||
return err
|
||||
}
|
||||
|
||||
func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
|
||||
func (hs *HTTPServer) listenAndServeTLS(certfile, keyfile string) error {
|
||||
if certfile == "" {
|
||||
return fmt.Errorf("cert_file cannot be empty when using HTTPS")
|
||||
}
|
||||
@@ -136,15 +164,35 @@ func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
|
||||
}
|
||||
|
||||
hs.httpSrv.TLSConfig = tlsCfg
|
||||
hs.httpSrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0)
|
||||
hs.httpSrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||
|
||||
return hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
|
||||
}
|
||||
|
||||
func (hs *HttpServer) newMacaron() *macaron.Macaron {
|
||||
func (hs *HTTPServer) newMacaron() *macaron.Macaron {
|
||||
macaron.Env = setting.Env
|
||||
m := macaron.New()
|
||||
|
||||
// automatically set HEAD for every GET
|
||||
m.SetAutoHead(true)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) applyRoutes() {
|
||||
// start with middlewares & static routes
|
||||
hs.addMiddlewaresAndStaticRoutes()
|
||||
// then add view routes & api routes
|
||||
hs.RouteRegister.Register(hs.macaron)
|
||||
// then custom app proxy routes
|
||||
hs.initAppPluginRoutes(hs.macaron)
|
||||
// lastly not found route
|
||||
hs.macaron.NotFound(hs.NotFoundHandler)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
m := hs.macaron
|
||||
|
||||
m.Use(middleware.Logger())
|
||||
|
||||
if setting.EnableGzip {
|
||||
@@ -156,14 +204,15 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
|
||||
for _, route := range plugins.StaticRoutes {
|
||||
pluginRoute := path.Join("/public/plugins/", route.PluginId)
|
||||
hs.log.Debug("Plugins: Adding route", "route", pluginRoute, "dir", route.Directory)
|
||||
hs.mapStatic(m, route.Directory, "", pluginRoute)
|
||||
hs.mapStatic(hs.macaron, route.Directory, "", pluginRoute)
|
||||
}
|
||||
|
||||
hs.mapStatic(m, setting.StaticRootPath, "build", "public/build")
|
||||
hs.mapStatic(m, setting.StaticRootPath, "", "public")
|
||||
hs.mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt")
|
||||
|
||||
if setting.ImageUploadProvider == "local" {
|
||||
hs.mapStatic(m, setting.ImagesDir, "", "/public/img/attachments")
|
||||
hs.mapStatic(m, hs.Cfg.ImagesDir, "", "/public/img/attachments")
|
||||
}
|
||||
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
@@ -175,7 +224,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
|
||||
m.Use(hs.healthHandler)
|
||||
m.Use(hs.metricsEndpoint)
|
||||
m.Use(middleware.GetContextHandler())
|
||||
m.Use(middleware.Sessioner(&setting.SessionOptions))
|
||||
m.Use(middleware.Sessioner(&setting.SessionOptions, setting.SessionConnMaxLifetime))
|
||||
m.Use(middleware.OrgRedirect())
|
||||
|
||||
// needs to be after context handler
|
||||
@@ -183,12 +232,15 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
|
||||
m.Use(middleware.ValidateHostHeader(setting.Domain))
|
||||
}
|
||||
|
||||
m.Use(middleware.HandleNoCacheHeader())
|
||||
m.Use(middleware.AddDefaultResponseHeaders())
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
|
||||
func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
|
||||
if !hs.Cfg.MetricsEndpointEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/metrics" {
|
||||
return
|
||||
}
|
||||
@@ -197,7 +249,7 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
|
||||
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
||||
}
|
||||
|
||||
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
|
||||
func (hs *HTTPServer) healthHandler(ctx *macaron.Context) {
|
||||
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
|
||||
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
|
||||
return
|
||||
@@ -221,11 +273,17 @@ func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
|
||||
ctx.Resp.Write(dataBytes)
|
||||
}
|
||||
|
||||
func (hs *HttpServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
|
||||
func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
|
||||
headers := func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
}
|
||||
|
||||
if prefix == "public/build" {
|
||||
headers = func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
}
|
||||
}
|
||||
|
||||
if setting.Env == setting.DEV {
|
||||
headers = func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache")
|
||||
|
||||
165
pkg/api/index.go
165
pkg/api/index.go
@@ -11,13 +11,19 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
settings, err := getFrontendSettingsMap(c)
|
||||
const (
|
||||
// Themes
|
||||
lightName = "light"
|
||||
darkName = "dark"
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
settings, err := hs.getFrontendSettingsMap(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||
prefsQuery := m.GetPreferencesWithDefaultsQuery{User: c.SignedInUser}
|
||||
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,44 +38,52 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
locale = parts[0]
|
||||
}
|
||||
|
||||
appUrl := setting.AppUrl
|
||||
appSubUrl := setting.AppSubUrl
|
||||
appURL := setting.AppUrl
|
||||
appSubURL := setting.AppSubUrl
|
||||
|
||||
// special case when doing localhost call from phantomjs
|
||||
if c.IsRenderCall {
|
||||
appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
|
||||
appSubUrl = ""
|
||||
appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
|
||||
appSubURL = ""
|
||||
settings["appSubUrl"] = ""
|
||||
}
|
||||
|
||||
hasEditPermissionInFoldersQuery := m.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
|
||||
if err := bus.Dispatch(&hasEditPermissionInFoldersQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 == "light",
|
||||
Timezone: prefs.Timezone,
|
||||
Locale: locale,
|
||||
HelpFlags1: c.HelpFlags1,
|
||||
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,
|
||||
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(setting.ApplicationName),
|
||||
}
|
||||
|
||||
if setting.DisableGravatar {
|
||||
@@ -80,23 +94,32 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
data.User.Name = data.User.Login
|
||||
}
|
||||
|
||||
themeUrlParam := c.Query("theme")
|
||||
if themeUrlParam == "light" {
|
||||
themeURLParam := c.Query("theme")
|
||||
if themeURLParam == lightName {
|
||||
data.User.LightTheme = true
|
||||
data.Theme = "light"
|
||||
data.Theme = lightName
|
||||
} else if themeURLParam == darkName {
|
||||
data.User.LightTheme = false
|
||||
data.Theme = darkName
|
||||
}
|
||||
|
||||
if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
|
||||
if hasEditPermissionInFoldersQuery.Result {
|
||||
children := []*dtos.NavLink{
|
||||
{Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"},
|
||||
}
|
||||
|
||||
if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
|
||||
children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"})
|
||||
}
|
||||
|
||||
children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"})
|
||||
|
||||
data.NavTree = append(data.NavTree, &dtos.NavLink{
|
||||
Text: "Create",
|
||||
Id: "create",
|
||||
Icon: "fa fa-fw fa-plus",
|
||||
Url: setting.AppSubUrl + "/dashboard/new",
|
||||
Children: []*dtos.NavLink{
|
||||
{Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"},
|
||||
{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"},
|
||||
{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"},
|
||||
},
|
||||
Text: "Create",
|
||||
Id: "create",
|
||||
Icon: "fa fa-fw fa-plus",
|
||||
Url: setting.AppSubUrl + "/dashboard/new",
|
||||
Children: children,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,10 +140,28 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
Children: dashboardChildNavs,
|
||||
})
|
||||
|
||||
if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
|
||||
data.NavTree = append(data.NavTree, &dtos.NavLink{
|
||||
Text: "Explore",
|
||||
Id: "explore",
|
||||
SubTitle: "Explore your data",
|
||||
Icon: "fa fa-rocket",
|
||||
Url: setting.AppSubUrl + "/explore",
|
||||
Children: []*dtos.NavLink{
|
||||
{Text: "New tab", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/explore"},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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: c.SignedInUser.Login,
|
||||
SubTitle: login,
|
||||
Id: "profile",
|
||||
Img: data.User.GravatarUrl,
|
||||
Url: setting.AppSubUrl + "/profile",
|
||||
@@ -204,7 +245,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.OrgRole == m.ROLE_ADMIN {
|
||||
if c.IsGrafanaAdmin || c.OrgRole == m.ROLE_ADMIN {
|
||||
cfgNode := &dtos.NavLink{
|
||||
Id: "cfg",
|
||||
Text: "Configuration",
|
||||
@@ -258,10 +299,24 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
},
|
||||
}
|
||||
|
||||
if c.IsGrafanaAdmin {
|
||||
if c.OrgRole != m.ROLE_ADMIN {
|
||||
cfgNode = &dtos.NavLink{
|
||||
Id: "cfg",
|
||||
Text: "Configuration",
|
||||
SubTitle: "Organization: " + c.OrgName,
|
||||
Icon: "gicon gicon-cog",
|
||||
Url: setting.AppSubUrl + "/admin/users",
|
||||
Children: make([]*dtos.NavLink, 0),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -284,6 +339,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
|
||||
data.NavTree = append(data.NavTree, &dtos.NavLink{
|
||||
Text: "Help",
|
||||
SubTitle: fmt.Sprintf(`%s v%s (%s)`, setting.ApplicationName, setting.BuildVersion, setting.BuildCommit),
|
||||
Id: "help",
|
||||
Url: "#",
|
||||
Icon: "gicon gicon-question",
|
||||
@@ -295,28 +351,41 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
|
||||
},
|
||||
})
|
||||
|
||||
hs.HooksService.RunIndexDataHooks(&data)
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func Index(c *m.ReqContext) {
|
||||
if data, err := setIndexViewData(c); err != nil {
|
||||
func (hs *HTTPServer) Index(c *m.ReqContext) {
|
||||
data, err := hs.setIndexViewData(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
} else {
|
||||
c.HTML(200, "index", data)
|
||||
}
|
||||
c.HTML(200, "index", data)
|
||||
}
|
||||
|
||||
func NotFoundHandler(c *m.ReqContext) {
|
||||
func (hs *HTTPServer) NotFoundHandler(c *m.ReqContext) {
|
||||
if c.IsApiRequest() {
|
||||
c.JsonApiErr(404, "Not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if data, err := setIndexViewData(c); err != nil {
|
||||
data, err := hs.setIndexViewData(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
} else {
|
||||
c.HTML(404, "index", data)
|
||||
}
|
||||
|
||||
c.HTML(404, "index", data)
|
||||
}
|
||||
|
||||
func getAppNameBodyClass(name string) string {
|
||||
switch name {
|
||||
case setting.APP_NAME:
|
||||
return "app-grafana"
|
||||
case setting.APP_NAME_ENTERPRISE:
|
||||
return "app-enterprise"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (c *connection) readPump() {
|
||||
func (c *connection) handleMessage(message []byte) {
|
||||
json, err := simplejson.NewJson(message)
|
||||
if err != nil {
|
||||
log.Error(3, "Unreadable message on websocket channel:", err)
|
||||
log.Error(3, "Unreadable message on websocket channel. error: %v", err)
|
||||
}
|
||||
|
||||
msgType := json.Get("action").MustString()
|
||||
|
||||
@@ -37,9 +37,6 @@ func newHub() *hub {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hub) removeConnection() {
|
||||
}
|
||||
|
||||
func (h *hub) run(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
VIEW_INDEX = "index"
|
||||
ViewIndex = "index"
|
||||
)
|
||||
|
||||
func LoginView(c *m.ReqContext) {
|
||||
viewData, err := setIndexViewData(c)
|
||||
func (hs *HTTPServer) LoginView(c *m.ReqContext) {
|
||||
viewData, err := hs.setIndexViewData(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
@@ -40,7 +40,7 @@ func LoginView(c *m.ReqContext) {
|
||||
}
|
||||
|
||||
if !tryLoginUsingRememberCookie(c) {
|
||||
c.HTML(200, VIEW_INDEX, viewData)
|
||||
c.HTML(200, ViewIndex, viewData)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,7 +78,13 @@ func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
|
||||
user := userQuery.Result
|
||||
|
||||
// validate remember me cookie
|
||||
if val, _ := c.GetSuperSecureCookie(user.Rands+user.Password, setting.CookieRememberName); val != user.Login {
|
||||
signingKey := user.Rands + user.Password
|
||||
if len(signingKey) < 10 {
|
||||
c.Logger.Error("Invalid user signingKey")
|
||||
return false
|
||||
}
|
||||
|
||||
if val, _ := c.GetSuperSecureCookie(signingKey, setting.CookieRememberName); val != user.Login {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func LoginApiPing(c *m.ReqContext) {
|
||||
func LoginAPIPing(c *m.ReqContext) {
|
||||
if !tryLoginUsingRememberCookie(c) {
|
||||
c.JsonApiErr(401, "Unauthorized", nil)
|
||||
return
|
||||
@@ -98,21 +104,22 @@ func LoginApiPing(c *m.ReqContext) {
|
||||
|
||||
func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
|
||||
if setting.DisableLoginForm {
|
||||
return ApiError(401, "Login is disabled", nil)
|
||||
return Error(401, "Login is disabled", nil)
|
||||
}
|
||||
|
||||
authQuery := login.LoginUserQuery{
|
||||
Username: cmd.User,
|
||||
Password: cmd.Password,
|
||||
IpAddress: c.Req.RemoteAddr,
|
||||
authQuery := &m.LoginUserQuery{
|
||||
ReqContext: c,
|
||||
Username: cmd.User,
|
||||
Password: cmd.Password,
|
||||
IpAddress: c.Req.RemoteAddr,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&authQuery); err != nil {
|
||||
if err := bus.Dispatch(authQuery); err != nil {
|
||||
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
|
||||
return ApiError(401, "Invalid username or password", err)
|
||||
return Error(401, "Invalid username or password", err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Error while trying to authenticate user", err)
|
||||
return Error(500, "Error while trying to authenticate user", err)
|
||||
}
|
||||
|
||||
user := authQuery.User
|
||||
@@ -130,7 +137,7 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
|
||||
|
||||
metrics.M_Api_Login_Post.Inc()
|
||||
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func loginUserWithUser(user *m.User, c *m.ReqContext) {
|
||||
@@ -154,5 +161,9 @@ func Logout(c *m.ReqContext) {
|
||||
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/")
|
||||
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/")
|
||||
c.Session.Destory(c.Context)
|
||||
c.Redirect(setting.AppSubUrl + "/login")
|
||||
if setting.SignoutRedirectUrl != "" {
|
||||
c.Redirect(setting.SignoutRedirectUrl)
|
||||
} else {
|
||||
c.Redirect(setting.AppSubUrl + "/login")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -16,22 +15,15 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/session"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProviderDeniedRequest = errors.New("Login provider denied login request")
|
||||
ErrEmailNotAllowed = errors.New("Required email domain not fulfilled")
|
||||
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
|
||||
ErrUsersQuotaReached = errors.New("Users quota reached")
|
||||
ErrNoEmail = errors.New("Login provider didn't return an email address")
|
||||
oauthLogger = log.New("oauth")
|
||||
)
|
||||
var oauthLogger = log.New("oauth")
|
||||
|
||||
func GenStateString() string {
|
||||
rnd := make([]byte, 32)
|
||||
@@ -56,7 +48,7 @@ func OAuthLogin(ctx *m.ReqContext) {
|
||||
if errorParam != "" {
|
||||
errorDesc := ctx.Query("error_description")
|
||||
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
|
||||
redirectWithError(ctx, ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
|
||||
redirectWithError(ctx, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,6 +78,7 @@ func OAuthLogin(ctx *m.ReqContext) {
|
||||
|
||||
// handle call back
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: setting.OAuthService.OAuthInfos[name].TlsSkipVerify,
|
||||
},
|
||||
@@ -149,54 +142,43 @@ func OAuthLogin(ctx *m.ReqContext) {
|
||||
|
||||
// validate that we got at least an email address
|
||||
if userInfo.Email == "" {
|
||||
redirectWithError(ctx, ErrNoEmail)
|
||||
redirectWithError(ctx, login.ErrNoEmail)
|
||||
return
|
||||
}
|
||||
|
||||
// validate that the email is allowed to login to grafana
|
||||
if !connect.IsEmailAllowed(userInfo.Email) {
|
||||
redirectWithError(ctx, ErrEmailNotAllowed)
|
||||
redirectWithError(ctx, login.ErrEmailNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByEmailQuery{Email: userInfo.Email}
|
||||
err = bus.Dispatch(&userQuery)
|
||||
extUser := &m.ExternalUserInfo{
|
||||
AuthModule: "oauth_" + name,
|
||||
AuthId: userInfo.Id,
|
||||
Name: userInfo.Name,
|
||||
Login: userInfo.Login,
|
||||
Email: userInfo.Email,
|
||||
OrgRoles: map[int64]m.RoleType{},
|
||||
}
|
||||
|
||||
// create account if missing
|
||||
if err == m.ErrUserNotFound {
|
||||
if !connect.IsSignupAllowed() {
|
||||
redirectWithError(ctx, ErrSignUpNotAllowed)
|
||||
return
|
||||
}
|
||||
limitReached, err := quota.QuotaReached(ctx, "user")
|
||||
if err != nil {
|
||||
ctx.Handle(500, "Failed to get user quota", err)
|
||||
return
|
||||
}
|
||||
if limitReached {
|
||||
redirectWithError(ctx, ErrUsersQuotaReached)
|
||||
return
|
||||
}
|
||||
cmd := m.CreateUserCommand{
|
||||
Login: userInfo.Login,
|
||||
Email: userInfo.Email,
|
||||
Name: userInfo.Name,
|
||||
Company: userInfo.Company,
|
||||
DefaultOrgRole: userInfo.Role,
|
||||
}
|
||||
if userInfo.Role != "" {
|
||||
extUser.OrgRoles[1] = m.RoleType(userInfo.Role)
|
||||
}
|
||||
|
||||
if err = bus.Dispatch(&cmd); err != nil {
|
||||
ctx.Handle(500, "Failed to create account", err)
|
||||
return
|
||||
}
|
||||
|
||||
userQuery.Result = &cmd.Result
|
||||
} else if err != nil {
|
||||
ctx.Handle(500, "Unexpected error", err)
|
||||
// add/update user in grafana
|
||||
cmd := &m.UpsertUserCommand{
|
||||
ReqContext: ctx,
|
||||
ExternalUser: extUser,
|
||||
SignupAllowed: connect.IsSignupAllowed(),
|
||||
}
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
redirectWithError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// login
|
||||
loginUserWithUser(userQuery.Result, ctx)
|
||||
loginUserWithUser(cmd.Result, ctx)
|
||||
|
||||
metrics.M_Api_Login_OAuth.Inc()
|
||||
|
||||
|
||||
@@ -13,21 +13,24 @@ import (
|
||||
)
|
||||
|
||||
// POST /api/tsdb/query
|
||||
func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
|
||||
func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
|
||||
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
|
||||
|
||||
if len(reqDto.Queries) == 0 {
|
||||
return ApiError(400, "No queries found in query", nil)
|
||||
return Error(400, "No queries found in query", nil)
|
||||
}
|
||||
|
||||
dsId, err := reqDto.Queries[0].Get("datasourceId").Int64()
|
||||
datasourceId, err := reqDto.Queries[0].Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
return ApiError(400, "Query missing datasourceId", nil)
|
||||
return Error(400, "Query missing datasourceId", nil)
|
||||
}
|
||||
|
||||
dsQuery := m.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&dsQuery); err != nil {
|
||||
return ApiError(500, "failed to fetch data source", err)
|
||||
ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
if err == m.ErrDataSourceAccessDenied {
|
||||
return Error(403, "Access denied to datasource", err)
|
||||
}
|
||||
return Error(500, "Unable to load datasource meta data", err)
|
||||
}
|
||||
|
||||
request := &tsdb.TsdbQuery{TimeRange: timeRange}
|
||||
@@ -38,13 +41,13 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
|
||||
MaxDataPoints: query.Get("maxDataPoints").MustInt64(100),
|
||||
IntervalMs: query.Get("intervalMs").MustInt64(1000),
|
||||
Model: query,
|
||||
DataSource: dsQuery.Result,
|
||||
DataSource: ds,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request)
|
||||
resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request)
|
||||
if err != nil {
|
||||
return ApiError(500, "Metric request error", err)
|
||||
return Error(500, "Metric request error", err)
|
||||
}
|
||||
|
||||
statusCode := 200
|
||||
@@ -52,11 +55,11 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
|
||||
if res.Error != nil {
|
||||
res.ErrorString = res.Error.Error()
|
||||
resp.Message = res.ErrorString
|
||||
statusCode = 500
|
||||
statusCode = 400
|
||||
}
|
||||
}
|
||||
|
||||
return Json(statusCode, &resp)
|
||||
return JSON(statusCode, &resp)
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/scenarios
|
||||
@@ -72,22 +75,22 @@ func GetTestDataScenarios(c *m.ReqContext) Response {
|
||||
})
|
||||
}
|
||||
|
||||
return Json(200, &result)
|
||||
return JSON(200, &result)
|
||||
}
|
||||
|
||||
// Genereates a index out of range error
|
||||
// Generates a index out of range error
|
||||
func GenerateError(c *m.ReqContext) Response {
|
||||
var array []string
|
||||
return Json(200, array[20])
|
||||
return JSON(200, array[20])
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/gensql
|
||||
func GenerateSqlTestData(c *m.ReqContext) Response {
|
||||
func GenerateSQLTestData(c *m.ReqContext) Response {
|
||||
if err := bus.Dispatch(&m.InsertSqlTestDataCommand{}); err != nil {
|
||||
return ApiError(500, "Failed to insert test data", err)
|
||||
return Error(500, "Failed to insert test data", err)
|
||||
}
|
||||
|
||||
return Json(200, &util.DynMap{"message": "OK"})
|
||||
return JSON(200, &util.DynMap{"message": "OK"})
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/random-walk
|
||||
@@ -99,7 +102,7 @@ func GetTestDataRandomWalk(c *m.ReqContext) Response {
|
||||
timeRange := tsdb.NewTimeRange(from, to)
|
||||
request := &tsdb.TsdbQuery{TimeRange: timeRange}
|
||||
|
||||
dsInfo := &m.DataSource{Type: "grafana-testdata-datasource"}
|
||||
dsInfo := &m.DataSource{Type: "testdata"}
|
||||
request.Queries = append(request.Queries, &tsdb.Query{
|
||||
RefId: "A",
|
||||
IntervalMs: intervalMs,
|
||||
@@ -111,8 +114,8 @@ func GetTestDataRandomWalk(c *m.ReqContext) Response {
|
||||
|
||||
resp, err := tsdb.HandleRequest(context.Background(), dsInfo, request)
|
||||
if err != nil {
|
||||
return ApiError(500, "Metric request error", err)
|
||||
return Error(500, "Metric request error", err)
|
||||
}
|
||||
|
||||
return Json(200, &resp)
|
||||
return JSON(200, &resp)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func GetOrgCurrent(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId
|
||||
func GetOrgById(c *m.ReqContext) Response {
|
||||
func GetOrgByID(c *m.ReqContext) Response {
|
||||
return getOrgHelper(c.ParamsInt64(":orgId"))
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ func GetOrgByName(c *m.ReqContext) Response {
|
||||
query := m.GetOrgByNameQuery{Name: c.Params(":name")}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrOrgNotFound {
|
||||
return ApiError(404, "Organization not found", err)
|
||||
return Error(404, "Organization not found", err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to get organization", err)
|
||||
return Error(500, "Failed to get organization", err)
|
||||
}
|
||||
org := query.Result
|
||||
result := m.OrgDetailsDTO{
|
||||
@@ -43,18 +43,18 @@ func GetOrgByName(c *m.ReqContext) Response {
|
||||
},
|
||||
}
|
||||
|
||||
return Json(200, &result)
|
||||
return JSON(200, &result)
|
||||
}
|
||||
|
||||
func getOrgHelper(orgId int64) Response {
|
||||
query := m.GetOrgByIdQuery{Id: orgId}
|
||||
func getOrgHelper(orgID int64) Response {
|
||||
query := m.GetOrgByIdQuery{Id: orgID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrOrgNotFound {
|
||||
return ApiError(404, "Organization not found", err)
|
||||
return Error(404, "Organization not found", err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to get organization", err)
|
||||
return Error(500, "Failed to get organization", err)
|
||||
}
|
||||
|
||||
org := query.Result
|
||||
@@ -71,26 +71,26 @@ func getOrgHelper(orgId int64) Response {
|
||||
},
|
||||
}
|
||||
|
||||
return Json(200, &result)
|
||||
return JSON(200, &result)
|
||||
}
|
||||
|
||||
// POST /api/orgs
|
||||
func CreateOrg(c *m.ReqContext, cmd m.CreateOrgCommand) Response {
|
||||
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
|
||||
return ApiError(403, "Access denied", nil)
|
||||
return Error(403, "Access denied", nil)
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrOrgNameTaken {
|
||||
return ApiError(409, "Organization name taken", err)
|
||||
return Error(409, "Organization name taken", err)
|
||||
}
|
||||
return ApiError(500, "Failed to create organization", err)
|
||||
return Error(500, "Failed to create organization", err)
|
||||
}
|
||||
|
||||
metrics.M_Api_Org_Create.Inc()
|
||||
|
||||
return Json(200, &util.DynMap{
|
||||
return JSON(200, &util.DynMap{
|
||||
"orgId": cmd.Result.Id,
|
||||
"message": "Organization created",
|
||||
})
|
||||
@@ -106,16 +106,16 @@ func UpdateOrg(c *m.ReqContext, form dtos.UpdateOrgForm) Response {
|
||||
return updateOrgHelper(form, c.ParamsInt64(":orgId"))
|
||||
}
|
||||
|
||||
func updateOrgHelper(form dtos.UpdateOrgForm, orgId int64) Response {
|
||||
cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgId}
|
||||
func updateOrgHelper(form dtos.UpdateOrgForm, orgID int64) Response {
|
||||
cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgID}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrOrgNameTaken {
|
||||
return ApiError(400, "Organization name taken", err)
|
||||
return Error(400, "Organization name taken", err)
|
||||
}
|
||||
return ApiError(500, "Failed to update organization", err)
|
||||
return Error(500, "Failed to update organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Organization updated")
|
||||
return Success("Organization updated")
|
||||
}
|
||||
|
||||
// PUT /api/org/address
|
||||
@@ -128,9 +128,9 @@ func UpdateOrgAddress(c *m.ReqContext, form dtos.UpdateOrgAddressForm) Response
|
||||
return updateOrgAddressHelper(form, c.ParamsInt64(":orgId"))
|
||||
}
|
||||
|
||||
func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Response {
|
||||
func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgID int64) Response {
|
||||
cmd := m.UpdateOrgAddressCommand{
|
||||
OrgId: orgId,
|
||||
OrgId: orgID,
|
||||
Address: m.Address{
|
||||
Address1: form.Address1,
|
||||
Address2: form.Address2,
|
||||
@@ -142,21 +142,21 @@ func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Respons
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update org address", err)
|
||||
return Error(500, "Failed to update org address", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Address updated")
|
||||
return Success("Address updated")
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId
|
||||
func DeleteOrgById(c *m.ReqContext) Response {
|
||||
func DeleteOrgByID(c *m.ReqContext) Response {
|
||||
if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil {
|
||||
if err == m.ErrOrgNotFound {
|
||||
return ApiError(404, "Failed to delete organization. ID not found", nil)
|
||||
return Error(404, "Failed to delete organization. ID not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to update organization", err)
|
||||
return Error(500, "Failed to update organization", err)
|
||||
}
|
||||
return ApiSuccess("Organization deleted")
|
||||
return Success("Organization deleted")
|
||||
}
|
||||
|
||||
func SearchOrgs(c *m.ReqContext) Response {
|
||||
@@ -168,8 +168,8 @@ func SearchOrgs(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to search orgs", err)
|
||||
return Error(500, "Failed to search orgs", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
@@ -16,30 +16,30 @@ func GetPendingOrgInvites(c *m.ReqContext) Response {
|
||||
query := m.GetTempUsersQuery{OrgId: c.OrgId, Status: m.TmpUserInvitePending}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get invites from db", err)
|
||||
return Error(500, "Failed to get invites from db", err)
|
||||
}
|
||||
|
||||
for _, invite := range query.Result {
|
||||
invite.Url = setting.ToAbsUrl("invite/" + invite.Code)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
|
||||
if !inviteDto.Role.IsValid() {
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
return Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
// first try get existing user
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail}
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
if err != m.ErrUserNotFound {
|
||||
return ApiError(500, "Failed to query db for existing user check", err)
|
||||
return Error(500, "Failed to query db for existing user check", err)
|
||||
}
|
||||
|
||||
if setting.DisableLoginForm {
|
||||
return ApiError(401, "User could not be found", nil)
|
||||
return Error(401, "User could not be found", nil)
|
||||
}
|
||||
} else {
|
||||
return inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
|
||||
@@ -56,7 +56,7 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
|
||||
cmd.RemoteAddr = c.Req.RemoteAddr
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to save invite to database", err)
|
||||
return Error(500, "Failed to save invite to database", err)
|
||||
}
|
||||
|
||||
// send invite email
|
||||
@@ -74,18 +74,21 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&emailCmd); err != nil {
|
||||
return ApiError(500, "Failed to send email invite", err)
|
||||
if err == m.ErrSmtpNotEnabled {
|
||||
return Error(412, err.Error(), err)
|
||||
}
|
||||
return Error(500, "Failed to send email invite", err)
|
||||
}
|
||||
|
||||
emailSentCmd := m.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code}
|
||||
if err := bus.Dispatch(&emailSentCmd); err != nil {
|
||||
return ApiError(500, "Failed to update invite with email sent info", err)
|
||||
return Error(500, "Failed to update invite with email sent info", err)
|
||||
}
|
||||
|
||||
return ApiSuccess(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
|
||||
return Success(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
|
||||
}
|
||||
|
||||
return ApiSuccess(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
|
||||
return Success(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
|
||||
}
|
||||
|
||||
func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddInviteForm) Response {
|
||||
@@ -93,29 +96,28 @@ func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddI
|
||||
createOrgUserCmd := m.AddOrgUserCommand{OrgId: c.OrgId, UserId: user.Id, Role: inviteDto.Role}
|
||||
if err := bus.Dispatch(&createOrgUserCmd); err != nil {
|
||||
if err == m.ErrOrgUserAlreadyAdded {
|
||||
return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
|
||||
return Error(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
|
||||
}
|
||||
return ApiError(500, "Error while trying to create org user", err)
|
||||
} else {
|
||||
|
||||
if inviteDto.SendEmail && util.IsEmail(user.Email) {
|
||||
emailCmd := m.SendEmailCommand{
|
||||
To: []string{user.Email},
|
||||
Template: "invited_to_org.html",
|
||||
Data: map[string]interface{}{
|
||||
"Name": user.NameOrFallback(),
|
||||
"OrgName": c.OrgName,
|
||||
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&emailCmd); err != nil {
|
||||
return ApiError(500, "Failed to send email invited_to_org", err)
|
||||
}
|
||||
}
|
||||
|
||||
return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
|
||||
return Error(500, "Error while trying to create org user", err)
|
||||
}
|
||||
|
||||
if inviteDto.SendEmail && util.IsEmail(user.Email) {
|
||||
emailCmd := m.SendEmailCommand{
|
||||
To: []string{user.Email},
|
||||
Template: "invited_to_org.html",
|
||||
Data: map[string]interface{}{
|
||||
"Name": user.NameOrFallback(),
|
||||
"OrgName": c.OrgName,
|
||||
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&emailCmd); err != nil {
|
||||
return Error(500, "Failed to send email invited_to_org", err)
|
||||
}
|
||||
}
|
||||
|
||||
return Success(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
|
||||
}
|
||||
|
||||
func RevokeInvite(c *m.ReqContext) Response {
|
||||
@@ -123,7 +125,7 @@ func RevokeInvite(c *m.ReqContext) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
return ApiSuccess("Invite revoked")
|
||||
return Success("Invite revoked")
|
||||
}
|
||||
|
||||
func GetInviteInfoByCode(c *m.ReqContext) Response {
|
||||
@@ -131,14 +133,14 @@ func GetInviteInfoByCode(c *m.ReqContext) Response {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrTempUserNotFound {
|
||||
return ApiError(404, "Invite not found", nil)
|
||||
return Error(404, "Invite not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to get invite", err)
|
||||
return Error(500, "Failed to get invite", err)
|
||||
}
|
||||
|
||||
invite := query.Result
|
||||
|
||||
return Json(200, dtos.InviteInfo{
|
||||
return JSON(200, dtos.InviteInfo{
|
||||
Email: invite.Email,
|
||||
Name: invite.Name,
|
||||
Username: invite.Email,
|
||||
@@ -151,14 +153,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrTempUserNotFound {
|
||||
return ApiError(404, "Invite not found", nil)
|
||||
return Error(404, "Invite not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to get invite", err)
|
||||
return Error(500, "Failed to get invite", err)
|
||||
}
|
||||
|
||||
invite := query.Result
|
||||
if invite.Status != m.TmpUserInvitePending {
|
||||
return ApiError(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
|
||||
return Error(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
|
||||
}
|
||||
|
||||
cmd := m.CreateUserCommand{
|
||||
@@ -170,7 +172,7 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "failed to create user", err)
|
||||
return Error(500, "failed to create user", err)
|
||||
}
|
||||
|
||||
user := &cmd.Result
|
||||
@@ -189,14 +191,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
|
||||
metrics.M_Api_User_SignUpCompleted.Inc()
|
||||
metrics.M_Api_User_SignUpInvite.Inc()
|
||||
|
||||
return ApiSuccess("User created and logged in")
|
||||
return Success("User created and logged in")
|
||||
}
|
||||
|
||||
func updateTempUserStatus(code string, status m.TempUserStatus) (bool, Response) {
|
||||
// update temp user status
|
||||
updateTmpUserCmd := m.UpdateTempUserStatusCommand{Code: code, Status: status}
|
||||
if err := bus.Dispatch(&updateTmpUserCmd); err != nil {
|
||||
return false, ApiError(500, "Failed to update invite status", err)
|
||||
return false, Error(500, "Failed to update invite status", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -207,7 +209,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
|
||||
addOrgUserCmd := m.AddOrgUserCommand{OrgId: invite.OrgId, UserId: user.Id, Role: invite.Role}
|
||||
if err := bus.Dispatch(&addOrgUserCmd); err != nil {
|
||||
if err != m.ErrOrgUserAlreadyAdded {
|
||||
return false, ApiError(500, "Error while trying to create org user", err)
|
||||
return false, Error(500, "Error while trying to create org user", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +221,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
|
||||
if setActive {
|
||||
// set org to active
|
||||
if err := bus.Dispatch(&m.SetUsingOrgCommand{OrgId: invite.OrgId, UserId: user.Id}); err != nil {
|
||||
return false, ApiError(500, "Failed to set org as active", err)
|
||||
return false, Error(500, "Failed to set org as active", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ func AddOrgUser(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
|
||||
|
||||
func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
return Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
|
||||
err := bus.Dispatch(&userQuery)
|
||||
if err != nil {
|
||||
return ApiError(404, "User not found", nil)
|
||||
return Error(404, "User not found", nil)
|
||||
}
|
||||
|
||||
userToAdd := userQuery.Result
|
||||
@@ -35,17 +35,17 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrOrgUserAlreadyAdded {
|
||||
return ApiError(409, "User is already member of this organization", nil)
|
||||
return Error(409, "User is already member of this organization", nil)
|
||||
}
|
||||
return ApiError(500, "Could not add user to organization", err)
|
||||
return Error(500, "Could not add user to organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User added to organization")
|
||||
return Success("User added to organization")
|
||||
}
|
||||
|
||||
// GET /api/org/users
|
||||
func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response {
|
||||
return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
|
||||
return getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId/users
|
||||
@@ -53,22 +53,22 @@ func GetOrgUsers(c *m.ReqContext) Response {
|
||||
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
|
||||
}
|
||||
|
||||
func getOrgUsersHelper(orgId int64, query string, limit int) Response {
|
||||
func getOrgUsersHelper(orgID int64, query string, limit int) Response {
|
||||
q := m.GetOrgUsersQuery{
|
||||
OrgId: orgId,
|
||||
OrgId: orgID,
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&q); err != nil {
|
||||
return ApiError(500, "Failed to get account user", err)
|
||||
return Error(500, "Failed to get account user", err)
|
||||
}
|
||||
|
||||
for _, user := range q.Result {
|
||||
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
||||
}
|
||||
|
||||
return Json(200, q.Result)
|
||||
return JSON(200, q.Result)
|
||||
}
|
||||
|
||||
// PATCH /api/org/users/:userId
|
||||
@@ -87,41 +87,47 @@ func UpdateOrgUser(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
|
||||
|
||||
func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return ApiError(400, "Invalid role specified", nil)
|
||||
return Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
|
||||
return Error(400, "Cannot change role so that there is no organization admin left", nil)
|
||||
}
|
||||
return ApiError(500, "Failed update org user", err)
|
||||
return Error(500, "Failed update org user", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Organization user updated")
|
||||
return Success("Organization user updated")
|
||||
}
|
||||
|
||||
// DELETE /api/org/users/:userId
|
||||
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
|
||||
userId := c.ParamsInt64(":userId")
|
||||
return removeOrgUserHelper(c.OrgId, userId)
|
||||
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.OrgId,
|
||||
ShouldDeleteOrphanedUser: true,
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE /api/orgs/:orgId/users/:userId
|
||||
func RemoveOrgUser(c *m.ReqContext) Response {
|
||||
userId := c.ParamsInt64(":userId")
|
||||
orgId := c.ParamsInt64(":orgId")
|
||||
return removeOrgUserHelper(orgId, userId)
|
||||
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.ParamsInt64(":orgId"),
|
||||
})
|
||||
}
|
||||
|
||||
func removeOrgUserHelper(orgId int64, userId int64) Response {
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
func removeOrgUserHelper(cmd *m.RemoveOrgUserCommand) Response {
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
return ApiError(400, "Cannot remove last organization admin", nil)
|
||||
return Error(400, "Cannot remove last organization admin", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to remove user from organization", err)
|
||||
return Error(500, "Failed to remove user from organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User removed from organization")
|
||||
if cmd.UserWasDeleted {
|
||||
return Success("User deleted")
|
||||
}
|
||||
|
||||
return Success("User removed from organization")
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ func SendResetPasswordEmail(c *m.ReqContext, form dtos.SendResetPasswordEmailFor
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail)
|
||||
return ApiError(200, "Email sent", err)
|
||||
return Error(200, "Email sent", err)
|
||||
}
|
||||
|
||||
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
|
||||
if err := bus.Dispatch(&emailCmd); err != nil {
|
||||
return ApiError(500, "Failed to send email", err)
|
||||
return Error(500, "Failed to send email", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Email sent")
|
||||
return Success("Email sent")
|
||||
}
|
||||
|
||||
func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
|
||||
@@ -28,13 +28,13 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrInvalidEmailCode {
|
||||
return ApiError(400, "Invalid or expired reset password code", nil)
|
||||
return Error(400, "Invalid or expired reset password code", nil)
|
||||
}
|
||||
return ApiError(500, "Unknown error validating email code", err)
|
||||
return Error(500, "Unknown error validating email code", err)
|
||||
}
|
||||
|
||||
if form.NewPassword != form.ConfirmPassword {
|
||||
return ApiError(400, "Passwords do not match", nil)
|
||||
return Error(400, "Passwords do not match", nil)
|
||||
}
|
||||
|
||||
cmd := m.ChangeUserPasswordCommand{}
|
||||
@@ -42,8 +42,8 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
|
||||
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to change user password", err)
|
||||
return Error(500, "Failed to change user password", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User password changed")
|
||||
return Success("User password changed")
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func ValidateOrgPlaylist(c *m.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
if len(items) == 0 && c.Context.Req.Method != "DELETE" {
|
||||
c.JsonApiErr(404, "Playlist is empty", itemsErr)
|
||||
return
|
||||
}
|
||||
@@ -55,10 +55,10 @@ func SearchPlaylists(c *m.ReqContext) Response {
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
if err != nil {
|
||||
return ApiError(500, "Search failed", err)
|
||||
return Error(500, "Search failed", err)
|
||||
}
|
||||
|
||||
return Json(200, searchQuery.Result)
|
||||
return JSON(200, searchQuery.Result)
|
||||
}
|
||||
|
||||
func GetPlaylist(c *m.ReqContext) Response {
|
||||
@@ -66,7 +66,7 @@ func GetPlaylist(c *m.ReqContext) Response {
|
||||
cmd := m.GetPlaylistByIdQuery{Id: id}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Playlist not found", err)
|
||||
return Error(500, "Playlist not found", err)
|
||||
}
|
||||
|
||||
playlistDTOs, _ := LoadPlaylistItemDTOs(id)
|
||||
@@ -79,7 +79,7 @@ func GetPlaylist(c *m.ReqContext) Response {
|
||||
Items: playlistDTOs,
|
||||
}
|
||||
|
||||
return Json(200, dto)
|
||||
return JSON(200, dto)
|
||||
}
|
||||
|
||||
func LoadPlaylistItemDTOs(id int64) ([]m.PlaylistItemDTO, error) {
|
||||
@@ -120,21 +120,21 @@ func GetPlaylistItems(c *m.ReqContext) Response {
|
||||
playlistDTOs, err := LoadPlaylistItemDTOs(id)
|
||||
|
||||
if err != nil {
|
||||
return ApiError(500, "Could not load playlist items", err)
|
||||
return Error(500, "Could not load playlist items", err)
|
||||
}
|
||||
|
||||
return Json(200, playlistDTOs)
|
||||
return JSON(200, playlistDTOs)
|
||||
}
|
||||
|
||||
func GetPlaylistDashboards(c *m.ReqContext) Response {
|
||||
playlistId := c.ParamsInt64(":id")
|
||||
playlistID := c.ParamsInt64(":id")
|
||||
|
||||
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId)
|
||||
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistID)
|
||||
if err != nil {
|
||||
return ApiError(500, "Could not load dashboards", err)
|
||||
return Error(500, "Could not load dashboards", err)
|
||||
}
|
||||
|
||||
return Json(200, playlists)
|
||||
return JSON(200, playlists)
|
||||
}
|
||||
|
||||
func DeletePlaylist(c *m.ReqContext) Response {
|
||||
@@ -142,34 +142,35 @@ func DeletePlaylist(c *m.ReqContext) Response {
|
||||
|
||||
cmd := m.DeletePlaylistCommand{Id: id, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete playlist", err)
|
||||
return Error(500, "Failed to delete playlist", err)
|
||||
}
|
||||
|
||||
return Json(200, "")
|
||||
return JSON(200, "")
|
||||
}
|
||||
|
||||
func CreatePlaylist(c *m.ReqContext, cmd m.CreatePlaylistCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to create playlist", err)
|
||||
return Error(500, "Failed to create playlist", err)
|
||||
}
|
||||
|
||||
return Json(200, cmd.Result)
|
||||
return JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
func UpdatePlaylist(c *m.ReqContext, cmd m.UpdatePlaylistCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.Id = c.ParamsInt64(":id")
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to save playlist", err)
|
||||
return Error(500, "Failed to save playlist", err)
|
||||
}
|
||||
|
||||
playlistDTOs, err := LoadPlaylistItemDTOs(cmd.Id)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to save playlist", err)
|
||||
return Error(500, "Failed to save playlist", err)
|
||||
}
|
||||
|
||||
cmd.Result.Items = playlistDTOs
|
||||
return Json(200, cmd.Result)
|
||||
return JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
|
||||
func populateDashboardsByID(dashboardByIDs []int64, dashboardIDOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
|
||||
result := make(dtos.PlaylistDashboardsSlice, 0)
|
||||
|
||||
if len(dashboardByIds) > 0 {
|
||||
dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIds}
|
||||
if len(dashboardByIDs) > 0 {
|
||||
dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIDs}
|
||||
if err := bus.Dispatch(&dashboardQuery); err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
|
||||
Slug: item.Slug,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Order: dashboardIdOrder[item.Id],
|
||||
Order: dashboardIDOrder[item.Id],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -34,29 +34,27 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
|
||||
func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
|
||||
result := make(dtos.PlaylistDashboardsSlice, 0)
|
||||
|
||||
if len(dashboardByTag) > 0 {
|
||||
for _, tag := range dashboardByTag {
|
||||
searchQuery := search.Query{
|
||||
Title: "",
|
||||
Tags: []string{tag},
|
||||
SignedInUser: signedInUser,
|
||||
Limit: 100,
|
||||
IsStarred: false,
|
||||
OrgId: orgId,
|
||||
}
|
||||
for _, tag := range dashboardByTag {
|
||||
searchQuery := search.Query{
|
||||
Title: "",
|
||||
Tags: []string{tag},
|
||||
SignedInUser: signedInUser,
|
||||
Limit: 100,
|
||||
IsStarred: false,
|
||||
OrgId: orgID,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&searchQuery); err == nil {
|
||||
for _, item := range searchQuery.Result {
|
||||
result = append(result, dtos.PlaylistDashboard{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: item.Uri,
|
||||
Order: dashboardTagOrder[tag],
|
||||
})
|
||||
}
|
||||
if err := bus.Dispatch(&searchQuery); err == nil {
|
||||
for _, item := range searchQuery.Result {
|
||||
result = append(result, dtos.PlaylistDashboard{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: item.Uri,
|
||||
Order: dashboardTagOrder[tag],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,19 +62,19 @@ func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboar
|
||||
return result
|
||||
}
|
||||
|
||||
func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistId int64) (dtos.PlaylistDashboardsSlice, error) {
|
||||
playlistItems, _ := LoadPlaylistItems(playlistId)
|
||||
func LoadPlaylistDashboards(orgID int64, signedInUser *m.SignedInUser, playlistID int64) (dtos.PlaylistDashboardsSlice, error) {
|
||||
playlistItems, _ := LoadPlaylistItems(playlistID)
|
||||
|
||||
dashboardByIds := make([]int64, 0)
|
||||
dashboardByIDs := make([]int64, 0)
|
||||
dashboardByTag := make([]string, 0)
|
||||
dashboardIdOrder := make(map[int64]int)
|
||||
dashboardIDOrder := make(map[int64]int)
|
||||
dashboardTagOrder := make(map[string]int)
|
||||
|
||||
for _, i := range playlistItems {
|
||||
if i.Type == "dashboard_by_id" {
|
||||
dashboardId, _ := strconv.ParseInt(i.Value, 10, 64)
|
||||
dashboardByIds = append(dashboardByIds, dashboardId)
|
||||
dashboardIdOrder[dashboardId] = i.Order
|
||||
dashboardID, _ := strconv.ParseInt(i.Value, 10, 64)
|
||||
dashboardByIDs = append(dashboardByIDs, dashboardID)
|
||||
dashboardIDOrder[dashboardID] = i.Order
|
||||
}
|
||||
|
||||
if i.Type == "dashboard_by_tag" {
|
||||
@@ -87,9 +85,9 @@ func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistI
|
||||
|
||||
result := make(dtos.PlaylistDashboardsSlice, 0)
|
||||
|
||||
var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder)
|
||||
var k, _ = populateDashboardsByID(dashboardByIDs, dashboardIDOrder)
|
||||
result = append(result, k...)
|
||||
result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...)
|
||||
result = append(result, populateDashboardsByTag(orgID, signedInUser, dashboardByTag, dashboardTagOrder)...)
|
||||
|
||||
sort.Sort(result)
|
||||
return result, nil
|
||||
|
||||
171
pkg/api/pluginproxy/access_token_provider.go
Normal file
171
pkg/api/pluginproxy/access_token_provider.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
tokenCache = tokenCacheType{
|
||||
cache: map[string]*jwtToken{},
|
||||
}
|
||||
oauthJwtTokenCache = oauthJwtTokenCacheType{
|
||||
cache: map[string]*oauth2.Token{},
|
||||
}
|
||||
)
|
||||
|
||||
type tokenCacheType struct {
|
||||
cache map[string]*jwtToken
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
type oauthJwtTokenCacheType struct {
|
||||
cache map[string]*oauth2.Token
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
type accessTokenProvider struct {
|
||||
route *plugins.AppPluginRoute
|
||||
datasourceId int64
|
||||
datasourceVersion int
|
||||
}
|
||||
|
||||
type jwtToken struct {
|
||||
ExpiresOn time.Time `json:"-"`
|
||||
ExpiresOnString string `json:"expires_on"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
func newAccessTokenProvider(ds *models.DataSource, pluginRoute *plugins.AppPluginRoute) *accessTokenProvider {
|
||||
return &accessTokenProvider{
|
||||
datasourceId: ds.Id,
|
||||
datasourceVersion: ds.Version,
|
||||
route: pluginRoute,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *accessTokenProvider) getAccessToken(data templateData) (string, error) {
|
||||
tokenCache.Lock()
|
||||
defer tokenCache.Unlock()
|
||||
if cachedToken, found := tokenCache.cache[provider.getAccessTokenCacheKey()]; found {
|
||||
if cachedToken.ExpiresOn.After(time.Now().Add(time.Second * 10)) {
|
||||
logger.Info("Using token from cache")
|
||||
return cachedToken.AccessToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
urlInterpolated, err := interpolateString(provider.route.TokenAuth.Url, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
params := make(url.Values)
|
||||
for key, value := range provider.route.TokenAuth.Params {
|
||||
interpolatedParam, err := interpolateString(value, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params.Add(key, interpolatedParam)
|
||||
}
|
||||
|
||||
getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode()))
|
||||
getTokenReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
getTokenReq.Header.Add("Content-Length", strconv.Itoa(len(params.Encode())))
|
||||
|
||||
resp, err := client.Do(getTokenReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var token jwtToken
|
||||
if err := json.NewDecoder(resp.Body).Decode(&token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expiresOnEpoch, _ := strconv.ParseInt(token.ExpiresOnString, 10, 64)
|
||||
token.ExpiresOn = time.Unix(expiresOnEpoch, 0)
|
||||
tokenCache.cache[provider.getAccessTokenCacheKey()] = &token
|
||||
|
||||
logger.Info("Got new access token", "ExpiresOn", token.ExpiresOn)
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data templateData) (string, error) {
|
||||
oauthJwtTokenCache.Lock()
|
||||
defer oauthJwtTokenCache.Unlock()
|
||||
if cachedToken, found := oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()]; found {
|
||||
if cachedToken.Expiry.After(time.Now().Add(time.Second * 10)) {
|
||||
logger.Debug("Using token from cache")
|
||||
return cachedToken.AccessToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
conf := &jwt.Config{}
|
||||
|
||||
if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok {
|
||||
interpolatedVal, err := interpolateString(val, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conf.Email = interpolatedVal
|
||||
}
|
||||
|
||||
if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok {
|
||||
interpolatedVal, err := interpolateString(val, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conf.PrivateKey = []byte(interpolatedVal)
|
||||
}
|
||||
|
||||
if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok {
|
||||
interpolatedVal, err := interpolateString(val, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conf.TokenURL = interpolatedVal
|
||||
}
|
||||
|
||||
conf.Scopes = provider.route.JwtTokenAuth.Scopes
|
||||
|
||||
token, err := getTokenSource(conf, ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()] = token
|
||||
|
||||
logger.Info("Got new access token", "ExpiresOn", token.Expiry)
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
var getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
tokenSrc := conf.TokenSource(ctx)
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (provider *accessTokenProvider) getAccessTokenCacheKey() string {
|
||||
return fmt.Sprintf("%v_%v_%v_%v", provider.datasourceId, provider.datasourceVersion, provider.route.Path, provider.route.Method)
|
||||
}
|
||||
94
pkg/api/pluginproxy/access_token_provider_test.go
Normal file
94
pkg/api/pluginproxy/access_token_provider_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
func TestAccessToken(t *testing.T) {
|
||||
Convey("Plugin with JWT token auth route", t, func() {
|
||||
pluginRoute := &plugins.AppPluginRoute{
|
||||
Path: "pathwithjwttoken1",
|
||||
Url: "https://api.jwt.io/some/path",
|
||||
Method: "GET",
|
||||
JwtTokenAuth: &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Scopes: []string{
|
||||
"https://www.testapi.com/auth/monitoring.read",
|
||||
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||
},
|
||||
Params: map[string]string{
|
||||
"token_uri": "{{.JsonData.tokenUri}}",
|
||||
"client_email": "{{.JsonData.clientEmail}}",
|
||||
"private_key": "{{.SecureJsonData.privateKey}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
templateData := templateData{
|
||||
JsonData: map[string]interface{}{
|
||||
"clientEmail": "test@test.com",
|
||||
"tokenUri": "login.url.com/token",
|
||||
},
|
||||
SecureJsonData: map[string]string{
|
||||
"privateKey": "testkey",
|
||||
},
|
||||
}
|
||||
|
||||
ds := &models.DataSource{Id: 1, Version: 2}
|
||||
|
||||
Convey("should fetch token using jwt private key", func() {
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
}
|
||||
provider := newAccessTokenProvider(ds, pluginRoute)
|
||||
token, err := provider.getJwtAccessToken(context.Background(), templateData)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(token, ShouldEqual, "abc")
|
||||
})
|
||||
|
||||
Convey("should set jwt config values", func() {
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
So(conf.Email, ShouldEqual, "test@test.com")
|
||||
So(conf.PrivateKey, ShouldResemble, []byte("testkey"))
|
||||
So(len(conf.Scopes), ShouldEqual, 2)
|
||||
So(conf.Scopes[0], ShouldEqual, "https://www.testapi.com/auth/monitoring.read")
|
||||
So(conf.Scopes[1], ShouldEqual, "https://www.testapi.com/auth/cloudplatformprojects.readonly")
|
||||
So(conf.TokenURL, ShouldEqual, "login.url.com/token")
|
||||
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
}
|
||||
|
||||
provider := newAccessTokenProvider(ds, pluginRoute)
|
||||
_, err := provider.getJwtAccessToken(context.Background(), templateData)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("should use cached token on second call", func() {
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{
|
||||
AccessToken: "abc",
|
||||
Expiry: time.Now().Add(1 * time.Minute)}, nil
|
||||
}
|
||||
provider := newAccessTokenProvider(ds, pluginRoute)
|
||||
token1, err := provider.getJwtAccessToken(context.Background(), templateData)
|
||||
So(err, ShouldBeNil)
|
||||
So(token1, ShouldEqual, "abc")
|
||||
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "error: cache not used"}, nil
|
||||
}
|
||||
token2, err := provider.getJwtAccessToken(context.Background(), templateData)
|
||||
So(err, ShouldBeNil)
|
||||
So(token2, ShouldEqual, "abc")
|
||||
})
|
||||
})
|
||||
}
|
||||
109
pkg/api/pluginproxy/ds_auth_provider.go
Normal file
109
pkg/api/pluginproxy/ds_auth_provider.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
//ApplyRoute should use the plugin route data to set auth headers and custom headers
|
||||
func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute, ds *m.DataSource) {
|
||||
proxyPath = strings.TrimPrefix(proxyPath, route.Path)
|
||||
|
||||
data := templateData{
|
||||
JsonData: ds.JsonData.Interface().(map[string]interface{}),
|
||||
SecureJsonData: ds.SecureJsonData.Decrypt(),
|
||||
}
|
||||
|
||||
interpolatedURL, err := interpolateString(route.Url, data)
|
||||
if err != nil {
|
||||
logger.Error("Error interpolating proxy url", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
routeURL, err := url.Parse(interpolatedURL)
|
||||
if err != nil {
|
||||
logger.Error("Error parsing plugin route url", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.URL.Scheme = routeURL.Scheme
|
||||
req.URL.Host = routeURL.Host
|
||||
req.Host = routeURL.Host
|
||||
req.URL.Path = util.JoinUrlFragments(routeURL.Path, proxyPath)
|
||||
|
||||
if err := addHeaders(&req.Header, route, data); err != nil {
|
||||
logger.Error("Failed to render plugin headers", "error", err)
|
||||
}
|
||||
|
||||
tokenProvider := newAccessTokenProvider(ds, route)
|
||||
|
||||
if route.TokenAuth != nil {
|
||||
if token, err := tokenProvider.getAccessToken(data); err != nil {
|
||||
logger.Error("Failed to get access token", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
authenticationType := ds.JsonData.Get("authenticationType").MustString("jwt")
|
||||
if route.JwtTokenAuth != nil && authenticationType == "jwt" {
|
||||
if token, err := tokenProvider.getJwtAccessToken(ctx, data); err != nil {
|
||||
logger.Error("Failed to get access token", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
if authenticationType == "gce" {
|
||||
tokenSrc, err := google.DefaultTokenSource(ctx, route.JwtTokenAuth.Scopes...)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default token from meta data server", "error", err)
|
||||
} else {
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default access token from meta data server", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("Requesting", "url", req.URL.String())
|
||||
}
|
||||
|
||||
func interpolateString(text string, data templateData) (string, error) {
|
||||
t, err := template.New("content").Parse(text)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse template %s", text)
|
||||
}
|
||||
|
||||
var contentBuf bytes.Buffer
|
||||
err = t.Execute(&contentBuf, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute template %s", text)
|
||||
}
|
||||
|
||||
return contentBuf.String(), nil
|
||||
}
|
||||
|
||||
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error {
|
||||
for _, header := range route.Headers {
|
||||
interpolated, err := interpolateString(header.Content, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqHeaders.Add(header.Name, interpolated)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
21
pkg/api/pluginproxy/ds_auth_provider_test.go
Normal file
21
pkg/api/pluginproxy/ds_auth_provider_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDsAuthProvider(t *testing.T) {
|
||||
Convey("When interpolating string", t, func() {
|
||||
data := templateData{
|
||||
SecureJsonData: map[string]string{
|
||||
"Test": "0asd+asd",
|
||||
},
|
||||
}
|
||||
|
||||
interpolated, err := interpolateString("{{.SecureJsonData.Test}}", data)
|
||||
So(err, ShouldBeNil)
|
||||
So(interpolated, ShouldEqual, "0asd+asd")
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package pluginproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
@@ -25,20 +23,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
logger log.Logger = log.New("data-proxy-log")
|
||||
client *http.Client = &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
}
|
||||
tokenCache = map[int64]*jwtToken{}
|
||||
logger = log.New("data-proxy-log")
|
||||
client = newHTTPClient()
|
||||
)
|
||||
|
||||
type jwtToken struct {
|
||||
ExpiresOn time.Time `json:"-"`
|
||||
ExpiresOnString string `json:"expires_on"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type DataSourceProxy struct {
|
||||
ds *m.DataSource
|
||||
ctx *m.ReqContext
|
||||
@@ -48,15 +36,26 @@ type DataSourceProxy struct {
|
||||
plugin *plugins.DataSourcePlugin
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string) *DataSourceProxy {
|
||||
targetUrl, _ := url.Parse(ds.Url)
|
||||
targetURL, _ := url.Parse(ds.Url)
|
||||
|
||||
return &DataSourceProxy{
|
||||
ds: ds,
|
||||
plugin: plugin,
|
||||
ctx: ctx,
|
||||
proxyPath: proxyPath,
|
||||
targetUrl: targetUrl,
|
||||
targetUrl: targetURL,
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPClient() httpClient {
|
||||
return &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +88,9 @@ func (proxy *DataSourceProxy) HandleRequest() {
|
||||
span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
|
||||
span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId)
|
||||
|
||||
proxy.addTraceFromHeaderValue(span, "X-Panel-Id", "panel_id")
|
||||
proxy.addTraceFromHeaderValue(span, "X-Dashboard-Id", "dashboard_id")
|
||||
|
||||
opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
@@ -98,6 +100,36 @@ func (proxy *DataSourceProxy) HandleRequest() {
|
||||
proxy.ctx.Resp.Header().Del("Set-Cookie")
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, headerName string, tagName string) {
|
||||
panelId := proxy.ctx.Req.Header.Get(headerName)
|
||||
dashId, err := strconv.Atoi(panelId)
|
||||
if err == nil {
|
||||
span.SetTag(tagName, dashId)
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) useCustomHeaders(req *http.Request) {
|
||||
decryptSdj := proxy.ds.SecureJsonData.Decrypt()
|
||||
index := 1
|
||||
for {
|
||||
headerNameSuffix := fmt.Sprintf("httpHeaderName%d", index)
|
||||
headerValueSuffix := fmt.Sprintf("httpHeaderValue%d", index)
|
||||
if key := proxy.ds.JsonData.Get(headerNameSuffix).MustString(); key != "" {
|
||||
if val, ok := decryptSdj[headerValueSuffix]; ok {
|
||||
// remove if exists
|
||||
if req.Header.Get(key) != "" {
|
||||
req.Header.Del(key)
|
||||
}
|
||||
req.Header.Add(key, val)
|
||||
logger.Debug("Using custom header ", "CustomHeaders", key)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
|
||||
return func(req *http.Request) {
|
||||
req.URL.Scheme = proxy.targetUrl.Scheme
|
||||
@@ -121,12 +153,16 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
|
||||
} else {
|
||||
req.URL.Path = util.JoinUrlFragments(proxy.targetUrl.Path, proxy.proxyPath)
|
||||
}
|
||||
|
||||
if proxy.ds.BasicAuth {
|
||||
req.Header.Del("Authorization")
|
||||
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.BasicAuthPassword))
|
||||
}
|
||||
|
||||
// Lookup and use custom headers
|
||||
if proxy.ds.SecureJsonData != nil {
|
||||
proxy.useCustomHeaders(req)
|
||||
}
|
||||
|
||||
dsAuth := req.Header.Get("X-DS-Authorization")
|
||||
if len(dsAuth) > 0 {
|
||||
req.Header.Del("X-DS-Authorization")
|
||||
@@ -157,6 +193,11 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
|
||||
req.Header.Del("X-Forwarded-Host")
|
||||
req.Header.Del("X-Forwarded-Port")
|
||||
req.Header.Del("X-Forwarded-Proto")
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
||||
|
||||
// Clear Origin and Referer to avoir CORS issues
|
||||
req.Header.Del("Origin")
|
||||
req.Header.Del("Referer")
|
||||
|
||||
// set X-Forwarded-For header
|
||||
if req.RemoteAddr != "" {
|
||||
@@ -172,18 +213,12 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
|
||||
}
|
||||
|
||||
if proxy.route != nil {
|
||||
proxy.applyRoute(req)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) validateRequest() error {
|
||||
if proxy.ds.Type == m.DS_INFLUXDB {
|
||||
if proxy.ctx.Query("db") != proxy.ds.Database {
|
||||
return errors.New("Datasource is not configured to allow this database")
|
||||
}
|
||||
}
|
||||
|
||||
if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) {
|
||||
return errors.New("Target url is not a valid target")
|
||||
}
|
||||
@@ -270,110 +305,3 @@ func checkWhiteList(c *m.ReqContext, host string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
|
||||
proxy.proxyPath = strings.TrimPrefix(proxy.proxyPath, proxy.route.Path)
|
||||
|
||||
data := templateData{
|
||||
JsonData: proxy.ds.JsonData.Interface().(map[string]interface{}),
|
||||
SecureJsonData: proxy.ds.SecureJsonData.Decrypt(),
|
||||
}
|
||||
|
||||
routeUrl, err := url.Parse(proxy.route.Url)
|
||||
if err != nil {
|
||||
logger.Error("Error parsing plugin route url")
|
||||
return
|
||||
}
|
||||
|
||||
req.URL.Scheme = routeUrl.Scheme
|
||||
req.URL.Host = routeUrl.Host
|
||||
req.Host = routeUrl.Host
|
||||
req.URL.Path = util.JoinUrlFragments(routeUrl.Path, proxy.proxyPath)
|
||||
|
||||
if err := addHeaders(&req.Header, proxy.route, data); err != nil {
|
||||
logger.Error("Failed to render plugin headers", "error", err)
|
||||
}
|
||||
|
||||
if proxy.route.TokenAuth != nil {
|
||||
if token, err := proxy.getAccessToken(data); err != nil {
|
||||
logger.Error("Failed to get access token", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("Requesting", "url", req.URL.String())
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error) {
|
||||
if cachedToken, found := tokenCache[proxy.ds.Id]; found {
|
||||
if cachedToken.ExpiresOn.After(time.Now().Add(time.Second * 10)) {
|
||||
logger.Info("Using token from cache")
|
||||
return cachedToken.AccessToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
urlInterpolated, err := interpolateString(proxy.route.TokenAuth.Url, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
params := make(url.Values)
|
||||
for key, value := range proxy.route.TokenAuth.Params {
|
||||
if interpolatedParam, err := interpolateString(value, data); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
params.Add(key, interpolatedParam)
|
||||
}
|
||||
}
|
||||
|
||||
getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode()))
|
||||
getTokenReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
getTokenReq.Header.Add("Content-Length", strconv.Itoa(len(params.Encode())))
|
||||
|
||||
resp, err := client.Do(getTokenReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var token jwtToken
|
||||
if err := json.NewDecoder(resp.Body).Decode(&token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expiresOnEpoch, _ := strconv.ParseInt(token.ExpiresOnString, 10, 64)
|
||||
token.ExpiresOn = time.Unix(expiresOnEpoch, 0)
|
||||
tokenCache[proxy.ds.Id] = &token
|
||||
|
||||
logger.Info("Got new access token", "ExpiresOn", token.ExpiresOn)
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
func interpolateString(text string, data templateData) (string, error) {
|
||||
t, err := template.New("content").Parse(text)
|
||||
if err != nil {
|
||||
return "", errors.New(fmt.Sprintf("Could not parse template %s.", text))
|
||||
}
|
||||
|
||||
var contentBuf bytes.Buffer
|
||||
err = t.Execute(&contentBuf, data)
|
||||
if err != nil {
|
||||
return "", errors.New(fmt.Sprintf("Failed to execute template %s.", text))
|
||||
}
|
||||
|
||||
return contentBuf.String(), nil
|
||||
}
|
||||
|
||||
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error {
|
||||
for _, header := range route.Headers {
|
||||
interpolated, err := interpolateString(header.Content, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqHeaders.Add(header.Name, interpolated)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -44,6 +49,13 @@ func TestDSRouteRule(t *testing.T) {
|
||||
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "api/common",
|
||||
Url: "{{.JsonData.dynamicUrl}}",
|
||||
Headers: []plugins.AppPluginRouteHeader{
|
||||
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -52,7 +64,8 @@ func TestDSRouteRule(t *testing.T) {
|
||||
|
||||
ds := &m.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"clientId": "asd",
|
||||
"clientId": "asd",
|
||||
"dynamicUrl": "https://dynamic.grafana.com",
|
||||
}),
|
||||
SecureJsonData: map[string][]byte{
|
||||
"key": key,
|
||||
@@ -70,7 +83,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
Convey("When matching route path", func() {
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method")
|
||||
proxy.route = plugin.Routes[0]
|
||||
proxy.applyRoute(req)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
|
||||
Convey("should add headers and update url", func() {
|
||||
So(req.URL.String(), ShouldEqual, "https://www.google.com/some/method")
|
||||
@@ -78,6 +91,17 @@ func TestDSRouteRule(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When matching route path and has dynamic url", func() {
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method")
|
||||
proxy.route = plugin.Routes[3]
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
|
||||
Convey("should add headers and interpolate the url", func() {
|
||||
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com/some/method")
|
||||
So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Validating request", func() {
|
||||
Convey("plugin route with valid role", func() {
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method")
|
||||
@@ -100,21 +124,128 @@ func TestDSRouteRule(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Plugin with multiple routes for token auth", func() {
|
||||
plugin := &plugins.DataSourcePlugin{
|
||||
Routes: []*plugins.AppPluginRoute{
|
||||
{
|
||||
Path: "pathwithtoken1",
|
||||
Url: "https://api.nr1.io/some/path",
|
||||
TokenAuth: &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Params: map[string]string{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": "{{.JsonData.clientId}}",
|
||||
"client_secret": "{{.SecureJsonData.clientSecret}}",
|
||||
"resource": "https://api.nr1.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "pathwithtoken2",
|
||||
Url: "https://api.nr2.io/some/path",
|
||||
TokenAuth: &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Params: map[string]string{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": "{{.JsonData.clientId}}",
|
||||
"client_secret": "{{.SecureJsonData.clientSecret}}",
|
||||
"resource": "https://api.nr2.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
setting.SecretKey = "password"
|
||||
key, _ := util.Encrypt([]byte("123"), "password")
|
||||
|
||||
ds := &m.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"clientId": "asd",
|
||||
"tenantId": "mytenantId",
|
||||
}),
|
||||
SecureJsonData: map[string][]byte{
|
||||
"clientSecret": key,
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
||||
ctx := &m.ReqContext{
|
||||
Context: &macaron.Context{
|
||||
Req: macaron.Request{Request: req},
|
||||
},
|
||||
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR},
|
||||
}
|
||||
|
||||
Convey("When creating and caching access tokens", func() {
|
||||
var authorizationHeaderCall1 string
|
||||
var authorizationHeaderCall2 string
|
||||
|
||||
Convey("first call should add authorization header with access token", func() {
|
||||
json, err := ioutil.ReadFile("./test-data/access-token-1.json")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
client = newFakeHTTPClient(json)
|
||||
proxy1 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1")
|
||||
proxy1.route = plugin.Routes[0]
|
||||
ApplyRoute(proxy1.ctx.Req.Context(), req, proxy1.proxyPath, proxy1.route, proxy1.ds)
|
||||
|
||||
authorizationHeaderCall1 = req.Header.Get("Authorization")
|
||||
So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
|
||||
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
||||
|
||||
Convey("second call to another route should add a different access token", func() {
|
||||
json2, err := ioutil.ReadFile("./test-data/access-token-2.json")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
||||
client = newFakeHTTPClient(json2)
|
||||
proxy2 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2")
|
||||
proxy2.route = plugin.Routes[1]
|
||||
ApplyRoute(proxy2.ctx.Req.Context(), req, proxy2.proxyPath, proxy2.route, proxy2.ds)
|
||||
|
||||
authorizationHeaderCall2 = req.Header.Get("Authorization")
|
||||
|
||||
So(req.URL.String(), ShouldEqual, "https://api.nr2.io/some/path")
|
||||
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
||||
So(authorizationHeaderCall2, ShouldStartWith, "Bearer eyJ0e")
|
||||
So(authorizationHeaderCall2, ShouldNotEqual, authorizationHeaderCall1)
|
||||
|
||||
Convey("third call to first route should add cached access token", func() {
|
||||
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
||||
|
||||
client = newFakeHTTPClient([]byte{})
|
||||
proxy3 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1")
|
||||
proxy3.route = plugin.Routes[0]
|
||||
ApplyRoute(proxy3.ctx.Req.Context(), req, proxy3.proxyPath, proxy3.route, proxy3.ds)
|
||||
|
||||
authorizationHeaderCall3 := req.Header.Get("Authorization")
|
||||
So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
|
||||
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
||||
So(authorizationHeaderCall3, ShouldStartWith, "Bearer eyJ0e")
|
||||
So(authorizationHeaderCall3, ShouldEqual, authorizationHeaderCall1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When proxying graphite", func() {
|
||||
setting.BuildVersion = "5.3.0"
|
||||
plugin := &plugins.DataSourcePlugin{}
|
||||
ds := &m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
|
||||
ctx := &m.ReqContext{}
|
||||
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "/render")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestUrl}
|
||||
|
||||
proxy.getDirector()(&req)
|
||||
proxy.getDirector()(req)
|
||||
|
||||
Convey("Can translate request url and path", func() {
|
||||
So(req.URL.Host, ShouldEqual, "graphite:8080")
|
||||
So(req.URL.Path, ShouldEqual, "/render")
|
||||
So(req.Header.Get("User-Agent"), ShouldEqual, "Grafana/5.3.0")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -132,10 +263,10 @@ func TestDSRouteRule(t *testing.T) {
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
|
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestUrl}
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(&req)
|
||||
proxy.getDirector()(req)
|
||||
|
||||
Convey("Should add db to url", func() {
|
||||
So(req.URL.Path, ShouldEqual, "/db/site/")
|
||||
@@ -162,8 +293,8 @@ func TestDSRouteRule(t *testing.T) {
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
|
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestUrl, Header: make(http.Header)}
|
||||
requestURL, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestURL, Header: make(http.Header)}
|
||||
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
|
||||
req.Header.Set("Cookie", cookies)
|
||||
|
||||
@@ -188,8 +319,8 @@ func TestDSRouteRule(t *testing.T) {
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
|
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestUrl, Header: make(http.Header)}
|
||||
requestURL, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestURL, Header: make(http.Header)}
|
||||
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
|
||||
req.Header.Set("Cookie", cookies)
|
||||
|
||||
@@ -200,17 +331,86 @@ func TestDSRouteRule(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When interpolating string", func() {
|
||||
data := templateData{
|
||||
SecureJsonData: map[string]string{
|
||||
"Test": "0asd+asd",
|
||||
Convey("When proxying a data source with custom headers specified", func() {
|
||||
plugin := &plugins.DataSourcePlugin{}
|
||||
|
||||
encryptedData, err := util.Encrypt([]byte(`Bearer xf5yhfkpsnmgo`), setting.SecretKey)
|
||||
ds := &m.DataSource{
|
||||
Type: m.DS_PROMETHEUS,
|
||||
Url: "http://prometheus:9090",
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"httpHeaderName1": "Authorization",
|
||||
}),
|
||||
SecureJsonData: map[string][]byte{
|
||||
"httpHeaderValue1": encryptedData,
|
||||
},
|
||||
}
|
||||
|
||||
interpolated, err := interpolateString("{{.SecureJsonData.Test}}", data)
|
||||
So(err, ShouldBeNil)
|
||||
So(interpolated, ShouldEqual, "0asd+asd")
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
|
||||
|
||||
requestURL, _ := url.Parse("http://grafana.com/sub")
|
||||
req := http.Request{URL: requestURL, Header: make(http.Header)}
|
||||
proxy.getDirector()(&req)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(4, err.Error())
|
||||
}
|
||||
|
||||
Convey("Match header value after decryption", func() {
|
||||
So(req.Header.Get("Authorization"), ShouldEqual, "Bearer xf5yhfkpsnmgo")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When proxying a custom datasource", func() {
|
||||
plugin := &plugins.DataSourcePlugin{}
|
||||
ds := &m.DataSource{
|
||||
Type: "custom-datasource",
|
||||
Url: "http://host/root/",
|
||||
}
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
req.Header.Add("Origin", "grafana.com")
|
||||
req.Header.Add("Referer", "grafana.com")
|
||||
req.Header.Add("X-Canary", "stillthere")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
|
||||
Convey("Should keep user request (including trailing slash)", func() {
|
||||
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
|
||||
})
|
||||
|
||||
Convey("Origin and Referer headers should be dropped", func() {
|
||||
So(req.Header.Get("Origin"), ShouldEqual, "")
|
||||
So(req.Header.Get("Referer"), ShouldEqual, "")
|
||||
So(req.Header.Get("X-Canary"), ShouldEqual, "stillthere")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type httpClientStub struct {
|
||||
fakeBody []byte
|
||||
}
|
||||
|
||||
func (c *httpClientStub) Do(req *http.Request) (*http.Response, error) {
|
||||
bodyJSON, _ := simplejson.NewJson(c.fakeBody)
|
||||
_, passedTokenCacheTest := bodyJSON.CheckGet("expires_on")
|
||||
So(passedTokenCacheTest, ShouldBeTrue)
|
||||
|
||||
bodyJSON.Set("expires_on", fmt.Sprint(time.Now().Add(time.Second*60).Unix()))
|
||||
body, _ := bodyJSON.MarshalJSON()
|
||||
resp := &http.Response{
|
||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func newFakeHTTPClient(fakeBody []byte) httpClient {
|
||||
return &httpClientStub{
|
||||
fakeBody: fakeBody,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ type templateData struct {
|
||||
SecureJsonData map[string]string
|
||||
}
|
||||
|
||||
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
|
||||
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.Header, error) {
|
||||
result := http.Header{}
|
||||
|
||||
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
|
||||
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
@@ -37,16 +37,16 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.
|
||||
return result, err
|
||||
}
|
||||
|
||||
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy {
|
||||
targetUrl, _ := url.Parse(route.Url)
|
||||
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string) *httputil.ReverseProxy {
|
||||
targetURL, _ := url.Parse(route.Url)
|
||||
|
||||
director := func(req *http.Request) {
|
||||
|
||||
req.URL.Scheme = targetUrl.Scheme
|
||||
req.URL.Host = targetUrl.Host
|
||||
req.Host = targetUrl.Host
|
||||
req.URL.Scheme = targetURL.Scheme
|
||||
req.URL.Host = targetURL.Host
|
||||
req.Host = targetURL.Host
|
||||
|
||||
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
|
||||
req.URL.Path = util.JoinUrlFragments(targetURL.Path, proxyPath)
|
||||
|
||||
// clear cookie headers
|
||||
req.Header.Del("Cookie")
|
||||
@@ -80,7 +80,7 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
|
||||
req.Header.Add("X-Grafana-Context", string(ctxJson))
|
||||
|
||||
if len(route.Headers) > 0 {
|
||||
headers, err := getHeaders(route, ctx.OrgId, appId)
|
||||
headers, err := getHeaders(route, ctx.OrgId, appID)
|
||||
if err != nil {
|
||||
ctx.JsonApiErr(500, "Could not generate plugin route header", err)
|
||||
return
|
||||
|
||||
9
pkg/api/pluginproxy/test-data/access-token-1.json
Normal file
9
pkg/api/pluginproxy/test-data/access-token-1.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"token_type": "Bearer",
|
||||
"expires_in": "3599",
|
||||
"ext_expires_in": "0",
|
||||
"expires_on": "1528740417",
|
||||
"not_before": "1528736517",
|
||||
"resource": "https://api.nr1.io",
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL2FwaS5sb2dhbmFseXRpY3MuaW8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9lN2YzZjY2MS1hOTMzLTRiM2YtODE3Ni01MWM0Zjk4MmVjNDgvIiwiaWF0IjoxNTI4NzM2NTE3LCJuYmYiOjE1Mjg3MzY1MTcsImV4cCI6MTUyODc0MDQxNywiYWlvIjoiWTJkZ1lBaStzaWRsT3NmQ2JicGhLMSsremttN0NBQT0iLCJhcHBpZCI6IjdmMzJkYjdjLTZmNmYtNGU4OC05M2Q5LTlhZTEyNmMwYTU1ZiIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2U3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OC8iLCJvaWQiOiI1NDQ5ZmJjOS1mYWJhLTRkNjItODE2Yy05ZmMwMzZkMWViN2UiLCJzdWIiOiI1NDQ5ZmJjOS1mYWJhLTRkNjItODE2Yy05ZmMwMzZkMWViN2UiLCJ0aWQiOiJlN2YzZjY2MS1hOTMzLTRiM2YtODE3Ni01MWM0Zjk4MmVjNDgiLCJ1dGkiOiJZQTlQa2lxUy1VV1hMQjhIRnU0U0FBIiwidmVyIjoiMS4wIn0.ga5qudt4LDMKTStAxUmzjyZH8UFBAaFirJqpTdmYny4NtkH6JT2EILvjTjYxlKeTQisvwx9gof0PyicZIab9d6wlMa2xiLzr2nmaOonYClY8fqBaRTgc1xVjrKFw5SCgpx3FnEyJhIWvVPIfaWaogSHcQbIpe4kdk4tz-ccmrx0D1jsziSI4BZcJcX04aJuHZGz9k4mQZ_AA5sQSeQaNuojIng6rYoIifAXFYBZPTbeeeqmiGq8v0IOLeNKbC0POeQCJC_KKBG6Z_MV2KgPxFEzQuX2ZFmRD_wGPteV5TUBxh1kARdqexA3e0zAKSawR9kmrAiZ21lPr4tX2Br_HDg"
|
||||
}
|
||||
9
pkg/api/pluginproxy/test-data/access-token-2.json
Normal file
9
pkg/api/pluginproxy/test-data/access-token-2.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"token_type": "Bearer",
|
||||
"expires_in": "3599",
|
||||
"ext_expires_in": "0",
|
||||
"expires_on": "1528662059",
|
||||
"not_before": "1528658159",
|
||||
"resource": "https://api.nr2.io",
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2U3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OC8iLCJpYXQiOjE1Mjg2NTgxNTksIm5iZiI6MTUyODY1ODE1OSwiZXhwIjoxNTI4NjYyMDU5LCJhaW8iOiJZMmRnWUFpK3NpZGxPc2ZDYmJwaEsxKyt6a203Q0FBPSIsImFwcGlkIjoiODg5YjdlZDgtMWFlZC00ODZlLTk3ODktODE5NzcwYmJiNjFhIiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZTdmM2Y2NjEtYTkzMy00YjNmLTgxNzYtNTFjNGY5ODJlYzQ4LyIsIm9pZCI6IjY0YzQxNjMyLTliOWUtNDczNy05MTYwLTBlNjAzZTg3NjljYyIsInN1YiI6IjY0YzQxNjMyLTliOWUtNDczNy05MTYwLTBlNjAzZTg3NjljYyIsInRpZCI6ImU3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OCIsInV0aSI6IkQ1ODZHSGUySDBPd0ptOU0xeVlKQUEiLCJ2ZXIiOiIxLjAifQ.Pw8c8gpoZptw3lGreQoHQaMVOozSaTE5D38Vm2aCHRB3DvD3N-Qcm1x0ZCakUEV2sJd7jvx4XtPFuW7063T0V1deExL4rzzvIo0ZfMmURf9tCTiKFKYibqf8_PtfPSz0t9eNDEUGmWDh1Wgssb4W_H-wPqgl9VPMT7T6ynkfIm0-ODPZTBzgSHiY8C_L1-DkhsK7XiqbUlSDgx9FpfChZS3ah8QhA8geqnb_HVuSktg7WhpxmogSpK5QdrwSE3jsbItpzOfLJ4iBd2ExzS2C0y8H_Coluk3Y1YA07tAxJ6Y7oBv-XwGqNfZhveOCQOzX-U3dFod3fXXysjB0UB89WQ"
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func GetPluginList(c *m.ReqContext) Response {
|
||||
func (hs *HTTPServer) GetPluginList(c *m.ReqContext) Response {
|
||||
typeFilter := c.Query("type")
|
||||
enabledFilter := c.Query("enabled")
|
||||
embeddedFilter := c.Query("embedded")
|
||||
@@ -19,7 +19,7 @@ func GetPluginList(c *m.ReqContext) Response {
|
||||
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
|
||||
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to get list of plugins", err)
|
||||
return Error(500, "Failed to get list of plugins", err)
|
||||
}
|
||||
|
||||
result := make(dtos.PluginList, 0)
|
||||
@@ -39,6 +39,10 @@ func GetPluginList(c *m.ReqContext) Response {
|
||||
continue
|
||||
}
|
||||
|
||||
if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
|
||||
continue
|
||||
}
|
||||
|
||||
listItem := dtos.PluginListItem{
|
||||
Id: pluginDef.Id,
|
||||
Name: pluginDef.Name,
|
||||
@@ -75,92 +79,94 @@ func GetPluginList(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
sort.Sort(result)
|
||||
return Json(200, result)
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func GetPluginSettingById(c *m.ReqContext) Response {
|
||||
pluginId := c.Params(":pluginId")
|
||||
func GetPluginSettingByID(c *m.ReqContext) Response {
|
||||
pluginID := c.Params(":pluginId")
|
||||
|
||||
if def, exists := plugins.Plugins[pluginId]; !exists {
|
||||
return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
|
||||
} else {
|
||||
|
||||
dto := &dtos.PluginSetting{
|
||||
Type: def.Type,
|
||||
Id: def.Id,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
Dependencies: &def.Dependencies,
|
||||
Includes: def.Includes,
|
||||
BaseUrl: def.BaseUrl,
|
||||
Module: def.Module,
|
||||
DefaultNavUrl: def.DefaultNavUrl,
|
||||
LatestVersion: def.GrafanaNetVersion,
|
||||
HasUpdate: def.GrafanaNetHasUpdate,
|
||||
State: def.State,
|
||||
}
|
||||
|
||||
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err != m.ErrPluginSettingNotFound {
|
||||
return ApiError(500, "Failed to get login settings", nil)
|
||||
}
|
||||
} else {
|
||||
dto.Enabled = query.Result.Enabled
|
||||
dto.Pinned = query.Result.Pinned
|
||||
dto.JsonData = query.Result.JsonData
|
||||
}
|
||||
|
||||
return Json(200, dto)
|
||||
def, exists := plugins.Plugins[pluginID]
|
||||
if !exists {
|
||||
return Error(404, "Plugin not found, no installed plugin with that id", nil)
|
||||
}
|
||||
|
||||
dto := &dtos.PluginSetting{
|
||||
Type: def.Type,
|
||||
Id: def.Id,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
Dependencies: &def.Dependencies,
|
||||
Includes: def.Includes,
|
||||
BaseUrl: def.BaseUrl,
|
||||
Module: def.Module,
|
||||
DefaultNavUrl: def.DefaultNavUrl,
|
||||
LatestVersion: def.GrafanaNetVersion,
|
||||
HasUpdate: def.GrafanaNetHasUpdate,
|
||||
State: def.State,
|
||||
}
|
||||
|
||||
query := m.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err != m.ErrPluginSettingNotFound {
|
||||
return Error(500, "Failed to get login settings", nil)
|
||||
}
|
||||
} else {
|
||||
dto.Enabled = query.Result.Enabled
|
||||
dto.Pinned = query.Result.Pinned
|
||||
dto.JsonData = query.Result.JsonData
|
||||
}
|
||||
|
||||
return JSON(200, dto)
|
||||
}
|
||||
|
||||
func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response {
|
||||
pluginId := c.Params(":pluginId")
|
||||
pluginID := c.Params(":pluginId")
|
||||
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.PluginId = pluginId
|
||||
cmd.PluginId = pluginID
|
||||
|
||||
if _, ok := plugins.Apps[cmd.PluginId]; !ok {
|
||||
return ApiError(404, "Plugin not installed.", nil)
|
||||
return Error(404, "Plugin not installed.", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update plugin setting", err)
|
||||
return Error(500, "Failed to update plugin setting", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Plugin settings updated")
|
||||
return Success("Plugin settings updated")
|
||||
}
|
||||
|
||||
func GetPluginDashboards(c *m.ReqContext) Response {
|
||||
pluginId := c.Params(":pluginId")
|
||||
pluginID := c.Params(":pluginId")
|
||||
|
||||
if list, err := plugins.GetPluginDashboards(c.OrgId, pluginId); err != nil {
|
||||
list, err := plugins.GetPluginDashboards(c.OrgId, pluginID)
|
||||
if err != nil {
|
||||
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
|
||||
return ApiError(404, notfound.Error(), nil)
|
||||
return Error(404, notfound.Error(), nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to get plugin dashboards", err)
|
||||
} else {
|
||||
return Json(200, list)
|
||||
return Error(500, "Failed to get plugin dashboards", err)
|
||||
}
|
||||
|
||||
return JSON(200, list)
|
||||
}
|
||||
|
||||
func GetPluginMarkdown(c *m.ReqContext) Response {
|
||||
pluginId := c.Params(":pluginId")
|
||||
pluginID := c.Params(":pluginId")
|
||||
name := c.Params(":name")
|
||||
|
||||
if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil {
|
||||
content, err := plugins.GetPluginMarkdown(pluginID, name)
|
||||
if err != nil {
|
||||
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
|
||||
return ApiError(404, notfound.Error(), nil)
|
||||
return Error(404, notfound.Error(), nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Could not get markdown file", err)
|
||||
} else {
|
||||
resp := Respond(200, content)
|
||||
resp.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
return resp
|
||||
return Error(500, "Could not get markdown file", err)
|
||||
}
|
||||
|
||||
resp := Respond(200, content)
|
||||
resp.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
return resp
|
||||
}
|
||||
|
||||
func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Response {
|
||||
@@ -172,12 +178,13 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon
|
||||
Path: apiCmd.Path,
|
||||
Inputs: apiCmd.Inputs,
|
||||
Overwrite: apiCmd.Overwrite,
|
||||
FolderId: apiCmd.FolderId,
|
||||
Dashboard: apiCmd.Dashboard,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to import dashboard", err)
|
||||
return Error(500, "Failed to import dashboard", err)
|
||||
}
|
||||
|
||||
return Json(200, cmd.Result)
|
||||
return JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
@@ -13,60 +13,61 @@ func SetHomeDashboard(c *m.ReqContext, cmd m.SavePreferencesCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to set home dashboard", err)
|
||||
return Error(500, "Failed to set home dashboard", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Home dashboard set")
|
||||
return Success("Home dashboard set")
|
||||
}
|
||||
|
||||
// GET /api/user/preferences
|
||||
func GetUserPreferences(c *m.ReqContext) Response {
|
||||
return getPreferencesFor(c.OrgId, c.UserId)
|
||||
return getPreferencesFor(c.OrgId, c.UserId, 0)
|
||||
}
|
||||
|
||||
func getPreferencesFor(orgId int64, userId int64) Response {
|
||||
prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId}
|
||||
func getPreferencesFor(orgID, userID, teamID int64) Response {
|
||||
prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID, TeamId: teamID}
|
||||
|
||||
if err := bus.Dispatch(&prefsQuery); err != nil {
|
||||
return ApiError(500, "Failed to get preferences", err)
|
||||
return Error(500, "Failed to get preferences", err)
|
||||
}
|
||||
|
||||
dto := dtos.Prefs{
|
||||
Theme: prefsQuery.Result.Theme,
|
||||
HomeDashboardId: prefsQuery.Result.HomeDashboardId,
|
||||
HomeDashboardID: prefsQuery.Result.HomeDashboardId,
|
||||
Timezone: prefsQuery.Result.Timezone,
|
||||
}
|
||||
|
||||
return Json(200, &dto)
|
||||
return JSON(200, &dto)
|
||||
}
|
||||
|
||||
// PUT /api/user/preferences
|
||||
func UpdateUserPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
|
||||
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
|
||||
return updatePreferencesFor(c.OrgId, c.UserId, 0, &dtoCmd)
|
||||
}
|
||||
|
||||
func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
|
||||
func updatePreferencesFor(orgID, userID, teamId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
|
||||
saveCmd := m.SavePreferencesCommand{
|
||||
UserId: userId,
|
||||
OrgId: orgId,
|
||||
UserId: userID,
|
||||
OrgId: orgID,
|
||||
TeamId: teamId,
|
||||
Theme: dtoCmd.Theme,
|
||||
Timezone: dtoCmd.Timezone,
|
||||
HomeDashboardId: dtoCmd.HomeDashboardId,
|
||||
HomeDashboardId: dtoCmd.HomeDashboardID,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&saveCmd); err != nil {
|
||||
return ApiError(500, "Failed to save preferences", err)
|
||||
return Error(500, "Failed to save preferences", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Preferences updated")
|
||||
return Success("Preferences updated")
|
||||
}
|
||||
|
||||
// GET /api/org/preferences
|
||||
func GetOrgPreferences(c *m.ReqContext) Response {
|
||||
return getPreferencesFor(c.OrgId, 0)
|
||||
return getPreferencesFor(c.OrgId, 0, 0)
|
||||
}
|
||||
|
||||
// PUT /api/org/preferences
|
||||
func UpdateOrgPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
|
||||
return updatePreferencesFor(c.OrgId, 0, &dtoCmd)
|
||||
return updatePreferencesFor(c.OrgId, 0, 0, &dtoCmd)
|
||||
}
|
||||
|
||||
@@ -8,60 +8,60 @@ import (
|
||||
|
||||
func GetOrgQuotas(c *m.ReqContext) Response {
|
||||
if !setting.Quota.Enabled {
|
||||
return ApiError(404, "Quotas not enabled", nil)
|
||||
return Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
query := m.GetOrgQuotasQuery{OrgId: c.ParamsInt64(":orgId")}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get org quotas", err)
|
||||
return Error(500, "Failed to get org quotas", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func UpdateOrgQuota(c *m.ReqContext, cmd m.UpdateOrgQuotaCmd) Response {
|
||||
if !setting.Quota.Enabled {
|
||||
return ApiError(404, "Quotas not enabled", nil)
|
||||
return Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
cmd.Target = c.Params(":target")
|
||||
|
||||
if _, ok := setting.Quota.Org.ToMap()[cmd.Target]; !ok {
|
||||
return ApiError(404, "Invalid quota target", nil)
|
||||
return Error(404, "Invalid quota target", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update org quotas", err)
|
||||
return Error(500, "Failed to update org quotas", err)
|
||||
}
|
||||
return ApiSuccess("Organization quota updated")
|
||||
return Success("Organization quota updated")
|
||||
}
|
||||
|
||||
func GetUserQuotas(c *m.ReqContext) Response {
|
||||
if !setting.Quota.Enabled {
|
||||
return ApiError(404, "Quotas not enabled", nil)
|
||||
return Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
query := m.GetUserQuotasQuery{UserId: c.ParamsInt64(":id")}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get org quotas", err)
|
||||
return Error(500, "Failed to get org quotas", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func UpdateUserQuota(c *m.ReqContext, cmd m.UpdateUserQuotaCmd) Response {
|
||||
if !setting.Quota.Enabled {
|
||||
return ApiError(404, "Quotas not enabled", nil)
|
||||
return Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
cmd.UserId = c.ParamsInt64(":id")
|
||||
cmd.Target = c.Params(":target")
|
||||
|
||||
if _, ok := setting.Quota.User.ToMap()[cmd.Target]; !ok {
|
||||
return ApiError(404, "Invalid quota target", nil)
|
||||
return Error(404, "Invalid quota target", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update org quotas", err)
|
||||
return Error(500, "Failed to update org quotas", err)
|
||||
}
|
||||
return ApiSuccess("Organization quota updated")
|
||||
return Success("Organization quota updated")
|
||||
}
|
||||
|
||||
@@ -3,44 +3,75 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/renderer"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func RenderToPng(c *m.ReqContext) {
|
||||
func (hs *HTTPServer) RenderToPng(c *m.ReqContext) {
|
||||
queryReader, err := util.NewUrlQueryReader(c.Req.URL)
|
||||
if err != nil {
|
||||
c.Handle(400, "Render parameters error", err)
|
||||
return
|
||||
}
|
||||
|
||||
queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
|
||||
|
||||
renderOpts := &renderer.RenderOpts{
|
||||
Path: c.Params("*") + queryParams,
|
||||
Width: queryReader.Get("width", "800"),
|
||||
Height: queryReader.Get("height", "400"),
|
||||
Timeout: queryReader.Get("timeout", "60"),
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
OrgRole: c.OrgRole,
|
||||
Timezone: queryReader.Get("tz", ""),
|
||||
Encoding: queryReader.Get("encoding", ""),
|
||||
width, err := strconv.Atoi(queryReader.Get("width", "800"))
|
||||
if err != nil {
|
||||
c.Handle(400, "Render parameters error", fmt.Errorf("Cannot parse width as int: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
pngPath, err := renderer.RenderToPng(renderOpts)
|
||||
height, err := strconv.Atoi(queryReader.Get("height", "400"))
|
||||
if err != nil {
|
||||
c.Handle(400, "Render parameters error", fmt.Errorf("Cannot parse height as int: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && err == renderer.ErrTimeout {
|
||||
timeout, err := strconv.Atoi(queryReader.Get("timeout", "60"))
|
||||
if err != nil {
|
||||
c.Handle(400, "Render parameters error", fmt.Errorf("Cannot parse timeout as int: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := hs.RenderService.Render(c.Req.Context(), rendering.Opts{
|
||||
Width: width,
|
||||
Height: height,
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
OrgRole: c.OrgRole,
|
||||
Path: c.Params("*") + queryParams,
|
||||
Timezone: queryReader.Get("tz", ""),
|
||||
Encoding: queryReader.Get("encoding", ""),
|
||||
ConcurrentLimit: 30,
|
||||
})
|
||||
|
||||
if err != nil && err == rendering.ErrTimeout {
|
||||
c.Handle(500, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && err == rendering.ErrPhantomJSNotInstalled {
|
||||
if strings.HasPrefix(runtime.GOARCH, "arm") {
|
||||
c.Handle(500, "Rendering failed - PhantomJS isn't included in arm build per default", err)
|
||||
} else {
|
||||
c.Handle(500, "Rendering failed - PhantomJS isn't installed correctly", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Handle(500, "Rendering failed.", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Resp.Header().Set("Content-Type", "image/png")
|
||||
http.ServeFile(c.Resp, c.Req.Request, pngPath)
|
||||
http.ServeFile(c.Resp, c.Req.Request, result.FilePath)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package api
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
@@ -11,22 +12,43 @@ type Router interface {
|
||||
Get(pattern string, handlers ...macaron.Handler) *macaron.Route
|
||||
}
|
||||
|
||||
// RouteRegister allows you to add routes and macaron.Handlers
|
||||
// that the web server should serve.
|
||||
type RouteRegister interface {
|
||||
// Get adds a list of handlers to a given route with a GET HTTP verb
|
||||
Get(string, ...macaron.Handler)
|
||||
|
||||
// Post adds a list of handlers to a given route with a POST HTTP verb
|
||||
Post(string, ...macaron.Handler)
|
||||
|
||||
// Delete adds a list of handlers to a given route with a DELETE HTTP verb
|
||||
Delete(string, ...macaron.Handler)
|
||||
|
||||
// Put adds a list of handlers to a given route with a PUT HTTP verb
|
||||
Put(string, ...macaron.Handler)
|
||||
|
||||
// Patch adds a list of handlers to a given route with a PATCH HTTP verb
|
||||
Patch(string, ...macaron.Handler)
|
||||
|
||||
// Any adds a list of handlers to a given route with any HTTP verb
|
||||
Any(string, ...macaron.Handler)
|
||||
|
||||
// Group allows you to pass a function that can add multiple routes
|
||||
// with a shared prefix route.
|
||||
Group(string, func(RouteRegister), ...macaron.Handler)
|
||||
|
||||
Register(Router) *macaron.Router
|
||||
// Insert adds more routes to an existing Group.
|
||||
Insert(string, func(RouteRegister), ...macaron.Handler)
|
||||
|
||||
// Register iterates over all routes added to the RouteRegister
|
||||
// and add them to the `Router` pass as an parameter.
|
||||
Register(Router)
|
||||
}
|
||||
|
||||
type RegisterNamedMiddleware func(name string) macaron.Handler
|
||||
|
||||
func newRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister {
|
||||
// NewRouteRegister creates a new RouteRegister with all middlewares sent as params
|
||||
func NewRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister {
|
||||
return &routeRegister{
|
||||
prefix: "",
|
||||
routes: []route{},
|
||||
@@ -49,6 +71,24 @@ type routeRegister struct {
|
||||
groups []*routeRegister
|
||||
}
|
||||
|
||||
func (rr *routeRegister) Insert(pattern string, fn func(RouteRegister), handlers ...macaron.Handler) {
|
||||
|
||||
//loop over all groups at current level
|
||||
for _, g := range rr.groups {
|
||||
|
||||
// apply routes if the prefix matches the pattern
|
||||
if g.prefix == pattern {
|
||||
g.Group("", fn)
|
||||
break
|
||||
}
|
||||
|
||||
// go down one level if the prefix can be find in the pattern
|
||||
if strings.HasPrefix(pattern, g.prefix) {
|
||||
g.Insert(pattern, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *routeRegister) Group(pattern string, fn func(rr RouteRegister), handlers ...macaron.Handler) {
|
||||
group := &routeRegister{
|
||||
prefix: rr.prefix + pattern,
|
||||
@@ -61,7 +101,7 @@ func (rr *routeRegister) Group(pattern string, fn func(rr RouteRegister), handle
|
||||
rr.groups = append(rr.groups, group)
|
||||
}
|
||||
|
||||
func (rr *routeRegister) Register(router Router) *macaron.Router {
|
||||
func (rr *routeRegister) Register(router Router) {
|
||||
for _, r := range rr.routes {
|
||||
// GET requests have to be added to macaron routing using Get()
|
||||
// Otherwise HEAD requests will not be allowed.
|
||||
@@ -76,8 +116,6 @@ func (rr *routeRegister) Register(router Router) *macaron.Router {
|
||||
for _, g := range rr.groups {
|
||||
g.Register(router)
|
||||
}
|
||||
|
||||
return &macaron.Router{}
|
||||
}
|
||||
|
||||
func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handler) {
|
||||
@@ -89,6 +127,12 @@ func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handl
|
||||
h = append(h, rr.subfixHandlers...)
|
||||
h = append(h, handlers...)
|
||||
|
||||
for _, r := range rr.routes {
|
||||
if r.pattern == rr.prefix+pattern && r.method == method {
|
||||
panic("cannot add duplicate route")
|
||||
}
|
||||
}
|
||||
|
||||
rr.routes = append(rr.routes, route{
|
||||
method: method,
|
||||
pattern: rr.prefix + pattern,
|
||||
@@ -1,11 +1,11 @@
|
||||
package api
|
||||
package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type fakeRouter struct {
|
||||
@@ -33,7 +33,7 @@ func (fr *fakeRouter) Get(pattern string, handlers ...macaron.Handler) *macaron.
|
||||
}
|
||||
|
||||
func emptyHandlers(n int) []macaron.Handler {
|
||||
res := []macaron.Handler{}
|
||||
var res []macaron.Handler
|
||||
for i := 1; n >= i; i++ {
|
||||
res = append(res, emptyHandler(strconv.Itoa(i)))
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func TestRouteSimpleRegister(t *testing.T) {
|
||||
}
|
||||
|
||||
// Setup
|
||||
rr := newRouteRegister(func(name string) macaron.Handler {
|
||||
rr := NewRouteRegister(func(name string) macaron.Handler {
|
||||
return emptyHandler(name)
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestRouteGroupedRegister(t *testing.T) {
|
||||
}
|
||||
|
||||
// Setup
|
||||
rr := newRouteRegister()
|
||||
rr := NewRouteRegister()
|
||||
|
||||
rr.Delete("/admin", emptyHandler("1"))
|
||||
rr.Get("/down", emptyHandler("1"), emptyHandler("2"))
|
||||
@@ -138,7 +138,78 @@ func TestRouteGroupedRegister(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestRouteGroupInserting(t *testing.T) {
|
||||
testTable := []route{
|
||||
{method: http.MethodGet, pattern: "/api/", handlers: emptyHandlers(1)},
|
||||
{method: http.MethodPost, pattern: "/api/group/endpoint", handlers: emptyHandlers(1)},
|
||||
|
||||
{method: http.MethodGet, pattern: "/api/group/inserted", handlers: emptyHandlers(1)},
|
||||
{method: http.MethodDelete, pattern: "/api/inserted-endpoint", handlers: emptyHandlers(1)},
|
||||
}
|
||||
|
||||
// Setup
|
||||
rr := NewRouteRegister()
|
||||
|
||||
rr.Group("/api", func(api RouteRegister) {
|
||||
api.Get("/", emptyHandler("1"))
|
||||
|
||||
api.Group("/group", func(group RouteRegister) {
|
||||
group.Post("/endpoint", emptyHandler("1"))
|
||||
})
|
||||
})
|
||||
|
||||
rr.Insert("/api", func(api RouteRegister) {
|
||||
api.Delete("/inserted-endpoint", emptyHandler("1"))
|
||||
})
|
||||
|
||||
rr.Insert("/api/group", func(group RouteRegister) {
|
||||
group.Get("/inserted", emptyHandler("1"))
|
||||
})
|
||||
|
||||
fr := &fakeRouter{}
|
||||
rr.Register(fr)
|
||||
|
||||
// Validation
|
||||
if len(fr.route) != len(testTable) {
|
||||
t.Fatalf("want %v routes, got %v", len(testTable), len(fr.route))
|
||||
}
|
||||
|
||||
for i := range testTable {
|
||||
if testTable[i].method != fr.route[i].method {
|
||||
t.Errorf("want %s got %v", testTable[i].method, fr.route[i].method)
|
||||
}
|
||||
|
||||
if testTable[i].pattern != fr.route[i].pattern {
|
||||
t.Errorf("want %s got %v", testTable[i].pattern, fr.route[i].pattern)
|
||||
}
|
||||
|
||||
if len(testTable[i].handlers) != len(fr.route[i].handlers) {
|
||||
t.Errorf("want %d handlers got %d handlers \ntestcase: %v\nroute: %v\n",
|
||||
len(testTable[i].handlers),
|
||||
len(fr.route[i].handlers),
|
||||
testTable[i],
|
||||
fr.route[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateRoutShouldPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() != "cannot add duplicate route" {
|
||||
t.Errorf("Should cause panic if duplicate routes are added ")
|
||||
}
|
||||
}()
|
||||
|
||||
rr := NewRouteRegister(func(name string) macaron.Handler {
|
||||
return emptyHandler(name)
|
||||
})
|
||||
|
||||
rr.Get("/api", emptyHandler("1"))
|
||||
rr.Get("/api", emptyHandler("1"))
|
||||
|
||||
fr := &fakeRouter{}
|
||||
rr.Register(fr)
|
||||
}
|
||||
func TestNamedMiddlewareRouteRegister(t *testing.T) {
|
||||
testTable := []route{
|
||||
{method: "DELETE", pattern: "/admin", handlers: emptyHandlers(2)},
|
||||
@@ -150,7 +221,7 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) {
|
||||
}
|
||||
|
||||
// Setup
|
||||
rr := newRouteRegister(func(name string) macaron.Handler {
|
||||
rr := NewRouteRegister(func(name string) macaron.Handler {
|
||||
return emptyHandler(name)
|
||||
})
|
||||
|
||||
@@ -25,19 +25,19 @@ func Search(c *m.ReqContext) {
|
||||
permission = m.PERMISSION_EDIT
|
||||
}
|
||||
|
||||
dbids := make([]int64, 0)
|
||||
dbIDs := make([]int64, 0)
|
||||
for _, id := range c.QueryStrings("dashboardIds") {
|
||||
dashboardId, err := strconv.ParseInt(id, 10, 64)
|
||||
dashboardID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
dbids = append(dbids, dashboardId)
|
||||
dbIDs = append(dbIDs, dashboardID)
|
||||
}
|
||||
}
|
||||
|
||||
folderIds := make([]int64, 0)
|
||||
folderIDs := make([]int64, 0)
|
||||
for _, id := range c.QueryStrings("folderIds") {
|
||||
folderId, err := strconv.ParseInt(id, 10, 64)
|
||||
folderID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
folderIds = append(folderIds, folderId)
|
||||
folderIDs = append(folderIDs, folderID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ func Search(c *m.ReqContext) {
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
OrgId: c.OrgId,
|
||||
DashboardIds: dbids,
|
||||
DashboardIds: dbIDs,
|
||||
Type: dashboardType,
|
||||
FolderIds: folderIds,
|
||||
FolderIds: folderIDs,
|
||||
Permission: permission,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// GET /api/user/signup/options
|
||||
func GetSignUpOptions(c *m.ReqContext) Response {
|
||||
return Json(200, util.DynMap{
|
||||
return JSON(200, util.DynMap{
|
||||
"verifyEmailEnabled": setting.VerifyEmailEnabled,
|
||||
"autoAssignOrg": setting.AutoAssignOrg,
|
||||
})
|
||||
@@ -21,12 +21,12 @@ func GetSignUpOptions(c *m.ReqContext) Response {
|
||||
// POST /api/user/signup
|
||||
func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
|
||||
if !setting.AllowUserSignUp {
|
||||
return ApiError(401, "User signup is disabled", nil)
|
||||
return Error(401, "User signup is disabled", nil)
|
||||
}
|
||||
|
||||
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
|
||||
if err := bus.Dispatch(&existing); err == nil {
|
||||
return ApiError(422, "User with same email address already exists", nil)
|
||||
return Error(422, "User with same email address already exists", nil)
|
||||
}
|
||||
|
||||
cmd := m.CreateTempUserCommand{}
|
||||
@@ -38,7 +38,7 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
|
||||
cmd.RemoteAddr = c.Req.RemoteAddr
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to create signup", err)
|
||||
return Error(500, "Failed to create signup", err)
|
||||
}
|
||||
|
||||
bus.Publish(&events.SignUpStarted{
|
||||
@@ -48,12 +48,12 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
|
||||
|
||||
metrics.M_Api_User_SignUpStarted.Inc()
|
||||
|
||||
return Json(200, util.DynMap{"status": "SignUpCreated"})
|
||||
return JSON(200, util.DynMap{"status": "SignUpCreated"})
|
||||
}
|
||||
|
||||
func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
|
||||
if !setting.AllowUserSignUp {
|
||||
return ApiError(401, "User signup is disabled", nil)
|
||||
return Error(401, "User signup is disabled", nil)
|
||||
}
|
||||
|
||||
createUserCmd := m.CreateUserCommand{
|
||||
@@ -75,12 +75,12 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
|
||||
// check if user exists
|
||||
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
|
||||
if err := bus.Dispatch(&existing); err == nil {
|
||||
return ApiError(401, "User with same email address already exists", nil)
|
||||
return Error(401, "User with same email address already exists", nil)
|
||||
}
|
||||
|
||||
// dispatch create command
|
||||
if err := bus.Dispatch(&createUserCmd); err != nil {
|
||||
return ApiError(500, "Failed to create user", err)
|
||||
return Error(500, "Failed to create user", err)
|
||||
}
|
||||
|
||||
// publish signup event
|
||||
@@ -98,7 +98,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
|
||||
// check for pending invites
|
||||
invitesQuery := m.GetTempUsersQuery{Email: form.Email, Status: m.TmpUserInvitePending}
|
||||
if err := bus.Dispatch(&invitesQuery); err != nil {
|
||||
return ApiError(500, "Failed to query database for invites", err)
|
||||
return Error(500, "Failed to query database for invites", err)
|
||||
}
|
||||
|
||||
apiResponse := util.DynMap{"message": "User sign up completed successfully", "code": "redirect-to-landing-page"}
|
||||
@@ -112,7 +112,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
|
||||
loginUserWithUser(user, c)
|
||||
metrics.M_Api_User_SignUpCompleted.Inc()
|
||||
|
||||
return Json(200, apiResponse)
|
||||
return JSON(200, apiResponse)
|
||||
}
|
||||
|
||||
func verifyUserSignUpEmail(email string, code string) (bool, Response) {
|
||||
@@ -120,14 +120,14 @@ func verifyUserSignUpEmail(email string, code string) (bool, Response) {
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrTempUserNotFound {
|
||||
return false, ApiError(404, "Invalid email verification code", nil)
|
||||
return false, Error(404, "Invalid email verification code", nil)
|
||||
}
|
||||
return false, ApiError(500, "Failed to read temp user", err)
|
||||
return false, Error(500, "Failed to read temp user", err)
|
||||
}
|
||||
|
||||
tempUser := query.Result
|
||||
if tempUser.Email != email {
|
||||
return false, ApiError(404, "Email verification code does not match email", nil)
|
||||
return false, Error(404, "Email verification code does not match email", nil)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
||||
@@ -7,20 +7,20 @@ import (
|
||||
|
||||
func StarDashboard(c *m.ReqContext) Response {
|
||||
if !c.IsSignedIn {
|
||||
return ApiError(412, "You need to sign in to star dashboards", nil)
|
||||
return Error(412, "You need to sign in to star dashboards", nil)
|
||||
}
|
||||
|
||||
cmd := m.StarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
|
||||
|
||||
if cmd.DashboardId <= 0 {
|
||||
return ApiError(400, "Missing dashboard id", nil)
|
||||
return Error(400, "Missing dashboard id", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to star dashboard", err)
|
||||
return Error(500, "Failed to star dashboard", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Dashboard starred!")
|
||||
return Success("Dashboard starred!")
|
||||
}
|
||||
|
||||
func UnstarDashboard(c *m.ReqContext) Response {
|
||||
@@ -28,12 +28,12 @@ func UnstarDashboard(c *m.ReqContext) Response {
|
||||
cmd := m.UnstarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
|
||||
|
||||
if cmd.DashboardId <= 0 {
|
||||
return ApiError(400, "Missing dashboard id", nil)
|
||||
return Error(400, "Missing dashboard id", nil)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to unstar dashboard", err)
|
||||
return Error(500, "Failed to unstar dashboard", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Dashboard unstarred")
|
||||
return Success("Dashboard unstarred")
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ type StaticOptions struct {
|
||||
// Expires defines which user-defined function to use for producing a HTTP Expires Header
|
||||
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
|
||||
AddHeaders func(ctx *macaron.Context)
|
||||
// FileSystem is the interface for supporting any implmentation of file system.
|
||||
// FileSystem is the interface for supporting any implementation of file system.
|
||||
FileSystem http.FileSystem
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ func CreateTeam(c *m.ReqContext, cmd m.CreateTeamCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrTeamNameTaken {
|
||||
return ApiError(409, "Team name taken", err)
|
||||
return Error(409, "Team name taken", err)
|
||||
}
|
||||
return ApiError(500, "Failed to create Team", err)
|
||||
return Error(500, "Failed to create Team", err)
|
||||
}
|
||||
|
||||
return Json(200, &util.DynMap{
|
||||
return JSON(200, &util.DynMap{
|
||||
"teamId": cmd.Result.Id,
|
||||
"message": "Team created",
|
||||
})
|
||||
@@ -29,23 +29,23 @@ func UpdateTeam(c *m.ReqContext, cmd m.UpdateTeamCommand) Response {
|
||||
cmd.Id = c.ParamsInt64(":teamId")
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrTeamNameTaken {
|
||||
return ApiError(400, "Team name taken", err)
|
||||
return Error(400, "Team name taken", err)
|
||||
}
|
||||
return ApiError(500, "Failed to update Team", err)
|
||||
return Error(500, "Failed to update Team", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Team updated")
|
||||
return Success("Team updated")
|
||||
}
|
||||
|
||||
// DELETE /api/teams/:teamId
|
||||
func DeleteTeamById(c *m.ReqContext) Response {
|
||||
func DeleteTeamByID(c *m.ReqContext) Response {
|
||||
if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Failed to delete Team. ID not found", nil)
|
||||
return Error(404, "Failed to delete Team. ID not found", nil)
|
||||
}
|
||||
return ApiError(500, "Failed to update Team", err)
|
||||
return Error(500, "Failed to update Team", err)
|
||||
}
|
||||
return ApiSuccess("Team deleted")
|
||||
return Success("Team deleted")
|
||||
}
|
||||
|
||||
// GET /api/teams/search
|
||||
@@ -68,7 +68,7 @@ func SearchTeams(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to search Teams", err)
|
||||
return Error(500, "Failed to search Teams", err)
|
||||
}
|
||||
|
||||
for _, team := range query.Result.Teams {
|
||||
@@ -78,20 +78,31 @@ func SearchTeams(c *m.ReqContext) Response {
|
||||
query.Result.Page = page
|
||||
query.Result.PerPage = perPage
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// GET /api/teams/:teamId
|
||||
func GetTeamById(c *m.ReqContext) Response {
|
||||
func GetTeamByID(c *m.ReqContext) Response {
|
||||
query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Team not found", err)
|
||||
return Error(404, "Team not found", err)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to get Team", err)
|
||||
return Error(500, "Failed to get Team", err)
|
||||
}
|
||||
|
||||
return Json(200, &query.Result)
|
||||
query.Result.AvatarUrl = dtos.GetGravatarUrlWithDefault(query.Result.Email, query.Result.Name)
|
||||
return JSON(200, &query.Result)
|
||||
}
|
||||
|
||||
// GET /api/teams/:teamId/preferences
|
||||
func GetTeamPreferences(c *m.ReqContext) Response {
|
||||
return getPreferencesFor(c.OrgId, 0, c.ParamsInt64(":teamId"))
|
||||
}
|
||||
|
||||
// PUT /api/teams/:teamId/preferences
|
||||
func UpdateTeamPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
|
||||
return updatePreferencesFor(c.OrgId, 0, c.ParamsInt64(":teamId"), &dtoCmd)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -12,14 +13,19 @@ func GetTeamMembers(c *m.ReqContext) Response {
|
||||
query := m.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId")}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get Team Members", err)
|
||||
return Error(500, "Failed to get Team Members", err)
|
||||
}
|
||||
|
||||
for _, member := range query.Result {
|
||||
member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
|
||||
member.Labels = []string{}
|
||||
|
||||
if setting.IsEnterprise && setting.LdapEnabled && member.External {
|
||||
member.Labels = append(member.Labels, "LDAP")
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// POST /api/teams/:teamId/members
|
||||
@@ -29,17 +35,17 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Team not found", nil)
|
||||
return Error(404, "Team not found", nil)
|
||||
}
|
||||
|
||||
if err == m.ErrTeamMemberAlreadyAdded {
|
||||
return ApiError(400, "User is already added to this team", nil)
|
||||
return Error(400, "User is already added to this team", nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to add Member to Team", err)
|
||||
return Error(500, "Failed to add Member to Team", err)
|
||||
}
|
||||
|
||||
return Json(200, &util.DynMap{
|
||||
return JSON(200, &util.DynMap{
|
||||
"message": "Member added to Team",
|
||||
})
|
||||
}
|
||||
@@ -48,14 +54,14 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
|
||||
func RemoveTeamMember(c *m.ReqContext) Response {
|
||||
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Team not found", nil)
|
||||
return Error(404, "Team not found", nil)
|
||||
}
|
||||
|
||||
if err == m.ErrTeamMemberNotFound {
|
||||
return ApiError(404, "Team member not found", nil)
|
||||
return Error(404, "Team member not found", nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to remove Member from Team", err)
|
||||
return Error(500, "Failed to remove Member from Team", err)
|
||||
}
|
||||
return ApiSuccess("Team Member removed")
|
||||
return Success("Team Member removed")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func TestTeamApiEndpoint(t *testing.T) {
|
||||
Convey("Given two teams", t, func() {
|
||||
mockResult := models.SearchTeamQueryResult{
|
||||
Teams: []*models.SearchTeamDto{
|
||||
Teams: []*models.TeamDTO{
|
||||
{Name: "team1"},
|
||||
{Name: "team2"},
|
||||
},
|
||||
|
||||
123
pkg/api/user.go
123
pkg/api/user.go
@@ -14,21 +14,21 @@ func GetSignedInUser(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
// GET /api/users/:id
|
||||
func GetUserById(c *m.ReqContext) Response {
|
||||
func GetUserByID(c *m.ReqContext) Response {
|
||||
return getUserUserProfile(c.ParamsInt64(":id"))
|
||||
}
|
||||
|
||||
func getUserUserProfile(userId int64) Response {
|
||||
query := m.GetUserProfileQuery{UserId: userId}
|
||||
func getUserUserProfile(userID int64) Response {
|
||||
query := m.GetUserProfileQuery{UserId: userID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrUserNotFound {
|
||||
return ApiError(404, m.ErrUserNotFound.Error(), nil)
|
||||
return Error(404, m.ErrUserNotFound.Error(), nil)
|
||||
}
|
||||
return ApiError(500, "Failed to get user", err)
|
||||
return Error(500, "Failed to get user", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// GET /api/users/lookup
|
||||
@@ -36,9 +36,9 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
|
||||
query := m.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err == m.ErrUserNotFound {
|
||||
return ApiError(404, m.ErrUserNotFound.Error(), nil)
|
||||
return Error(404, m.ErrUserNotFound.Error(), nil)
|
||||
}
|
||||
return ApiError(500, "Failed to get user", err)
|
||||
return Error(500, "Failed to get user", err)
|
||||
}
|
||||
user := query.Result
|
||||
result := m.UserProfileDTO{
|
||||
@@ -50,17 +50,17 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
|
||||
IsGrafanaAdmin: user.IsAdmin,
|
||||
OrgId: user.OrgId,
|
||||
}
|
||||
return Json(200, &result)
|
||||
return JSON(200, &result)
|
||||
}
|
||||
|
||||
// POST /api/user
|
||||
func UpdateSignedInUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
|
||||
if setting.AuthProxyEnabled {
|
||||
if setting.AuthProxyHeaderProperty == "email" && cmd.Email != c.Email {
|
||||
return ApiError(400, "Not allowed to change email when auth proxy is using email property", nil)
|
||||
return Error(400, "Not allowed to change email when auth proxy is using email property", nil)
|
||||
}
|
||||
if setting.AuthProxyHeaderProperty == "username" && cmd.Login != c.Login {
|
||||
return ApiError(400, "Not allowed to change username when auth proxy is using username property", nil)
|
||||
return Error(400, "Not allowed to change username when auth proxy is using username property", nil)
|
||||
}
|
||||
}
|
||||
cmd.UserId = c.UserId
|
||||
@@ -75,35 +75,35 @@ func UpdateUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
|
||||
|
||||
//POST /api/users/:id/using/:orgId
|
||||
func UpdateUserActiveOrg(c *m.ReqContext) Response {
|
||||
userId := c.ParamsInt64(":id")
|
||||
orgId := c.ParamsInt64(":orgId")
|
||||
userID := c.ParamsInt64(":id")
|
||||
orgID := c.ParamsInt64(":orgId")
|
||||
|
||||
if !validateUsingOrg(userId, orgId) {
|
||||
return ApiError(401, "Not a valid organization", nil)
|
||||
if !validateUsingOrg(userID, orgID) {
|
||||
return Error(401, "Not a valid organization", nil)
|
||||
}
|
||||
|
||||
cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId}
|
||||
cmd := m.SetUsingOrgCommand{UserId: userID, OrgId: orgID}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to change active organization", err)
|
||||
return Error(500, "Failed to change active organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Active organization changed")
|
||||
return Success("Active organization changed")
|
||||
}
|
||||
|
||||
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
|
||||
if len(cmd.Login) == 0 {
|
||||
cmd.Login = cmd.Email
|
||||
if len(cmd.Login) == 0 {
|
||||
return ApiError(400, "Validation error, need to specify either username or email", nil)
|
||||
return Error(400, "Validation error, need to specify either username or email", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update user", err)
|
||||
return Error(500, "Failed to update user", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User updated")
|
||||
return Success("User updated")
|
||||
}
|
||||
|
||||
// GET /api/user/orgs
|
||||
@@ -111,23 +111,38 @@ func GetSignedInUserOrgList(c *m.ReqContext) Response {
|
||||
return getUserOrgList(c.UserId)
|
||||
}
|
||||
|
||||
// GET /api/user/teams
|
||||
func GetSignedInUserTeamList(c *m.ReqContext) Response {
|
||||
query := m.GetTeamsByUserQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return Error(500, "Failed to get user teams", err)
|
||||
}
|
||||
|
||||
for _, team := range query.Result {
|
||||
team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
|
||||
}
|
||||
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
// GET /api/user/:id/orgs
|
||||
func GetUserOrgList(c *m.ReqContext) Response {
|
||||
return getUserOrgList(c.ParamsInt64(":id"))
|
||||
}
|
||||
|
||||
func getUserOrgList(userId int64) Response {
|
||||
query := m.GetUserOrgListQuery{UserId: userId}
|
||||
func getUserOrgList(userID int64) Response {
|
||||
query := m.GetUserOrgListQuery{UserId: userID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to get user organizations", err)
|
||||
return Error(500, "Failed to get user organizations", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
query := m.GetUserOrgListQuery{UserId: userId}
|
||||
func validateUsingOrg(userID int64, orgID int64) bool {
|
||||
query := m.GetUserOrgListQuery{UserId: userID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return false
|
||||
@@ -136,7 +151,7 @@ func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
// validate that the org id in the list
|
||||
valid := false
|
||||
for _, other := range query.Result {
|
||||
if other.OrgId == orgId {
|
||||
if other.OrgId == orgID {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
@@ -146,33 +161,33 @@ func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
|
||||
// POST /api/user/using/:id
|
||||
func UserSetUsingOrg(c *m.ReqContext) Response {
|
||||
orgId := c.ParamsInt64(":id")
|
||||
orgID := c.ParamsInt64(":id")
|
||||
|
||||
if !validateUsingOrg(c.UserId, orgId) {
|
||||
return ApiError(401, "Not a valid organization", nil)
|
||||
if !validateUsingOrg(c.UserId, orgID) {
|
||||
return Error(401, "Not a valid organization", nil)
|
||||
}
|
||||
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to change active organization", err)
|
||||
return Error(500, "Failed to change active organization", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Active organization changed")
|
||||
return Success("Active organization changed")
|
||||
}
|
||||
|
||||
// GET /profile/switch-org/:id
|
||||
func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
|
||||
orgId := c.ParamsInt64(":id")
|
||||
func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
|
||||
orgID := c.ParamsInt64(":id")
|
||||
|
||||
if !validateUsingOrg(c.UserId, orgId) {
|
||||
NotFoundHandler(c)
|
||||
if !validateUsingOrg(c.UserId, orgID) {
|
||||
hs.NotFoundHandler(c)
|
||||
}
|
||||
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
|
||||
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
NotFoundHandler(c)
|
||||
hs.NotFoundHandler(c)
|
||||
}
|
||||
|
||||
c.Redirect(setting.AppSubUrl + "/")
|
||||
@@ -180,53 +195,53 @@ func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
|
||||
|
||||
func ChangeUserPassword(c *m.ReqContext, cmd m.ChangeUserPasswordCommand) Response {
|
||||
if setting.LdapEnabled || setting.AuthProxyEnabled {
|
||||
return ApiError(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
|
||||
return Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByIdQuery{Id: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
return ApiError(500, "Could not read user from database", err)
|
||||
return Error(500, "Could not read user from database", err)
|
||||
}
|
||||
|
||||
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
|
||||
if passwordHashed != userQuery.Result.Password {
|
||||
return ApiError(401, "Invalid old password", nil)
|
||||
return Error(401, "Invalid old password", nil)
|
||||
}
|
||||
|
||||
password := m.Password(cmd.NewPassword)
|
||||
if password.IsWeak() {
|
||||
return ApiError(400, "New password is too short", nil)
|
||||
return Error(400, "New password is too short", nil)
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to change user password", err)
|
||||
return Error(500, "Failed to change user password", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("User password changed")
|
||||
return Success("User password changed")
|
||||
}
|
||||
|
||||
// GET /api/users
|
||||
func SearchUsers(c *m.ReqContext) Response {
|
||||
query, err := searchUser(c)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to fetch users", err)
|
||||
return Error(500, "Failed to fetch users", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result.Users)
|
||||
return JSON(200, query.Result.Users)
|
||||
}
|
||||
|
||||
// GET /api/users/search
|
||||
func SearchUsersWithPaging(c *m.ReqContext) Response {
|
||||
query, err := searchUser(c)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to fetch users", err)
|
||||
return Error(500, "Failed to fetch users", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
return JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func searchUser(c *m.ReqContext) (*m.SearchUsersQuery, error) {
|
||||
@@ -269,10 +284,10 @@ func SetHelpFlag(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update help flag", err)
|
||||
return Error(500, "Failed to update help flag", err)
|
||||
}
|
||||
|
||||
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
||||
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
||||
}
|
||||
|
||||
func ClearHelpFlags(c *m.ReqContext) Response {
|
||||
@@ -282,8 +297,8 @@ func ClearHelpFlags(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to update help flag", err)
|
||||
return Error(500, "Failed to update help flag", err)
|
||||
}
|
||||
|
||||
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
||||
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package bus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@@ -10,21 +10,44 @@ type HandlerFunc interface{}
|
||||
type CtxHandlerFunc func()
|
||||
type Msg interface{}
|
||||
|
||||
var ErrHandlerNotFound = errors.New("handler not found")
|
||||
|
||||
type TransactionManager interface {
|
||||
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||
}
|
||||
|
||||
type Bus interface {
|
||||
Dispatch(msg Msg) error
|
||||
DispatchCtx(ctx context.Context, msg Msg) error
|
||||
Publish(msg Msg) error
|
||||
|
||||
// InTransaction starts a transaction and store it in the context.
|
||||
// The caller can then pass a function with multiple DispatchCtx calls that
|
||||
// all will be executed in the same transaction. InTransaction will rollback if the
|
||||
// callback returns an error.
|
||||
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||
|
||||
AddHandler(handler HandlerFunc)
|
||||
AddCtxHandler(handler HandlerFunc)
|
||||
AddHandlerCtx(handler HandlerFunc)
|
||||
AddEventListener(handler HandlerFunc)
|
||||
AddWildcardListener(handler HandlerFunc)
|
||||
|
||||
// SetTransactionManager allows the user to replace the internal
|
||||
// noop TransactionManager that is responsible for manageing
|
||||
// transactions in `InTransaction`
|
||||
SetTransactionManager(tm TransactionManager)
|
||||
}
|
||||
|
||||
func (b *InProcBus) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return b.txMng.InTransaction(ctx, fn)
|
||||
}
|
||||
|
||||
type InProcBus struct {
|
||||
handlers map[string]HandlerFunc
|
||||
handlersWithCtx map[string]HandlerFunc
|
||||
listeners map[string][]HandlerFunc
|
||||
wildcardListeners []HandlerFunc
|
||||
txMng TransactionManager
|
||||
}
|
||||
|
||||
// temp stuff, not sure how to handle bus instance, and init yet
|
||||
@@ -33,50 +56,70 @@ var globalBus = New()
|
||||
func New() Bus {
|
||||
bus := &InProcBus{}
|
||||
bus.handlers = make(map[string]HandlerFunc)
|
||||
bus.handlersWithCtx = make(map[string]HandlerFunc)
|
||||
bus.listeners = make(map[string][]HandlerFunc)
|
||||
bus.wildcardListeners = make([]HandlerFunc, 0)
|
||||
bus.txMng = &noopTransactionManager{}
|
||||
|
||||
return bus
|
||||
}
|
||||
|
||||
// Want to get rid of global bus
|
||||
func GetBus() Bus {
|
||||
return globalBus
|
||||
}
|
||||
|
||||
func (b *InProcBus) SetTransactionManager(tm TransactionManager) {
|
||||
b.txMng = tm
|
||||
}
|
||||
|
||||
func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error {
|
||||
var msgName = reflect.TypeOf(msg).Elem().Name()
|
||||
|
||||
var handler = b.handlers[msgName]
|
||||
var handler = b.handlersWithCtx[msgName]
|
||||
if handler == nil {
|
||||
return fmt.Errorf("handler not found for %s", msgName)
|
||||
return ErrHandlerNotFound
|
||||
}
|
||||
|
||||
var params = make([]reflect.Value, 2)
|
||||
params[0] = reflect.ValueOf(ctx)
|
||||
params[1] = reflect.ValueOf(msg)
|
||||
var params = []reflect.Value{}
|
||||
params = append(params, reflect.ValueOf(ctx))
|
||||
params = append(params, reflect.ValueOf(msg))
|
||||
|
||||
ret := reflect.ValueOf(handler).Call(params)
|
||||
err := ret[0].Interface()
|
||||
if err == nil {
|
||||
return nil
|
||||
} else {
|
||||
return err.(error)
|
||||
}
|
||||
return err.(error)
|
||||
}
|
||||
|
||||
func (b *InProcBus) Dispatch(msg Msg) error {
|
||||
var msgName = reflect.TypeOf(msg).Elem().Name()
|
||||
|
||||
var handler = b.handlers[msgName]
|
||||
var handler = b.handlersWithCtx[msgName]
|
||||
withCtx := true
|
||||
|
||||
if handler == nil {
|
||||
return fmt.Errorf("handler not found for %s", msgName)
|
||||
withCtx = false
|
||||
handler = b.handlers[msgName]
|
||||
}
|
||||
|
||||
var params = make([]reflect.Value, 1)
|
||||
params[0] = reflect.ValueOf(msg)
|
||||
if handler == nil {
|
||||
return ErrHandlerNotFound
|
||||
}
|
||||
|
||||
var params = []reflect.Value{}
|
||||
if withCtx {
|
||||
params = append(params, reflect.ValueOf(context.Background()))
|
||||
}
|
||||
params = append(params, reflect.ValueOf(msg))
|
||||
|
||||
ret := reflect.ValueOf(handler).Call(params)
|
||||
err := ret[0].Interface()
|
||||
if err == nil {
|
||||
return nil
|
||||
} else {
|
||||
return err.(error)
|
||||
}
|
||||
return err.(error)
|
||||
}
|
||||
|
||||
func (b *InProcBus) Publish(msg Msg) error {
|
||||
@@ -115,10 +158,10 @@ func (b *InProcBus) AddHandler(handler HandlerFunc) {
|
||||
b.handlers[queryTypeName] = handler
|
||||
}
|
||||
|
||||
func (b *InProcBus) AddCtxHandler(handler HandlerFunc) {
|
||||
func (b *InProcBus) AddHandlerCtx(handler HandlerFunc) {
|
||||
handlerType := reflect.TypeOf(handler)
|
||||
queryTypeName := handlerType.In(1).Elem().Name()
|
||||
b.handlers[queryTypeName] = handler
|
||||
b.handlersWithCtx[queryTypeName] = handler
|
||||
}
|
||||
|
||||
func (b *InProcBus) AddEventListener(handler HandlerFunc) {
|
||||
@@ -137,8 +180,8 @@ func AddHandler(implName string, handler HandlerFunc) {
|
||||
}
|
||||
|
||||
// Package level functions
|
||||
func AddCtxHandler(implName string, handler HandlerFunc) {
|
||||
globalBus.AddCtxHandler(handler)
|
||||
func AddHandlerCtx(implName string, handler HandlerFunc) {
|
||||
globalBus.AddHandlerCtx(handler)
|
||||
}
|
||||
|
||||
// Package level functions
|
||||
@@ -162,6 +205,20 @@ func Publish(msg Msg) error {
|
||||
return globalBus.Publish(msg)
|
||||
}
|
||||
|
||||
// InTransaction starts a transaction and store it in the context.
|
||||
// The caller can then pass a function with multiple DispatchCtx calls that
|
||||
// all will be executed in the same transaction. InTransaction will rollback if the
|
||||
// callback returns an error.
|
||||
func InTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return globalBus.InTransaction(ctx, fn)
|
||||
}
|
||||
|
||||
func ClearBusHandlers() {
|
||||
globalBus = New()
|
||||
}
|
||||
|
||||
type noopTransactionManager struct{}
|
||||
|
||||
func (*noopTransactionManager) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,67 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestQuery struct {
|
||||
type testQuery struct {
|
||||
Id int64
|
||||
Resp string
|
||||
}
|
||||
|
||||
func TestDispatchCtxCanUseNormalHandlers(t *testing.T) {
|
||||
bus := New()
|
||||
|
||||
handlerWithCtxCallCount := 0
|
||||
handlerCallCount := 0
|
||||
|
||||
handlerWithCtx := func(ctx context.Context, query *testQuery) error {
|
||||
handlerWithCtxCallCount++
|
||||
return nil
|
||||
}
|
||||
|
||||
handler := func(query *testQuery) error {
|
||||
handlerCallCount++
|
||||
return nil
|
||||
}
|
||||
|
||||
err := bus.DispatchCtx(context.Background(), &testQuery{})
|
||||
if err != ErrHandlerNotFound {
|
||||
t.Errorf("expected bus to return HandlerNotFound is no handler is registered")
|
||||
}
|
||||
|
||||
bus.AddHandler(handler)
|
||||
|
||||
t.Run("when a normal handler is registered", func(t *testing.T) {
|
||||
bus.Dispatch(&testQuery{})
|
||||
|
||||
if handlerCallCount != 1 {
|
||||
t.Errorf("Expected normal handler to be called 1 time. was called %d", handlerCallCount)
|
||||
}
|
||||
|
||||
t.Run("when a ctx handler is registered", func(t *testing.T) {
|
||||
bus.AddHandlerCtx(handlerWithCtx)
|
||||
bus.Dispatch(&testQuery{})
|
||||
|
||||
if handlerWithCtxCallCount != 1 {
|
||||
t.Errorf("Expected ctx handler to be called 1 time. was called %d", handlerWithCtxCallCount)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestQueryHandlerReturnsError(t *testing.T) {
|
||||
bus := New()
|
||||
|
||||
bus.AddHandler(func(query *TestQuery) error {
|
||||
bus.AddHandler(func(query *testQuery) error {
|
||||
return errors.New("handler error")
|
||||
})
|
||||
|
||||
err := bus.Dispatch(&TestQuery{})
|
||||
err := bus.Dispatch(&testQuery{})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Send query failed " + err.Error())
|
||||
@@ -30,12 +73,12 @@ func TestQueryHandlerReturnsError(t *testing.T) {
|
||||
func TestQueryHandlerReturn(t *testing.T) {
|
||||
bus := New()
|
||||
|
||||
bus.AddHandler(func(q *TestQuery) error {
|
||||
bus.AddHandler(func(q *testQuery) error {
|
||||
q.Resp = "hello from handler"
|
||||
return nil
|
||||
})
|
||||
|
||||
query := &TestQuery{}
|
||||
query := &testQuery{}
|
||||
err := bus.Dispatch(query)
|
||||
|
||||
if err != nil {
|
||||
@@ -49,17 +92,17 @@ func TestEventListeners(t *testing.T) {
|
||||
bus := New()
|
||||
count := 0
|
||||
|
||||
bus.AddEventListener(func(query *TestQuery) error {
|
||||
bus.AddEventListener(func(query *testQuery) error {
|
||||
count += 1
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddEventListener(func(query *TestQuery) error {
|
||||
bus.AddEventListener(func(query *testQuery) error {
|
||||
count += 10
|
||||
return nil
|
||||
})
|
||||
|
||||
err := bus.Publish(&TestQuery{})
|
||||
err := bus.Publish(&testQuery{})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Publish event failed " + err.Error())
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/fatih/color"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -15,13 +16,17 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli
|
||||
return func(context *cli.Context) {
|
||||
cmd := &contextCommandLine{context}
|
||||
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
Config: cmd.String("config"),
|
||||
HomePath: cmd.String("homepath"),
|
||||
Args: flag.Args(),
|
||||
})
|
||||
|
||||
sqlstore.NewEngine()
|
||||
engine := &sqlstore.SqlStore{}
|
||||
engine.Cfg = cfg
|
||||
engine.Bus = bus.GetBus()
|
||||
engine.Init()
|
||||
|
||||
if err := command(cmd); err != nil {
|
||||
logger.Errorf("\n%s: ", color.RedString("Error"))
|
||||
|
||||
@@ -33,7 +33,7 @@ func validateInput(c CommandLine, pluginFolder string) error {
|
||||
fileInfo, err := os.Stat(pluginsDir)
|
||||
if err != nil {
|
||||
if err = os.MkdirAll(pluginsDir, os.ModePerm); err != nil {
|
||||
return errors.New(fmt.Sprintf("pluginsDir (%s) is not a directory", pluginsDir))
|
||||
return fmt.Errorf("pluginsDir (%s) is not a writable directory", pluginsDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func SelectVersion(plugin m.Plugin, version string) (m.Version, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return m.Version{}, errors.New("Could not find the version your looking for")
|
||||
return m.Version{}, errors.New("Could not find the version you're looking for")
|
||||
}
|
||||
|
||||
func RemoveGitBuildFromName(pluginName, filename string) string {
|
||||
@@ -152,7 +152,7 @@ func downloadFile(pluginName, filePath, url string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength)
|
||||
r, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ var validateLsCommand = func(pluginDir string) error {
|
||||
return fmt.Errorf("error: %s", err)
|
||||
}
|
||||
|
||||
if pluginDirInfo.IsDir() == false {
|
||||
if !pluginDirInfo.IsDir() {
|
||||
return errors.New("plugin path is not a directory")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ package commands
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
"strings"
|
||||
|
||||
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
)
|
||||
|
||||
var getPluginss func(path string) []m.InstalledPlugin = services.GetLocalPlugins
|
||||
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
|
||||
|
||||
func removeCommand(c CommandLine) error {
|
||||
|
||||
@@ -53,8 +53,7 @@ func upgradeAllCommand(c CommandLine) error {
|
||||
for _, p := range pluginsToUpgrade {
|
||||
logger.Infof("Updating %v \n", p.Id)
|
||||
|
||||
var err error
|
||||
err = s.RemoveInstalledPlugin(pluginsDir, p.Id)
|
||||
err := s.RemoveInstalledPlugin(pluginsDir, p.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func upgradeCommand(c CommandLine) error {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err2 := s.GetPlugin(localPlugin.Id, c.RepoDirectory())
|
||||
v, err2 := s.GetPlugin(pluginName, c.RepoDirectory())
|
||||
|
||||
if err2 != nil {
|
||||
return err2
|
||||
@@ -24,9 +24,9 @@ func upgradeCommand(c CommandLine) error {
|
||||
|
||||
if ShouldUpgrade(localPlugin.Info.Version, v) {
|
||||
s.RemoveInstalledPlugin(pluginsDir, pluginName)
|
||||
return InstallPlugin(localPlugin.Id, "", c)
|
||||
return InstallPlugin(pluginName, "", c)
|
||||
}
|
||||
|
||||
logger.Infof("%s %s is up to date \n", color.GreenString("✔"), localPlugin.Id)
|
||||
logger.Infof("%s %s is up to date \n", color.GreenString("✔"), pluginName)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
@@ -42,7 +43,7 @@ func Init(version string, skipTLSVerify bool) {
|
||||
}
|
||||
|
||||
HttpClient = http.Client{
|
||||
Timeout: time.Duration(10 * time.Second),
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: tr,
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,7 @@ func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
|
||||
var data m.PluginRepo
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
logger.Info("Failed to unmarshal graphite response error: %v", err)
|
||||
logger.Info("Failed to unmarshal plugin repo response error:", err)
|
||||
return m.PluginRepo{}, err
|
||||
}
|
||||
|
||||
@@ -139,7 +140,7 @@ func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
|
||||
var data m.Plugin
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
logger.Info("Failed to unmarshal graphite response error: %v", err)
|
||||
logger.Info("Failed to unmarshal plugin repo response error:", err)
|
||||
return m.Plugin{}, err
|
||||
}
|
||||
|
||||
@@ -155,6 +156,8 @@ func sendRequest(repoUrl string, subPaths ...string) ([]byte, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
|
||||
req.Header.Set("grafana-version", grafanaVersion)
|
||||
req.Header.Set("grafana-os", runtime.GOOS)
|
||||
req.Header.Set("grafana-arch", runtime.GOARCH)
|
||||
req.Header.Set("User-Agent", "grafana "+grafanaVersion)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -42,6 +42,8 @@ func returnOsDefault(currentOs string) string {
|
||||
return "/usr/local/var/lib/grafana/plugins"
|
||||
case "freebsd":
|
||||
return "/var/db/grafana/plugins"
|
||||
case "openbsd":
|
||||
return "/var/grafana/plugins"
|
||||
default: //"linux"
|
||||
return "/var/lib/grafana/plugins"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
@@ -11,34 +13,32 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
extensions "github.com/grafana/grafana/pkg/extensions"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
|
||||
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/cloudwatch"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/elasticsearch"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/influxdb"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/mysql"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/opentsdb"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/postgres"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/stackdriver"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/testdata"
|
||||
)
|
||||
|
||||
var version = "5.0.0"
|
||||
var commit = "NA"
|
||||
var buildBranch = "master"
|
||||
var buildstamp string
|
||||
var build_date string
|
||||
|
||||
var configFile = flag.String("config", "", "path to config file")
|
||||
var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory")
|
||||
var pidFile = flag.String("pidfile", "", "path to pid file")
|
||||
var exitChan = make(chan int)
|
||||
|
||||
func main() {
|
||||
v := flag.Bool("v", false, "prints current version and exits")
|
||||
@@ -46,7 +46,7 @@ func main() {
|
||||
profilePort := flag.Int("profile-port", 6060, "Define custom port for profiling")
|
||||
flag.Parse()
|
||||
if *v {
|
||||
fmt.Printf("Version %s (commit: %s)\n", version, commit)
|
||||
fmt.Printf("Version %s (commit: %s, branch: %s)\n", version, commit, buildBranch)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -77,45 +77,37 @@ func main() {
|
||||
setting.BuildVersion = version
|
||||
setting.BuildCommit = commit
|
||||
setting.BuildStamp = buildstampInt64
|
||||
setting.BuildBranch = buildBranch
|
||||
setting.IsEnterprise = extensions.IsEnterprise
|
||||
|
||||
metrics.SetBuildInformation(version, commit, buildBranch)
|
||||
|
||||
metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
|
||||
shutdownCompleted := make(chan int)
|
||||
server := NewGrafanaServer()
|
||||
|
||||
go listenToSystemSignals(server, shutdownCompleted)
|
||||
go listenToSystemSignals(server)
|
||||
|
||||
go func() {
|
||||
code := 0
|
||||
if err := server.Start(); err != nil {
|
||||
log.Error2("Startup failed", "error", err)
|
||||
code = 1
|
||||
}
|
||||
err := server.Run()
|
||||
|
||||
exitChan <- code
|
||||
}()
|
||||
|
||||
code := <-shutdownCompleted
|
||||
log.Info2("Grafana shutdown completed.", "code", code)
|
||||
code := server.Exit(err)
|
||||
trace.Stop()
|
||||
log.Close()
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
|
||||
func listenToSystemSignals(server *GrafanaServerImpl) {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
ignoreChan := make(chan os.Signal, 1)
|
||||
code := 0
|
||||
sighupChan := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(ignoreChan, syscall.SIGHUP)
|
||||
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
signal.Notify(sighupChan, syscall.SIGHUP)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case sig := <-signalChan:
|
||||
trace.Stop() // Stops trace if profiling has been enabled
|
||||
server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
|
||||
shutdownCompleted <- 0
|
||||
case code = <-exitChan:
|
||||
trace.Stop() // Stops trace if profiling has been enabled
|
||||
server.Shutdown(code, "startup error")
|
||||
shutdownCompleted <- code
|
||||
for {
|
||||
select {
|
||||
case <-sighupChan:
|
||||
log.Reload()
|
||||
case sig := <-signalChan:
|
||||
server.Shutdown(fmt.Sprintf("System signal: %s", sig))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,33 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/facebookgo/inject"
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/cleanup"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/cache"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
"github.com/grafana/grafana/pkg/tracing"
|
||||
// self registering services
|
||||
_ "github.com/grafana/grafana/pkg/extensions"
|
||||
_ "github.com/grafana/grafana/pkg/metrics"
|
||||
_ "github.com/grafana/grafana/pkg/plugins"
|
||||
_ "github.com/grafana/grafana/pkg/services/alerting"
|
||||
_ "github.com/grafana/grafana/pkg/services/cleanup"
|
||||
_ "github.com/grafana/grafana/pkg/services/notifications"
|
||||
_ "github.com/grafana/grafana/pkg/services/provisioning"
|
||||
_ "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 {
|
||||
@@ -40,110 +49,143 @@ func NewGrafanaServer() *GrafanaServerImpl {
|
||||
shutdownFn: shutdownFn,
|
||||
childRoutines: childRoutines,
|
||||
log: log.New("server"),
|
||||
cfg: setting.NewCfg(),
|
||||
}
|
||||
}
|
||||
|
||||
type GrafanaServerImpl struct {
|
||||
context context.Context
|
||||
shutdownFn context.CancelFunc
|
||||
childRoutines *errgroup.Group
|
||||
log log.Logger
|
||||
context context.Context
|
||||
shutdownFn context.CancelFunc
|
||||
childRoutines *errgroup.Group
|
||||
log log.Logger
|
||||
cfg *setting.Cfg
|
||||
shutdownReason string
|
||||
shutdownInProgress bool
|
||||
|
||||
httpServer *api.HttpServer
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
HttpServer *api.HTTPServer `inject:""`
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) Start() error {
|
||||
g.initLogging()
|
||||
func (g *GrafanaServerImpl) Run() error {
|
||||
g.loadConfiguration()
|
||||
g.writePIDFile()
|
||||
|
||||
initSql()
|
||||
|
||||
metrics.Init(setting.Cfg)
|
||||
search.Init()
|
||||
login.Init()
|
||||
social.NewOAuthService()
|
||||
|
||||
pluginManager, err := plugins.NewPluginManager(g.context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to start plugins. error: %v", err)
|
||||
}
|
||||
g.childRoutines.Go(func() error { return pluginManager.Run(g.context) })
|
||||
serviceGraph := inject.Graph{}
|
||||
serviceGraph.Provide(&inject.Object{Value: bus.GetBus()})
|
||||
serviceGraph.Provide(&inject.Object{Value: g.cfg})
|
||||
serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)})
|
||||
serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)})
|
||||
|
||||
if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
|
||||
return fmt.Errorf("Failed to provision Grafana from config. error: %v", err)
|
||||
// self registered services
|
||||
services := registry.GetServices()
|
||||
|
||||
// Add all services to dependency graph
|
||||
for _, service := range services {
|
||||
serviceGraph.Provide(&inject.Object{Value: service.Instance})
|
||||
}
|
||||
|
||||
tracingCloser, err := tracing.Init(setting.Cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Tracing settings is not valid. error: %v", err)
|
||||
}
|
||||
defer tracingCloser.Close()
|
||||
serviceGraph.Provide(&inject.Object{Value: g})
|
||||
|
||||
// init alerting
|
||||
if setting.AlertingEnabled && setting.ExecuteAlerts {
|
||||
engine := alerting.NewEngine()
|
||||
g.childRoutines.Go(func() error { return engine.Run(g.context) })
|
||||
// Inject dependencies to services
|
||||
if err := serviceGraph.Populate(); err != nil {
|
||||
return fmt.Errorf("Failed to populate service dependency: %v", err)
|
||||
}
|
||||
|
||||
// cleanup service
|
||||
cleanUpService := cleanup.NewCleanUpService()
|
||||
g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
|
||||
// Init & start services
|
||||
for _, service := range services {
|
||||
if registry.IsDisabled(service.Instance) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = notifications.Init(); err != nil {
|
||||
return fmt.Errorf("Notification service failed to initialize. error: %v", err)
|
||||
g.log.Info("Initializing " + service.Name)
|
||||
|
||||
if err := service.Instance.Init(); err != nil {
|
||||
return fmt.Errorf("Service init failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start background services
|
||||
for _, srv := range services {
|
||||
// variable needed for accessing loop variable in function callback
|
||||
descriptor := srv
|
||||
service, ok := srv.Instance.(registry.BackgroundService)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if registry.IsDisabled(descriptor.Instance) {
|
||||
continue
|
||||
}
|
||||
|
||||
g.childRoutines.Go(func() error {
|
||||
// Skip starting new service when shutting down
|
||||
// Can happen when service stop/return during startup
|
||||
if g.shutdownInProgress {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := service.Run(g.context)
|
||||
|
||||
// If error is not canceled then the service crashed
|
||||
if err != context.Canceled && err != nil {
|
||||
g.log.Error("Stopped "+descriptor.Name, "reason", err)
|
||||
} else {
|
||||
g.log.Info("Stopped "+descriptor.Name, "reason", err)
|
||||
}
|
||||
|
||||
// Mark that we are in shutdown mode
|
||||
// So more services are not started
|
||||
g.shutdownInProgress = true
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
sendSystemdNotification("READY=1")
|
||||
|
||||
return g.startHttpServer()
|
||||
return g.childRoutines.Wait()
|
||||
}
|
||||
|
||||
func initSql() {
|
||||
sqlstore.NewEngine()
|
||||
sqlstore.EnsureAdminUser()
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) initLogging() {
|
||||
err := setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
func (g *GrafanaServerImpl) loadConfiguration() {
|
||||
err := g.cfg.Load(&setting.CommandLineArgs{
|
||||
Config: *configFile,
|
||||
HomePath: *homePath,
|
||||
Args: flag.Args(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
g.log.Error(err.Error())
|
||||
fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
g.log.Info("Starting Grafana", "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0))
|
||||
setting.LogConfigurationInfo()
|
||||
g.log.Info("Starting "+setting.ApplicationName, "version", version, "commit", commit, "branch", buildBranch, "compiled", time.Unix(setting.BuildStamp, 0))
|
||||
g.cfg.LogConfigSources()
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) startHttpServer() error {
|
||||
g.httpServer = api.NewHttpServer()
|
||||
|
||||
err := g.httpServer.Start(g.context)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Fail to start server. error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
|
||||
g.log.Info("Shutdown started", "code", code, "reason", reason)
|
||||
|
||||
err := g.httpServer.Shutdown(g.context)
|
||||
if err != nil {
|
||||
g.log.Error("Failed to shutdown server", "error", err)
|
||||
}
|
||||
func (g *GrafanaServerImpl) Shutdown(reason string) {
|
||||
g.log.Info("Shutdown started", "reason", reason)
|
||||
g.shutdownReason = reason
|
||||
g.shutdownInProgress = true
|
||||
|
||||
// call cancel func on root context
|
||||
g.shutdownFn()
|
||||
err = g.childRoutines.Wait()
|
||||
if err != nil && err != context.Canceled {
|
||||
g.log.Error("Server shutdown completed with an error", "error", err)
|
||||
|
||||
// wait for child routines
|
||||
g.childRoutines.Wait()
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) Exit(reason error) int {
|
||||
// default exit code is 1
|
||||
code := 1
|
||||
|
||||
if reason == context.Canceled && g.shutdownReason != "" {
|
||||
reason = fmt.Errorf(g.shutdownReason)
|
||||
code = 0
|
||||
}
|
||||
|
||||
g.log.Error("Server shutdown", "reason", reason)
|
||||
return code
|
||||
}
|
||||
|
||||
func (g *GrafanaServerImpl) writePIDFile() {
|
||||
|
||||
@@ -33,7 +33,7 @@ func New(orgId int64, name string) KeyGenResult {
|
||||
|
||||
jsonString, _ := json.Marshal(jsonKey)
|
||||
|
||||
result.ClientSecret = base64.StdEncoding.EncodeToString([]byte(jsonString))
|
||||
result.ClientSecret = base64.StdEncoding.EncodeToString(jsonString)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func Decode(keyString string) (*ApiKeyJson, error) {
|
||||
}
|
||||
|
||||
var keyObj ApiKeyJson
|
||||
err = json.Unmarshal([]byte(jsonString), &keyObj)
|
||||
err = json.Unmarshal(jsonString, &keyObj)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidApiKey
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
diff "github.com/yudai/gojsondiff"
|
||||
deltaFormatter "github.com/yudai/gojsondiff/formatter"
|
||||
@@ -15,11 +14,8 @@ import (
|
||||
var (
|
||||
// ErrUnsupportedDiffType occurs when an invalid diff type is used.
|
||||
ErrUnsupportedDiffType = errors.New("dashdiff: unsupported diff type")
|
||||
|
||||
// ErrNilDiff occurs when two compared interfaces are identical.
|
||||
ErrNilDiff = errors.New("dashdiff: diff is nil")
|
||||
|
||||
diffLogger = log.New("dashdiffs")
|
||||
)
|
||||
|
||||
type DiffType int
|
||||
@@ -145,5 +141,9 @@ func getDiff(baseData, newData *simplejson.Json) (interface{}, diff.Diff, error)
|
||||
|
||||
left := make(map[string]interface{})
|
||||
err = json.Unmarshal(leftBytes, &left)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return left, jsonDiff, nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// changeTypeToSymbol is used for populating the terminating characer in
|
||||
// changeTypeToSymbol is used for populating the terminating character in
|
||||
// the diff
|
||||
changeTypeToSymbol = map[ChangeType]string{
|
||||
ChangeNil: "",
|
||||
|
||||
@@ -134,9 +134,8 @@ func (v *Value) get(key string) (*Value, error) {
|
||||
child, ok := obj.Map()[key]
|
||||
if ok {
|
||||
return child, nil
|
||||
} else {
|
||||
return nil, KeyNotFoundError{key}
|
||||
}
|
||||
return nil, KeyNotFoundError{key}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
@@ -174,17 +173,13 @@ func (v *Object) GetObject(keys ...string) (*Object, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
obj, err := child.Object()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
}
|
||||
obj, err := child.Object()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a string.
|
||||
@@ -196,18 +191,17 @@ func (v *Object) GetString(keys ...string) (string, error) {
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return child.String()
|
||||
}
|
||||
return child.String()
|
||||
}
|
||||
|
||||
func (v *Object) MustGetString(path string, def string) string {
|
||||
keys := strings.Split(path, ".")
|
||||
if str, err := v.GetString(keys...); err != nil {
|
||||
str, err := v.GetString(keys...)
|
||||
if err != nil {
|
||||
return def
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into null.
|
||||
@@ -233,16 +227,13 @@ func (v *Object) GetNumber(keys ...string) (json.Number, error) {
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
|
||||
n, err := child.Number()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
n, err := child.Number()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
@@ -254,16 +245,13 @@ func (v *Object) GetFloat64(keys ...string) (float64, error) {
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
n, err := child.Float64()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
n, err := child.Float64()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
@@ -275,16 +263,13 @@ func (v *Object) GetInt64(keys ...string) (int64, error) {
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
n, err := child.Int64()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
n, err := child.Int64()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a float64.
|
||||
@@ -296,9 +281,8 @@ func (v *Object) GetInterface(keys ...string) (interface{}, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return child.Interface(), nil
|
||||
}
|
||||
return child.Interface(), nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into a bool.
|
||||
@@ -311,7 +295,6 @@ func (v *Object) GetBoolean(keys ...string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return child.Boolean()
|
||||
}
|
||||
|
||||
@@ -328,11 +311,8 @@ func (v *Object) GetValueArray(keys ...string) ([]*Value, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
return child.Array()
|
||||
|
||||
}
|
||||
return child.Array()
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of objects.
|
||||
@@ -347,30 +327,24 @@ func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]*Object, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.
|
||||
Object()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]*Object, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.
|
||||
Object()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of string.
|
||||
@@ -387,29 +361,23 @@ func (v *Object) GetStringArray(keys ...string) ([]string, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]string, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.String()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]string, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.String()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of numbers.
|
||||
@@ -424,29 +392,23 @@ func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]json.Number, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Number()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]json.Number, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Number()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of floats.
|
||||
@@ -456,29 +418,23 @@ func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]float64, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Float64()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]float64, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Float64()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of ints.
|
||||
@@ -488,29 +444,23 @@ func (v *Object) GetInt64Array(keys ...string) ([]int64, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]int64, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Int64()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]int64, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Int64()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of bools.
|
||||
@@ -520,29 +470,23 @@ func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) {
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedArray := make([]bool, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Boolean()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
|
||||
typedArray := make([]bool, len(array))
|
||||
|
||||
for index, arrayItem := range array {
|
||||
typedArrayItem, err := arrayItem.Boolean()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
typedArray[index] = typedArrayItem
|
||||
}
|
||||
return typedArray, nil
|
||||
}
|
||||
|
||||
// Gets the value at key path and attempts to typecast the value into an array of nulls.
|
||||
@@ -552,29 +496,23 @@ func (v *Object) GetNullArray(keys ...string) (int64, error) {
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
}
|
||||
array, err := child.Array()
|
||||
|
||||
array, err := child.Array()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var length int64 = 0
|
||||
|
||||
for _, arrayItem := range array {
|
||||
err := arrayItem.Null()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
var length int64 = 0
|
||||
|
||||
for _, arrayItem := range array {
|
||||
err := arrayItem.Null()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
length++
|
||||
}
|
||||
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
length++
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// Returns an error if the value is not actually null
|
||||
@@ -585,15 +523,12 @@ func (v *Value) Null() error {
|
||||
switch v.data.(type) {
|
||||
case nil:
|
||||
valid = v.exists // Valid only if j also exists, since other values could possibly also be nil
|
||||
break
|
||||
}
|
||||
|
||||
if valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotNull
|
||||
|
||||
}
|
||||
|
||||
// Attempts to typecast the current value into an array.
|
||||
@@ -607,24 +542,19 @@ func (v *Value) Array() ([]*Value, error) {
|
||||
switch v.data.(type) {
|
||||
case []interface{}:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
// Unsure if this is a good way to use slices, it's probably not
|
||||
var slice []*Value
|
||||
|
||||
if valid {
|
||||
|
||||
for _, element := range v.data.([]interface{}) {
|
||||
child := Value{element, true}
|
||||
slice = append(slice, &child)
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
return slice, ErrNotArray
|
||||
|
||||
}
|
||||
|
||||
// Attempts to typecast the current value into a number.
|
||||
@@ -638,7 +568,6 @@ func (v *Value) Number() (json.Number, error) {
|
||||
switch v.data.(type) {
|
||||
case json.Number:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
if valid {
|
||||
@@ -687,7 +616,6 @@ func (v *Value) Boolean() (bool, error) {
|
||||
switch v.data.(type) {
|
||||
case bool:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
if valid {
|
||||
@@ -709,7 +637,6 @@ func (v *Value) Object() (*Object, error) {
|
||||
switch v.data.(type) {
|
||||
case map[string]interface{}:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
if valid {
|
||||
@@ -746,7 +673,6 @@ func (v *Value) ObjectArray() ([]*Object, error) {
|
||||
switch v.data.(type) {
|
||||
case []interface{}:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
// Unsure if this is a good way to use slices, it's probably not
|
||||
@@ -782,7 +708,6 @@ func (v *Value) String() (string, error) {
|
||||
switch v.data.(type) {
|
||||
case string:
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
if valid {
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewAssert(t *testing.T) *Assert {
|
||||
}
|
||||
|
||||
func (assert *Assert) True(value bool, message string) {
|
||||
if value == false {
|
||||
if !value {
|
||||
log.Panicln("Assert: ", message)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ func TestFirst(t *testing.T) {
|
||||
}`
|
||||
|
||||
j, err := NewObjectFromBytes([]byte(testJSON))
|
||||
assert.True(err == nil, "failed to create new object from bytes")
|
||||
|
||||
a, err := j.GetObject("address")
|
||||
assert.True(a != nil && err == nil, "failed to create json from string")
|
||||
@@ -76,10 +77,10 @@ func TestFirst(t *testing.T) {
|
||||
assert.True(s == "fallback", "must get string return fallback")
|
||||
|
||||
s, err = j.GetString("name")
|
||||
assert.True(s == "anton" && err == nil, "name shoud match")
|
||||
assert.True(s == "anton" && err == nil, "name should match")
|
||||
|
||||
s, err = j.GetString("address", "street")
|
||||
assert.True(s == "Street 42" && err == nil, "street shoud match")
|
||||
assert.True(s == "Street 42" && err == nil, "street should match")
|
||||
//log.Println("s: ", s.String())
|
||||
|
||||
_, err = j.GetNumber("age")
|
||||
@@ -108,6 +109,7 @@ func TestFirst(t *testing.T) {
|
||||
//log.Println("address: ", address)
|
||||
|
||||
s, err = address.GetString("street")
|
||||
assert.True(s == "Street 42" && err == nil, "street mismatching")
|
||||
|
||||
addressAsString, err := j.GetString("address")
|
||||
assert.True(addressAsString == "" && err != nil, "address should not be an string")
|
||||
@@ -119,13 +121,13 @@ func TestFirst(t *testing.T) {
|
||||
assert.True(s == "" && err != nil, "nonexistent string fail")
|
||||
|
||||
b, err := j.GetBoolean("true")
|
||||
assert.True(b == true && err == nil, "bool true test")
|
||||
assert.True(b && err == nil, "bool true test")
|
||||
|
||||
b, err = j.GetBoolean("false")
|
||||
assert.True(b == false && err == nil, "bool false test")
|
||||
assert.True(!b && err == nil, "bool false test")
|
||||
|
||||
b, err = j.GetBoolean("invalid_field")
|
||||
assert.True(b == false && err != nil, "bool invalid test")
|
||||
assert.True(!b && err != nil, "bool invalid test")
|
||||
|
||||
list, err := j.GetValueArray("list")
|
||||
assert.True(list != nil && err == nil, "list should be an array")
|
||||
@@ -148,6 +150,7 @@ func TestFirst(t *testing.T) {
|
||||
//assert.True(element.IsObject() == true, "first fail")
|
||||
|
||||
element, err := elementValue.Object()
|
||||
assert.True(err == nil, "create element fail")
|
||||
|
||||
s, err = element.GetString("street")
|
||||
assert.True(s == "Street 42" && err == nil, "second fail")
|
||||
@@ -232,6 +235,7 @@ func TestSecond(t *testing.T) {
|
||||
assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
|
||||
|
||||
actions, err := dataItem.GetObjectArray("actions")
|
||||
assert.True(err == nil, "get object from array failed")
|
||||
|
||||
for index, action := range actions {
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func (az *AzureBlobUploader) Upload(ctx context.Context, imageDiskPath string) (
|
||||
}
|
||||
randomFileName := util.GetRandomString(30) + ".png"
|
||||
// upload image
|
||||
az.log.Debug("Uploading image to azure_blob", "conatiner_name", az.container_name, "blob_name", randomFileName)
|
||||
az.log.Debug("Uploading image to azure_blob", "container_name", az.container_name, "blob_name", randomFileName)
|
||||
resp, err := blob.FileUpload(az.container_name, randomFileName, file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -127,8 +127,6 @@ type xmlError struct {
|
||||
const ms_date_layout = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||
const version = "2017-04-17"
|
||||
|
||||
var client = &http.Client{}
|
||||
|
||||
type StorageClient struct {
|
||||
Auth *Auth
|
||||
Transport http.RoundTripper
|
||||
@@ -225,7 +223,7 @@ func (a *Auth) SignRequest(req *http.Request) {
|
||||
)
|
||||
decodedKey, _ := base64.StdEncoding.DecodeString(a.Key)
|
||||
|
||||
sha256 := hmac.New(sha256.New, []byte(decodedKey))
|
||||
sha256 := hmac.New(sha256.New, decodedKey)
|
||||
sha256.Write([]byte(strToSign))
|
||||
|
||||
signature := base64.StdEncoding.EncodeToString(sha256.Sum(nil))
|
||||
@@ -274,10 +272,10 @@ func (a *Auth) canonicalizedHeaders(req *http.Request) string {
|
||||
}
|
||||
}
|
||||
|
||||
splitted := strings.Split(buffer.String(), "\n")
|
||||
sort.Strings(splitted)
|
||||
split := strings.Split(buffer.String(), "\n")
|
||||
sort.Strings(split)
|
||||
|
||||
return strings.Join(splitted, "\n")
|
||||
return strings.Join(split, "\n")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -313,8 +311,8 @@ func (a *Auth) canonicalizedResource(req *http.Request) string {
|
||||
buffer.WriteString(fmt.Sprintf("\n%s:%s", key, strings.Join(values, ",")))
|
||||
}
|
||||
|
||||
splitted := strings.Split(buffer.String(), "\n")
|
||||
sort.Strings(splitted)
|
||||
split := strings.Split(buffer.String(), "\n")
|
||||
sort.Strings(split)
|
||||
|
||||
return strings.Join(splitted, "\n")
|
||||
return strings.Join(split, "\n")
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
|
||||
func TestUploadToAzureBlob(t *testing.T) {
|
||||
SkipConvey("[Integration test] for external_image_store.azure_blob", t, func() {
|
||||
err := setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
err := cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
uploader, _ := NewImageUploader()
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
|
||||
func TestUploadToGCS(t *testing.T) {
|
||||
SkipConvey("[Integration test] for external_image_store.gcs", t, func() {
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package imguploader
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"regexp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ func NewImageUploader() (ImageUploader, error) {
|
||||
|
||||
switch setting.ImageUploadProvider {
|
||||
case "s3":
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
s3sec, err := setting.Raw.GetSection("external_image_storage.s3")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func NewImageUploader() (ImageUploader, error) {
|
||||
|
||||
return NewS3Uploader(region, bucket, path, "public-read", accessKey, secretKey), nil
|
||||
case "webdav":
|
||||
webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
|
||||
webdavSec, err := setting.Raw.GetSection("external_image_storage.webdav")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,7 +68,7 @@ func NewImageUploader() (ImageUploader, error) {
|
||||
|
||||
return NewWebdavImageUploader(url, username, password, public_url)
|
||||
case "gcs":
|
||||
gcssec, err := setting.Cfg.GetSection("external_image_storage.gcs")
|
||||
gcssec, err := setting.Raw.GetSection("external_image_storage.gcs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,7 +79,7 @@ func NewImageUploader() (ImageUploader, error) {
|
||||
|
||||
return NewGCSUploader(keyFile, bucketName, path), nil
|
||||
case "azure_blob":
|
||||
azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob")
|
||||
azureBlobSec, err := setting.Raw.GetSection("external_image_storage.azure_blob")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@ import (
|
||||
func TestImageUploaderFactory(t *testing.T) {
|
||||
Convey("Can create image uploader for ", t, func() {
|
||||
Convey("S3ImageUploader config", func() {
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
setting.ImageUploadProvider = "s3"
|
||||
|
||||
Convey("with bucket url https://foo.bar.baz.s3-us-east-2.amazonaws.com", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
s3sec, err := setting.Raw.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://foo.bar.baz.s3-us-east-2.amazonaws.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
@@ -36,7 +38,8 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("with bucket url https://s3.amazonaws.com/mybucket", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
s3sec, err := setting.Raw.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://s3.amazonaws.com/my.bucket.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
@@ -54,16 +57,16 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("with bucket url https://s3-us-west-2.amazonaws.com/mybucket", func() {
|
||||
s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
|
||||
s3sec, err := setting.Raw.GetSection("external_image_storage.s3")
|
||||
So(err, ShouldBeNil)
|
||||
s3sec.NewKey("bucket_url", "https://s3-us-west-2.amazonaws.com/my.bucket.com")
|
||||
s3sec.NewKey("access_key", "access_key")
|
||||
s3sec.NewKey("secret_key", "secret_key")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*S3Uploader)
|
||||
|
||||
original, ok := uploader.(*S3Uploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.region, ShouldEqual, "us-west-2")
|
||||
So(original.bucket, ShouldEqual, "my.bucket.com")
|
||||
@@ -75,13 +78,15 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
Convey("Webdav uploader", func() {
|
||||
var err error
|
||||
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
setting.ImageUploadProvider = "webdav"
|
||||
|
||||
webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
|
||||
webdavSec, err := cfg.Raw.GetSection("external_image_storage.webdav")
|
||||
So(err, ShouldBeNil)
|
||||
webdavSec.NewKey("url", "webdavUrl")
|
||||
webdavSec.NewKey("username", "username")
|
||||
webdavSec.NewKey("password", "password")
|
||||
@@ -100,43 +105,45 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
Convey("GCS uploader", func() {
|
||||
var err error
|
||||
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
setting.ImageUploadProvider = "gcs"
|
||||
|
||||
gcpSec, err := setting.Cfg.GetSection("external_image_storage.gcs")
|
||||
gcpSec, err := cfg.Raw.GetSection("external_image_storage.gcs")
|
||||
So(err, ShouldBeNil)
|
||||
gcpSec.NewKey("key_file", "/etc/secrets/project-79a52befa3f6.json")
|
||||
gcpSec.NewKey("bucket", "project-grafana-east")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*GCSUploader)
|
||||
|
||||
original, ok := uploader.(*GCSUploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.keyFile, ShouldEqual, "/etc/secrets/project-79a52befa3f6.json")
|
||||
So(original.bucket, ShouldEqual, "project-grafana-east")
|
||||
})
|
||||
|
||||
Convey("AzureBlobUploader config", func() {
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
setting.ImageUploadProvider = "azure_blob"
|
||||
|
||||
Convey("with container name", func() {
|
||||
azureBlobSec, err := setting.Cfg.GetSection("external_image_storage.azure_blob")
|
||||
azureBlobSec, err := cfg.Raw.GetSection("external_image_storage.azure_blob")
|
||||
So(err, ShouldBeNil)
|
||||
azureBlobSec.NewKey("account_name", "account_name")
|
||||
azureBlobSec.NewKey("account_key", "account_key")
|
||||
azureBlobSec.NewKey("container_name", "container_name")
|
||||
|
||||
uploader, err := NewImageUploader()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
original, ok := uploader.(*AzureBlobUploader)
|
||||
|
||||
original, ok := uploader.(*AzureBlobUploader)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(original.account_name, ShouldEqual, "account_name")
|
||||
So(original.account_key, ShouldEqual, "account_key")
|
||||
@@ -147,7 +154,8 @@ func TestImageUploaderFactory(t *testing.T) {
|
||||
Convey("Local uploader", func() {
|
||||
var err error
|
||||
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@ package imguploader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
|
||||
"github.com/aws/aws-sdk-go/aws/defaults"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@@ -50,7 +53,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
|
||||
SecretAccessKey: u.secretKey,
|
||||
}},
|
||||
&credentials.EnvProvider{},
|
||||
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
|
||||
remoteCredProvider(sess),
|
||||
})
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(u.region),
|
||||
@@ -60,7 +63,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
|
||||
s3_endpoint, _ := endpoints.DefaultResolver().EndpointFor("s3", u.region)
|
||||
key := u.path + util.GetRandomString(20) + ".png"
|
||||
image_url := s3_endpoint.URL + "/" + u.bucket + "/" + key
|
||||
log.Debug("Uploading image to s3", "url = ", image_url)
|
||||
log.Debug("Uploading image to s3. url = %s", image_url)
|
||||
|
||||
file, err := os.Open(imageDiskPath)
|
||||
if err != nil {
|
||||
@@ -85,3 +88,27 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
|
||||
}
|
||||
return image_url, nil
|
||||
}
|
||||
|
||||
func remoteCredProvider(sess *session.Session) credentials.Provider {
|
||||
ecsCredURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
|
||||
|
||||
if len(ecsCredURI) > 0 {
|
||||
return ecsCredProvider(sess, ecsCredURI)
|
||||
}
|
||||
return ec2RoleProvider(sess)
|
||||
}
|
||||
|
||||
func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
|
||||
const host = `169.254.170.2`
|
||||
|
||||
d := defaults.Get()
|
||||
return endpointcreds.NewProviderClient(
|
||||
*d.Config,
|
||||
d.Handlers,
|
||||
fmt.Sprintf("http://%s%s", host, uri),
|
||||
func(p *endpointcreds.Provider) { p.ExpiryWindow = 5 * time.Minute })
|
||||
}
|
||||
|
||||
func ec2RoleProvider(sess *session.Session) credentials.Provider {
|
||||
return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
|
||||
func TestUploadToS3(t *testing.T) {
|
||||
SkipConvey("[Integration test] for external_image_store.s3", t, func() {
|
||||
setting.NewConfigContext(&setting.CommandLineArgs{
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Load(&setting.CommandLineArgs{
|
||||
HomePath: "../../../",
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -35,20 +36,36 @@ var netClient = &http.Client{
|
||||
Transport: netTransport,
|
||||
}
|
||||
|
||||
func (u *WebdavUploader) PublicURL(filename string) string {
|
||||
if strings.Contains(u.public_url, "${file}") {
|
||||
return strings.Replace(u.public_url, "${file}", filename, -1)
|
||||
} else {
|
||||
publicURL, _ := url.Parse(u.public_url)
|
||||
publicURL.Path = path.Join(publicURL.Path, filename)
|
||||
return publicURL.String()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error) {
|
||||
url, _ := url.Parse(u.url)
|
||||
filename := util.GetRandomString(20) + ".png"
|
||||
url.Path = path.Join(url.Path, filename)
|
||||
|
||||
imgData, err := ioutil.ReadFile(pa)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if u.username != "" {
|
||||
req.SetBasicAuth(u.username, u.password)
|
||||
}
|
||||
|
||||
res, err := netClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -59,9 +76,7 @@ func (u *WebdavUploader) Upload(ctx context.Context, pa string) (string, error)
|
||||
}
|
||||
|
||||
if u.public_url != "" {
|
||||
publicURL, _ := url.Parse(u.public_url)
|
||||
publicURL.Path = path.Join(publicURL.Path, filename)
|
||||
return publicURL.String(), nil
|
||||
return u.PublicURL(filename), nil
|
||||
}
|
||||
|
||||
return url.String(), nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package imguploader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
@@ -26,3 +27,15 @@ func TestUploadToWebdav(t *testing.T) {
|
||||
So(path, ShouldStartWith, "http://publicurl:8888/webdav/")
|
||||
})
|
||||
}
|
||||
|
||||
func TestPublicURL(t *testing.T) {
|
||||
Convey("Given a public URL with parameters, and no template", t, func() {
|
||||
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files=")
|
||||
parsed, _ := url.Parse(webdavUploader.PublicURL("fileyfile.png"))
|
||||
So(parsed.Path, ShouldEndWith, "fileyfile.png")
|
||||
})
|
||||
Convey("Given a public URL with parameters, and a template", t, func() {
|
||||
webdavUploader, _ := NewWebdavImageUploader("http://localhost:8888/webdav/", "test", "test", "http://cloudycloud.me/s/DOIFDOMV/download?files=${file}")
|
||||
So(webdavUploader.PublicURL("fileyfile.png"), ShouldEndWith, "fileyfile.png")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
nullString = "null"
|
||||
)
|
||||
|
||||
// Float is a nullable float64.
|
||||
// It does not consider zero values to be null.
|
||||
// It will decode to null, not zero, if null.
|
||||
@@ -50,7 +54,7 @@ func (f *Float) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
f.Float64 = float64(x)
|
||||
f.Float64 = x
|
||||
case map[string]interface{}:
|
||||
err = json.Unmarshal(data, &f.NullFloat64)
|
||||
case nil:
|
||||
@@ -68,7 +72,7 @@ func (f *Float) UnmarshalJSON(data []byte) error {
|
||||
// It will return an error if the input is not an integer, blank, or "null".
|
||||
func (f *Float) UnmarshalText(text []byte) error {
|
||||
str := string(text)
|
||||
if str == "" || str == "null" {
|
||||
if str == "" || str == nullString {
|
||||
f.Valid = false
|
||||
return nil
|
||||
}
|
||||
@@ -82,7 +86,7 @@ func (f *Float) UnmarshalText(text []byte) error {
|
||||
// It will encode null if this Float is null.
|
||||
func (f Float) MarshalJSON() ([]byte, error) {
|
||||
if !f.Valid {
|
||||
return []byte("null"), nil
|
||||
return []byte(nullString), nil
|
||||
}
|
||||
return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
|
||||
}
|
||||
@@ -100,12 +104,21 @@ func (f Float) MarshalText() ([]byte, error) {
|
||||
// It will encode a blank string if this Float is null.
|
||||
func (f Float) String() string {
|
||||
if !f.Valid {
|
||||
return "null"
|
||||
return nullString
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%1.3f", f.Float64)
|
||||
}
|
||||
|
||||
// FullString returns float as string in full precision
|
||||
func (f Float) FullString() string {
|
||||
if !f.Valid {
|
||||
return nullString
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%f", f.Float64)
|
||||
}
|
||||
|
||||
// SetValid changes this Float's value and also sets it to be non-null.
|
||||
func (f *Float) SetValid(n float64) {
|
||||
f.Float64 = n
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"strconv"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type RenderOpts struct {
|
||||
Path string
|
||||
Width string
|
||||
Height string
|
||||
Timeout string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
OrgRole models.RoleType
|
||||
Timezone string
|
||||
IsAlertContext bool
|
||||
Encoding string
|
||||
}
|
||||
|
||||
var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
|
||||
var rendererLog log.Logger = log.New("png-renderer")
|
||||
|
||||
func isoTimeOffsetToPosixTz(isoOffset string) string {
|
||||
// invert offset
|
||||
if strings.HasPrefix(isoOffset, "UTC+") {
|
||||
return strings.Replace(isoOffset, "UTC+", "UTC-", 1)
|
||||
}
|
||||
if strings.HasPrefix(isoOffset, "UTC-") {
|
||||
return strings.Replace(isoOffset, "UTC-", "UTC+", 1)
|
||||
}
|
||||
return isoOffset
|
||||
}
|
||||
|
||||
func appendEnviron(baseEnviron []string, name string, value string) []string {
|
||||
results := make([]string, 0)
|
||||
prefix := fmt.Sprintf("%s=", name)
|
||||
for _, v := range baseEnviron {
|
||||
if !strings.HasPrefix(v, prefix) {
|
||||
results = append(results, v)
|
||||
}
|
||||
}
|
||||
return append(results, fmt.Sprintf("%s=%s", name, value))
|
||||
}
|
||||
|
||||
func RenderToPng(params *RenderOpts) (string, error) {
|
||||
rendererLog.Info("Rendering", "path", params.Path)
|
||||
|
||||
var executable = "phantomjs"
|
||||
if runtime.GOOS == "windows" {
|
||||
executable = executable + ".exe"
|
||||
}
|
||||
|
||||
localDomain := "localhost"
|
||||
if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
|
||||
localDomain = setting.HttpAddr
|
||||
}
|
||||
|
||||
// &render=1 signals to the legacy redirect layer to
|
||||
// avoid redirect these requests.
|
||||
url := fmt.Sprintf("%s://%s:%s/%s&render=1", setting.Protocol, localDomain, setting.HttpPort, params.Path)
|
||||
|
||||
binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
|
||||
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
|
||||
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
|
||||
pngPath = pngPath + ".png"
|
||||
|
||||
orgRole := params.OrgRole
|
||||
if params.IsAlertContext {
|
||||
orgRole = models.ROLE_ADMIN
|
||||
}
|
||||
renderKey := middleware.AddRenderAuthKey(params.OrgId, params.UserId, orgRole)
|
||||
defer middleware.RemoveRenderAuthKey(renderKey)
|
||||
|
||||
timeout, err := strconv.Atoi(params.Timeout)
|
||||
if err != nil {
|
||||
timeout = 15
|
||||
}
|
||||
|
||||
phantomDebugArg := "--debug=false"
|
||||
if log.GetLogLevelFor("png-renderer") >= log.LvlDebug {
|
||||
phantomDebugArg = "--debug=true"
|
||||
}
|
||||
|
||||
cmdArgs := []string{
|
||||
"--ignore-ssl-errors=true",
|
||||
"--web-security=false",
|
||||
phantomDebugArg,
|
||||
scriptPath,
|
||||
"url=" + url,
|
||||
"width=" + params.Width,
|
||||
"height=" + params.Height,
|
||||
"png=" + pngPath,
|
||||
"domain=" + localDomain,
|
||||
"timeout=" + strconv.Itoa(timeout),
|
||||
"renderKey=" + renderKey,
|
||||
}
|
||||
|
||||
if params.Encoding != "" {
|
||||
cmdArgs = append([]string{fmt.Sprintf("--output-encoding=%s", params.Encoding)}, cmdArgs...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(binPath, cmdArgs...)
|
||||
output, err := cmd.StdoutPipe()
|
||||
|
||||
if err != nil {
|
||||
rendererLog.Error("Could not acquire stdout pipe", err)
|
||||
return "", err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if params.Timezone != "" {
|
||||
baseEnviron := os.Environ()
|
||||
cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(params.Timezone))
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
rendererLog.Error("Could not start command", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
logWriter := log.NewLogWriter(rendererLog, log.LvlDebug, "[phantom] ")
|
||||
go io.Copy(logWriter, output)
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
rendererLog.Error("failed to render an image", "error", err)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
rendererLog.Error("failed to kill", "error", err)
|
||||
}
|
||||
return "", ErrTimeout
|
||||
case <-done:
|
||||
}
|
||||
|
||||
rendererLog.Debug("Image rendered", "path", pngPath)
|
||||
return pngPath, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package renderer
|
||||
|
||||
//
|
||||
// import (
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "testing"
|
||||
//
|
||||
// . "github.com/smartystreets/goconvey/convey"
|
||||
// )
|
||||
//
|
||||
// func TestPhantomRender(t *testing.T) {
|
||||
//
|
||||
// Convey("Can render url", t, func() {
|
||||
// tempDir, _ := ioutil.TempDir("", "img")
|
||||
// ipng, err := RenderToPng("http://www.google.com")
|
||||
// So(err, ShouldBeNil)
|
||||
// So(exists(png), ShouldEqual, true)
|
||||
//
|
||||
// //_, err = os.Stat(store.getFilePathForDashboard("hello"))
|
||||
// //So(err, ShouldBeNil)
|
||||
// })
|
||||
//
|
||||
// }
|
||||
//
|
||||
// func exists(path string) bool {
|
||||
// _, err := os.Stat(path)
|
||||
// if err == nil {
|
||||
// return true
|
||||
// }
|
||||
// if os.IsNotExist(err) {
|
||||
// return false
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
@@ -256,7 +256,7 @@ func (j *Json) StringArray() ([]string, error) {
|
||||
|
||||
// MustArray guarantees the return of a `[]interface{}` (with optional default)
|
||||
//
|
||||
// useful when you want to interate over array values in a succinct manner:
|
||||
// useful when you want to iterate over array values in a succinct manner:
|
||||
// for i, v := range js.Get("results").MustArray() {
|
||||
// fmt.Println(i, v)
|
||||
// }
|
||||
@@ -281,7 +281,7 @@ func (j *Json) MustArray(args ...[]interface{}) []interface{} {
|
||||
|
||||
// MustMap guarantees the return of a `map[string]interface{}` (with optional default)
|
||||
//
|
||||
// useful when you want to interate over map values in a succinct manner:
|
||||
// useful when you want to iterate over map values in a succinct manner:
|
||||
// for k, v := range js.Get("dictionary").MustMap() {
|
||||
// fmt.Println(k, v)
|
||||
// }
|
||||
@@ -329,7 +329,7 @@ func (j *Json) MustString(args ...string) string {
|
||||
|
||||
// MustStringArray guarantees the return of a `[]string` (with optional default)
|
||||
//
|
||||
// useful when you want to interate over array values in a succinct manner:
|
||||
// useful when you want to iterate over array values in a succinct manner:
|
||||
// for i, s := range js.Get("results").MustStringArray() {
|
||||
// fmt.Println(i, s)
|
||||
// }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user