mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(plugin-editors): more work on plugin editor loading
This commit is contained in:
parent
30a8a434a1
commit
12f487e223
@ -16,6 +16,8 @@ import "./directives/password_strenght";
|
|||||||
import "./directives/spectrum_picker";
|
import "./directives/spectrum_picker";
|
||||||
import "./directives/tags";
|
import "./directives/tags";
|
||||||
import "./directives/value_select_dropdown";
|
import "./directives/value_select_dropdown";
|
||||||
|
import "./directives/plugin_directive_loader";
|
||||||
|
import "./directives/rebuild_on_change";
|
||||||
import "./directives/give_focus";
|
import "./directives/give_focus";
|
||||||
import './jquery_extended';
|
import './jquery_extended';
|
||||||
import './partials';
|
import './partials';
|
||||||
|
92
public/app/core/directives/plugin_directive_loader.ts
Normal file
92
public/app/core/directives/plugin_directive_loader.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import coreModule from '../core_module';
|
||||||
|
|
||||||
|
function pluginDirectiveLoader($compile, datasourceSrv) {
|
||||||
|
|
||||||
|
function getPluginComponentDirective(options) {
|
||||||
|
return function() {
|
||||||
|
return {
|
||||||
|
templateUrl: options.Component.templateUrl,
|
||||||
|
restrict: 'E',
|
||||||
|
controller: options.Component,
|
||||||
|
controllerAs: 'ctrl',
|
||||||
|
bindToController: true,
|
||||||
|
scope: options.bindings,
|
||||||
|
link: (scope, elem, attrs, ctrl) => {
|
||||||
|
if (ctrl.link) {
|
||||||
|
ctrl.link(scope, elem, attrs, ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(scope, attrs) {
|
||||||
|
switch (attrs.type) {
|
||||||
|
case "metrics-query-editor":
|
||||||
|
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
|
||||||
|
return datasourceSrv.get(datasource).then(ds => {
|
||||||
|
if (!scope.target.refId) {
|
||||||
|
scope.target.refId = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.import(ds.meta.module).then(dsModule => {
|
||||||
|
return {
|
||||||
|
name: 'metrics-query-editor-' + ds.meta.id,
|
||||||
|
bindings: {target: "=", panelCtrl: "="},
|
||||||
|
attrs: {"target": "target", "panel-ctrl": "ctrl"},
|
||||||
|
Component: dsModule.MetricsQueryEditor
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
case 'datasource-config-view':
|
||||||
|
return System.import(scope.datasourceMeta.module).then(function(dsModule) {
|
||||||
|
return {
|
||||||
|
name: 'ds-config-' + scope.datasourceMeta.id,
|
||||||
|
bindings: {meta: "=", current: "="},
|
||||||
|
attrs: {meta: "datasourceMeta", current: "current"},
|
||||||
|
Component: dsModule.ConfigView,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendAndCompile(scope, elem, componentInfo) {
|
||||||
|
console.log('compile', elem, componentInfo);
|
||||||
|
var child = angular.element(document.createElement(componentInfo.name));
|
||||||
|
_.each(componentInfo.attrs, (value, key) => {
|
||||||
|
child.attr(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$compile(child)(scope);
|
||||||
|
|
||||||
|
elem.empty();
|
||||||
|
elem.append(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerPluginComponent(scope, elem, attrs, componentInfo) {
|
||||||
|
if (!componentInfo.Component.registered) {
|
||||||
|
var directiveName = attrs.$normalize(componentInfo.name);
|
||||||
|
var directiveFn = getPluginComponentDirective(componentInfo);
|
||||||
|
coreModule.directive(directiveName, directiveFn);
|
||||||
|
componentInfo.Component.registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
appendAndCompile(scope, elem, componentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
link: function(scope, elem, attrs) {
|
||||||
|
getModule(scope, attrs).then(function (componentInfo) {
|
||||||
|
registerPluginComponent(scope, elem, attrs, componentInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.directive('pluginDirectiveLoader', pluginDirectiveLoader);
|
58
public/app/core/directives/rebuild_on_change.ts
Normal file
58
public/app/core/directives/rebuild_on_change.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import coreModule from '../core_module';
|
||||||
|
|
||||||
|
function getBlockNodes(nodes) {
|
||||||
|
var node = nodes[0];
|
||||||
|
var endNode = nodes[nodes.length - 1];
|
||||||
|
var blockNodes;
|
||||||
|
|
||||||
|
for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
|
||||||
|
if (blockNodes || nodes[i] !== node) {
|
||||||
|
if (!blockNodes) {
|
||||||
|
blockNodes = $([].slice.call(nodes, 0, i));
|
||||||
|
}
|
||||||
|
blockNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockNodes || nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildOnChange($compile) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
transclude: true,
|
||||||
|
priority: 600,
|
||||||
|
restrict: 'A',
|
||||||
|
link: function(scope, elem, attrs, ctrl, transclude) {
|
||||||
|
var childScope, previousElements;
|
||||||
|
var uncompiledHtml;
|
||||||
|
|
||||||
|
scope.$watch(attrs.rebuildOnChange, function rebuildOnChangeAction(value) {
|
||||||
|
|
||||||
|
if (childScope) {
|
||||||
|
childScope.$destroy();
|
||||||
|
childScope = null;
|
||||||
|
elem.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
if (!childScope) {
|
||||||
|
transclude(function(clone, newScope) {
|
||||||
|
childScope = newScope;
|
||||||
|
elem.append($compile(clone)(childScope));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
coreModule.directive('rebuildOnChange', rebuildOnChange);
|
@ -41,7 +41,12 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ds-config-view ng-if="datasourceMeta.id" ds-meta="datasourceMeta" current="current"></ds-config-view>
|
<div rebuild-on-change="datasourceMeta.id">
|
||||||
|
<plugin-directive-loader type="datasource-config-view">
|
||||||
|
</plugin-directive-loader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <ds-config-view ds-meta="datasourceMeta" current="current"></ds-config-view> -->
|
||||||
|
|
||||||
<div ng-if="testing" style="margin-top: 25px">
|
<div ng-if="testing" style="margin-top: 25px">
|
||||||
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||||
|
@ -50,7 +50,7 @@ var module = angular.module('grafana.directives');
|
|||||||
module.directive('grafanaPanel', function() {
|
module.directive('grafanaPanel', function() {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: 'app/features/panel/partials/panel.html',
|
templateUrl: 'public/app/features/panel/partials/panel.html',
|
||||||
transclude: true,
|
transclude: true,
|
||||||
scope: { ctrl: "=" },
|
scope: { ctrl: "=" },
|
||||||
link: function(scope, elem) {
|
link: function(scope, elem) {
|
||||||
|
@ -5,105 +5,6 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
var directivesModule = angular.module('grafana.directives');
|
var directivesModule = angular.module('grafana.directives');
|
||||||
|
|
||||||
function pluginDirectiveLoader($compile, datasourceSrv) {
|
|
||||||
|
|
||||||
function getPluginComponentDirective(options) {
|
|
||||||
return function() {
|
|
||||||
return {
|
|
||||||
templateUrl: options.Component.templateUrl,
|
|
||||||
restrict: 'E',
|
|
||||||
controller: options.Component,
|
|
||||||
controllerAs: 'ctrl',
|
|
||||||
bindToController: true,
|
|
||||||
scope: options.bindings,
|
|
||||||
link: (scope, elem, attrs, ctrl) => {
|
|
||||||
if (ctrl.link) {
|
|
||||||
ctrl.link(scope, elem, attrs, ctrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModule(scope, attrs) {
|
|
||||||
switch (attrs.type) {
|
|
||||||
case "metrics-query-editor": {
|
|
||||||
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
|
|
||||||
return datasourceSrv.get(datasource).then(ds => {
|
|
||||||
if (!scope.target.refId) {
|
|
||||||
scope.target.refId = 'A';
|
|
||||||
}
|
|
||||||
|
|
||||||
return System.import(ds.meta.module).then(dsModule => {
|
|
||||||
return {
|
|
||||||
name: 'metrics-query-editor-' + ds.meta.id,
|
|
||||||
bindings: {target: "=", panelCtrl: "="},
|
|
||||||
attrs: {"target": "target", "panel-ctrl": "ctrl"},
|
|
||||||
Component: dsModule.MetricsQueryEditor
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendAndCompile(scope, elem, componentInfo) {
|
|
||||||
var child = angular.element(document.createElement(componentInfo.name));
|
|
||||||
_.each(componentInfo.attrs, (value, key) => {
|
|
||||||
child.attr(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
$compile(child)(scope);
|
|
||||||
|
|
||||||
elem.empty();
|
|
||||||
elem.append(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerPluginComponent(scope, elem, attrs, componentInfo) {
|
|
||||||
if (!componentInfo.Component.registered) {
|
|
||||||
var directiveName = attrs.$normalize(componentInfo.name);
|
|
||||||
var directiveFn = getPluginComponentDirective(componentInfo);
|
|
||||||
directivesModule.directive(directiveName, directiveFn);
|
|
||||||
componentInfo.Component.registered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendAndCompile(scope, elem, componentInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, elem, attrs) {
|
|
||||||
getModule(scope, attrs).then(function (componentInfo) {
|
|
||||||
registerPluginComponent(scope, elem, attrs, componentInfo);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
|
|
||||||
return dynamicDirectiveSrv.create({
|
|
||||||
watchPath: "ctrl.panel.datasource",
|
|
||||||
directive: scope => {
|
|
||||||
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
|
|
||||||
return datasourceSrv.get(datasource).then(ds => {
|
|
||||||
scope.datasource = ds;
|
|
||||||
|
|
||||||
if (!scope.target.refId) {
|
|
||||||
scope.target.refId = 'A';
|
|
||||||
}
|
|
||||||
|
|
||||||
return System.import(ds.meta.module).then(dsModule => {
|
|
||||||
return {
|
|
||||||
name: 'metrics-query-editor-' + ds.meta.id,
|
|
||||||
fn: dsModule.metricsQueryEditor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
|
function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
|
||||||
return dynamicDirectiveSrv.create({
|
return dynamicDirectiveSrv.create({
|
||||||
@ -121,6 +22,4 @@ function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
directivesModule.directive('pluginDirectiveLoader', pluginDirectiveLoader);
|
|
||||||
directivesModule.directive('metricsQueryEditor', metricsQueryEditor);
|
|
||||||
directivesModule.directive('metricsQueryOptions', metricsQueryOptions);
|
directivesModule.directive('metricsQueryOptions', metricsQueryOptions);
|
||||||
|
@ -23,11 +23,16 @@ function (GraphiteDatasource) {
|
|||||||
return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'};
|
return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConfigView() {
|
||||||
|
}
|
||||||
|
ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Datasource: GraphiteDatasource,
|
Datasource: GraphiteDatasource,
|
||||||
configView: configView,
|
configView: configView,
|
||||||
annotationsQueryEditor: annotationsQueryEditor,
|
annotationsQueryEditor: annotationsQueryEditor,
|
||||||
metricsQueryEditor: metricsQueryEditor,
|
metricsQueryEditor: metricsQueryEditor,
|
||||||
metricsQueryOptions: metricsQueryOptions,
|
metricsQueryOptions: metricsQueryOptions,
|
||||||
|
ConfigView: ConfigView
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
declare var Datasource: any;
|
|
||||||
export default Datasource;
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
|||||||
define([
|
|
||||||
'angular',
|
|
||||||
'lodash',
|
|
||||||
'moment',
|
|
||||||
'app/core/utils/datemath',
|
|
||||||
'./query_ctrl',
|
|
||||||
],
|
|
||||||
function (angular, _, moment, dateMath) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
|
||||||
this.type = 'prometheus';
|
|
||||||
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
|
|
||||||
this.name = instanceSettings.name;
|
|
||||||
this.supportMetrics = true;
|
|
||||||
this.url = instanceSettings.url;
|
|
||||||
this.directUrl = instanceSettings.directUrl;
|
|
||||||
this.basicAuth = instanceSettings.basicAuth;
|
|
||||||
this.withCredentials = instanceSettings.withCredentials;
|
|
||||||
this.lastErrors = {};
|
|
||||||
|
|
||||||
this._request = function(method, url) {
|
|
||||||
var options = {
|
|
||||||
url: this.url + url,
|
|
||||||
method: method
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.basicAuth || this.withCredentials) {
|
|
||||||
options.withCredentials = true;
|
|
||||||
}
|
|
||||||
if (this.basicAuth) {
|
|
||||||
options.headers = {
|
|
||||||
"Authorization": this.basicAuth
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return backendSrv.datasourceRequest(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Called once per panel (graph)
|
|
||||||
this.query = function(options) {
|
|
||||||
var start = getPrometheusTime(options.range.from, false);
|
|
||||||
var end = getPrometheusTime(options.range.to, true);
|
|
||||||
|
|
||||||
var queries = [];
|
|
||||||
options = _.clone(options);
|
|
||||||
_.each(options.targets, _.bind(function(target) {
|
|
||||||
if (!target.expr || target.hide) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = {};
|
|
||||||
query.expr = templateSrv.replace(target.expr, options.scopedVars);
|
|
||||||
|
|
||||||
var interval = target.interval || options.interval;
|
|
||||||
var intervalFactor = target.intervalFactor || 1;
|
|
||||||
target.step = query.step = this.calculateInterval(interval, intervalFactor);
|
|
||||||
var range = Math.ceil(end - start);
|
|
||||||
// Prometheus drop query if range/step > 11000
|
|
||||||
// calibrate step if it is too big
|
|
||||||
if (query.step !== 0 && range / query.step > 11000) {
|
|
||||||
target.step = query.step = Math.ceil(range / 11000);
|
|
||||||
}
|
|
||||||
|
|
||||||
queries.push(query);
|
|
||||||
}, this));
|
|
||||||
|
|
||||||
// No valid targets, return the empty result to save a round trip.
|
|
||||||
if (_.isEmpty(queries)) {
|
|
||||||
var d = $q.defer();
|
|
||||||
d.resolve({ data: [] });
|
|
||||||
return d.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allQueryPromise = _.map(queries, _.bind(function(query) {
|
|
||||||
return this.performTimeSeriesQuery(query, start, end);
|
|
||||||
}, this));
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
return $q.all(allQueryPromise)
|
|
||||||
.then(function(allResponse) {
|
|
||||||
var result = [];
|
|
||||||
|
|
||||||
_.each(allResponse, function(response, index) {
|
|
||||||
if (response.status === 'error') {
|
|
||||||
self.lastErrors.query = response.error;
|
|
||||||
throw response.error;
|
|
||||||
}
|
|
||||||
delete self.lastErrors.query;
|
|
||||||
|
|
||||||
_.each(response.data.data.result, function(metricData) {
|
|
||||||
result.push(transformMetricData(metricData, options.targets[index], start, end));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return { data: result };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.performTimeSeriesQuery = function(query, start, end) {
|
|
||||||
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
|
|
||||||
return this._request('GET', url);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.performSuggestQuery = function(query) {
|
|
||||||
var url = '/api/v1/label/__name__/values';
|
|
||||||
|
|
||||||
return this._request('GET', url).then(function(result) {
|
|
||||||
return _.filter(result.data.data, function (metricName) {
|
|
||||||
return metricName.indexOf(query) !== 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.metricFindQuery = function(query) {
|
|
||||||
if (!query) { return $q.when([]); }
|
|
||||||
|
|
||||||
var interpolated;
|
|
||||||
try {
|
|
||||||
interpolated = templateSrv.replace(query);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return $q.reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/;
|
|
||||||
var metric_names_regex = /^metrics\((.+)\)$/;
|
|
||||||
|
|
||||||
var url;
|
|
||||||
var label_values_query = interpolated.match(label_values_regex);
|
|
||||||
if (label_values_query) {
|
|
||||||
if (!label_values_query[2]) {
|
|
||||||
// return label values globally
|
|
||||||
url = '/api/v1/label/' + label_values_query[1] + '/values';
|
|
||||||
|
|
||||||
return this._request('GET', url).then(function(result) {
|
|
||||||
return _.map(result.data.data, function(value) {
|
|
||||||
return {text: value};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
url = '/api/v1/series?match[]=' + encodeURIComponent(label_values_query[1]);
|
|
||||||
|
|
||||||
return this._request('GET', url)
|
|
||||||
.then(function(result) {
|
|
||||||
return _.map(result.data.data, function(metric) {
|
|
||||||
return {
|
|
||||||
text: metric[label_values_query[2]],
|
|
||||||
expandable: true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var metric_names_query = interpolated.match(metric_names_regex);
|
|
||||||
if (metric_names_query) {
|
|
||||||
url = '/api/v1/label/__name__/values';
|
|
||||||
|
|
||||||
return this._request('GET', url)
|
|
||||||
.then(function(result) {
|
|
||||||
return _.chain(result.data.data)
|
|
||||||
.filter(function(metricName) {
|
|
||||||
var r = new RegExp(metric_names_query[1]);
|
|
||||||
return r.test(metricName);
|
|
||||||
})
|
|
||||||
.map(function(matchedMetricName) {
|
|
||||||
return {
|
|
||||||
text: matchedMetricName,
|
|
||||||
expandable: true
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.value();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// if query contains full metric name, return metric name and label list
|
|
||||||
url = '/api/v1/series?match[]=' + encodeURIComponent(interpolated);
|
|
||||||
|
|
||||||
return this._request('GET', url)
|
|
||||||
.then(function(result) {
|
|
||||||
return _.map(result.data.data, function(metric) {
|
|
||||||
return {
|
|
||||||
text: getOriginalMetricName(metric),
|
|
||||||
expandable: true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.testDatasource = function() {
|
|
||||||
return this.metricFindQuery('metrics(.*)').then(function() {
|
|
||||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) {
|
|
||||||
var m = interval.match(durationSplitRegexp);
|
|
||||||
var dur = moment.duration(parseInt(m[1]), m[2]);
|
|
||||||
var sec = dur.asSeconds();
|
|
||||||
if (sec < 1) {
|
|
||||||
sec = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.ceil(sec * intervalFactor);
|
|
||||||
};
|
|
||||||
|
|
||||||
function transformMetricData(md, options, start, end) {
|
|
||||||
var dps = [],
|
|
||||||
metricLabel = null;
|
|
||||||
|
|
||||||
metricLabel = createMetricLabel(md.metric, options);
|
|
||||||
|
|
||||||
var stepMs = parseInt(options.step) * 1000;
|
|
||||||
var baseTimestamp = start * 1000;
|
|
||||||
_.each(md.values, function(value) {
|
|
||||||
var dp_value = parseFloat(value[1]);
|
|
||||||
if (_.isNaN(dp_value)) {
|
|
||||||
dp_value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timestamp = value[0] * 1000;
|
|
||||||
for (var t = baseTimestamp; t < timestamp; t += stepMs) {
|
|
||||||
dps.push([null, t]);
|
|
||||||
}
|
|
||||||
baseTimestamp = timestamp + stepMs;
|
|
||||||
dps.push([dp_value, timestamp]);
|
|
||||||
});
|
|
||||||
|
|
||||||
var endTimestamp = end * 1000;
|
|
||||||
for (var t = baseTimestamp; t <= endTimestamp; t += stepMs) {
|
|
||||||
dps.push([null, t]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { target: metricLabel, datapoints: dps };
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMetricLabel(labelData, options) {
|
|
||||||
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
|
|
||||||
return getOriginalMetricName(labelData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var originalSettings = _.templateSettings;
|
|
||||||
_.templateSettings = {
|
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
|
||||||
};
|
|
||||||
|
|
||||||
var template = _.template(templateSrv.replace(options.legendFormat));
|
|
||||||
var metricName;
|
|
||||||
try {
|
|
||||||
metricName = template(labelData);
|
|
||||||
} catch (e) {
|
|
||||||
metricName = '{}';
|
|
||||||
}
|
|
||||||
|
|
||||||
_.templateSettings = originalSettings;
|
|
||||||
|
|
||||||
return metricName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOriginalMetricName(labelData) {
|
|
||||||
var metricName = labelData.__name__ || '';
|
|
||||||
delete labelData.__name__;
|
|
||||||
var labelPart = _.map(_.pairs(labelData), function(label) {
|
|
||||||
return label[0] + '="' + label[1] + '"';
|
|
||||||
}).join(',');
|
|
||||||
return metricName + '{' + labelPart + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrometheusTime(date, roundUp) {
|
|
||||||
if (_.isString(date)) {
|
|
||||||
date = dateMath.parse(date, roundUp);
|
|
||||||
}
|
|
||||||
return (date.valueOf() / 1000).toFixed(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PrometheusDatasource;
|
|
||||||
});
|
|
278
public/app/plugins/datasource/prometheus/datasource.ts
Normal file
278
public/app/plugins/datasource/prometheus/datasource.ts
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import * as dateMath from 'app/core/utils/datemath';
|
||||||
|
|
||||||
|
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||||
|
this.type = 'prometheus';
|
||||||
|
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
|
||||||
|
this.name = instanceSettings.name;
|
||||||
|
this.supportMetrics = true;
|
||||||
|
this.url = instanceSettings.url;
|
||||||
|
this.directUrl = instanceSettings.directUrl;
|
||||||
|
this.basicAuth = instanceSettings.basicAuth;
|
||||||
|
this.withCredentials = instanceSettings.withCredentials;
|
||||||
|
this.lastErrors = {};
|
||||||
|
|
||||||
|
this._request = function(method, url) {
|
||||||
|
var options: any = {
|
||||||
|
url: this.url + url,
|
||||||
|
method: method
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.basicAuth || this.withCredentials) {
|
||||||
|
options.withCredentials = true;
|
||||||
|
}
|
||||||
|
if (this.basicAuth) {
|
||||||
|
options.headers = {
|
||||||
|
"Authorization": this.basicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return backendSrv.datasourceRequest(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Called once per panel (graph)
|
||||||
|
this.query = function(options) {
|
||||||
|
var start = getPrometheusTime(options.range.from, false);
|
||||||
|
var end = getPrometheusTime(options.range.to, true);
|
||||||
|
|
||||||
|
var queries = [];
|
||||||
|
options = _.clone(options);
|
||||||
|
_.each(options.targets, _.bind(function(target) {
|
||||||
|
if (!target.expr || target.hide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query: any = {};
|
||||||
|
query.expr = templateSrv.replace(target.expr, options.scopedVars);
|
||||||
|
|
||||||
|
var interval = target.interval || options.interval;
|
||||||
|
var intervalFactor = target.intervalFactor || 1;
|
||||||
|
target.step = query.step = this.calculateInterval(interval, intervalFactor);
|
||||||
|
var range = Math.ceil(end - start);
|
||||||
|
// Prometheus drop query if range/step > 11000
|
||||||
|
// calibrate step if it is too big
|
||||||
|
if (query.step !== 0 && range / query.step > 11000) {
|
||||||
|
target.step = query.step = Math.ceil(range / 11000);
|
||||||
|
}
|
||||||
|
|
||||||
|
queries.push(query);
|
||||||
|
}, this));
|
||||||
|
|
||||||
|
// No valid targets, return the empty result to save a round trip.
|
||||||
|
if (_.isEmpty(queries)) {
|
||||||
|
var d = $q.defer();
|
||||||
|
d.resolve({ data: [] });
|
||||||
|
return d.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allQueryPromise = _.map(queries, _.bind(function(query) {
|
||||||
|
return this.performTimeSeriesQuery(query, start, end);
|
||||||
|
}, this));
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
return $q.all(allQueryPromise)
|
||||||
|
.then(function(allResponse) {
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
_.each(allResponse, function(response, index) {
|
||||||
|
if (response.status === 'error') {
|
||||||
|
self.lastErrors.query = response.error;
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
delete self.lastErrors.query;
|
||||||
|
|
||||||
|
_.each(response.data.data.result, function(metricData) {
|
||||||
|
result.push(transformMetricData(metricData, options.targets[index], start, end));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data: result };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.performTimeSeriesQuery = function(query, start, end) {
|
||||||
|
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
|
||||||
|
return this._request('GET', url);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.performSuggestQuery = function(query) {
|
||||||
|
var url = '/api/v1/label/__name__/values';
|
||||||
|
|
||||||
|
return this._request('GET', url).then(function(result) {
|
||||||
|
return _.filter(result.data.data, function (metricName) {
|
||||||
|
return metricName.indexOf(query) !== 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.metricFindQuery = function(query) {
|
||||||
|
if (!query) { return $q.when([]); }
|
||||||
|
|
||||||
|
var interpolated;
|
||||||
|
try {
|
||||||
|
interpolated = templateSrv.replace(query);
|
||||||
|
} catch (err) {
|
||||||
|
return $q.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/;
|
||||||
|
var metric_names_regex = /^metrics\((.+)\)$/;
|
||||||
|
|
||||||
|
var url;
|
||||||
|
var label_values_query = interpolated.match(label_values_regex);
|
||||||
|
if (label_values_query) {
|
||||||
|
if (!label_values_query[2]) {
|
||||||
|
// return label values globally
|
||||||
|
url = '/api/v1/label/' + label_values_query[1] + '/values';
|
||||||
|
|
||||||
|
return this._request('GET', url).then(function(result) {
|
||||||
|
return _.map(result.data.data, function(value) {
|
||||||
|
return {text: value};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
url = '/api/v1/series?match[]=' + encodeURIComponent(label_values_query[1]);
|
||||||
|
|
||||||
|
return this._request('GET', url)
|
||||||
|
.then(function(result) {
|
||||||
|
return _.map(result.data.data, function(metric) {
|
||||||
|
return {
|
||||||
|
text: metric[label_values_query[2]],
|
||||||
|
expandable: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var metric_names_query = interpolated.match(metric_names_regex);
|
||||||
|
if (metric_names_query) {
|
||||||
|
url = '/api/v1/label/__name__/values';
|
||||||
|
|
||||||
|
return this._request('GET', url)
|
||||||
|
.then(function(result) {
|
||||||
|
return _.chain(result.data.data)
|
||||||
|
.filter(function(metricName) {
|
||||||
|
var r = new RegExp(metric_names_query[1]);
|
||||||
|
return r.test(metricName);
|
||||||
|
})
|
||||||
|
.map(function(matchedMetricName) {
|
||||||
|
return {
|
||||||
|
text: matchedMetricName,
|
||||||
|
expandable: true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.value();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if query contains full metric name, return metric name and label list
|
||||||
|
url = '/api/v1/series?match[]=' + encodeURIComponent(interpolated);
|
||||||
|
|
||||||
|
return this._request('GET', url)
|
||||||
|
.then(function(result) {
|
||||||
|
return _.map(result.data.data, function(metric) {
|
||||||
|
return {
|
||||||
|
text: getOriginalMetricName(metric),
|
||||||
|
expandable: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testDatasource = function() {
|
||||||
|
return this.metricFindQuery('metrics(.*)').then(function() {
|
||||||
|
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) {
|
||||||
|
var m = interval.match(durationSplitRegexp);
|
||||||
|
var dur = moment.duration(parseInt(m[1]), m[2]);
|
||||||
|
var sec = dur.asSeconds();
|
||||||
|
if (sec < 1) {
|
||||||
|
sec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.ceil(sec * intervalFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformMetricData(md, options, start, end) {
|
||||||
|
var dps = [],
|
||||||
|
metricLabel = null;
|
||||||
|
|
||||||
|
metricLabel = createMetricLabel(md.metric, options);
|
||||||
|
|
||||||
|
var stepMs = parseInt(options.step) * 1000;
|
||||||
|
var baseTimestamp = start * 1000;
|
||||||
|
_.each(md.values, function(value) {
|
||||||
|
var dp_value = parseFloat(value[1]);
|
||||||
|
if (_.isNaN(dp_value)) {
|
||||||
|
dp_value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp = value[0] * 1000;
|
||||||
|
for (var t = baseTimestamp; t < timestamp; t += stepMs) {
|
||||||
|
dps.push([null, t]);
|
||||||
|
}
|
||||||
|
baseTimestamp = timestamp + stepMs;
|
||||||
|
dps.push([dp_value, timestamp]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var endTimestamp = end * 1000;
|
||||||
|
for (var t = baseTimestamp; t <= endTimestamp; t += stepMs) {
|
||||||
|
dps.push([null, t]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { target: metricLabel, datapoints: dps };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMetricLabel(labelData, options) {
|
||||||
|
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
|
||||||
|
return getOriginalMetricName(labelData);
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalSettings = _.templateSettings;
|
||||||
|
_.templateSettings = {
|
||||||
|
interpolate: /\{\{(.+?)\}\}/g
|
||||||
|
};
|
||||||
|
|
||||||
|
var template = _.template(templateSrv.replace(options.legendFormat));
|
||||||
|
var metricName;
|
||||||
|
try {
|
||||||
|
metricName = template(labelData);
|
||||||
|
} catch (e) {
|
||||||
|
metricName = '{}';
|
||||||
|
}
|
||||||
|
|
||||||
|
_.templateSettings = originalSettings;
|
||||||
|
|
||||||
|
return metricName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOriginalMetricName(labelData) {
|
||||||
|
var metricName = labelData.__name__ || '';
|
||||||
|
delete labelData.__name__;
|
||||||
|
var labelPart = _.map(_.pairs(labelData), function(label) {
|
||||||
|
return label[0] + '="' + label[1] + '"';
|
||||||
|
}).join(',');
|
||||||
|
return metricName + '{' + labelPart + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrometheusTime(date, roundUp): number {
|
||||||
|
if (_.isString(date)) {
|
||||||
|
date = dateMath.parse(date, roundUp);
|
||||||
|
}
|
||||||
|
return Math.floor(date.valueOf() / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {PrometheusDatasource};
|
@ -1,20 +0,0 @@
|
|||||||
define([
|
|
||||||
'./datasource',
|
|
||||||
],
|
|
||||||
function (PromDatasource) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function metricsQueryEditor() {
|
|
||||||
return {controller: 'PrometheusQueryCtrl', templateUrl: 'public/app/plugins/datasource/prometheus/partials/query.editor.html'};
|
|
||||||
}
|
|
||||||
|
|
||||||
function configView() {
|
|
||||||
return {templateUrl: 'public/app/plugins/datasource/prometheus/partials/config.html'};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
Datasource: PromDatasource,
|
|
||||||
metricsQueryEditor: metricsQueryEditor,
|
|
||||||
configView: configView,
|
|
||||||
};
|
|
||||||
});
|
|
23
public/app/plugins/datasource/prometheus/module.ts
Normal file
23
public/app/plugins/datasource/prometheus/module.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {PrometheusDatasource} from './datasource';
|
||||||
|
import {PrometheusQueryCtrl} from './query_ctrl';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// function metricsQueryEditor() {
|
||||||
|
// return {controller: 'PrometheusQueryCtrl', templateUrl: 'public/app/plugins/datasource/prometheus/partials/query.editor.html'};
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function configView() {
|
||||||
|
// return {templateUrl: ''};
|
||||||
|
// }
|
||||||
|
|
||||||
|
class PrometheusConfigViewCtrl {
|
||||||
|
static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
PrometheusDatasource as Datasource,
|
||||||
|
PrometheusQueryCtrl as MetricsQueryEditor,
|
||||||
|
PrometheusConfigViewCtrl as ConfigView
|
||||||
|
};
|
@ -1,67 +0,0 @@
|
|||||||
define([
|
|
||||||
'angular',
|
|
||||||
'lodash',
|
|
||||||
],
|
|
||||||
function (angular, _) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var module = angular.module('grafana.controllers');
|
|
||||||
|
|
||||||
module.controller('PrometheusQueryCtrl', function($scope, templateSrv) {
|
|
||||||
$scope.panelCtrl = $scope.ctrl;
|
|
||||||
$scope.panel = $scope.panelCtrl.panel;
|
|
||||||
|
|
||||||
$scope.init = function() {
|
|
||||||
var target = $scope.target;
|
|
||||||
|
|
||||||
target.expr = target.expr || '';
|
|
||||||
target.intervalFactor = target.intervalFactor || 2;
|
|
||||||
|
|
||||||
$scope.metric = '';
|
|
||||||
$scope.resolutions = _.map([1,2,3,4,5,10], function(f) {
|
|
||||||
return {factor: f, label: '1/' + f};
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('typeahead-updated', function() {
|
|
||||||
$scope.$apply($scope.inputMetric);
|
|
||||||
$scope.refreshMetricData();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.refreshMetricData = function() {
|
|
||||||
if (!_.isEqual($scope.oldTarget, $scope.target)) {
|
|
||||||
$scope.oldTarget = angular.copy($scope.target);
|
|
||||||
$scope.paneCtrl.refresh();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.inputMetric = function() {
|
|
||||||
$scope.target.expr += $scope.target.metric;
|
|
||||||
$scope.metric = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.suggestMetrics = function(query, callback) {
|
|
||||||
$scope.datasource
|
|
||||||
.performSuggestQuery(query)
|
|
||||||
.then(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.linkToPrometheus = function() {
|
|
||||||
var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000);
|
|
||||||
var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm');
|
|
||||||
var expr = {
|
|
||||||
expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars),
|
|
||||||
range_input: range + 's',
|
|
||||||
end_input: endTime,
|
|
||||||
step_input: '',
|
|
||||||
stacked: $scope.panel.stack,
|
|
||||||
tab: 0
|
|
||||||
};
|
|
||||||
var hash = encodeURIComponent(JSON.stringify([expr]));
|
|
||||||
return $scope.datasource.directUrl + '/graph#' + hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
66
public/app/plugins/datasource/prometheus/query_ctrl.ts
Normal file
66
public/app/plugins/datasource/prometheus/query_ctrl.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import * as dateMath from 'app/core/utils/datemath';
|
||||||
|
|
||||||
|
function PrometheusQueryCtrl($scope, templateSrv) {
|
||||||
|
$scope.panelCtrl = $scope.ctrl;
|
||||||
|
$scope.panel = $scope.panelCtrl.panel;
|
||||||
|
|
||||||
|
$scope.init = function() {
|
||||||
|
var target = $scope.target;
|
||||||
|
|
||||||
|
target.expr = target.expr || '';
|
||||||
|
target.intervalFactor = target.intervalFactor || 2;
|
||||||
|
|
||||||
|
$scope.metric = '';
|
||||||
|
$scope.resolutions = _.map([1,2,3,4,5,10], function(f) {
|
||||||
|
return {factor: f, label: '1/' + f};
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$on('typeahead-updated', function() {
|
||||||
|
$scope.$apply($scope.inputMetric);
|
||||||
|
$scope.refreshMetricData();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.refreshMetricData = function() {
|
||||||
|
if (!_.isEqual($scope.oldTarget, $scope.target)) {
|
||||||
|
$scope.oldTarget = angular.copy($scope.target);
|
||||||
|
$scope.paneCtrl.refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.inputMetric = function() {
|
||||||
|
$scope.target.expr += $scope.target.metric;
|
||||||
|
$scope.metric = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.suggestMetrics = function(query, callback) {
|
||||||
|
$scope.datasource
|
||||||
|
.performSuggestQuery(query)
|
||||||
|
.then(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.linkToPrometheus = function() {
|
||||||
|
var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000);
|
||||||
|
var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm');
|
||||||
|
var expr = {
|
||||||
|
expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars),
|
||||||
|
range_input: range + 's',
|
||||||
|
end_input: endTime,
|
||||||
|
step_input: '',
|
||||||
|
stacked: $scope.panel.stack,
|
||||||
|
tab: 0
|
||||||
|
};
|
||||||
|
var hash = encodeURIComponent(JSON.stringify([expr]));
|
||||||
|
return $scope.datasource.directUrl + '/graph#' + hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
export {PrometheusQueryCtrl};
|
@ -59,7 +59,6 @@
|
|||||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es5-shim/es5-shim.js"></script>
|
<script src="[[.AppSubUrl]]/public/vendor/npm/es5-shim/es5-shim.js"></script>
|
||||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-shim/es6-shim.js"></script>
|
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-shim/es6-shim.js"></script>
|
||||||
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-promise/dist/es6-promise.js"></script>
|
<script src="[[.AppSubUrl]]/public/vendor/npm/es6-promise/dist/es6-promise.js"></script>
|
||||||
<script src="[[.AppSubUrl]]/public/vendor/npm/systemjs/dist/system-polyfills.js"></script>
|
|
||||||
<script src="[[.AppSubUrl]]/public/vendor/npm/systemjs/dist/system.src.js"></script>
|
<script src="[[.AppSubUrl]]/public/vendor/npm/systemjs/dist/system.src.js"></script>
|
||||||
<script src="[[.AppSubUrl]]/public/app/system.conf.js"></script>
|
<script src="[[.AppSubUrl]]/public/app/system.conf.js"></script>
|
||||||
<script src="[[.AppSubUrl]]/public/app/boot.js"></script>
|
<script src="[[.AppSubUrl]]/public/app/boot.js"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user