diff --git a/pkg/api/api.go b/pkg/api/api.go index 4cebd56d5b1..8c45c8e6d8b 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -211,7 +211,7 @@ func Register(r *macaron.Macaron) { // Dashboard r.Group("/dashboards", func() { r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard) - r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard) + r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), wrap(PostDashboard)) r.Get("/file/:file", GetDashboardFromJsonFile) r.Get("/home", wrap(GetHomeDashboard)) r.Get("/tags", GetDashboardTags) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index cbad74444bf..0a29a91347a 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -109,7 +110,7 @@ func DeleteDashboard(c *middleware.Context) { c.JSON(200, resp) } -func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) { +func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response { cmd.OrgId = c.OrgId if !c.IsSignedIn { @@ -122,35 +123,43 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) { if dash.Id == 0 { limitReached, err := middleware.QuotaReached(c, "dashboard") if err != nil { - c.JsonApiErr(500, "failed to get quota", err) - return + return ApiError(500, "failed to get quota", err) } if limitReached { - c.JsonApiErr(403, "Quota reached", nil) - return + return ApiError(403, "Quota reached", nil) + } + } + + if !cmd.Overwrite { + if autoUpdate, exists := dash.Data.CheckGet("autoUpdate"); exists { + message := "Dashboard marked as auto updated." + + if pluginId, err := autoUpdate.Get("pluginId").String(); err == nil { + if pluginDef, ok := plugins.Plugins[pluginId]; ok { + message = "Dashboard updated automatically when plugin " + pluginDef.Name + " is updated." + } + } + + return Json(412, util.DynMap{"status": "auto-update-dashboard", "message": message}) } } err := bus.Dispatch(&cmd) if err != nil { if err == m.ErrDashboardWithSameNameExists { - c.JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()}) - return + return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()}) } if err == m.ErrDashboardVersionMismatch { - c.JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()}) - return + return Json(412, util.DynMap{"status": "version-mismatch", "message": err.Error()}) } if err == m.ErrDashboardNotFound { - c.JSON(404, util.DynMap{"status": "not-found", "message": err.Error()}) - return + return Json(404, util.DynMap{"status": "not-found", "message": err.Error()}) } - c.JsonApiErr(500, "Failed to save dashboard", err) - return + return ApiError(500, "Failed to save dashboard", err) } c.TimeRequest(metrics.M_Api_Dashboard_Save) - c.JSON(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version}) + return Json(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version}) } func canEditDashboard(role m.RoleType) bool { diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go index f3bae6cbcd3..e4feb3442fb 100644 --- a/pkg/api/playlist_play.go +++ b/pkg/api/playlist_play.go @@ -18,7 +18,7 @@ func populateDashboardsById(dashboardByIds []int64) ([]m.PlaylistDashboardDto, e return result, err } - for _, item := range *dashboardQuery.Result { + for _, item := range dashboardQuery.Result { result = append(result, m.PlaylistDashboardDto{ Id: item.Id, Slug: item.Slug, diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 610f29e70aa..733d989b278 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -26,11 +26,12 @@ var ( // Dashboard model type Dashboard struct { - Id int64 - Slug string - OrgId int64 - GnetId int64 - Version int + Id int64 + Slug string + OrgId int64 + GnetId int64 + Version int + PluginId string Created time.Time Updated time.Time @@ -151,7 +152,13 @@ type GetDashboardTagsQuery struct { type GetDashboardsQuery struct { DashboardIds []int64 - Result *[]Dashboard + Result []*Dashboard +} + +type GetDashboardsByPluginIdQuery struct { + OrgId int64 + PluginId string + Result []*Dashboard } type GetDashboardSlugByIdQuery struct { diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go index 1a160fe6632..8bde60b64c8 100644 --- a/pkg/plugins/dashboards.go +++ b/pkg/plugins/dashboards.go @@ -18,6 +18,7 @@ type PluginDashboardInfoDTO struct { Revision int64 `json:"revision"` Description string `json:"description"` Path string `json:"path"` + Removed bool } func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDTO, error) { @@ -29,14 +30,40 @@ func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDT result := make([]*PluginDashboardInfoDTO, 0) + // load current dashboards + query := m.GetDashboardsByPluginIdQuery{OrgId: orgId, PluginId: pluginId} + if err := bus.Dispatch(&query); err != nil { + return nil, err + } + for _, include := range plugin.Includes { - if include.Type == PluginTypeDashboard { - if dashInfo, err := getDashboardImportStatus(orgId, plugin, include.Path); err != nil { - return nil, err - } else { - result = append(result, dashInfo) + if include.Type != PluginTypeDashboard { + continue + } + + res := &PluginDashboardInfoDTO{} + var dashboard *m.Dashboard + var err error + + if dashboard, err = loadPluginDashboard(plugin.Id, include.Path); err != nil { + return nil, err + } + + res.Path = include.Path + res.PluginId = plugin.Id + res.Title = dashboard.Title + res.Revision = dashboard.Data.Get("revision").MustInt64(1) + + // find existing dashboard + for _, existingDash := range query.Result { + if existingDash.Slug == dashboard.Slug { + res.Imported = true + res.ImportedUri = "db/" + existingDash.Slug + res.ImportedRevision = existingDash.Data.Get("revision").MustInt64(1) } } + + result = append(result, res) } return result, nil diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index ef36fd75ab6..3712ce6579f 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -19,6 +19,7 @@ func init() { bus.AddHandler("sql", SearchDashboards) bus.AddHandler("sql", GetDashboardTags) bus.AddHandler("sql", GetDashboardSlugById) + bus.AddHandler("sql", GetDashboardsByPluginId) } func SaveDashboard(cmd *m.SaveDashboardCommand) error { @@ -245,10 +246,23 @@ func GetDashboards(query *m.GetDashboardsQuery) error { return m.ErrCommandValidationFailed } - var dashboards = make([]m.Dashboard, 0) + var dashboards = make([]*m.Dashboard, 0) err := x.In("id", query.DashboardIds).Find(&dashboards) - query.Result = &dashboards + query.Result = dashboards + + if err != nil { + return err + } + + return nil +} + +func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error { + var dashboards = make([]*m.Dashboard, 0) + + err := x.Where("org_id=? AND plugin_id=?", query.OrgId, query.PluginId).Find(&dashboards) + query.Result = dashboards if err != nil { return err diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 38c25a315f2..4f286dce68a 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -111,4 +111,13 @@ func addDashboardMigration(mg *Migrator) { mg.AddMigration("Add index for gnetId in dashboard", NewAddIndexMigration(dashboardV2, &Index{ Cols: []string{"gnet_id"}, Type: IndexType, })) + + // add column to store plugin_id + mg.AddMigration("Add column plugin_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{ + Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255, + })) + + mg.AddMigration("Add index for plugin_id in dashboard", NewAddIndexMigration(dashboardV2, &Index{ + Cols: []string{"org_id", "plugin_id"}, Type: IndexType, + })) } diff --git a/public/app/core/services/alert_srv.ts b/public/app/core/services/alert_srv.ts index 971743b7285..3003e59c74b 100644 --- a/public/app/core/services/alert_srv.ts +++ b/public/app/core/services/alert_srv.ts @@ -28,10 +28,6 @@ export class AlertSrv { }, this.$rootScope); appEvents.on('confirm-modal', this.showConfirmModal.bind(this)); - - this.$rootScope.onAppEvent('confirm-modal', (e, data) => { - this.showConfirmModal(data); - }, this.$rootScope); } set(title, text, severity, timeout) { diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 0a3c1b95f95..937d5266c56 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -22,6 +22,7 @@ function (angular, $, _, moment) { this.id = data.id || null; this.title = data.title || 'No Title'; + this.autoUpdate = data.autoUpdate; this.description = data.description; this.tags = data.tags || []; this.style = data.style || "dark"; diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 62f598e1cbd..25bb587edeb 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -134,6 +134,21 @@ export class DashNavCtrl { } }); } + + if (err.data && err.data.status === "auto-update-dashboard") { + err.isHandled = true; + + $scope.appEvent('confirm-modal', { + title: 'Auto Update Dashboard', + text: err.data.message, + text2: 'Use Save As... to create copy or ignore this warning.', + yesText: "Save & Overwrite", + icon: "fa-warning", + onConfirm: function() { + $scope.saveDashboard({overwrite: true}); + } + }); + } }; $scope.deleteDashboard = function() { diff --git a/public/app/features/dashboard/saveDashboardAsCtrl.js b/public/app/features/dashboard/saveDashboardAsCtrl.js index 92c93b7e485..211863b66e6 100644 --- a/public/app/features/dashboard/saveDashboardAsCtrl.js +++ b/public/app/features/dashboard/saveDashboardAsCtrl.js @@ -12,6 +12,8 @@ function (angular) { $scope.clone.id = null; $scope.clone.editable = true; $scope.clone.title = $scope.clone.title + " Copy"; + // remove auto update + delete $scope.clone.autoUpdate; }; function saveDashboard(options) { @@ -37,8 +39,9 @@ function (angular) { err.isHandled = true; $scope.appEvent('confirm-modal', { - title: 'Another dashboard with the same name exists', - text: "Would you still like to save this dashboard?", + title: 'Conflict', + text: 'Dashboard with the same name exists.', + text2: 'Would you still like to save this dashboard?', yesText: "Save & Overwrite", icon: "fa-warning", onConfirm: function() {