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", } ErrDashboardCorrupt = DashboardErr{ Reason: "Dashboard data is missing or corrupt", StatusCode: 500, Status: "not-found", } ErrDashboardPanelNotFound = DashboardErr{ Reason: "Dashboard panel 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, Status: "empty-name", } 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, Status: "name-match", } 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, } ErrDashboardIdentifierInvalid = DashboardErr{ Reason: "Dashboard ID not a number", StatusCode: 400, } ErrDashboardPanelIdentifierInvalid = DashboardErr{ Reason: "Dashboard panel ID not a number", StatusCode: 400, } ErrDashboardOrPanelIdentifierNotSet = DashboardErr{ Reason: "Unique identifier needed to be able to get a dashboard panel", StatusCode: 400, } ErrProvisionedDashboardNotFound = DashboardErr{ Reason: "Dashboard is not provisioned", StatusCode: 404, Status: "not-found", } ErrDashboardThumbnailNotFound = DashboardErr{ Reason: "Dashboard thumbnail not found", StatusCode: 404, Status: "not-found", } ) // 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 IsPublic bool Title string Data *simplejson.Json } type PublicDashboardConfig struct { IsPublic bool `json:"isPublic"` } 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) } // GetKioskModeDashboardUrl returns the HTML url for a dashboard in kiosk mode. func GetKioskModeDashboardUrl(uid string, slug string, theme Theme) string { return fmt.Sprintf("%s?kiosk&theme=%s", GetDashboardUrl(uid, slug), string(theme)) } // 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 `json:"-"` } type TrimDashboardCommand struct { Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` Meta *simplejson.Json `json:"meta"` Result *Dashboard `json:"-"` } 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 } type SavePublicDashboardConfigCommand struct { Uid string OrgId int64 PublicDashboardConfig PublicDashboardConfig } // // 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 DashboardUIds []string 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 }