unsavedChangesSrv.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. dash.editMode = false;
  63. // filter row and panels properties that should be ignored
  64. dash.rows = _.filter(dash.rows, function(row) {
  65. if (row.repeatRowId) {
  66. return false;
  67. }
  68. row.panels = _.filter(row.panels, function(panel) {
  69. if (panel.repeatPanelId) {
  70. return false;
  71. }
  72. // remove scopedVars
  73. panel.scopedVars = null;
  74. // ignore span changes
  75. panel.span = null;
  76. // ignore panel legend sort
  77. if (panel.legend) {
  78. delete panel.legend.sort;
  79. delete panel.legend.sortDesc;
  80. }
  81. return true;
  82. });
  83. // ignore collapse state
  84. row.collapse = false;
  85. return true;
  86. });
  87. // ignore template variable values
  88. _.each(dash.templating.list, function(value) {
  89. value.current = null;
  90. value.options = null;
  91. value.filters = null;
  92. });
  93. };
  94. p.hasChanges = function() {
  95. var current = this.current.getSaveModelClone();
  96. var original = this.original;
  97. this.cleanDashboardFromIgnoredChanges(current);
  98. this.cleanDashboardFromIgnoredChanges(original);
  99. var currentTimepicker = _.find(current.nav, { type: 'timepicker' });
  100. var originalTimepicker = _.find(original.nav, { type: 'timepicker' });
  101. if (currentTimepicker && originalTimepicker) {
  102. currentTimepicker.now = originalTimepicker.now;
  103. }
  104. var currentJson = angular.toJson(current);
  105. var originalJson = angular.toJson(original);
  106. return currentJson !== originalJson;
  107. };
  108. p.discardChanges = function() {
  109. this.original = null;
  110. this.gotoNext();
  111. };
  112. p.open_modal = function() {
  113. $rootScope.appEvent('show-modal', {
  114. templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
  115. modalClass: 'modal--narrow confirm-modal'
  116. });
  117. };
  118. p.saveChanges = function() {
  119. var self = this;
  120. var cancel = $rootScope.$on('dashboard-saved', function() {
  121. cancel();
  122. $timeout(function() {
  123. self.gotoNext();
  124. });
  125. });
  126. $rootScope.appEvent('save-dashboard');
  127. };
  128. p.gotoNext = function() {
  129. var baseLen = $location.absUrl().length - $location.url().length;
  130. var nextUrl = this.next.substring(baseLen);
  131. $location.url(nextUrl);
  132. };
  133. this.Tracker = Tracker;
  134. this.init = function(dashboard, scope) {
  135. this.tracker = new Tracker(dashboard, scope, 1000);
  136. return this.tracker;
  137. };
  138. });
  139. });