mirror of https://github.com/grafana/grafana
parent
af34f9977e
commit
9b63a81756
@ -1,109 +0,0 @@ |
||||
define([ |
||||
'angular', |
||||
'moment', |
||||
'lodash', |
||||
'jquery', |
||||
'app/core/utils/kbn', |
||||
'app/core/utils/datemath', |
||||
'app/core/services/impression_srv' |
||||
], |
||||
function (angular, moment, _, $, kbn, dateMath, impressionSrv) { |
||||
'use strict'; |
||||
|
||||
kbn = kbn.default; |
||||
impressionSrv = impressionSrv.default; |
||||
|
||||
var module = angular.module('grafana.services'); |
||||
|
||||
module.service('dashboardLoaderSrv', function(backendSrv, |
||||
dashboardSrv, |
||||
datasourceSrv, |
||||
$http, $q, $timeout, |
||||
contextSrv, $routeParams, |
||||
$rootScope) { |
||||
var self = this; |
||||
|
||||
this._dashboardLoadFailed = function(title, snapshot) { |
||||
snapshot = snapshot || false; |
||||
return { |
||||
meta: { canStar: false, isSnapshot: snapshot, canDelete: false, canSave: false, canEdit: false, dashboardNotFound: true }, |
||||
dashboard: {title: title } |
||||
}; |
||||
}; |
||||
|
||||
this.loadDashboard = function(type, slug) { |
||||
var promise; |
||||
|
||||
if (type === 'script') { |
||||
promise = this._loadScriptedDashboard(slug); |
||||
} else if (type === 'snapshot') { |
||||
promise = backendSrv.get('/api/snapshots/' + $routeParams.slug) |
||||
.catch(function() { |
||||
return self._dashboardLoadFailed("Snapshot not found", true); |
||||
}); |
||||
} else { |
||||
promise = backendSrv.getDashboard($routeParams.type, $routeParams.slug) |
||||
.then(function(result) { |
||||
if (result.meta.isFolder) { |
||||
$rootScope.appEvent("alert-error", ['Dashboard not found']); |
||||
throw new Error("Dashboard not found"); |
||||
} |
||||
return result; |
||||
}) |
||||
.catch(function() { |
||||
return self._dashboardLoadFailed("Not found"); |
||||
}); |
||||
} |
||||
|
||||
promise.then(function(result) { |
||||
|
||||
if (result.meta.dashboardNotFound !== true) { |
||||
impressionSrv.addDashboardImpression(result.dashboard.id); |
||||
} |
||||
|
||||
return result; |
||||
}); |
||||
|
||||
return promise; |
||||
}; |
||||
|
||||
this._loadScriptedDashboard = function(file) { |
||||
var url = 'public/dashboards/'+file.replace(/\.(?!js)/,"/") + '?' + new Date().getTime(); |
||||
|
||||
return $http({ url: url, method: "GET" }) |
||||
.then(this._executeScript).then(function(result) { |
||||
return { meta: { fromScript: true, canDelete: false, canSave: false, canStar: false}, dashboard: result.data }; |
||||
}, function(err) { |
||||
console.log('Script dashboard error '+ err); |
||||
$rootScope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]); |
||||
return self._dashboardLoadFailed('Scripted dashboard'); |
||||
}); |
||||
}; |
||||
|
||||
this._executeScript = function(result) { |
||||
var services = { |
||||
dashboardSrv: dashboardSrv, |
||||
datasourceSrv: datasourceSrv, |
||||
$q: $q, |
||||
}; |
||||
|
||||
/*jshint -W054 */ |
||||
var script_func = new Function('ARGS','kbn','dateMath','_','moment','window','document','$','jQuery', 'services', result.data); |
||||
var script_result = script_func($routeParams, kbn, dateMath, _ , moment, window, document, $, $, services); |
||||
|
||||
// Handle async dashboard scripts
|
||||
if (_.isFunction(script_result)) { |
||||
var deferred = $q.defer(); |
||||
script_result(function(dashboard) { |
||||
$timeout(function() { |
||||
deferred.resolve({ data: dashboard }); |
||||
}); |
||||
}); |
||||
return deferred.promise; |
||||
} |
||||
|
||||
return { data: script_result }; |
||||
}; |
||||
|
||||
}); |
||||
}); |
@ -0,0 +1,158 @@ |
||||
import angular from 'angular'; |
||||
import moment from 'moment'; |
||||
import _ from 'lodash'; |
||||
import $ from 'jquery'; |
||||
import kbn from 'app/core/utils/kbn'; |
||||
import * as dateMath from 'app/core/utils/datemath'; |
||||
import impressionSrv from 'app/core/services/impression_srv'; |
||||
|
||||
export class DashboardLoaderSrv { |
||||
/** @ngInject */ |
||||
constructor( |
||||
private backendSrv, |
||||
private dashboardSrv, |
||||
private datasourceSrv, |
||||
private $http, |
||||
private $q, |
||||
private $timeout, |
||||
contextSrv, |
||||
private $routeParams, |
||||
private $rootScope |
||||
) {} |
||||
|
||||
_dashboardLoadFailed(title, snapshot) { |
||||
snapshot = snapshot || false; |
||||
return { |
||||
meta: { |
||||
canStar: false, |
||||
isSnapshot: snapshot, |
||||
canDelete: false, |
||||
canSave: false, |
||||
canEdit: false, |
||||
dashboardNotFound: true, |
||||
}, |
||||
dashboard: { title: title }, |
||||
}; |
||||
} |
||||
|
||||
loadDashboard(type, slug) { |
||||
var promise; |
||||
|
||||
if (type === 'script') { |
||||
promise = this._loadScriptedDashboard(slug); |
||||
} else if (type === 'snapshot') { |
||||
promise = this.backendSrv |
||||
.get('/api/snapshots/' + this.$routeParams.slug) |
||||
.catch(() => { |
||||
return this._dashboardLoadFailed('Snapshot not found', true); |
||||
}); |
||||
} else { |
||||
promise = this.backendSrv |
||||
.getDashboard(this.$routeParams.type, this.$routeParams.slug) |
||||
.then(result => { |
||||
if (result.meta.isFolder) { |
||||
this.$rootScope.appEvent('alert-error', ['Dashboard not found']); |
||||
throw new Error('Dashboard not found'); |
||||
} |
||||
return result; |
||||
}) |
||||
.catch(() => { |
||||
return this._dashboardLoadFailed('Not found', true); |
||||
}); |
||||
} |
||||
|
||||
promise.then(function(result) { |
||||
if (result.meta.dashboardNotFound !== true) { |
||||
impressionSrv.addDashboardImpression(result.dashboard.id); |
||||
} |
||||
|
||||
return result; |
||||
}); |
||||
|
||||
return promise; |
||||
} |
||||
|
||||
_loadScriptedDashboard(file) { |
||||
var url = |
||||
'public/dashboards/' + |
||||
file.replace(/\.(?!js)/, '/') + |
||||
'?' + |
||||
new Date().getTime(); |
||||
|
||||
return this.$http({ url: url, method: 'GET' }) |
||||
.then(this._executeScript) |
||||
.then( |
||||
function(result) { |
||||
return { |
||||
meta: { |
||||
fromScript: true, |
||||
canDelete: false, |
||||
canSave: false, |
||||
canStar: false, |
||||
}, |
||||
dashboard: result.data, |
||||
}; |
||||
}, |
||||
function(err) { |
||||
console.log('Script dashboard error ' + err); |
||||
this.$rootScope.appEvent('alert-error', [ |
||||
'Script Error', |
||||
'Please make sure it exists and returns a valid dashboard', |
||||
]); |
||||
return this._dashboardLoadFailed('Scripted dashboard'); |
||||
} |
||||
); |
||||
} |
||||
|
||||
_executeScript(result) { |
||||
var services = { |
||||
dashboardSrv: this.dashboardSrv, |
||||
datasourceSrv: this.datasourceSrv, |
||||
$q: this.$q, |
||||
}; |
||||
|
||||
/*jshint -W054 */ |
||||
var script_func = new Function( |
||||
'ARGS', |
||||
'kbn', |
||||
'dateMath', |
||||
'_', |
||||
'moment', |
||||
'window', |
||||
'document', |
||||
'$', |
||||
'jQuery', |
||||
'services', |
||||
result.data |
||||
); |
||||
var script_result = script_func( |
||||
this.$routeParams, |
||||
kbn, |
||||
dateMath, |
||||
_, |
||||
moment, |
||||
window, |
||||
document, |
||||
$, |
||||
$, |
||||
services |
||||
); |
||||
|
||||
// Handle async dashboard scripts
|
||||
if (_.isFunction(script_result)) { |
||||
var deferred = this.$q.defer(); |
||||
script_result(dashboard => { |
||||
this.$timeout(() => { |
||||
deferred.resolve({ data: dashboard }); |
||||
}); |
||||
}); |
||||
return deferred.promise; |
||||
} |
||||
|
||||
return { data: script_result }; |
||||
} |
||||
} |
||||
|
||||
angular |
||||
.module('grafana.services') |
||||
.service('dashboardLoaderSrv', DashboardLoaderSrv); |
@ -1,189 +0,0 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash', |
||||
], |
||||
function(angular, _) { |
||||
'use strict'; |
||||
|
||||
var module = angular.module('grafana.services'); |
||||
|
||||
module.service('unsavedChangesSrv', function($rootScope, $q, $location, $timeout, contextSrv, dashboardSrv, $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; |
||||
|
||||
// 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, function(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; |
||||
}); |
||||
}; |
||||
|
||||
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.discardChanges = function() { |
||||
this.original = null; |
||||
this.gotoNext(); |
||||
}; |
||||
|
||||
p.open_modal = function() { |
||||
$rootScope.appEvent('show-modal', { |
||||
templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>', |
||||
modalClass: 'modal--narrow confirm-modal' |
||||
}); |
||||
}; |
||||
|
||||
p.saveChanges = function() { |
||||
var self = this; |
||||
var cancel = $rootScope.$on('dashboard-saved', function() { |
||||
cancel(); |
||||
$timeout(function() { |
||||
self.gotoNext(); |
||||
}); |
||||
}); |
||||
|
||||
$rootScope.appEvent('save-dashboard'); |
||||
}; |
||||
|
||||
p.gotoNext = 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) { |
||||
this.tracker = new Tracker(dashboard, scope, 1000); |
||||
return this.tracker; |
||||
}; |
||||
}); |
||||
}); |
@ -0,0 +1,236 @@ |
||||
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 ''; |
||||
} |
||||
if (this.hasChanges()) { |
||||
return 'There are unsaved changes to this dashboard'; |
||||
} |
||||
return ''; |
||||
}; |
||||
|
||||
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; |
||||
|
||||
// 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); |
Loading…
Reference in new issue