Browse Source

Merge branch 'unsaved_changes_warning' (Closes #324)

Torkel Ödegaard 11 years ago
parent
commit
035b5163fb

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@
 - Increased resolution for graphite datapoints (maxDataPoints), now equal to panel pixel width. (Closes #5)
 - Improvement to influxdb query editor, can now add where clause and alias (Issue #331, thanks @mavimo)
 - New config setting for graphite datasource to control if json render request is POST or GET (Issue #345)
+- Unsaved changes warning feature (Issue #324)
+
 
 # 1.5.3 (2014-04-17)
 - Add support for async scripted dashboards (Issue #274)

+ 1 - 0
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

+ 2 - 1
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) {
 
     $scope.requiredElasticSearchVersion = ">=0.90.3";
 

+ 12 - 15
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) {

+ 16 - 0
src/app/partials/unsaved-changes.html

@@ -0,0 +1,16 @@
+<div class="modal-header">
+</div>
+<div class="modal-body">
+
+  <h3 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h3>
+  <div class="row-fluid">
+	<span class="span3"></span>
+	<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
+	<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
+	<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
+	<span class="span3"></span>
+  </div>
+
+</div>
+<div class="modal-footer">
+</div>

+ 1 - 0
src/app/services/all.js

@@ -8,5 +8,6 @@ define([
   './keyboardManager',
   './annotationsSrv',
   './playlistSrv',
+  './unsavedChangesSrv',
 ],
 function () {});

+ 8 - 10
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();
     });
@@ -157,16 +158,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
       // Set the current dashboard
       self.current = 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);
@@ -181,6 +174,10 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
 
       $rootScope.$emit('dashboard-loaded');
 
+      $timeout(function() {
+        self.original = angular.copy(self.current);
+      }, 500);
+
       return true;
     };
 
@@ -393,6 +390,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
           if(type === 'dashboard') {
             $location.path('/dashboard/elasticsearch/'+title);
           }
+          self.original = angular.copy(self.current);
           return result;
         },
         // Failure

+ 102 - 0
src/app/services/unsavedChangesSrv.js

@@ -0,0 +1,102 @@
+define([
+  'angular',
+  'underscore',
+  'config',
+],
+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, $timeout) {
+    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()) {
+          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 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(original);
+
+      if (currentJson !== originalJson) {
+        return true;
+      }
+
+      return false;
+    };
+
+    this.goto_next = function () {
+      var baseLen = $location.absUrl().length - $location.url().length;
+      var nextUrl = self.next.substring(baseLen);
+      $location.url(nextUrl);
+    };
+
+    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();
+  });
+});

+ 9 - 0
src/config.sample.js

@@ -57,8 +57,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'