unsaved_changes_srv.ts 5.2 KB

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