unsavedChangesSrv.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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, $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. });
  92. };
  93. p.hasChanges = function() {
  94. var current = this.current.getSaveModelClone();
  95. var original = this.original;
  96. this.cleanDashboardFromIgnoredChanges(current);
  97. this.cleanDashboardFromIgnoredChanges(original);
  98. var currentTimepicker = _.find(current.nav, { type: 'timepicker' });
  99. var originalTimepicker = _.find(original.nav, { type: 'timepicker' });
  100. if (currentTimepicker && originalTimepicker) {
  101. currentTimepicker.now = originalTimepicker.now;
  102. }
  103. var currentJson = angular.toJson(current);
  104. var originalJson = angular.toJson(original);
  105. return currentJson !== originalJson;
  106. };
  107. p.open_modal = function() {
  108. var tracker = this;
  109. var modalScope = this.scope.$new();
  110. modalScope.ignore = function() {
  111. tracker.original = null;
  112. tracker.goto_next();
  113. };
  114. modalScope.save = function() {
  115. var cancel = $rootScope.$on('dashboard-saved', function() {
  116. cancel();
  117. $timeout(function() {
  118. tracker.goto_next();
  119. });
  120. });
  121. $rootScope.$emit('save-dashboard');
  122. };
  123. $rootScope.appEvent('show-modal', {
  124. src: 'public/app/partials/unsaved-changes.html',
  125. modalClass: 'confirm-modal',
  126. scope: modalScope,
  127. });
  128. };
  129. p.goto_next = function() {
  130. var baseLen = $location.absUrl().length - $location.url().length;
  131. var nextUrl = this.next.substring(baseLen);
  132. $location.url(nextUrl);
  133. };
  134. this.Tracker = Tracker;
  135. this.init = function(dashboard, scope) {
  136. return new Tracker(dashboard, scope, 1000);
  137. };
  138. });
  139. });