| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- import angular from 'angular';
- import _ from 'lodash';
- import { DashboardModel } from './dashboard_model';
- export class ChangeTracker {
- current: any;
- originalPath: any;
- scope: any;
- original: any;
- next: any;
- $window: any;
- /** @ngInject */
- constructor(
- dashboard,
- scope,
- originalCopyDelay,
- private $location,
- $window,
- private $timeout,
- private contextSrv,
- private $rootScope
- ) {
- this.$location = $location;
- this.$window = $window;
- this.current = dashboard;
- this.originalPath = $location.path();
- this.scope = scope;
- // register events
- scope.onAppEvent('dashboard-saved', () => {
- this.original = this.current.getSaveModelClone();
- this.originalPath = $location.path();
- });
- $window.onbeforeunload = () => {
- if (this.ignoreChanges()) {
- return undefined;
- }
- if (this.hasChanges()) {
- return 'There are unsaved changes to this dashboard';
- }
- return undefined;
- };
- scope.$on('$locationChangeStart', (event, next) => {
- // check if we should look for changes
- if (this.originalPath === $location.path()) {
- return true;
- }
- if (this.ignoreChanges()) {
- return true;
- }
- if (this.hasChanges()) {
- event.preventDefault();
- this.next = next;
- this.$timeout(() => {
- this.open_modal();
- });
- }
- return false;
- });
- if (originalCopyDelay) {
- this.$timeout(() => {
- // wait for different services to patch the dashboard (missing properties)
- this.original = dashboard.getSaveModelClone();
- }, originalCopyDelay);
- } else {
- this.original = dashboard.getSaveModelClone();
- }
- }
- // for some dashboards and users
- // changes should be ignored
- ignoreChanges() {
- if (!this.original) {
- return true;
- }
- if (!this.contextSrv.isEditor) {
- return true;
- }
- if (!this.current || !this.current.meta) {
- return true;
- }
- const meta = this.current.meta;
- return !meta.canSave || meta.fromScript || meta.fromFile;
- }
- // remove stuff that should not count in diff
- cleanDashboardFromIgnoredChanges(dashData) {
- // need to new up the domain model class to get access to expand / collapse row logic
- const model = new DashboardModel(dashData);
- // Expand all rows before making comparison. This is required because row expand / collapse
- // change order of panel array and panel positions.
- model.expandRows();
- const dash = model.getSaveModelClone();
- // ignore time and refresh
- dash.time = 0;
- dash.refresh = 0;
- dash.schemaVersion = 0;
- // ignore iteration property
- delete dash.iteration;
- dash.panels = _.filter(dash.panels, panel => {
- if (panel.repeatPanelId) {
- return false;
- }
- // remove scopedVars
- panel.scopedVars = null;
- // ignore panel legend sort
- if (panel.legend) {
- delete panel.legend.sort;
- delete panel.legend.sortDesc;
- }
- return true;
- });
- // ignore template variable values
- _.each(dash.templating.list, value => {
- value.current = null;
- value.options = null;
- value.filters = null;
- });
- return dash;
- }
- hasChanges() {
- const current = this.cleanDashboardFromIgnoredChanges(this.current.getSaveModelClone());
- const original = this.cleanDashboardFromIgnoredChanges(this.original);
- const currentTimepicker = _.find(current.nav, { type: 'timepicker' });
- const originalTimepicker = _.find(original.nav, { type: 'timepicker' });
- if (currentTimepicker && originalTimepicker) {
- currentTimepicker.now = originalTimepicker.now;
- }
- const currentJson = angular.toJson(current, true);
- const originalJson = angular.toJson(original, true);
- return currentJson !== originalJson;
- }
- discardChanges() {
- this.original = null;
- this.gotoNext();
- }
- open_modal() {
- this.$rootScope.appEvent('show-modal', {
- templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
- modalClass: 'modal--narrow confirm-modal',
- });
- }
- saveChanges() {
- const self = this;
- const cancel = this.$rootScope.$on('dashboard-saved', () => {
- cancel();
- this.$timeout(() => {
- self.gotoNext();
- });
- });
- this.$rootScope.appEvent('save-dashboard');
- }
- gotoNext() {
- const baseLen = this.$location.absUrl().length - this.$location.url().length;
- const nextUrl = this.next.substring(baseLen);
- this.$location.url(nextUrl);
- }
- }
|