unsavedChangesSrv.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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) {
  10. var self = this;
  11. this.original = dashboard.getSaveModelClone();
  12. this.current = dashboard;
  13. this.originalPath = $location.path();
  14. this.scope = scope;
  15. // register events
  16. scope.onAppEvent('dashboard-saved', function() {
  17. self.original = self.current.getSaveModelClone();
  18. self.originalPath = $location.path();
  19. });
  20. $window.onbeforeunload = function() {
  21. if (self.ignoreChanges()) { return; }
  22. if (self.hasChanges()) {
  23. return "There are unsaved changes to this dashboard";
  24. }
  25. };
  26. scope.$on("$locationChangeStart", function(event, next) {
  27. // check if we should look for changes
  28. if (self.originalPath === $location.path()) { return true; }
  29. if (self.ignoreChanges()) { return true; }
  30. if (self.hasChanges()) {
  31. event.preventDefault();
  32. self.next = next;
  33. $timeout(function() {
  34. self.open_modal();
  35. });
  36. }
  37. });
  38. }
  39. var p = Tracker.prototype;
  40. // for some dashboards and users
  41. // changes should be ignored
  42. p.ignoreChanges = function() {
  43. if (!this.original) { return true; }
  44. if (!contextSrv.isEditor) { return true; }
  45. if (!this.current || !this.current.meta) { return true; }
  46. var meta = this.current.meta;
  47. return !meta.canSave || meta.fromScript || meta.fromFile;
  48. };
  49. // remove stuff that should not count in diff
  50. p.cleanDashboardFromIgnoredChanges = function(dash) {
  51. // ignore time and refresh
  52. dash.time = 0;
  53. dash.refresh = 0;
  54. dash.schemaVersion = 0;
  55. // filter row and panels properties that should be ignored
  56. dash.rows = _.filter(dash.rows, function(row) {
  57. if (row.repeatRowId) {
  58. return false;
  59. }
  60. row.panels = _.filter(row.panels, function(panel) {
  61. if (panel.repeatPanelId) {
  62. return false;
  63. }
  64. // remove scopedVars
  65. panel.scopedVars = null;
  66. // ignore span changes
  67. panel.span = null;
  68. // ignore panel legend sort
  69. if (panel.legend) {
  70. delete panel.legend.sort;
  71. delete panel.legend.sortDesc;
  72. }
  73. return true;
  74. });
  75. // ignore collapse state
  76. row.collapse = false;
  77. return true;
  78. });
  79. // ignore template variable values
  80. _.each(dash.templating.list, function(value) {
  81. value.current = null;
  82. value.options = null;
  83. });
  84. };
  85. p.hasChanges = function() {
  86. var current = this.current.getSaveModelClone();
  87. var original = this.original;
  88. this.cleanDashboardFromIgnoredChanges(current);
  89. this.cleanDashboardFromIgnoredChanges(original);
  90. var currentTimepicker = _.findWhere(current.nav, { type: 'timepicker' });
  91. var originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' });
  92. if (currentTimepicker && originalTimepicker) {
  93. currentTimepicker.now = originalTimepicker.now;
  94. }
  95. var currentJson = angular.toJson(current);
  96. var originalJson = angular.toJson(original);
  97. return currentJson !== originalJson;
  98. };
  99. p.open_modal = function() {
  100. var tracker = this;
  101. var modalScope = this.scope.$new();
  102. modalScope.ignore = function() {
  103. tracker.original = null;
  104. tracker.goto_next();
  105. };
  106. modalScope.save = function() {
  107. tracker.scope.$emit('save-dashboard');
  108. };
  109. $rootScope.appEvent('show-modal', {
  110. src: 'public/app/partials/unsaved-changes.html',
  111. modalClass: 'modal-no-header confirm-modal',
  112. scope: modalScope,
  113. });
  114. };
  115. p.goto_next = function() {
  116. var baseLen = $location.absUrl().length - $location.url().length;
  117. var nextUrl = this.next.substring(baseLen);
  118. $location.url(nextUrl);
  119. };
  120. this.Tracker = Tracker;
  121. this.init = function(dashboard, scope) {
  122. // wait for different services to patch the dashboard (missing properties)
  123. $timeout(function() { new Tracker(dashboard, scope); }, 1200);
  124. };
  125. });
  126. });