From c26926148c982b8ad4c3bd52df9e529df56e083e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 23 Apr 2014 21:16:53 +0200 Subject: [PATCH 1/5] began work on unsaved changes warning when changing dashboard, Issue #324 --- src/app/services/dashboard.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index 002e8e05156..5872df9bcb2 100644 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -60,6 +60,12 @@ function (angular, $, kbn, _, config, moment, Modernizr) { this.last = {}; this.availablePanels = []; + $rootScope.$on("$locationChangeStart", function(event, next, current) { + if (!self.confirm_dash_change()) { + event.preventDefault(); + } + }); + $rootScope.$on('$routeChangeSuccess',function(){ // Clear the current dashboard to prevent reloading self.current = {}; @@ -156,6 +162,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { // Set the current dashboard self.current = angular.copy(dashboard); + self.original = angular.copy(dashboard); // Delay this until we're sure that querySrv and filterSrv are ready $timeout(function() { @@ -184,6 +191,22 @@ function (angular, $, kbn, _, config, moment, Modernizr) { return true; }; + this.confirm_dash_change = function() { + if (!self.original) { + return true; + } + + var current = angular.copy(self.current); + var currentJson = angular.toJson(current); + var originalJson = angular.toJson(self.original); + + if (currentJson !== originalJson) { + return confirm('There are unsaved changes, are you sure you want to change dashboard?'); + } + + return true; + }; + this.gist_id = function(string) { if(self.is_gist(string)) { return string.match(gist_pattern)[0].replace(/.*\//, ''); From bfb2376aa3b7eab9aa4d01f3caed00fa01cad54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 25 Apr 2014 15:23:17 +0200 Subject: [PATCH 2/5] working on unsaved changes warning --- src/app/controllers/dash.js | 3 +- src/app/partials/unsaved-changes.html | 12 ++++++ src/app/services/all.js | 1 + src/app/services/dashboard.js | 23 +---------- src/app/services/unsavedChangesSrv.js | 59 +++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 src/app/partials/unsaved-changes.html create mode 100644 src/app/services/unsavedChangesSrv.js diff --git a/src/app/controllers/dash.js b/src/app/controllers/dash.js index b9556a8e4da..cda62826aa5 100644 --- a/src/app/controllers/dash.js +++ b/src/app/controllers/dash.js @@ -30,7 +30,8 @@ function (angular, $, config, _) { var module = angular.module('kibana.controllers'); module.controller('DashCtrl', function( - $scope, $rootScope, $route, ejsResource, dashboard, alertSrv, panelMove, keyboardManager, grafanaVersion) { + $scope, $rootScope, ejsResource, dashboard, + alertSrv, panelMove, keyboardManager, grafanaVersion, unsavedChangesSrv) { $scope.requiredElasticSearchVersion = ">=0.90.3"; diff --git a/src/app/partials/unsaved-changes.html b/src/app/partials/unsaved-changes.html new file mode 100644 index 00000000000..38d86cd2ede --- /dev/null +++ b/src/app/partials/unsaved-changes.html @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/src/app/services/all.js b/src/app/services/all.js index 2f2ecfb3543..53a4f521ac6 100644 --- a/src/app/services/all.js +++ b/src/app/services/all.js @@ -8,5 +8,6 @@ define([ './keyboardManager', './annotationsSrv', './playlistSrv', + './unsavedChangesSrv', ], function () {}); \ No newline at end of file diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index 5872df9bcb2..5b7e4eb88e7 100644 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -60,12 +60,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) { this.last = {}; this.availablePanels = []; - $rootScope.$on("$locationChangeStart", function(event, next, current) { - if (!self.confirm_dash_change()) { - event.preventDefault(); - } - }); - $rootScope.$on('$routeChangeSuccess',function(){ // Clear the current dashboard to prevent reloading self.current = {}; @@ -191,22 +185,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) { return true; }; - this.confirm_dash_change = function() { - if (!self.original) { - return true; - } - - var current = angular.copy(self.current); - var currentJson = angular.toJson(current); - var originalJson = angular.toJson(self.original); - - if (currentJson !== originalJson) { - return confirm('There are unsaved changes, are you sure you want to change dashboard?'); - } - - return true; - }; - this.gist_id = function(string) { if(self.is_gist(string)) { return string.match(gist_pattern)[0].replace(/.*\//, ''); @@ -416,6 +394,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { if(type === 'dashboard') { $location.path('/dashboard/elasticsearch/'+title); } + self.original = angular.copy(self.current); return result; }, // Failure diff --git a/src/app/services/unsavedChangesSrv.js b/src/app/services/unsavedChangesSrv.js new file mode 100644 index 00000000000..b10c63d7b2a --- /dev/null +++ b/src/app/services/unsavedChangesSrv.js @@ -0,0 +1,59 @@ +define([ + 'angular', + 'underscore' +], +function (angular, _) { + 'use strict'; + + var module = angular.module('kibana.services'); + + module.service('unsavedChangesSrv', function($rootScope, $modal, dashboard, $q, $location) { + var self = this; + + var modalScope = $rootScope.$new(); + + $rootScope.$on("$locationChangeStart", function(event, next, current) { + if (self.has_unsaved_changes()) { + event.preventDefault(); + self.next = next; + self.open_modal(); + } + }); + + this.open_modal = function() { + var confirmModal = $modal({ + template: './app/partials/unsaved-changes.html', + persist: true, + show: false, + scope: modalScope, + keyboard: false + }); + + $q.when(confirmModal).then(function(modalEl) { + modalEl.modal('show'); + }); + }; + + this.has_unsaved_changes = function() { + if (!dashboard.original) { + return false; + } + + var current = angular.copy(dashboard.current); + var currentJson = angular.toJson(current); + var originalJson = angular.toJson(dashboard.original); + + if (currentJson !== originalJson) { + return true; //confirm('There are unsaved changes, are you sure you want to change dashboard?'); + } + + return false; + }; + + modalScope.ignore = function() { + dashboard.original = null; + $location.path(self.next) + }; + + }); +}); \ No newline at end of file From 8ad00faebc69d9974aee044aa2791b73c4878e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 25 Apr 2014 16:50:35 +0200 Subject: [PATCH 3/5] small progress on unsaved changes warning --- src/app/partials/unsaved-changes.html | 12 ++++++++---- src/app/services/unsavedChangesSrv.js | 8 +++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/app/partials/unsaved-changes.html b/src/app/partials/unsaved-changes.html index 38d86cd2ede..5ea9082d13f 100644 --- a/src/app/partials/unsaved-changes.html +++ b/src/app/partials/unsaved-changes.html @@ -1,12 +1,16 @@ \ No newline at end of file diff --git a/src/app/services/unsavedChangesSrv.js b/src/app/services/unsavedChangesSrv.js index b10c63d7b2a..3789b9fa2eb 100644 --- a/src/app/services/unsavedChangesSrv.js +++ b/src/app/services/unsavedChangesSrv.js @@ -52,7 +52,13 @@ function (angular, _) { modalScope.ignore = function() { dashboard.original = null; - $location.path(self.next) + var baseLen = $location.absUrl().length - $location.url().length; + var nextUrl = self.next.substring(baseLen); + $location.url(nextUrl); + }; + + modalScope.save = function() { + }; }); From bdd2c9d0332569e89324ce7122846bd3bc339f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 27 Apr 2014 12:22:38 +0200 Subject: [PATCH 4/5] More work on unsaved changes warning (Issue #324) --- src/app/components/settings.js | 1 + src/app/controllers/dash.js | 2 +- src/app/controllers/dashLoader.js | 27 +++++------ src/app/partials/unsaved-changes.html | 6 +-- src/app/services/dashboard.js | 18 +++---- src/app/services/unsavedChangesSrv.js | 69 +++++++++++++++++++-------- src/config.sample.js | 9 ++++ 7 files changed, 83 insertions(+), 49 deletions(-) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index b7785d5ce4b..734dd34e947 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -25,6 +25,7 @@ function (_, crypto) { grafana_index : 'grafana-dash', elasticsearch_all_disabled : false, timezoneOffset : null, + unsaved_changes_warning : true }; // This initializes a new hash on purpose, to avoid adding parameters to diff --git a/src/app/controllers/dash.js b/src/app/controllers/dash.js index cda62826aa5..98f5df2983e 100644 --- a/src/app/controllers/dash.js +++ b/src/app/controllers/dash.js @@ -31,7 +31,7 @@ function (angular, $, config, _) { module.controller('DashCtrl', function( $scope, $rootScope, ejsResource, dashboard, - alertSrv, panelMove, keyboardManager, grafanaVersion, unsavedChangesSrv) { + alertSrv, panelMove, keyboardManager, grafanaVersion) { $scope.requiredElasticSearchVersion = ">=0.90.3"; diff --git a/src/app/controllers/dashLoader.js b/src/app/controllers/dashLoader.js index 4cbf5c3aa1b..7f9dc9232dd 100644 --- a/src/app/controllers/dashLoader.js +++ b/src/app/controllers/dashLoader.js @@ -65,23 +65,20 @@ function (angular, _, moment) { }; $scope.elasticsearch_save = function(type,ttl) { - dashboard.elasticsearch_save( - type, - ($scope.elasticsearch.title || dashboard.current.title), - ($scope.loader.save_temp_ttl_enable ? ttl : false) - ).then(function(result) { - if(_.isUndefined(result._id)) { - alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000); - return; - } + dashboard.elasticsearch_save(type, dashboard.current.title, ttl) + .then(function(result) { + if(_.isUndefined(result._id)) { + alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000); + return; + } - alertSrv.set('Dashboard Saved', 'This dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000); - if(type === 'temp') { - $scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id); - } + alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000); + if(type === 'temp') { + $scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id); + } - $rootScope.$emit('dashboard-saved'); - }); + $rootScope.$emit('dashboard-saved'); + }); }; $scope.elasticsearch_delete = function(id) { diff --git a/src/app/partials/unsaved-changes.html b/src/app/partials/unsaved-changes.html index 5ea9082d13f..87220e41e8d 100644 --- a/src/app/partials/unsaved-changes.html +++ b/src/app/partials/unsaved-changes.html @@ -5,9 +5,9 @@

Unsaved changes

- - - + + +
diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index 5b7e4eb88e7..2addf29d0b9 100644 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -63,6 +63,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { $rootScope.$on('$routeChangeSuccess',function(){ // Clear the current dashboard to prevent reloading self.current = {}; + self.original = null; self.indices = []; route(); }); @@ -156,18 +157,9 @@ function (angular, $, kbn, _, config, moment, Modernizr) { // Set the current dashboard self.current = angular.copy(dashboard); - self.original = angular.copy(dashboard); - // Delay this until we're sure that querySrv and filterSrv are ready - $timeout(function() { - // Ok, now that we've setup the current dashboard, we can inject our services - filterSrv = $injector.get('filterSrv'); - filterSrv.init(); - - },0).then(function() { - // Call refresh to calculate the indices and notify the panels that we're ready to roll - self.refresh(); - }); + filterSrv = $injector.get('filterSrv'); + filterSrv.init(); if(dashboard.refresh) { self.set_interval(dashboard.refresh); @@ -182,6 +174,10 @@ function (angular, $, kbn, _, config, moment, Modernizr) { $rootScope.$emit('dashboard-loaded'); + $timeout(function() { + self.original = angular.copy(self.current); + }, 500); + return true; }; diff --git a/src/app/services/unsavedChangesSrv.js b/src/app/services/unsavedChangesSrv.js index 3789b9fa2eb..682d3a4fc4b 100644 --- a/src/app/services/unsavedChangesSrv.js +++ b/src/app/services/unsavedChangesSrv.js @@ -1,26 +1,32 @@ define([ 'angular', - 'underscore' + 'underscore', + 'config', ], -function (angular, _) { +function (angular, _, config) { 'use strict'; + if (!config.unsaved_changes_warning) { + return; + } + var module = angular.module('kibana.services'); - module.service('unsavedChangesSrv', function($rootScope, $modal, dashboard, $q, $location) { + module.service('unsavedChangesSrv', function($rootScope, $modal, dashboard, $q, $location, $timeout) { var self = this; - var modalScope = $rootScope.$new(); - $rootScope.$on("$locationChangeStart", function(event, next, current) { - if (self.has_unsaved_changes()) { - event.preventDefault(); - self.next = next; - self.open_modal(); - } - }); + this.init = function() { + $rootScope.$on("$locationChangeStart", function(event, next) { + if (self.has_unsaved_changes()) { + event.preventDefault(); + self.next = next; + self.open_modal(); + } + }); + }; - this.open_modal = function() { + this.open_modal = function () { var confirmModal = $modal({ template: './app/partials/unsaved-changes.html', persist: true, @@ -34,32 +40,57 @@ function (angular, _) { }); }; - this.has_unsaved_changes = function() { + this.has_unsaved_changes = function () { if (!dashboard.original) { return false; } var current = angular.copy(dashboard.current); + var original = dashboard.original; + + // ignore timespan changes + current.services.filter.time = original.services.filter.time = {}; + current.refresh = original.refresh; + + var currentTimepicker = _.findWhere(current.nav, { type: 'timepicker' }); + var originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' }); + + if (currentTimepicker && originalTimepicker) { + currentTimepicker.now = originalTimepicker.now; + } + var currentJson = angular.toJson(current); - var originalJson = angular.toJson(dashboard.original); + var originalJson = angular.toJson(original); if (currentJson !== originalJson) { - return true; //confirm('There are unsaved changes, are you sure you want to change dashboard?'); + return true; } return false; }; - modalScope.ignore = function() { - dashboard.original = null; + this.goto_next = function () { var baseLen = $location.absUrl().length - $location.url().length; var nextUrl = self.next.substring(baseLen); $location.url(nextUrl); }; - modalScope.save = function() { - + modalScope.ignore = function() { + dashboard.original = null; + self.goto_next(); }; + modalScope.save = function() { + var unregister = $rootScope.$on('dashboard-saved', function() { + self.goto_next(); + }); + + $timeout(unregister, 2000); + + $rootScope.$emit('save-dashboard'); + }; + + }).run(function(unsavedChangesSrv) { + unsavedChangesSrv.init(); }); }); \ No newline at end of file diff --git a/src/config.sample.js b/src/config.sample.js index 3673e97c417..968af7bf4e4 100644 --- a/src/config.sample.js +++ b/src/config.sample.js @@ -48,8 +48,17 @@ function (Settings) { timezoneOffset: null, + /** + * Elasticsearch index for storing dashboards + * + */ grafana_index: "grafana-dash", + /** + * set to false to disable unsaved changes warning + */ + unsaved_changes_warning: true, + panel_names: [ 'text', 'graphite' From a1d5e26f6b3504ff4285692cf37a27544ae5e417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 27 Apr 2014 12:27:06 +0200 Subject: [PATCH 5/5] Added onbeforeunload check to unsaved changes warning feature --- src/app/services/unsavedChangesSrv.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/services/unsavedChangesSrv.js b/src/app/services/unsavedChangesSrv.js index 682d3a4fc4b..a0d2a079d34 100644 --- a/src/app/services/unsavedChangesSrv.js +++ b/src/app/services/unsavedChangesSrv.js @@ -16,6 +16,12 @@ function (angular, _, config) { var self = this; var modalScope = $rootScope.$new(); + window.onbeforeunload = function () { + if (self.has_unsaved_changes()) { + return "There are unsaved changes to this dashboard"; + } + }; + this.init = function() { $rootScope.$on("$locationChangeStart", function(event, next) { if (self.has_unsaved_changes()) {