mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 08:02:25 +08:00

* backend/sqlstore split: move dashboard snapshot funcs to dashboardsnapshotservice This commit moves the dashboard snapshot related sql functions in the dashboardsnapshots service. I split the dashboards package up so the interfaces live in dashboarsnapshots and the store and service implementations are in their own packages. This took some minor refactoring, but none of the actual underlying code has changed, just where it lives.
338 lines
9.4 KiB
Go
338 lines
9.4 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/guardian"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
var client = &http.Client{
|
|
Timeout: time.Second * 5,
|
|
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
|
}
|
|
|
|
func GetSharingOptions(c *models.ReqContext) {
|
|
c.JSON(http.StatusOK, util.DynMap{
|
|
"externalSnapshotURL": setting.ExternalSnapshotUrl,
|
|
"externalSnapshotName": setting.ExternalSnapshotName,
|
|
"externalEnabled": setting.ExternalEnabled,
|
|
})
|
|
}
|
|
|
|
type CreateExternalSnapshotResponse struct {
|
|
Key string `json:"key"`
|
|
DeleteKey string `json:"deleteKey"`
|
|
Url string `json:"url"`
|
|
DeleteUrl string `json:"deleteUrl"`
|
|
}
|
|
|
|
func createExternalDashboardSnapshot(cmd models.CreateDashboardSnapshotCommand) (*CreateExternalSnapshotResponse, error) {
|
|
var createSnapshotResponse CreateExternalSnapshotResponse
|
|
message := map[string]interface{}{
|
|
"name": cmd.Name,
|
|
"expires": cmd.Expires,
|
|
"dashboard": cmd.Dashboard,
|
|
"key": cmd.Key,
|
|
"deleteKey": cmd.DeleteKey,
|
|
}
|
|
|
|
messageBytes, err := simplejson.NewFromAny(message).Encode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response, err := client.Post(setting.ExternalSnapshotUrl+"/api/snapshots", "application/json", bytes.NewBuffer(messageBytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err := response.Body.Close(); err != nil {
|
|
plog.Warn("Failed to close response body", "err", err)
|
|
}
|
|
}()
|
|
|
|
if response.StatusCode != 200 {
|
|
return nil, fmt.Errorf("create external snapshot response status code %d", response.StatusCode)
|
|
}
|
|
|
|
if err := json.NewDecoder(response.Body).Decode(&createSnapshotResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &createSnapshotResponse, nil
|
|
}
|
|
|
|
// POST /api/snapshots
|
|
func (hs *HTTPServer) CreateDashboardSnapshot(c *models.ReqContext) response.Response {
|
|
cmd := models.CreateDashboardSnapshotCommand{}
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
if cmd.Name == "" {
|
|
cmd.Name = "Unnamed snapshot"
|
|
}
|
|
|
|
var url string
|
|
cmd.ExternalUrl = ""
|
|
cmd.OrgId = c.OrgId
|
|
cmd.UserId = c.UserId
|
|
|
|
if cmd.External {
|
|
if !setting.ExternalEnabled {
|
|
c.JsonApiErr(403, "External dashboard creation is disabled", nil)
|
|
return nil
|
|
}
|
|
|
|
response, err := createExternalDashboardSnapshot(cmd)
|
|
if err != nil {
|
|
c.JsonApiErr(500, "Failed to create external snapshot", err)
|
|
return nil
|
|
}
|
|
|
|
url = response.Url
|
|
cmd.Key = response.Key
|
|
cmd.DeleteKey = response.DeleteKey
|
|
cmd.ExternalUrl = response.Url
|
|
cmd.ExternalDeleteUrl = response.DeleteUrl
|
|
cmd.Dashboard = simplejson.New()
|
|
|
|
metrics.MApiDashboardSnapshotExternal.Inc()
|
|
} else {
|
|
if cmd.Key == "" {
|
|
var err error
|
|
cmd.Key, err = util.GetRandomString(32)
|
|
if err != nil {
|
|
c.JsonApiErr(500, "Could not generate random string", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if cmd.DeleteKey == "" {
|
|
var err error
|
|
cmd.DeleteKey, err = util.GetRandomString(32)
|
|
if err != nil {
|
|
c.JsonApiErr(500, "Could not generate random string", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
url = setting.ToAbsUrl("dashboard/snapshot/" + cmd.Key)
|
|
|
|
metrics.MApiDashboardSnapshotCreate.Inc()
|
|
}
|
|
|
|
if err := hs.dashboardsnapshotsService.CreateDashboardSnapshot(c.Req.Context(), &cmd); err != nil {
|
|
c.JsonApiErr(500, "Failed to create snapshot", err)
|
|
return nil
|
|
}
|
|
|
|
c.JSON(http.StatusOK, util.DynMap{
|
|
"key": cmd.Key,
|
|
"deleteKey": cmd.DeleteKey,
|
|
"url": url,
|
|
"deleteUrl": setting.ToAbsUrl("api/snapshots-delete/" + cmd.DeleteKey),
|
|
"id": cmd.Result.Id,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// GET /api/snapshots/:key
|
|
func (hs *HTTPServer) GetDashboardSnapshot(c *models.ReqContext) response.Response {
|
|
key := web.Params(c.Req)[":key"]
|
|
if len(key) == 0 {
|
|
return response.Error(404, "Snapshot not found", nil)
|
|
}
|
|
|
|
query := &models.GetDashboardSnapshotQuery{Key: key}
|
|
|
|
err := hs.dashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get dashboard snapshot", err)
|
|
}
|
|
|
|
snapshot := query.Result
|
|
|
|
// expired snapshots should also be removed from db
|
|
if snapshot.Expires.Before(time.Now()) {
|
|
return response.Error(404, "Dashboard snapshot not found", err)
|
|
}
|
|
|
|
dto := dtos.DashboardFullWithMeta{
|
|
Dashboard: snapshot.Dashboard,
|
|
Meta: dtos.DashboardMeta{
|
|
Type: models.DashTypeSnapshot,
|
|
IsSnapshot: true,
|
|
Created: snapshot.Created,
|
|
Expires: snapshot.Expires,
|
|
},
|
|
}
|
|
|
|
metrics.MApiDashboardSnapshotGet.Inc()
|
|
|
|
return response.JSON(http.StatusOK, dto).SetHeader("Cache-Control", "public, max-age=3600")
|
|
}
|
|
|
|
func deleteExternalDashboardSnapshot(externalUrl string) error {
|
|
response, err := client.Get(externalUrl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err := response.Body.Close(); err != nil {
|
|
plog.Warn("Failed to close response body", "err", err)
|
|
}
|
|
}()
|
|
|
|
if response.StatusCode == 200 {
|
|
return nil
|
|
}
|
|
|
|
// Gracefully ignore "snapshot not found" errors as they could have already
|
|
// been removed either via the cleanup script or by request.
|
|
if response.StatusCode == 500 {
|
|
var respJson map[string]interface{}
|
|
if err := json.NewDecoder(response.Body).Decode(&respJson); err != nil {
|
|
return err
|
|
}
|
|
|
|
if respJson["message"] == "Failed to get dashboard snapshot" {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("unexpected response when deleting external snapshot, status code: %d", response.StatusCode)
|
|
}
|
|
|
|
// GET /api/snapshots-delete/:deleteKey
|
|
func (hs *HTTPServer) DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) response.Response {
|
|
key := web.Params(c.Req)[":deleteKey"]
|
|
if len(key) == 0 {
|
|
return response.Error(404, "Snapshot not found", nil)
|
|
}
|
|
|
|
query := &models.GetDashboardSnapshotQuery{DeleteKey: key}
|
|
err := hs.dashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get dashboard snapshot", err)
|
|
}
|
|
|
|
if query.Result.External {
|
|
err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to delete external dashboard", err)
|
|
}
|
|
}
|
|
|
|
cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
|
|
|
if err := hs.dashboardsnapshotsService.DeleteDashboardSnapshot(c.Req.Context(), cmd); err != nil {
|
|
return response.Error(500, "Failed to delete dashboard snapshot", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, util.DynMap{
|
|
"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
|
|
"id": query.Result.Id,
|
|
})
|
|
}
|
|
|
|
// DELETE /api/snapshots/:key
|
|
func (hs *HTTPServer) DeleteDashboardSnapshot(c *models.ReqContext) response.Response {
|
|
key := web.Params(c.Req)[":key"]
|
|
if len(key) == 0 {
|
|
return response.Error(404, "Snapshot not found", nil)
|
|
}
|
|
|
|
query := &models.GetDashboardSnapshotQuery{Key: key}
|
|
|
|
err := hs.dashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get dashboard snapshot", err)
|
|
}
|
|
if query.Result == nil {
|
|
return response.Error(404, "Failed to get dashboard snapshot", nil)
|
|
}
|
|
|
|
dashboardID := query.Result.Dashboard.Get("id").MustInt64()
|
|
|
|
guardian := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser)
|
|
canEdit, err := guardian.CanEdit()
|
|
if err != nil {
|
|
return response.Error(500, "Error while checking permissions for snapshot", err)
|
|
}
|
|
|
|
if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
|
|
return response.Error(403, "Access denied to this snapshot", nil)
|
|
}
|
|
|
|
if query.Result.External {
|
|
err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to delete external dashboard", err)
|
|
}
|
|
}
|
|
|
|
cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
|
|
|
if err := hs.dashboardsnapshotsService.DeleteDashboardSnapshot(c.Req.Context(), cmd); err != nil {
|
|
return response.Error(500, "Failed to delete dashboard snapshot", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, util.DynMap{
|
|
"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
|
|
"id": query.Result.Id,
|
|
})
|
|
}
|
|
|
|
// GET /api/dashboard/snapshots
|
|
func (hs *HTTPServer) SearchDashboardSnapshots(c *models.ReqContext) response.Response {
|
|
query := c.Query("query")
|
|
limit := c.QueryInt("limit")
|
|
|
|
if limit == 0 {
|
|
limit = 1000
|
|
}
|
|
|
|
searchQuery := models.GetDashboardSnapshotsQuery{
|
|
Name: query,
|
|
Limit: limit,
|
|
OrgId: c.OrgId,
|
|
SignedInUser: c.SignedInUser,
|
|
}
|
|
|
|
err := hs.dashboardsnapshotsService.SearchDashboardSnapshots(c.Req.Context(), &searchQuery)
|
|
if err != nil {
|
|
return response.Error(500, "Search failed", err)
|
|
}
|
|
|
|
dtos := make([]*models.DashboardSnapshotDTO, len(searchQuery.Result))
|
|
for i, snapshot := range searchQuery.Result {
|
|
dtos[i] = &models.DashboardSnapshotDTO{
|
|
Id: snapshot.Id,
|
|
Name: snapshot.Name,
|
|
Key: snapshot.Key,
|
|
OrgId: snapshot.OrgId,
|
|
UserId: snapshot.UserId,
|
|
External: snapshot.External,
|
|
ExternalUrl: snapshot.ExternalUrl,
|
|
Expires: snapshot.Expires,
|
|
Created: snapshot.Created,
|
|
Updated: snapshot.Updated,
|
|
}
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos)
|
|
}
|