mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
220 lines
5.2 KiB
TypeScript
220 lines
5.2 KiB
TypeScript
import angular from 'angular';
|
|
import _ from 'lodash';
|
|
|
|
export class Tracker {
|
|
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;
|
|
}
|
|
|
|
var meta = this.current.meta;
|
|
return !meta.canSave || meta.fromScript || meta.fromFile;
|
|
}
|
|
|
|
// remove stuff that should not count in diff
|
|
cleanDashboardFromIgnoredChanges(dash) {
|
|
// ignore time and refresh
|
|
dash.time = 0;
|
|
dash.refresh = 0;
|
|
dash.schemaVersion = 0;
|
|
|
|
// ignore iteration property
|
|
delete dash.iteration;
|
|
|
|
// 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;
|
|
});
|
|
|
|
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, function(value) {
|
|
value.current = null;
|
|
value.options = null;
|
|
value.filters = null;
|
|
});
|
|
}
|
|
|
|
hasChanges() {
|
|
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;
|
|
}
|
|
|
|
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() {
|
|
var self = this;
|
|
var cancel = this.$rootScope.$on('dashboard-saved', () => {
|
|
cancel();
|
|
this.$timeout(() => {
|
|
self.gotoNext();
|
|
});
|
|
});
|
|
|
|
this.$rootScope.appEvent('save-dashboard');
|
|
}
|
|
|
|
gotoNext() {
|
|
var baseLen = this.$location.absUrl().length - this.$location.url().length;
|
|
var nextUrl = this.next.substring(baseLen);
|
|
this.$location.url(nextUrl);
|
|
}
|
|
}
|
|
|
|
/** @ngInject */
|
|
export function unsavedChangesSrv($rootScope, $q, $location, $timeout, contextSrv, dashboardSrv, $window) {
|
|
this.Tracker = Tracker;
|
|
this.init = function(dashboard, scope) {
|
|
this.tracker = new Tracker(dashboard, scope, 1000, $location, $window, $timeout, contextSrv, $rootScope);
|
|
return this.tracker;
|
|
};
|
|
}
|
|
|
|
angular.module('grafana.services').service('unsavedChangesSrv', unsavedChangesSrv);
|