mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			448 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
| package models
 | |
| 
 | |
| import (
 | |
| 	"encoding/base64"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gosimple/slug"
 | |
| 	"github.com/grafana/grafana/pkg/components/simplejson"
 | |
| 	"github.com/grafana/grafana/pkg/setting"
 | |
| 	"github.com/grafana/grafana/pkg/util"
 | |
| )
 | |
| 
 | |
| const RootFolderName = "General"
 | |
| 
 | |
| // Typed errors
 | |
| var (
 | |
| 	ErrDashboardNotFound = DashboardErr{
 | |
| 		Reason:     "Dashboard not found",
 | |
| 		StatusCode: 404,
 | |
| 		Status:     "not-found",
 | |
| 	}
 | |
| 	ErrDashboardFolderNotFound = DashboardErr{
 | |
| 		Reason:     "Folder not found",
 | |
| 		StatusCode: 404,
 | |
| 	}
 | |
| 	ErrDashboardSnapshotNotFound = DashboardErr{
 | |
| 		Reason:     "Dashboard snapshot not found",
 | |
| 		StatusCode: 404,
 | |
| 	}
 | |
| 	ErrDashboardWithSameUIDExists = DashboardErr{
 | |
| 		Reason:     "A dashboard with the same uid already exists",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardWithSameNameInFolderExists = DashboardErr{
 | |
| 		Reason:     "A dashboard with the same name in the folder already exists",
 | |
| 		StatusCode: 412,
 | |
| 		Status:     "name-exists",
 | |
| 	}
 | |
| 	ErrDashboardVersionMismatch = DashboardErr{
 | |
| 		Reason:     "The dashboard has been changed by someone else",
 | |
| 		StatusCode: 412,
 | |
| 		Status:     "version-mismatch",
 | |
| 	}
 | |
| 	ErrDashboardTitleEmpty = DashboardErr{
 | |
| 		Reason:     "Dashboard title cannot be empty",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardFolderCannotHaveParent = DashboardErr{
 | |
| 		Reason:     "A Dashboard Folder cannot be added to another folder",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardsWithSameSlugExists = DashboardErr{
 | |
| 		Reason:     "Multiple dashboards with the same slug exists",
 | |
| 		StatusCode: 412,
 | |
| 	}
 | |
| 	ErrDashboardFailedGenerateUniqueUid = DashboardErr{
 | |
| 		Reason:     "Failed to generate unique dashboard id",
 | |
| 		StatusCode: 500,
 | |
| 	}
 | |
| 	ErrDashboardTypeMismatch = DashboardErr{
 | |
| 		Reason:     "Dashboard cannot be changed to a folder",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardFolderWithSameNameAsDashboard = DashboardErr{
 | |
| 		Reason:     "Folder name cannot be the same as one of its dashboards",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardWithSameNameAsFolder = DashboardErr{
 | |
| 		Reason:     "Dashboard name cannot be the same as folder",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardFolderNameExists = DashboardErr{
 | |
| 		Reason:     "A folder with that name already exists",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardUpdateAccessDenied = DashboardErr{
 | |
| 		Reason:     "Access denied to save dashboard",
 | |
| 		StatusCode: 403,
 | |
| 	}
 | |
| 	ErrDashboardInvalidUid = DashboardErr{
 | |
| 		Reason:     "uid contains illegal characters",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardUidTooLong = DashboardErr{
 | |
| 		Reason:     "uid too long, max 40 characters",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{
 | |
| 		Reason:     "Cannot save provisioned dashboard",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardRefreshIntervalTooShort = DashboardErr{
 | |
| 		Reason:     "Dashboard refresh interval is too low",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{
 | |
| 		Reason:     "provisioned dashboard cannot be deleted",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| 	ErrDashboardIdentifierNotSet = DashboardErr{
 | |
| 		Reason:     "Unique identifier needed to be able to get a dashboard",
 | |
| 		StatusCode: 400,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // DashboardErr represents a dashboard error.
 | |
| type DashboardErr struct {
 | |
| 	StatusCode int
 | |
| 	Status     string
 | |
| 	Reason     string
 | |
| }
 | |
| 
 | |
| // Equal returns whether equal to another DashboardErr.
 | |
| func (e DashboardErr) Equal(o DashboardErr) bool {
 | |
| 	return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason
 | |
| }
 | |
| 
 | |
| // Error returns the error message.
 | |
| func (e DashboardErr) Error() string {
 | |
| 	if e.Reason != "" {
 | |
| 		return e.Reason
 | |
| 	}
 | |
| 	return "Dashboard Error"
 | |
| }
 | |
| 
 | |
| // Body returns the error's response body, if applicable.
 | |
| func (e DashboardErr) Body() util.DynMap {
 | |
| 	if e.Status == "" {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return util.DynMap{"status": e.Status, "message": e.Error()}
 | |
| }
 | |
| 
 | |
| type UpdatePluginDashboardError struct {
 | |
| 	PluginId string
 | |
| }
 | |
| 
 | |
| func (d UpdatePluginDashboardError) Error() string {
 | |
| 	return "Dashboard belongs to plugin"
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	DashTypeDB       = "db"
 | |
| 	DashTypeSnapshot = "snapshot"
 | |
| )
 | |
| 
 | |
| // Dashboard model
 | |
| type Dashboard struct {
 | |
| 	Id       int64
 | |
| 	Uid      string
 | |
| 	Slug     string
 | |
| 	OrgId    int64
 | |
| 	GnetId   int64
 | |
| 	Version  int
 | |
| 	PluginId string
 | |
| 
 | |
| 	Created time.Time
 | |
| 	Updated time.Time
 | |
| 
 | |
| 	UpdatedBy int64
 | |
| 	CreatedBy int64
 | |
| 	FolderId  int64
 | |
| 	IsFolder  bool
 | |
| 	HasAcl    bool
 | |
| 
 | |
| 	Title string
 | |
| 	Data  *simplejson.Json
 | |
| }
 | |
| 
 | |
| func (d *Dashboard) SetId(id int64) {
 | |
| 	d.Id = id
 | |
| 	d.Data.Set("id", id)
 | |
| }
 | |
| 
 | |
| func (d *Dashboard) SetUid(uid string) {
 | |
| 	d.Uid = uid
 | |
| 	d.Data.Set("uid", uid)
 | |
| }
 | |
| 
 | |
| func (d *Dashboard) SetVersion(version int) {
 | |
| 	d.Version = version
 | |
| 	d.Data.Set("version", version)
 | |
| }
 | |
| 
 | |
| // GetDashboardIdForSavePermissionCheck return the dashboard id to be used for checking permission of dashboard
 | |
| func (d *Dashboard) GetDashboardIdForSavePermissionCheck() int64 {
 | |
| 	if d.Id == 0 {
 | |
| 		return d.FolderId
 | |
| 	}
 | |
| 
 | |
| 	return d.Id
 | |
| }
 | |
| 
 | |
| // NewDashboard creates a new dashboard
 | |
| func NewDashboard(title string) *Dashboard {
 | |
| 	dash := &Dashboard{}
 | |
| 	dash.Data = simplejson.New()
 | |
| 	dash.Data.Set("title", title)
 | |
| 	dash.Title = title
 | |
| 	dash.Created = time.Now()
 | |
| 	dash.Updated = time.Now()
 | |
| 	dash.UpdateSlug()
 | |
| 	return dash
 | |
| }
 | |
| 
 | |
| // NewDashboardFolder creates a new dashboard folder
 | |
| func NewDashboardFolder(title string) *Dashboard {
 | |
| 	folder := NewDashboard(title)
 | |
| 	folder.IsFolder = true
 | |
| 	folder.Data.Set("schemaVersion", 17)
 | |
| 	folder.Data.Set("version", 0)
 | |
| 	folder.IsFolder = true
 | |
| 	return folder
 | |
| }
 | |
| 
 | |
| // GetTags turns the tags in data json into go string array
 | |
| func (d *Dashboard) GetTags() []string {
 | |
| 	return d.Data.Get("tags").MustStringArray()
 | |
| }
 | |
| 
 | |
| func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
 | |
| 	dash := &Dashboard{}
 | |
| 	dash.Data = data
 | |
| 	dash.Title = dash.Data.Get("title").MustString()
 | |
| 	dash.UpdateSlug()
 | |
| 	update := false
 | |
| 
 | |
| 	if id, err := dash.Data.Get("id").Float64(); err == nil {
 | |
| 		dash.Id = int64(id)
 | |
| 		update = true
 | |
| 	}
 | |
| 
 | |
| 	if uid, err := dash.Data.Get("uid").String(); err == nil {
 | |
| 		dash.Uid = uid
 | |
| 		update = true
 | |
| 	}
 | |
| 
 | |
| 	if version, err := dash.Data.Get("version").Float64(); err == nil && update {
 | |
| 		dash.Version = int(version)
 | |
| 		dash.Updated = time.Now()
 | |
| 	} else {
 | |
| 		dash.Data.Set("version", 0)
 | |
| 		dash.Created = time.Now()
 | |
| 		dash.Updated = time.Now()
 | |
| 	}
 | |
| 
 | |
| 	if gnetId, err := dash.Data.Get("gnetId").Float64(); err == nil {
 | |
| 		dash.GnetId = int64(gnetId)
 | |
| 	}
 | |
| 
 | |
| 	return dash
 | |
| }
 | |
| 
 | |
| // GetDashboardModel turns the command into the saveable model
 | |
| func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
 | |
| 	dash := NewDashboardFromJson(cmd.Dashboard)
 | |
| 	userId := cmd.UserId
 | |
| 
 | |
| 	if userId == 0 {
 | |
| 		userId = -1
 | |
| 	}
 | |
| 
 | |
| 	dash.UpdatedBy = userId
 | |
| 	dash.OrgId = cmd.OrgId
 | |
| 	dash.PluginId = cmd.PluginId
 | |
| 	dash.IsFolder = cmd.IsFolder
 | |
| 	dash.FolderId = cmd.FolderId
 | |
| 	dash.UpdateSlug()
 | |
| 	return dash
 | |
| }
 | |
| 
 | |
| // UpdateSlug updates the slug
 | |
| func (d *Dashboard) UpdateSlug() {
 | |
| 	title := d.Data.Get("title").MustString()
 | |
| 	d.Slug = SlugifyTitle(title)
 | |
| }
 | |
| 
 | |
| func SlugifyTitle(title string) string {
 | |
| 	s := slug.Make(strings.ToLower(title))
 | |
| 	if s == "" {
 | |
| 		// If the dashboard name is only characters outside of the
 | |
| 		// sluggable characters, the slug creation will return an
 | |
| 		// empty string which will mess up URLs. This failsafe picks
 | |
| 		// that up and creates the slug as a base64 identifier instead.
 | |
| 		s = base64.RawURLEncoding.EncodeToString([]byte(title))
 | |
| 		if slug.MaxLength != 0 && len(s) > slug.MaxLength {
 | |
| 			s = s[:slug.MaxLength]
 | |
| 		}
 | |
| 	}
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| // GetUrl return the html url for a folder if it's folder, otherwise for a dashboard
 | |
| func (d *Dashboard) GetUrl() string {
 | |
| 	return GetDashboardFolderUrl(d.IsFolder, d.Uid, d.Slug)
 | |
| }
 | |
| 
 | |
| // GetDashboardFolderUrl return the html url for a folder if it's folder, otherwise for a dashboard
 | |
| func GetDashboardFolderUrl(isFolder bool, uid string, slug string) string {
 | |
| 	if isFolder {
 | |
| 		return GetFolderUrl(uid, slug)
 | |
| 	}
 | |
| 
 | |
| 	return GetDashboardUrl(uid, slug)
 | |
| }
 | |
| 
 | |
| // GetDashboardUrl returns the HTML url for a dashboard.
 | |
| func GetDashboardUrl(uid string, slug string) string {
 | |
| 	return fmt.Sprintf("%s/d/%s/%s", setting.AppSubUrl, uid, slug)
 | |
| }
 | |
| 
 | |
| // GetFullDashboardUrl returns the full URL for a dashboard.
 | |
| func GetFullDashboardUrl(uid string, slug string) string {
 | |
| 	return fmt.Sprintf("%sd/%s/%s", setting.AppUrl, uid, slug)
 | |
| }
 | |
| 
 | |
| // GetFolderUrl returns the HTML url for a folder.
 | |
| func GetFolderUrl(folderUid string, slug string) string {
 | |
| 	return fmt.Sprintf("%s/dashboards/f/%s/%s", setting.AppSubUrl, folderUid, slug)
 | |
| }
 | |
| 
 | |
| type ValidateDashboardBeforeSaveResult struct {
 | |
| 	IsParentFolderChanged bool
 | |
| }
 | |
| 
 | |
| //
 | |
| // COMMANDS
 | |
| //
 | |
| 
 | |
| type SaveDashboardCommand struct {
 | |
| 	Dashboard    *simplejson.Json `json:"dashboard" binding:"Required"`
 | |
| 	UserId       int64            `json:"userId"`
 | |
| 	Overwrite    bool             `json:"overwrite"`
 | |
| 	Message      string           `json:"message"`
 | |
| 	OrgId        int64            `json:"-"`
 | |
| 	RestoredFrom int              `json:"-"`
 | |
| 	PluginId     string           `json:"-"`
 | |
| 	FolderId     int64            `json:"folderId"`
 | |
| 	FolderUid    string           `json:"folderUid"`
 | |
| 	IsFolder     bool             `json:"isFolder"`
 | |
| 
 | |
| 	UpdatedAt time.Time
 | |
| 
 | |
| 	Result *Dashboard
 | |
| }
 | |
| 
 | |
| type TrimDashboardCommand struct {
 | |
| 	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
 | |
| 	Meta      *simplejson.Json `json:"meta"`
 | |
| 	Result    *Dashboard
 | |
| }
 | |
| 
 | |
| type DashboardProvisioning struct {
 | |
| 	Id          int64
 | |
| 	DashboardId int64
 | |
| 	Name        string
 | |
| 	ExternalId  string
 | |
| 	CheckSum    string
 | |
| 	Updated     int64
 | |
| }
 | |
| 
 | |
| type DeleteDashboardCommand struct {
 | |
| 	Id                     int64
 | |
| 	OrgId                  int64
 | |
| 	ForceDeleteFolderRules bool
 | |
| }
 | |
| 
 | |
| type DeleteOrphanedProvisionedDashboardsCommand struct {
 | |
| 	ReaderNames []string
 | |
| }
 | |
| 
 | |
| //
 | |
| // QUERIES
 | |
| //
 | |
| 
 | |
| type GetDashboardQuery struct {
 | |
| 	Slug  string // required if no Id or Uid is specified
 | |
| 	Id    int64  // optional if slug is set
 | |
| 	Uid   string // optional if slug is set
 | |
| 	OrgId int64
 | |
| 
 | |
| 	Result *Dashboard
 | |
| }
 | |
| 
 | |
| type DashboardTagCloudItem struct {
 | |
| 	Term  string `json:"term"`
 | |
| 	Count int    `json:"count"`
 | |
| }
 | |
| 
 | |
| type GetDashboardTagsQuery struct {
 | |
| 	OrgId  int64
 | |
| 	Result []*DashboardTagCloudItem
 | |
| }
 | |
| 
 | |
| type GetDashboardsQuery struct {
 | |
| 	DashboardIds []int64
 | |
| 	Result       []*Dashboard
 | |
| }
 | |
| 
 | |
| type GetDashboardPermissionsForUserQuery struct {
 | |
| 	DashboardIds []int64
 | |
| 	OrgId        int64
 | |
| 	UserId       int64
 | |
| 	OrgRole      RoleType
 | |
| 	Result       []*DashboardPermissionForUser
 | |
| }
 | |
| 
 | |
| type GetDashboardsByPluginIdQuery struct {
 | |
| 	OrgId    int64
 | |
| 	PluginId string
 | |
| 	Result   []*Dashboard
 | |
| }
 | |
| 
 | |
| type GetDashboardSlugByIdQuery struct {
 | |
| 	Id     int64
 | |
| 	Result string
 | |
| }
 | |
| 
 | |
| type GetDashboardsBySlugQuery struct {
 | |
| 	OrgId int64
 | |
| 	Slug  string
 | |
| 
 | |
| 	Result []*Dashboard
 | |
| }
 | |
| 
 | |
| type DashboardPermissionForUser struct {
 | |
| 	DashboardId    int64          `json:"dashboardId"`
 | |
| 	Permission     PermissionType `json:"permission"`
 | |
| 	PermissionName string         `json:"permissionName"`
 | |
| }
 | |
| 
 | |
| type DashboardRef struct {
 | |
| 	Uid  string
 | |
| 	Slug string
 | |
| }
 | |
| 
 | |
| type GetDashboardRefByIdQuery struct {
 | |
| 	Id     int64
 | |
| 	Result *DashboardRef
 | |
| }
 | |
| 
 | |
| type UnprovisionDashboardCommand struct {
 | |
| 	Id int64
 | |
| }
 |