grafana/public/app/features/dashboard/unsavedChangesSrv.js

176 lines
4.7 KiB
JavaScript

define([
'angular',
'lodash',
],
function(angular, _) {
'use strict';
var module = angular.module('grafana.services');
module.service('unsavedChangesSrv', function($rootScope, $q, $location, $timeout, contextSrv, $window) {
function Tracker(dashboard, scope, originalCopyDelay) {
var self = this;
this.current = dashboard;
this.originalPath = $location.path();
this.scope = scope;
// register events
scope.onAppEvent('dashboard-saved', function() {
this.original = this.current.getSaveModelClone();
this.originalPath = $location.path();
}.bind(this));
$window.onbeforeunload = function() {
if (self.ignoreChanges()) { return; }
if (self.hasChanges()) {
return "There are unsaved changes to this dashboard";
}
};
scope.$on("$locationChangeStart", function(event, next) {
// check if we should look for changes
if (self.originalPath === $location.path()) { return true; }
if (self.ignoreChanges()) { return true; }
if (self.hasChanges()) {
event.preventDefault();
self.next = next;
$timeout(function() {
self.open_modal();
});
}
});
if (originalCopyDelay) {
$timeout(function() {
// wait for different services to patch the dashboard (missing properties)
self.original = dashboard.getSaveModelClone();
}, originalCopyDelay);
} else {
self.original = dashboard.getSaveModelClone();
}
}
var p = Tracker.prototype;
// for some dashboards and users
// changes should be ignored
p.ignoreChanges = function() {
if (!this.original) { return true; }
if (!contextSrv.isEditor) { return true; }
if (!this.current || !this.current.meta) { return true; }
var meta = this.current.meta;
return !meta.canSave || meta.fromScript || meta.fromFile;
};
// remove stuff that should not count in diff
p.cleanDashboardFromIgnoredChanges = function(dash) {
// ignore time and refresh
dash.time = 0;
dash.refresh = 0;
dash.schemaVersion = 0;
dash.editMode = false;
// filter row and panels properties that should be ignored
dash.rows = _.filter(dash.rows, function(row) {
if (row.repeatRowId) {
return false;
}
row.panels = _.filter(row.panels, function(panel) {
if (panel.repeatPanelId) {
return false;
}
// remove scopedVars
panel.scopedVars = null;
// ignore span changes
panel.span = null;
// ignore panel legend sort
if (panel.legend) {
delete panel.legend.sort;
delete panel.legend.sortDesc;
}
return true;
});
// ignore collapse state
row.collapse = false;
return true;
});
// ignore template variable values
_.each(dash.templating.list, function(value) {
value.current = null;
value.options = null;
value.filters = null;
});
};
p.hasChanges = function() {
var current = this.current.getSaveModelClone();
var original = this.original;
this.cleanDashboardFromIgnoredChanges(current);
this.cleanDashboardFromIgnoredChanges(original);
var currentTimepicker = _.find(current.nav, { type: 'timepicker' });
var originalTimepicker = _.find(original.nav, { type: 'timepicker' });
if (currentTimepicker && originalTimepicker) {
currentTimepicker.now = originalTimepicker.now;
}
var currentJson = angular.toJson(current);
var originalJson = angular.toJson(original);
return currentJson !== originalJson;
};
p.open_modal = function() {
var tracker = this;
var modalScope = this.scope.$new();
modalScope.ignore = function() {
tracker.original = null;
tracker.goto_next();
};
modalScope.save = function() {
var cancel = $rootScope.$on('dashboard-saved', function() {
cancel();
$timeout(function() {
tracker.goto_next();
});
});
$rootScope.$emit('save-dashboard');
};
$rootScope.appEvent('show-modal', {
src: 'public/app/partials/unsaved-changes.html',
modalClass: 'confirm-modal',
scope: modalScope,
});
};
p.goto_next = function() {
var baseLen = $location.absUrl().length - $location.url().length;
var nextUrl = this.next.substring(baseLen);
$location.url(nextUrl);
};
this.Tracker = Tracker;
this.init = function(dashboard, scope) {
return new Tracker(dashboard, scope, 1000);
};
});
});