unsavedChangesSrv.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. define([
  2. 'angular',
  3. 'lodash',
  4. ],
  5. function(angular, _) {
  6. 'use strict';
  7. var module = angular.module('grafana.services');
  8. module.service('unsavedChangesSrv', function($rootScope, $q, $location, $timeout, contextSrv, dashboardSrv, $window) {
  9. function Tracker(dashboard, scope, originalCopyDelay) {
  10. var self = this;
  11. this.current = dashboard;
  12. this.originalPath = $location.path();
  13. this.scope = scope;
  14. // register events
  15. scope.onAppEvent('dashboard-saved', function() {
  16. this.original = this.current.getSaveModelClone();
  17. this.originalPath = $location.path();
  18. }.bind(this));
  19. $window.onbeforeunload = function() {
  20. if (self.ignoreChanges()) { return; }
  21. if (self.hasChanges()) {
  22. return "There are unsaved changes to this dashboard";
  23. }
  24. };
  25. scope.$on("$locationChangeStart", function(event, next) {
  26. // check if we should look for changes
  27. if (self.originalPath === $location.path()) { return true; }
  28. if (self.ignoreChanges()) { return true; }
  29. if (self.hasChanges()) {
  30. event.preventDefault();
  31. self.next = next;
  32. $timeout(function() {
  33. self.open_modal();
  34. });
  35. }
  36. });
  37. if (originalCopyDelay) {
  38. $timeout(function() {
  39. // wait for different services to patch the dashboard (missing properties)
  40. self.original = dashboard.getSaveModelClone();
  41. }, originalCopyDelay);
  42. } else {
  43. self.original = dashboard.getSaveModelClone();
  44. }
  45. }
  46. var p = Tracker.prototype;
  47. // for some dashboards and users
  48. // changes should be ignored
  49. p.ignoreChanges = function() {
  50. if (!this.original) { return true; }
  51. if (!contextSrv.isEditor) { return true; }
  52. if (!this.current || !this.current.meta) { return true; }
  53. var meta = this.current.meta;
  54. return !meta.canSave || meta.fromScript || meta.fromFile;
  55. };
  56. // remove stuff that should not count in diff
  57. p.cleanDashboardFromIgnoredChanges = function(dash) {
  58. // ignore time and refresh
  59. dash.time = 0;
  60. dash.refresh = 0;
  61. dash.schemaVersion = 0;
  62. // filter row and panels properties that should be ignored
  63. dash.rows = _.filter(dash.rows, function(row) {
  64. if (row.repeatRowId) {
  65. return false;
  66. }
  67. row.panels = _.filter(row.panels, function(panel) {
  68. if (panel.repeatPanelId) {
  69. return false;
  70. }
  71. // remove scopedVars
  72. panel.scopedVars = null;
  73. // ignore span changes
  74. panel.span = null;
  75. // ignore panel legend sort
  76. if (panel.legend) {
  77. delete panel.legend.sort;
  78. delete panel.legend.sortDesc;
  79. }
  80. return true;
  81. });
  82. // ignore collapse state
  83. row.collapse = false;
  84. return true;
  85. });
  86. dash.panels = _.filter(dash.panels, function(panel) {
  87. if (panel.repeatPanelId) {
  88. return false;
  89. }
  90. // remove scopedVars
  91. panel.scopedVars = null;
  92. // ignore panel legend sort
  93. if (panel.legend) {
  94. delete panel.legend.sort;
  95. delete panel.legend.sortDesc;
  96. }
  97. return true;
  98. });
  99. // ignore template variable values
  100. _.each(dash.templating.list, function(value) {
  101. value.current = null;
  102. value.options = null;
  103. value.filters = null;
  104. });
  105. };
  106. p.hasChanges = function() {
  107. var current = this.current.getSaveModelClone();
  108. var original = this.original;
  109. this.cleanDashboardFromIgnoredChanges(current);
  110. this.cleanDashboardFromIgnoredChanges(original);
  111. var currentTimepicker = _.find(current.nav, { type: 'timepicker' });
  112. var originalTimepicker = _.find(original.nav, { type: 'timepicker' });
  113. if (currentTimepicker && originalTimepicker) {
  114. currentTimepicker.now = originalTimepicker.now;
  115. }
  116. var currentJson = angular.toJson(current);
  117. var originalJson = angular.toJson(original);
  118. return currentJson !== originalJson;
  119. };
  120. p.discardChanges = function() {
  121. this.original = null;
  122. this.gotoNext();
  123. };
  124. p.open_modal = function() {
  125. $rootScope.appEvent('show-modal', {
  126. templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
  127. modalClass: 'modal--narrow confirm-modal'
  128. });
  129. };
  130. p.saveChanges = function() {
  131. var self = this;
  132. var cancel = $rootScope.$on('dashboard-saved', function() {
  133. cancel();
  134. $timeout(function() {
  135. self.gotoNext();
  136. });
  137. });
  138. $rootScope.appEvent('save-dashboard');
  139. };
  140. p.gotoNext = function() {
  141. var baseLen = $location.absUrl().length - $location.url().length;
  142. var nextUrl = this.next.substring(baseLen);
  143. $location.url(nextUrl);
  144. };
  145. this.Tracker = Tracker;
  146. this.init = function(dashboard, scope) {
  147. this.tracker = new Tracker(dashboard, scope, 1000);
  148. return this.tracker;
  149. };
  150. });
  151. });