mirror of
https://github.com/grafana/grafana.git
synced 2024-11-30 04:34:23 -06:00
Merge branch 'dynamic-directives'
This commit is contained in:
commit
88a132b878
@ -18,10 +18,10 @@ Press `Shift`+`?` to open the keyboard shortcut dialog from anywhere within the
|
||||
|---|---|
|
||||
|`Esc`|Exit fullscreen edit/view mode, close search or any editor view|
|
||||
|`F`|Open dashboard search view (also contains import/playlist controls)|
|
||||
|`R`|Refresh (Fetches new data and rerenders panels)|
|
||||
|`CTRL`+`S`|Save dashboard|
|
||||
|`CTRL`+`H`|Hide row controls|
|
||||
|`CTRL`+`Z`|Zoom out|
|
||||
|`CTRL`+`R`|Refresh (Fetches new data and rerenders panels)|
|
||||
|`CTRL`+`O`|Enable/Disable shared graph crosshair|
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@
|
||||
"grunt-ng-annotate": "^1.0.1",
|
||||
"grunt-string-replace": "~1.2.1",
|
||||
"grunt-systemjs-builder": "^0.2.5",
|
||||
"grunt-tslint": "^2.5.0",
|
||||
"grunt-tslint": "^3.0.1",
|
||||
"grunt-typescript": "^0.8.0",
|
||||
"grunt-usemin": "3.0.0",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
@ -70,7 +70,7 @@
|
||||
"lodash": "^2.4.1",
|
||||
"sinon": "1.16.1",
|
||||
"systemjs-builder": "^0.14.15",
|
||||
"tslint": "^3.2.0",
|
||||
"tslint": "^3.2.1",
|
||||
"typescript": "^1.7.5"
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ type AppSettings struct {
|
||||
AppId string `json:"appId"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
Module string `json:"module"`
|
||||
Info *plugins.PluginInfo `json:"info"`
|
||||
Pages []*plugins.AppPluginPage `json:"pages"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
@ -17,10 +18,11 @@ type AppSettings struct {
|
||||
|
||||
func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
|
||||
dto := &AppSettings{
|
||||
AppId: def.Id,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
Pages: def.Pages,
|
||||
AppId: def.Id,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
Module: def.Module,
|
||||
Pages: def.Pages,
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
|
@ -47,13 +47,13 @@ coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootSc
|
||||
};
|
||||
|
||||
$rootScope.colors = [
|
||||
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
|
||||
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
|
||||
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
|
||||
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
|
||||
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
|
||||
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
|
||||
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0",
|
||||
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477",
|
||||
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0",
|
||||
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93",
|
||||
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7",
|
||||
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B",
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"
|
||||
];
|
||||
|
||||
$scope.getTotalWatcherCount = function() {
|
||||
@ -85,6 +85,7 @@ coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootSc
|
||||
$scope.$watch(function digestCounter() {
|
||||
count++;
|
||||
}, function() {
|
||||
// something
|
||||
});
|
||||
|
||||
$rootScope.performance.panels = [];
|
||||
|
@ -1,6 +1,5 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
import coreModule from '../core_module';
|
||||
|
||||
@ -8,10 +7,10 @@ export class SignUpCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope : any,
|
||||
private $location : any,
|
||||
private contextSrv : any,
|
||||
private backendSrv : any) {
|
||||
private $scope: any,
|
||||
private $location: any,
|
||||
private contextSrv: any,
|
||||
private backendSrv: any) {
|
||||
|
||||
contextSrv.sidemenu = false;
|
||||
$scope.ctrl = this;
|
||||
|
@ -17,15 +17,15 @@ import "./directives/spectrum_picker";
|
||||
import "./directives/tags";
|
||||
import "./directives/topnav";
|
||||
import "./directives/value_select_dropdown";
|
||||
import "./directives/give_focus";
|
||||
import './jquery_extended';
|
||||
import './partials';
|
||||
|
||||
import {arrayJoin} from './directives/array_join';
|
||||
import * as controllers from 'app/core/controllers/all';
|
||||
import * as services from 'app/core/services/all';
|
||||
import * as routes from 'app/core/routes/all';
|
||||
import 'app/core/controllers/all';
|
||||
import 'app/core/services/all';
|
||||
import 'app/core/routes/all';
|
||||
import './filters/filters';
|
||||
import coreModule from './core_module';
|
||||
|
||||
// export * from './directives/give_focus'
|
||||
|
||||
export {arrayJoin, controllers, services, routes};
|
||||
export {arrayJoin, coreModule};
|
||||
|
@ -1,7 +1,6 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import coreModule from '../core_module';
|
||||
|
||||
export function arrayJoin() {
|
||||
|
@ -1,9 +1,8 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular = require('angular');
|
||||
import coreModule from '../core_module';
|
||||
|
||||
coreModule.default.directive('giveFocus', function() {
|
||||
coreModule.directive('giveFocus', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
|
@ -1,6 +1,5 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import jquery from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
@ -59,7 +58,7 @@ coreModule.filter('noXml', function() {
|
||||
});
|
||||
|
||||
coreModule.filter('interpolateTemplateVars', function (templateSrv) {
|
||||
var filterFunc : any = function (text, scope) {
|
||||
var filterFunc: any = function(text, scope) {
|
||||
if (scope.panel) {
|
||||
return templateSrv.replaceWithText(text, scope.panel.scopedVars);
|
||||
} else {
|
||||
|
@ -9,5 +9,6 @@ define([
|
||||
'./popover_srv',
|
||||
'./segment_srv',
|
||||
'./backend_srv',
|
||||
'./dynamic_directive_srv',
|
||||
],
|
||||
function () {});
|
||||
|
64
public/app/core/services/dynamic_directive_srv.ts
Normal file
64
public/app/core/services/dynamic_directive_srv.ts
Normal file
@ -0,0 +1,64 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import coreModule from '../core_module';
|
||||
|
||||
class DynamicDirectiveSrv {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $compile, private $parse, private $rootScope) {}
|
||||
|
||||
addDirective(element, name, scope) {
|
||||
var child = angular.element(document.createElement(name));
|
||||
this.$compile(child)(scope);
|
||||
|
||||
element.empty();
|
||||
element.append(child);
|
||||
}
|
||||
|
||||
link(scope, elem, attrs, options) {
|
||||
options.directive(scope).then(directiveInfo => {
|
||||
if (!directiveInfo || !directiveInfo.fn) {
|
||||
elem.empty();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!directiveInfo.fn.registered) {
|
||||
coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn);
|
||||
directiveInfo.fn.registered = true;
|
||||
}
|
||||
|
||||
this.addDirective(elem, directiveInfo.name, scope);
|
||||
}).catch(err => {
|
||||
console.log('Plugin load:', err);
|
||||
this.$rootScope.appEvent('alert-error', ['Plugin error', err.toString()]);
|
||||
});
|
||||
}
|
||||
|
||||
create(options) {
|
||||
let directiveDef = {
|
||||
restrict: 'E',
|
||||
scope: options.scope,
|
||||
link: (scope, elem, attrs) => {
|
||||
if (options.watch) {
|
||||
let childScope = null;
|
||||
scope.$watch(options.watch, () => {
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
}
|
||||
childScope = scope.$new();
|
||||
this.link(childScope, elem, attrs, options);
|
||||
});
|
||||
} else {
|
||||
this.link(scope, elem, attrs, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return directiveDef;
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('dynamicDirectiveSrv', DynamicDirectiveSrv);
|
||||
|
||||
|
@ -132,7 +132,7 @@ export default class TimeSeries {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentValue != 0) {
|
||||
if (currentValue !== 0) {
|
||||
this.allIsZero = false;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,6 @@ import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
var units = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
|
||||
var unitsAsc = _.sortBy(units, function (unit) {
|
||||
return moment.duration(1, unit).valueOf();
|
||||
});
|
||||
|
||||
var unitsDesc = unitsAsc.reverse();
|
||||
|
||||
export function parse(text, roundUp?) {
|
||||
if (!text) { return undefined; }
|
||||
@ -104,8 +99,7 @@ export function parseDateMath(mathString, time, roundUp?) {
|
||||
if (type === 0) {
|
||||
if (roundUp) {
|
||||
dateTime.endOf(unit);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
dateTime.startOf(unit);
|
||||
}
|
||||
} else if (type === 1) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import * as dateMath from './datemath';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
define([
|
||||
'./panellinks/module',
|
||||
'./dashlinks/module',
|
||||
'./annotations/annotationsSrv',
|
||||
'./annotations/annotations_srv',
|
||||
'./templating/templateSrv',
|
||||
'./dashboard/all',
|
||||
'./playlist/all',
|
||||
|
@ -1,7 +1,8 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./editorCtrl'
|
||||
'./editor_ctrl',
|
||||
'./query_editor'
|
||||
], function (angular, _) {
|
||||
'use strict';
|
||||
|
@ -31,10 +31,10 @@ function (angular, _, $) {
|
||||
};
|
||||
|
||||
$scope.datasourceChanged = function() {
|
||||
$scope.currentDatasource = _.findWhere($scope.datasources, { name: $scope.currentAnnotation.datasource });
|
||||
if (!$scope.currentDatasource) {
|
||||
$scope.currentDatasource = $scope.datasources[0];
|
||||
}
|
||||
datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
|
||||
$scope.currentDatasource = ds;
|
||||
$scope.currentAnnotation.datasource = ds.name;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.edit = function(annotation) {
|
||||
@ -50,7 +50,6 @@ function (angular, _, $) {
|
||||
$scope.currentAnnotation = angular.copy(annotationDefaults);
|
||||
$scope.currentIsNew = true;
|
||||
$scope.datasourceChanged();
|
||||
$scope.currentAnnotation.datasource = $scope.currentDatasource.name;
|
||||
};
|
||||
|
||||
$scope.update = function() {
|
@ -91,7 +91,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<datasource-editor-view datasource="currentAnnotation.datasource" name="annotations-query-editor"></datasource-editor-view>
|
||||
<annotations-query-editor datasource="currentDatasource" annotation="currentAnnotation">
|
||||
</annotations-query-editor>
|
||||
|
||||
<br>
|
||||
<button ng-show="mode === 'new'" type="button" class="btn btn-success" ng-click="add()">Add</button>
|
||||
|
25
public/app/features/annotations/query_editor.ts
Normal file
25
public/app/features/annotations/query_editor.ts
Normal file
@ -0,0 +1,25 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
/** @ngInject */
|
||||
function annotationsQueryEditor(dynamicDirectiveSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
scope: {
|
||||
annotation: "=",
|
||||
datasource: "="
|
||||
},
|
||||
watch: "datasource.type",
|
||||
directive: scope => {
|
||||
return System.import(scope.datasource.meta.module).then(function(dsModule) {
|
||||
return {
|
||||
name: 'annotation-query-editor-' + scope.datasource.meta.id,
|
||||
fn: dsModule.annotationsQueryEditor,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
angular.module('grafana.directives').directive('annotationsQueryEditor', annotationsQueryEditor);
|
@ -1,2 +1,3 @@
|
||||
import './edit_ctrl';
|
||||
import './list_ctrl';
|
||||
import './config_view';
|
||||
|
@ -1,43 +0,0 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
export class AppSrv {
|
||||
apps: any = {};
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $rootScope,
|
||||
private $timeout,
|
||||
private $q,
|
||||
private backendSrv) {
|
||||
}
|
||||
|
||||
get(type) {
|
||||
return this.getAll().then(() => {
|
||||
return this.apps[type];
|
||||
});
|
||||
}
|
||||
|
||||
getAll() {
|
||||
if (!_.isEmpty(this.apps)) {
|
||||
return this.$q.when(this.apps);
|
||||
}
|
||||
|
||||
return this.backendSrv.get('api/org/apps').then(results => {
|
||||
return results.reduce((prev, current) => {
|
||||
prev[current.type] = current;
|
||||
return prev;
|
||||
}, this.apps);
|
||||
});
|
||||
}
|
||||
|
||||
update(app) {
|
||||
return this.backendSrv.post('api/org/apps', app).then(resp => {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.services').service('appSrv', AppSrv);
|
23
public/app/features/apps/config_view.ts
Normal file
23
public/app/features/apps/config_view.ts
Normal file
@ -0,0 +1,23 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
/** @ngInject */
|
||||
function appConfigView(dynamicDirectiveSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
scope: {
|
||||
appModel: "="
|
||||
},
|
||||
directive: scope => {
|
||||
return System.import(scope.appModel.module).then(function(appModule) {
|
||||
return {
|
||||
name: 'app-config-' + scope.appModel.appId,
|
||||
fn: appModule.configView,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
angular.module('grafana.directives').directive('appConfigView', appConfigView);
|
@ -1,6 +1,5 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import config from 'app/core/config';
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import config = require('app/core/config');
|
||||
import angular from 'angular';
|
||||
|
||||
export class AppListCtrl {
|
||||
|
@ -29,7 +29,6 @@
|
||||
<span style="small">
|
||||
Version: {{ctrl.appModel.info.version}} Updated: {{ctrl.appModel.info.updated}}
|
||||
</span>
|
||||
|
||||
</em>
|
||||
<br><br>
|
||||
|
||||
@ -94,9 +93,12 @@
|
||||
<section class="simple-box">
|
||||
<h3 class="simple-box-header">Configuration:</h3>
|
||||
<div class="simple-box-body">
|
||||
<div ng-if="ctrl.appModel.appId">
|
||||
<app-config-view app-model="ctrl.appModel"></app-config-view>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-config-loader></app-config-loader>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@ function(angular, $) {
|
||||
scope.appEvent('save-dashboard', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+r', function() {
|
||||
keyboardManager.bind('r', function() {
|
||||
scope.broadcastRefresh();
|
||||
}, { inputDisabled: true });
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
|
||||
export function inputDateDirective() {
|
||||
|
@ -1,19 +1,17 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import * as rangeUtil from 'app/core/utils/rangeutil';
|
||||
|
||||
export class TimePickerCtrl {
|
||||
|
||||
static tooltipFormat = 'MMM D, YYYY HH:mm:ss';
|
||||
static defaults = {
|
||||
time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
|
||||
refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
|
||||
time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'],
|
||||
refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
|
||||
};
|
||||
|
||||
dashboard: any;
|
||||
|
@ -1,4 +1,5 @@
|
||||
define([
|
||||
'./list_ctrl',
|
||||
'./edit_ctrl',
|
||||
'./config_view',
|
||||
], function () {});
|
||||
|
25
public/app/features/datasources/config_view.ts
Normal file
25
public/app/features/datasources/config_view.ts
Normal file
@ -0,0 +1,25 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
/** @ngInject */
|
||||
function dsConfigView(dynamicDirectiveSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
scope: {
|
||||
dsMeta: "=",
|
||||
current: "="
|
||||
},
|
||||
watch: "dsMeta.module",
|
||||
directive: scope => {
|
||||
return System.import(scope.dsMeta.module).then(function(dsModule) {
|
||||
return {
|
||||
name: 'ds-config-' + scope.dsMeta.id,
|
||||
fn: dsModule.configView,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
angular.module('grafana.directives').directive('dsConfigView', dsConfigView);
|
@ -42,7 +42,7 @@
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<datasource-custom-settings-view ds-meta="datasourceMeta" current="current"></datasource-custom-settings-view>
|
||||
<ds-config-view ng-if="datasourceMeta.id" ds-meta="datasourceMeta" current="current"></ds-config-view>
|
||||
|
||||
<div ng-if="testing" style="margin-top: 25px">
|
||||
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||
|
@ -4,4 +4,6 @@ define([
|
||||
'./panel_srv',
|
||||
'./panel_helper',
|
||||
'./solo_panel_ctrl',
|
||||
'./panel_loader',
|
||||
'./query_editor',
|
||||
], function () {});
|
||||
|
@ -1,32 +1,12 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'app/core/config',
|
||||
],
|
||||
function (angular, $, config) {
|
||||
function (angular, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('panelLoader', function($compile, $parse) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attr) {
|
||||
var getter = $parse(attr.type), panelType = getter(scope);
|
||||
var module = config.panels[panelType].module;
|
||||
|
||||
System.import(module).then(function() {
|
||||
var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
|
||||
elem.append(panelEl);
|
||||
$compile(panelEl)(scope);
|
||||
}).catch(function(err) {
|
||||
console.log('Failed to load panel:', err);
|
||||
scope.appEvent('alert-error', ['Panel Load Error', 'Failed to load panel ' + panelType + ', ' + err]);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('grafanaPanel', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
@ -43,103 +23,6 @@ function (angular, $, config) {
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('datasourceCustomSettingsView', function($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
dsMeta: "=",
|
||||
current: "=",
|
||||
},
|
||||
link: function(scope, elem) {
|
||||
scope.$watch("dsMeta.module", function() {
|
||||
if (!scope.dsMeta) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.import(scope.dsMeta.module).then(function() {
|
||||
elem.empty();
|
||||
var panelEl = angular.element(document.createElement('datasource-custom-settings-view-' + scope.dsMeta.id));
|
||||
elem.append(panelEl);
|
||||
$compile(panelEl)(scope);
|
||||
}).catch(function(err) {
|
||||
console.log('Failed to load plugin:', err);
|
||||
scope.appEvent('alert-error', ['Plugin Load Error', 'Failed to load plugin ' + scope.dsMeta.id + ', ' + err]);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) {
|
||||
var self = this;
|
||||
|
||||
this.addDirective = function(options, type, editorScope) {
|
||||
var panelEl = angular.element(document.createElement(options.name + '-' + type));
|
||||
options.parentElem.append(panelEl);
|
||||
$compile(panelEl)(editorScope);
|
||||
};
|
||||
|
||||
this.define = function(options) {
|
||||
var editorScope;
|
||||
options.scope.$watch(options.datasourceProperty, function(newVal) {
|
||||
if (editorScope) {
|
||||
editorScope.$destroy();
|
||||
options.parentElem.empty();
|
||||
}
|
||||
|
||||
editorScope = options.scope.$new();
|
||||
datasourceSrv.get(newVal).then(function(ds) {
|
||||
self.addDirective(options, ds.meta.id, editorScope);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('datasourceEditorView', function(dynamicDirectiveSrv) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
dynamicDirectiveSrv.define({
|
||||
datasourceProperty: attrs.datasource,
|
||||
name: attrs.name,
|
||||
scope: scope,
|
||||
parentElem: elem,
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem) {
|
||||
var editorScope;
|
||||
|
||||
scope.$watch("panel.datasource", function() {
|
||||
var datasource = scope.target.datasource || scope.panel.datasource;
|
||||
|
||||
datasourceSrv.get(datasource).then(function(ds) {
|
||||
if (editorScope) {
|
||||
editorScope.$destroy();
|
||||
elem.empty();
|
||||
}
|
||||
|
||||
editorScope = scope.$new();
|
||||
editorScope.datasource = ds;
|
||||
|
||||
if (!scope.target.refId) {
|
||||
scope.target.refId = 'A';
|
||||
}
|
||||
|
||||
var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.id));
|
||||
elem.append(panelEl);
|
||||
$compile(panelEl)(editorScope);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('panelResizer', function($rootScope) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
22
public/app/features/panel/panel_loader.ts
Normal file
22
public/app/features/panel/panel_loader.ts
Normal file
@ -0,0 +1,22 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
|
||||
/** @ngInject */
|
||||
function panelLoader($parse, dynamicDirectiveSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
directive: scope => {
|
||||
let modulePath = config.panels[scope.panel.type].module;
|
||||
|
||||
return System.import(modulePath).then(function(panelModule) {
|
||||
return {
|
||||
name: 'panel-directive-' + scope.panel.type,
|
||||
fn: panelModule.panel,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
angular.module('grafana.directives').directive('panelLoader', panelLoader);
|
48
public/app/features/panel/query_editor.ts
Normal file
48
public/app/features/panel/query_editor.ts
Normal file
@ -0,0 +1,48 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
/** @ngInject */
|
||||
function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
watch: "panel.datasource",
|
||||
directive: scope => {
|
||||
let datasource = scope.target.datasource || scope.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 */
|
||||
function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
|
||||
return dynamicDirectiveSrv.create({
|
||||
watch: "panel.datasource",
|
||||
directive: scope => {
|
||||
return datasourceSrv.get(scope.panel.datasource).then(ds => {
|
||||
return System.import(ds.meta.module).then(dsModule => {
|
||||
return {
|
||||
name: 'metrics-query-options-' + ds.meta.id,
|
||||
fn: dsModule.metricsQueryOptions
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
angular.module('grafana.directives')
|
||||
.directive('metricsQueryEditor', metricsQueryEditor)
|
||||
.directive('metricsQueryOptions', metricsQueryOptions);
|
@ -1,5 +1,5 @@
|
||||
import '../playlist_edit_ctrl';
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
|
||||
import helpers from 'test/specs/helpers';
|
||||
|
||||
describe('PlaylistEditCtrl', function() {
|
||||
@ -83,4 +83,4 @@ describe('PlaylistEditCtrl', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -8,12 +8,12 @@ import 'angular-sanitize';
|
||||
import 'angular-dragdrop';
|
||||
import 'angular-bindonce';
|
||||
import 'angular-ui';
|
||||
import 'app/core/core';
|
||||
|
||||
import $ from 'jquery';
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
import _ from 'lodash';
|
||||
import {coreModule} from './core/core';
|
||||
|
||||
export class GrafanaApp {
|
||||
registerFunctions: any;
|
||||
@ -67,6 +67,9 @@ export class GrafanaApp {
|
||||
this.useModule(angular.module(moduleName, []));
|
||||
});
|
||||
|
||||
// makes it possible to add dynamic stuff
|
||||
this.useModule(coreModule);
|
||||
|
||||
var preBootRequires = [System.import('app/features/all')];
|
||||
var pluginModules = config.bootData.pluginModules || [];
|
||||
|
||||
|
@ -1,220 +0,0 @@
|
||||
<div class="editor-row" style="margin-bottom: 20px;">
|
||||
<span style="float: right; font-size: 12px;"><i>Last updated by Grafana October 4, 2015 12:15:04 by $username</i></span>
|
||||
<div class="section">
|
||||
<h5>General Alerting Options</h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
Alert Title
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-xlarge tight-form-input"></input>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
Alerting Backend
|
||||
</li>
|
||||
<li>
|
||||
<select class="input-medium tight-form-input">
|
||||
<option>Grafana Alerting</option>
|
||||
</select>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<label class="checkbox-label" for="alerting-enabled">Enabled</label>
|
||||
<input class="cr1" id="alerting-enabled" type="checkbox">
|
||||
<label for="alerting-enabled" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" style="margin-bottom: 20px;">
|
||||
<h5>Choose your query:</h5>
|
||||
<p>Select an exising query to alert on:</p>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item">None</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item last">aliasByNode(2)</li>
|
||||
<li><div class="copy-query" bs-tooltip="'Copy to custom query'" data-placement="top"></div></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">B</li>
|
||||
<li class="tight-form-item last"><span class="query-keyword">Metric:</span> us-west-2 AWS/EC2 CPUUtilization <span class="query-keyword">Stats:</span> Minimum Maximum <span class="query-keyword">Dimensions</span> InstanceIS <span class="query-segment-operator">=</span> i-b0e8a447 <span class="query-keyword">Alias</span> {{stat}} <span class="query-keyword">Period</span> 60</li>
|
||||
<li><div class="copy-query" bs-tooltip="'Copy to custom query'" data-placement="top"></div></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">C</li>
|
||||
<li class="tight-form-item last"><span class="query-keyword">Query:</span> avg(counters_logins) by(server) <span class="query-keyword">Legend Format:</span> {{app}} - {{server}} <span class="query-keyword">Step:</span> 1s <span class="query-keyword">Resolution:</span> 1/2</li>
|
||||
<li><div class="copy-query" bs-tooltip="'Copy to custom query'" data-placement="top"></div></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">D</li>
|
||||
<li class="tight-form-item last"><span class="query-keyword">SELECT</span> mean(value) <span class="query-keyword">FROM</span> logins.count <span class="query-keyword">WHERE</span> hostname <span class="query-segment-operator">=</span> /$Hostname$/ <span class="query-keyword">GROUP BY</span> time($internal) hostname</li>
|
||||
<li><div class="copy-query" bs-tooltip="'Copy to custom query'" data-placement="top"></div></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" checked /></li>
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">E</li>
|
||||
<li class="tight-form-item last"><span class="query-keyword">Metric:</span> apps.backend.backend_01.counters.requests.count <span class="query-keyword">Alias:</span> Bristow <span class="query-keyword">Aggregator:</span> Sum <span class="query-keyword">Downsample:</span> 1m <span class="query-keyword">Aggregator</span> Sum <span class="query-keyword">Tags</span> host = test</li>
|
||||
<li><div class="copy-query" bs-tooltip="'Copy to custom query'" data-placement="top"></div></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" style="margin-bottom: 20px;">
|
||||
<p>Or write a new custom alerting query:</p>
|
||||
<div class="section">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item"><input type="radio" class="radio input-small" name="query" style="margin: 0 4px 4px;" /></li>
|
||||
<li class="tight-form-item">
|
||||
<a class="pointer">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
select metric
|
||||
</li>
|
||||
<li>
|
||||
<a class="tight-form-item tight-form-func last dropdown-toggle"><i class="fa fa-plus"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" style="margin-bottom: 10px;">
|
||||
<div class="section">
|
||||
<h5>Define Your States</h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
by
|
||||
</li>
|
||||
<li>
|
||||
<select class="input-medium tight-form-input">
|
||||
<option>Averaging</option>
|
||||
</select>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
the values in the query over the last
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-mini tight-form-input last"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" style="margin-bottom: 20px;">
|
||||
<div class="section">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px;">
|
||||
<span class="alert-state alert-state-warning">Warn</span>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-mini tight-form-input" value=">" style="text-align: center;"></input>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-mini tight-form-input" value="#B" style="text-align: center;"></input>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
.notify
|
||||
</li>
|
||||
<li class="alert-notify-emails">
|
||||
<bootstrap-tagsinput tagclass="label label-tag label-tag-email"></bootstrap-tagsinput>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<label class="checkbox-label" for="state-enabled">Enabled</label>
|
||||
<input class="cr1" id="state-enabled" type="checkbox">
|
||||
<label for="state-enabled" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px;">
|
||||
<span class="alert-state alert-state-critical">Critical</span>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-mini tight-form-input"></input>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-mini tight-form-input"></input>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
.notify
|
||||
</li>
|
||||
<li class="alert-notify-emails">
|
||||
<bootstrap-tagsinput tagclass="label label-tag label-tag-email"></bootstrap-tagsinput>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<label class="checkbox-label" for="state-enabled2">Enabled</label>
|
||||
<input class="cr1" id="state-enabled2" type="checkbox">
|
||||
<label for="state-enabled2" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>What to Say <span style="float: right; font-size: 12px; font-weight: normal;"><a href="#">Variables</a> | <a href="#">Preview</a></span></h5>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px;">
|
||||
Summary
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-xxlarge tight-form-input last"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px;">
|
||||
Description
|
||||
</li>
|
||||
<li>
|
||||
<textarea class="tight-form-textarea input-xxlarge last"></textarea>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -83,7 +83,7 @@
|
||||
|
||||
<div ng-repeat="(name, panel) in row.panels track by panel.id" class="panel" ui-draggable="!dashboardViewState.fullscreen" drag="panel.id"
|
||||
ui-on-Drop="onDrop($data, row, panel)" drag-handle-class="drag-handle" panel-width>
|
||||
<panel-loader type="panel.type" class="panel-margin"></panel-loader>
|
||||
<panel-loader class="panel-margin"></panel-loader>
|
||||
</div>
|
||||
|
||||
<div panel-drop-zone class="panel panel-drop-zone" ui-on-drop="onDrop($data, row)" data-drop="true">
|
||||
|
@ -24,6 +24,10 @@
|
||||
<td><span class="label label-info">F</span></td>
|
||||
<td>Open dashboard search view (also contains import/playlist controls)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">R</span></td>
|
||||
<td>Refresh (Fetches new data and rerenders panels)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+S</span></td>
|
||||
<td>Save dashboard</td>
|
||||
@ -36,10 +40,6 @@
|
||||
<td><span class="label label-info">CTRL+Z</span></td>
|
||||
<td>Zoom out</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+R</span></td>
|
||||
<td>Refresh (Fetches new data and rerenders panels)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+O</span></td>
|
||||
<td>Enable/Disable shared graph crosshair</td>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="tight-form-container">
|
||||
<query-editor-loader ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
|
||||
</query-editor-loader>
|
||||
<metrics-query-editor ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
|
||||
</metrics-query-editor>
|
||||
</div>
|
||||
|
||||
<div style="margin: 20px 0 0 0">
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<datasource-editor-view datasource="panel.datasource" name="metric-query-options"></datasource-editor-view>
|
||||
<metrics-query-options></metrics-query-options>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin-top: 30px">
|
||||
|
@ -3,7 +3,6 @@ define([
|
||||
'lodash',
|
||||
'moment',
|
||||
'app/core/utils/datemath',
|
||||
'./query_ctrl',
|
||||
],
|
||||
function (angular, _, moment, dateMath) {
|
||||
'use strict';
|
||||
|
@ -1,39 +1,27 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
'./query_parameter_ctrl',
|
||||
'./query_ctrl',
|
||||
],
|
||||
function (angular, CloudWatchDatasource) {
|
||||
function (CloudWatchDatasource) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorCloudwatch', function() {
|
||||
function metricsQueryEditor() {
|
||||
return {controller: 'CloudWatchQueryCtrl', templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('annotationsQueryEditorCloudwatch', function() {
|
||||
function annotationsQueryEditor() {
|
||||
return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/annotations.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('cloudwatchQueryParameter', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html',
|
||||
controller: 'CloudWatchQueryParameterCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
datasourceName: "@",
|
||||
onChange: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('datasourceCustomSettingsViewCloudwatch', function() {
|
||||
function configView() {
|
||||
return {templateUrl: 'app/plugins/datasource/cloudwatch/partials/edit_view.html'};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: CloudWatchDatasource
|
||||
Datasource: CloudWatchDatasource,
|
||||
configView: configView,
|
||||
annotationsQueryEditor: annotationsQueryEditor,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
};
|
||||
});
|
||||
|
@ -1 +1 @@
|
||||
<cloudwatch-query-parameter target="currentAnnotation" datasource-name="{{currentAnnotation.datasource}}"></cloudwatch-query-parameter>
|
||||
<cloudwatch-query-parameter target="annotation" datasource="datasource"></cloudwatch-query-parameter>
|
||||
|
@ -35,4 +35,4 @@
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<cloudwatch-query-parameter target="target" datasource-name="{{datasource.name}}" on-change="refreshMetricData()"></cloudwatch-query-parameter>
|
||||
<cloudwatch-query-parameter target="target" datasource="datasource" on-change="refreshMetricData()"></cloudwatch-query-parameter>
|
||||
|
@ -7,6 +7,19 @@ function (angular, _) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.directive('cloudwatchQueryParameter', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.parameter.html',
|
||||
controller: 'CloudWatchQueryParameterCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
datasource: "=",
|
||||
onChange: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('CloudWatchQueryParameterCtrl', function($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) {
|
||||
|
||||
$scope.init = function() {
|
||||
@ -38,12 +51,9 @@ function (angular, _) {
|
||||
$scope.removeDimSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove dimension --'});
|
||||
$scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
|
||||
|
||||
datasourceSrv.get($scope.datasourceName).then(function(datasource) {
|
||||
$scope.datasource = datasource;
|
||||
if (_.isEmpty($scope.target.region)) {
|
||||
$scope.target.region = $scope.datasource.getDefaultRegion();
|
||||
}
|
||||
});
|
||||
if (_.isEmpty($scope.target.region)) {
|
||||
$scope.target.region = $scope.datasource.getDefaultRegion();
|
||||
}
|
||||
|
||||
if (!$scope.onChange) {
|
||||
$scope.onChange = function() {};
|
||||
|
@ -228,10 +228,8 @@ describe('CloudWatchDatasource', function() {
|
||||
switch (params.data.action) {
|
||||
case 'DescribeAlarmsForMetric':
|
||||
return ctx.$q.when({data: alarmResponse});
|
||||
break;
|
||||
case 'DescribeAlarmHistory':
|
||||
return ctx.$q.when({data: historyResponse});
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -8,6 +8,20 @@ function (angular, _, queryDef) {
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('elasticBucketAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html',
|
||||
controller: 'ElasticBucketAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('ElasticBucketAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
var bucketAggs = $scope.target.bucketAggs;
|
||||
|
||||
|
@ -8,6 +8,21 @@ function (angular, _, queryDef) {
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('elasticMetricAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
||||
controller: 'ElasticMetricAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
esVersion: '='
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('ElasticMetricAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
var metricAggs = $scope.target.metrics;
|
||||
|
||||
|
@ -1,60 +1,30 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
'./edit_view',
|
||||
'./bucket_agg',
|
||||
'./metric_agg',
|
||||
],
|
||||
function (angular, ElasticDatasource, editView) {
|
||||
function (ElasticDatasource, editView) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorElasticsearch', function() {
|
||||
function metricsQueryEditor() {
|
||||
return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('metricQueryOptionsElasticsearch', function() {
|
||||
function metricsQueryOptions() {
|
||||
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('annotationsQueryEditorElasticsearch', function() {
|
||||
function annotationsQueryEditor() {
|
||||
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'};
|
||||
});
|
||||
|
||||
module.directive('elasticMetricAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
||||
controller: 'ElasticMetricAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
esVersion: '='
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('elasticBucketAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html',
|
||||
controller: 'ElasticBucketAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('datasourceCustomSettingsViewElasticsearch', editView.default);
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: ElasticDatasource,
|
||||
configView: editView.default,
|
||||
annotationsQueryEditor: annotationsQueryEditor,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
metricsQueryOptions: metricsQueryOptions,
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
<div class="editor-row">
|
||||
<div class="section" ng-if="currentAnnotation.index">
|
||||
<div class="section" ng-if="annotation.index">
|
||||
<h5>Index name</h5>
|
||||
<div class="editor-option">
|
||||
<input type="text" class="span4" ng-model='currentAnnotation.index' placeholder="events-*"></input>
|
||||
<input type="text" class="span4" ng-model='annotation.index' placeholder="events-*"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Search query (lucene) <tip>Use [[filterName]] in query to replace part of the query with a filter value</tip></h5>
|
||||
<div class="editor-option">
|
||||
<input type="text" class="span6" ng-model='currentAnnotation.query' placeholder="tags:deploy"></input>
|
||||
<input type="text" class="span6" ng-model='annotation.query' placeholder="tags:deploy"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,22 +18,22 @@
|
||||
<h5>Field mappings</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Time</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.timeField' placeholder="@timestamp"></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.timeField' placeholder="@timestamp"></input>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Title</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.titleField' placeholder="desc"></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.titleField' placeholder="desc"></input>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Tags</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.tagsField' placeholder="tags"></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.tagsField' placeholder="tags"></input>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Text</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.textField' placeholder=""></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.textField' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
|
||||
import {describe, beforeEach, it, expect} from 'test/lib/common';
|
||||
import ElasticResponse from '../elastic_response';
|
||||
|
||||
describe('ElasticResponse', function() {
|
||||
@ -497,11 +497,6 @@ describe('ElasticResponse', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('', function() {
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('Raw documents query', function() {
|
||||
beforeEach(function() {
|
||||
targets = [{ refId: 'A', metrics: [{type: 'raw_document', id: '1'}], bucketAggs: [] }];
|
||||
|
@ -5,10 +5,14 @@ import {GrafanaDatasource} from './datasource';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorGrafana', function() {
|
||||
function grafanaMetricsQueryEditor() {
|
||||
return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export {GrafanaDatasource, GrafanaDatasource as Datasource};
|
||||
export {
|
||||
GrafanaDatasource,
|
||||
GrafanaDatasource as Datasource,
|
||||
grafanaMetricsQueryEditor as metricsQueryEditor
|
||||
};
|
||||
|
||||
|
@ -1,29 +1,30 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
],
|
||||
function (angular, GraphiteDatasource) {
|
||||
function (GraphiteDatasource) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorGraphite', function() {
|
||||
function metricsQueryEditor() {
|
||||
return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('metricQueryOptionsGraphite', function() {
|
||||
function metricsQueryOptions() {
|
||||
return {templateUrl: 'app/plugins/datasource/graphite/partials/query.options.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('annotationsQueryEditorGraphite', function() {
|
||||
function annotationsQueryEditor() {
|
||||
return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('datasourceCustomSettingsViewGraphite', function() {
|
||||
function configView() {
|
||||
return {templateUrl: 'app/plugins/datasource/graphite/partials/config.html'};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: GraphiteDatasource,
|
||||
configView: configView,
|
||||
annotationsQueryEditor: annotationsQueryEditor,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
metricsQueryOptions: metricsQueryOptions,
|
||||
};
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
<div class="editor-row">
|
||||
<div class="editor-option">
|
||||
<label class="small">Graphite target expression</label>
|
||||
<input type="text" class="span10" ng-model='currentAnnotation.target' placeholder=""></input>
|
||||
<input type="text" class="span10" ng-model='annotation.target' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="editor-option">
|
||||
<label class="small">Graphite event tags</label>
|
||||
<input type="text" ng-model='currentAnnotation.tags' placeholder=""></input>
|
||||
<input type="text" ng-model='annotation.tags' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
///<amd-dependency path="app/plugins/datasource/graphite/gfunc" name="gfunc" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
|
||||
import {describe, it, expect} from 'test/lib/common';
|
||||
import gfunc from '../gfunc';
|
||||
|
||||
describe('when creating func instance from func names', function() {
|
||||
|
@ -1,29 +1,30 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
],
|
||||
function (angular, InfluxDatasource) {
|
||||
function (InfluxDatasource) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorInfluxdb', function() {
|
||||
function influxMetricsQueryEditor() {
|
||||
return {controller: 'InfluxQueryCtrl', templateUrl: 'app/plugins/datasource/influxdb/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('metricQueryOptionsInfluxdb', function() {
|
||||
function influxMetricsQueryOptions() {
|
||||
return {templateUrl: 'app/plugins/datasource/influxdb/partials/query.options.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('annotationsQueryEditorInfluxdb', function() {
|
||||
function influxAnnotationsQueryEditor() {
|
||||
return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('datasourceCustomSettingsViewInfluxdb', function() {
|
||||
function influxConfigView() {
|
||||
return {templateUrl: 'app/plugins/datasource/influxdb/partials/config.html'};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: InfluxDatasource
|
||||
Datasource: InfluxDatasource,
|
||||
metricsQueryEditor: influxMetricsQueryEditor,
|
||||
metricsQueryOptions: influxMetricsQueryOptions,
|
||||
annotationsQueryEditor: influxAnnotationsQueryEditor,
|
||||
configView: influxConfigView,
|
||||
};
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="section">
|
||||
<h5>InfluxDB Query <tip>Example: select text from events where $timeFilter</tip></h5>
|
||||
<div class="editor-option">
|
||||
<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where $timeFilter"></input>
|
||||
<input type="text" class="span10" ng-model='annotation.query' placeholder="select text from events where $timeFilter"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -12,17 +12,17 @@
|
||||
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Title</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.titleColumn' placeholder=""></input>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Tags</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.tagsColumn' placeholder=""></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.tagsColumn' placeholder=""></input>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Text</label>
|
||||
<input type="text" class="input-small" ng-model='currentAnnotation.textColumn' placeholder=""></input>
|
||||
<input type="text" class="input-small" ng-model='annotation.textColumn' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -126,9 +126,7 @@ function addMathStrategy(selectParts, partModel) {
|
||||
if (selectParts[partCount-2].def.type === 'math') {
|
||||
selectParts[partCount-2] = partModel;
|
||||
return;
|
||||
}
|
||||
// if last is alias add it before
|
||||
else if (selectParts[partCount-1].def.type === 'alias') {
|
||||
} else if (selectParts[partCount-1].def.type === 'alias') { // if last is alias add it before
|
||||
selectParts.splice(partCount-1, 0, partModel);
|
||||
return;
|
||||
}
|
||||
@ -399,8 +397,7 @@ class QueryPart {
|
||||
|
||||
if (strValue === '' && this.def.params[index].optional) {
|
||||
this.params.splice(index, 1);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.params[index] = strValue;
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,5 @@
|
||||
import angular from 'angular';
|
||||
import {MixedDatasource} from './datasource';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorMixed', function() {
|
||||
return {templateUrl: 'app/plugins/datasource/mixed/partials/query.editor.html'};
|
||||
});
|
||||
|
||||
|
||||
export {MixedDatasource, MixedDatasource as Datasource};
|
||||
|
||||
|
@ -1,24 +1,23 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
],
|
||||
function (angular, OpenTsDatasource) {
|
||||
function (OpenTsDatasource) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorOpentsdb', function() {
|
||||
function metricsQueryEditor() {
|
||||
return {
|
||||
controller: 'OpenTSDBQueryCtrl',
|
||||
templateUrl: 'app/plugins/datasource/opentsdb/partials/query.editor.html',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('datasourceCustomSettingsViewOpentsdb', function() {
|
||||
function configView() {
|
||||
return {templateUrl: 'app/plugins/datasource/opentsdb/partials/config.html'};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: OpenTsDatasource
|
||||
Datasource: OpenTsDatasource,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
configView: configView,
|
||||
};
|
||||
});
|
||||
|
@ -1,21 +1,20 @@
|
||||
define([
|
||||
'angular',
|
||||
'./datasource',
|
||||
],
|
||||
function (angular, PromDatasource) {
|
||||
function (PromDatasource) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorPrometheus', function() {
|
||||
function metricsQueryEditor() {
|
||||
return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'};
|
||||
});
|
||||
}
|
||||
|
||||
module.directive('datasourceCustomSettingsViewPrometheus', function() {
|
||||
function configView() {
|
||||
return {templateUrl: 'app/plugins/datasource/prometheus/partials/config.html'};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
Datasource: PromDatasource
|
||||
Datasource: PromDatasource,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
configView: configView,
|
||||
};
|
||||
});
|
||||
|
@ -11,14 +11,8 @@ function (angular, app, _, config, PanelMeta) {
|
||||
var module = angular.module('grafana.panels.dashlist', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.directive('grafanaPanelDashlist', function() {
|
||||
return {
|
||||
controller: 'DashListPanelCtrl',
|
||||
templateUrl: 'app/plugins/panel/dashlist/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('DashListPanelCtrl', function($scope, panelSrv, backendSrv) {
|
||||
/** @ngInject */
|
||||
function DashListPanelCtrl($scope, panelSrv, backendSrv) {
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
panelName: 'Dashboard list',
|
||||
@ -73,5 +67,16 @@ function (angular, app, _, config, PanelMeta) {
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
}
|
||||
|
||||
function dashListPanelDirective() {
|
||||
return {
|
||||
controller: DashListPanelCtrl,
|
||||
templateUrl: 'app/plugins/panel/dashlist/module.html',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
panel: dashListPanelDirective
|
||||
};
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ define([
|
||||
'moment',
|
||||
'lodash',
|
||||
'app/core/utils/kbn',
|
||||
'./graph.tooltip',
|
||||
'./graph_tooltip',
|
||||
'jquery.flot',
|
||||
'jquery.flot.events',
|
||||
'jquery.flot.selection',
|
||||
|
2
public/app/plugins/panel/graph/graph_tooltip.d.ts
vendored
Normal file
2
public/app/plugins/panel/graph/graph_tooltip.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
declare var GraphTooltip: any;
|
||||
export default GraphTooltip;
|
3
public/app/plugins/panel/graph/module.d.ts
vendored
Normal file
3
public/app/plugins/panel/graph/module.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare var panel: any;
|
||||
declare var GraphCtrl: any;
|
||||
export {panel, GraphCtrl};
|
@ -12,16 +12,8 @@ define([
|
||||
function (angular, _, moment, kbn, TimeSeries, PanelMeta) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.graph');
|
||||
|
||||
module.directive('grafanaPanelGraph', function() {
|
||||
return {
|
||||
controller: 'GraphCtrl',
|
||||
templateUrl: 'app/plugins/panel/graph/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) {
|
||||
/** @ngInject */
|
||||
function GraphCtrl($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) {
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
panelName: 'Graph',
|
||||
@ -294,7 +286,17 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) {
|
||||
};
|
||||
|
||||
panelSrv.init($scope);
|
||||
}
|
||||
|
||||
});
|
||||
function graphPanelDirective() {
|
||||
return {
|
||||
controller: GraphCtrl,
|
||||
templateUrl: 'app/plugins/panel/graph/module.html',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
GraphCtrl: GraphCtrl,
|
||||
panel: graphPanelDirective,
|
||||
};
|
||||
});
|
||||
|
53
public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts
Normal file
53
public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts
Normal file
@ -0,0 +1,53 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||
|
||||
import 'app/features/panel/panel_srv';
|
||||
import 'app/features/panel/panel_helper';
|
||||
|
||||
import angular from 'angular';
|
||||
import {GraphCtrl} from '../module';
|
||||
import helpers from '../../../../../test/specs/helpers';
|
||||
|
||||
angular.module('grafana.controllers').controller('GraphCtrl', GraphCtrl);
|
||||
|
||||
describe('GraphCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(angularMocks.module('grafana.controllers'));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('GraphCtrl'));
|
||||
|
||||
describe('get_data with 2 series', function() {
|
||||
beforeEach(function() {
|
||||
ctx.annotationsSrv.getAnnotations = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.datasource.query = sinon.stub().returns(ctx.$q.when({
|
||||
data: [
|
||||
{ target: 'test.cpu1', datapoints: [[1, 10]]},
|
||||
{ target: 'test.cpu2', datapoints: [[1, 10]]}
|
||||
]
|
||||
}));
|
||||
ctx.scope.render = sinon.spy();
|
||||
ctx.scope.refreshData(ctx.datasource);
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
it('should send time series to render', function() {
|
||||
var data = ctx.scope.render.getCall(0).args[0];
|
||||
expect(data.length).to.be(2);
|
||||
});
|
||||
|
||||
describe('get_data failure following success', function() {
|
||||
beforeEach(function() {
|
||||
ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error'));
|
||||
ctx.scope.refreshData(ctx.datasource);
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
230
public/app/plugins/panel/graph/specs/graph_specs.ts
Normal file
230
public/app/plugins/panel/graph/specs/graph_specs.ts
Normal file
@ -0,0 +1,230 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||
|
||||
import '../module';
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import helpers from '../../../../../test/specs/helpers';
|
||||
import TimeSeries from '../../../../core/time_series2';
|
||||
|
||||
describe('grafanaGraph', function() {
|
||||
|
||||
beforeEach(angularMocks.module('grafana.directives'));
|
||||
|
||||
function graphScenario(desc, func) {
|
||||
describe(desc, function() {
|
||||
var ctx: any = {};
|
||||
|
||||
ctx.setup = function(setupFunc) {
|
||||
|
||||
beforeEach(angularMocks.module(function($provide) {
|
||||
$provide.value("timeSrv", new helpers.TimeSrvStub());
|
||||
}));
|
||||
|
||||
beforeEach(angularMocks.inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
var element = angular.element("<div style='width:500px' grafana-graph><div>");
|
||||
|
||||
scope.height = '200px';
|
||||
scope.panel = {
|
||||
legend: {},
|
||||
grid: { },
|
||||
y_formats: [],
|
||||
seriesOverrides: [],
|
||||
tooltip: {
|
||||
shared: true
|
||||
}
|
||||
};
|
||||
|
||||
scope.panelRenderingComplete = sinon.spy();
|
||||
scope.appEvent = sinon.spy();
|
||||
scope.onAppEvent = sinon.spy();
|
||||
scope.hiddenSeries = {};
|
||||
scope.dashboard = { timezone: 'browser' };
|
||||
scope.range = {
|
||||
from: new Date('2014-08-09 10:00:00'),
|
||||
to: new Date('2014-09-09 13:00:00')
|
||||
};
|
||||
ctx.data = [];
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
alias: 'series1'
|
||||
}));
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
alias: 'series2'
|
||||
}));
|
||||
|
||||
setupFunc(scope, ctx.data);
|
||||
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
$.plot = ctx.plotSpy = sinon.spy();
|
||||
|
||||
scope.$emit('render', ctx.data);
|
||||
ctx.plotData = ctx.plotSpy.getCall(0).args[1];
|
||||
ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
|
||||
}));
|
||||
};
|
||||
|
||||
func(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
graphScenario('simple lines options', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.linewidth = 3;
|
||||
scope.panel.steppedLine = true;
|
||||
});
|
||||
|
||||
it('should configure plot with correct options', function() {
|
||||
expect(ctx.plotOptions.series.lines.show).to.be(true);
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
|
||||
expect(ctx.plotOptions.series.lines.steps).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('grid thresholds 100, 200', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 100,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 200,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(100);
|
||||
expect(markings[0].yaxis.to).to.be(200);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(200);
|
||||
expect(markings[1].yaxis.to).to.be(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('inverted grid thresholds 200, 100', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 200,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 100,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(200);
|
||||
expect(markings[0].yaxis.to).to.be(100);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(100);
|
||||
expect(markings[1].yaxis.to).to.be(-Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('grid thresholds from zero', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 0,
|
||||
threshold1Color: "#111",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when logBase is log 10', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
leftMax: null,
|
||||
rightMax: null,
|
||||
leftMin: null,
|
||||
rightMin: null,
|
||||
leftLogBase: 10,
|
||||
};
|
||||
});
|
||||
|
||||
it('should apply axis transform and ticks', function() {
|
||||
var axis = ctx.plotOptions.yaxes[0];
|
||||
expect(axis.transform(100)).to.be(Math.log(100+0.1));
|
||||
expect(axis.ticks[0]).to.be(0);
|
||||
expect(axis.ticks[1]).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('should use timeStep for barWidth', function(ctx) {
|
||||
ctx.setup(function(scope, data) {
|
||||
scope.panel.bars = true;
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[1,10],[2,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set barWidth', function() {
|
||||
expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('series option overrides, fill & points', function(ctx) {
|
||||
ctx.setup(function(scope, data) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.seriesOverrides = [
|
||||
{ alias: 'test', fill: 0, points: true }
|
||||
];
|
||||
|
||||
data[1].alias = 'test';
|
||||
});
|
||||
|
||||
it('should match second series and fill zero, and enable points', function() {
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotData[1].lines.fill).to.be(0.001);
|
||||
expect(ctx.plotData[1].points.show).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('should order series order according to zindex', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
|
||||
});
|
||||
|
||||
it('should move zindex 2 last', function() {
|
||||
expect(ctx.plotData[0].alias).to.be('series2');
|
||||
expect(ctx.plotData[1].alias).to.be('series1');
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when series is hidden', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.hiddenSeries = {'series2': true};
|
||||
});
|
||||
|
||||
it('should remove datapoints and disable stack', function() {
|
||||
expect(ctx.plotData[0].alias).to.be('series1');
|
||||
expect(ctx.plotData[1].data.length).to.be(0);
|
||||
expect(ctx.plotData[1].stack).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when stack and percent', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.percentage = true;
|
||||
scope.panel.stack = true;
|
||||
});
|
||||
|
||||
it('should show percentage', function() {
|
||||
var axis = ctx.plotOptions.yaxes[0];
|
||||
expect(axis.tickFormatter(100, axis)).to.be("100%");
|
||||
});
|
||||
});
|
||||
});
|
171
public/app/plugins/panel/graph/specs/tooltip_specs.ts
Normal file
171
public/app/plugins/panel/graph/specs/tooltip_specs.ts
Normal file
@ -0,0 +1,171 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||
|
||||
import $ from 'jquery';
|
||||
import GraphTooltip from '../graph_tooltip';
|
||||
|
||||
var scope = {
|
||||
appEvent: sinon.spy(),
|
||||
onAppEvent: sinon.spy(),
|
||||
};
|
||||
|
||||
var elem = $('<div></div>');
|
||||
var dashboard = { };
|
||||
|
||||
function describeSharedTooltip(desc, fn) {
|
||||
var ctx: any = {};
|
||||
ctx.scope = scope;
|
||||
ctx.scope.panel = {
|
||||
tooltip: {
|
||||
shared: true
|
||||
},
|
||||
legend: { },
|
||||
stack: false
|
||||
};
|
||||
|
||||
ctx.setup = function(setupFn) {
|
||||
ctx.setupFn = setupFn;
|
||||
};
|
||||
|
||||
describe(desc, function() {
|
||||
beforeEach(function() {
|
||||
ctx.setupFn();
|
||||
var tooltip = new GraphTooltip(elem, dashboard, scope);
|
||||
ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos);
|
||||
});
|
||||
|
||||
fn(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{ data: [[10, 15], [12, 20]], lines: {} },
|
||||
{ data: [[10, 2], [12, 3]], lines: {} }
|
||||
];
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(ctx.results.length).to.be(2);
|
||||
});
|
||||
it('should add time to results array', function() {
|
||||
expect(ctx.results.time).to.be(10);
|
||||
});
|
||||
it('should set value and hoverIndex', function() {
|
||||
expect(ctx.results[0].value).to.be(15);
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
expect(ctx.results[0].hoverIndex).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("one series is hidden", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{ data: [[10, 15], [12, 20]], },
|
||||
{ data: [] }
|
||||
];
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10,15], [12,20]],
|
||||
},
|
||||
stack: true,
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: true
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(17);
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual false, series stack false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 15], [12, 20]],
|
||||
},
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: false
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should not show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 15], [12, 20]],
|
||||
},
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: false
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.scope.panel.tooltip.value_type = 'individual';
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should not show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
245
public/app/plugins/panel/singlestat/controller.js
Normal file
245
public/app/plugins/panel/singlestat/controller.js
Normal file
@ -0,0 +1,245 @@
|
||||
define([
|
||||
'angular',
|
||||
'app/app',
|
||||
'lodash',
|
||||
'app/core/utils/kbn',
|
||||
'app/core/time_series',
|
||||
'app/features/panel/panel_meta',
|
||||
],
|
||||
function (angular, app, _, kbn, TimeSeries, PanelMeta) {
|
||||
'use strict';
|
||||
|
||||
/** @ngInject */
|
||||
function SingleStatCtrl($scope, panelSrv, panelHelper) {
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
panelName: 'Singlestat',
|
||||
editIcon: "fa fa-dashboard",
|
||||
fullscreen: true,
|
||||
metricsEditor: true
|
||||
});
|
||||
|
||||
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
links: [],
|
||||
datasource: null,
|
||||
maxDataPoints: 100,
|
||||
interval: null,
|
||||
targets: [{}],
|
||||
cacheTimeout: null,
|
||||
format: 'none',
|
||||
prefix: '',
|
||||
postfix: '',
|
||||
nullText: null,
|
||||
valueMaps: [
|
||||
{ value: 'null', op: '=', text: 'N/A' }
|
||||
],
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
valueFontSize: '80%',
|
||||
postfixFontSize: '50%',
|
||||
thresholds: '',
|
||||
colorBackground: false,
|
||||
colorValue: false,
|
||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||
sparkline: {
|
||||
show: false,
|
||||
full: false,
|
||||
lineColor: 'rgb(31, 120, 193)',
|
||||
fillColor: 'rgba(31, 118, 189, 0.18)',
|
||||
}
|
||||
};
|
||||
|
||||
_.defaults($scope.panel, _d);
|
||||
$scope.unitFormats = kbn.getUnitFormats();
|
||||
|
||||
$scope.setUnitFormat = function(subItem) {
|
||||
$scope.panel.format = subItem.value;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init($scope);
|
||||
};
|
||||
|
||||
$scope.refreshData = function(datasource) {
|
||||
panelHelper.updateTimeRange($scope);
|
||||
|
||||
return panelHelper.issueMetricQuery($scope, datasource)
|
||||
.then($scope.dataHandler, function(err) {
|
||||
$scope.series = [];
|
||||
$scope.render();
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadSnapshot = function(snapshotData) {
|
||||
panelHelper.updateTimeRange($scope);
|
||||
$scope.dataHandler(snapshotData);
|
||||
};
|
||||
|
||||
$scope.dataHandler = function(results) {
|
||||
$scope.series = _.map(results.data, $scope.seriesHandler);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.seriesHandler = function(seriesData) {
|
||||
var series = new TimeSeries({
|
||||
datapoints: seriesData.datapoints,
|
||||
alias: seriesData.target,
|
||||
});
|
||||
|
||||
series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
$scope.setColoring = function(options) {
|
||||
if (options.background) {
|
||||
$scope.panel.colorValue = false;
|
||||
$scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
|
||||
}
|
||||
else {
|
||||
$scope.panel.colorBackground = false;
|
||||
$scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
|
||||
}
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.invertColorOrder = function() {
|
||||
var tmp = $scope.panel.colors[0];
|
||||
$scope.panel.colors[0] = $scope.panel.colors[2];
|
||||
$scope.panel.colors[2] = tmp;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.getDecimalsForValue = function(value) {
|
||||
if (_.isNumber($scope.panel.decimals)) {
|
||||
return { decimals: $scope.panel.decimals, scaledDecimals: null };
|
||||
}
|
||||
|
||||
var delta = value / 2;
|
||||
var dec = -Math.floor(Math.log(delta) / Math.LN10);
|
||||
|
||||
var magn = Math.pow(10, -dec),
|
||||
norm = delta / magn, // norm is between 1.0 and 10.0
|
||||
size;
|
||||
|
||||
if (norm < 1.5) {
|
||||
size = 1;
|
||||
} else if (norm < 3) {
|
||||
size = 2;
|
||||
// special case for 2.5, requires an extra decimal
|
||||
if (norm > 2.25) {
|
||||
size = 2.5;
|
||||
++dec;
|
||||
}
|
||||
} else if (norm < 7.5) {
|
||||
size = 5;
|
||||
} else {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
size *= magn;
|
||||
|
||||
// reduce starting decimals if not needed
|
||||
if (Math.floor(value) === value) { dec = 0; }
|
||||
|
||||
var result = {};
|
||||
result.decimals = Math.max(0, dec);
|
||||
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
var data = {};
|
||||
|
||||
$scope.setValues(data);
|
||||
|
||||
data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
|
||||
return Number(strVale.trim());
|
||||
});
|
||||
|
||||
data.colorMap = $scope.panel.colors;
|
||||
|
||||
$scope.data = data;
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.setValues = function(data) {
|
||||
data.flotpairs = [];
|
||||
|
||||
if($scope.series.length > 1) {
|
||||
$scope.inspector.error = new Error();
|
||||
$scope.inspector.error.message = 'Multiple Series Error';
|
||||
$scope.inspector.error.data = 'Metric query returns ' + $scope.series.length +
|
||||
' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series);
|
||||
throw $scope.inspector.error;
|
||||
}
|
||||
|
||||
if ($scope.series && $scope.series.length > 0) {
|
||||
var lastPoint = _.last($scope.series[0].datapoints);
|
||||
var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
|
||||
|
||||
if (_.isString(lastValue)) {
|
||||
data.value = 0;
|
||||
data.valueFormated = lastValue;
|
||||
data.valueRounded = 0;
|
||||
} else {
|
||||
data.value = $scope.series[0].stats[$scope.panel.valueName];
|
||||
data.flotpairs = $scope.series[0].flotpairs;
|
||||
|
||||
var decimalInfo = $scope.getDecimalsForValue(data.value);
|
||||
var formatFunc = kbn.valueFormats[$scope.panel.format];
|
||||
data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
}
|
||||
}
|
||||
|
||||
// check value to text mappings
|
||||
for(var i = 0; i < $scope.panel.valueMaps.length; i++) {
|
||||
var map = $scope.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.value) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = "no value";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeValueMap = function(map) {
|
||||
var index = _.indexOf($scope.panel.valueMaps, map);
|
||||
$scope.panel.valueMaps.splice(index, 1);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.addValueMap = function() {
|
||||
$scope.panel.valueMaps.push({value: '', op: '=', text: '' });
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
|
||||
return SingleStatCtrl;
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
<grafana-panel>
|
||||
<div class="singlestat-panel" singlestat-panel></div>
|
||||
<div class="singlestat-panel"></div>
|
||||
<div class="clearfix"></div>
|
||||
</grafana-panel>
|
||||
|
@ -1,253 +1,231 @@
|
||||
define([
|
||||
'angular',
|
||||
'app/app',
|
||||
'./controller',
|
||||
'lodash',
|
||||
'app/core/utils/kbn',
|
||||
'app/core/time_series',
|
||||
'app/features/panel/panel_meta',
|
||||
'./singleStatPanel',
|
||||
'jquery',
|
||||
'jquery.flot',
|
||||
],
|
||||
function (angular, app, _, kbn, TimeSeries, PanelMeta) {
|
||||
function (SingleStatCtrl, _, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.singlestat');
|
||||
app.useModule(module);
|
||||
|
||||
module.directive('grafanaPanelSinglestat', function() {
|
||||
/** @ngInject */
|
||||
function singleStatPanel($location, linkSrv, $timeout, templateSrv) {
|
||||
return {
|
||||
controller: 'SingleStatCtrl',
|
||||
controller: SingleStatCtrl,
|
||||
templateUrl: 'app/plugins/panel/singlestat/module.html',
|
||||
};
|
||||
});
|
||||
link: function(scope, elem) {
|
||||
var data, panel, linkInfo, $panelContainer;
|
||||
var firstRender = true;
|
||||
|
||||
module.controller('SingleStatCtrl', function($scope, panelSrv, panelHelper) {
|
||||
scope.$on('render', function() {
|
||||
if (firstRender) {
|
||||
var inner = elem.find('.singlestat-panel');
|
||||
if (inner.length) {
|
||||
elem = inner;
|
||||
$panelContainer = elem.parents('.panel-container');
|
||||
firstRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
panelName: 'Singlestat',
|
||||
editIcon: "fa fa-dashboard",
|
||||
fullscreen: true,
|
||||
metricsEditor: true
|
||||
});
|
||||
|
||||
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
links: [],
|
||||
datasource: null,
|
||||
maxDataPoints: 100,
|
||||
interval: null,
|
||||
targets: [{}],
|
||||
cacheTimeout: null,
|
||||
format: 'none',
|
||||
prefix: '',
|
||||
postfix: '',
|
||||
nullText: null,
|
||||
valueMaps: [
|
||||
{ value: 'null', op: '=', text: 'N/A' }
|
||||
],
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
valueFontSize: '80%',
|
||||
postfixFontSize: '50%',
|
||||
thresholds: '',
|
||||
colorBackground: false,
|
||||
colorValue: false,
|
||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||
sparkline: {
|
||||
show: false,
|
||||
full: false,
|
||||
lineColor: 'rgb(31, 120, 193)',
|
||||
fillColor: 'rgba(31, 118, 189, 0.18)',
|
||||
}
|
||||
};
|
||||
|
||||
_.defaults($scope.panel, _d);
|
||||
$scope.unitFormats = kbn.getUnitFormats();
|
||||
|
||||
$scope.setUnitFormat = function(subItem) {
|
||||
$scope.panel.format = subItem.value;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init($scope);
|
||||
};
|
||||
|
||||
$scope.refreshData = function(datasource) {
|
||||
panelHelper.updateTimeRange($scope);
|
||||
|
||||
return panelHelper.issueMetricQuery($scope, datasource)
|
||||
.then($scope.dataHandler, function(err) {
|
||||
$scope.series = [];
|
||||
$scope.render();
|
||||
throw err;
|
||||
render();
|
||||
scope.panelRenderingComplete();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadSnapshot = function(snapshotData) {
|
||||
panelHelper.updateTimeRange($scope);
|
||||
$scope.dataHandler(snapshotData);
|
||||
};
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = scope.height || panel.height || scope.row.height;
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
$scope.dataHandler = function(results) {
|
||||
$scope.series = _.map(results.data, $scope.seriesHandler);
|
||||
$scope.render();
|
||||
};
|
||||
height -= 5; // padding
|
||||
height -= panel.title ? 24 : 9; // subtract panel title bar
|
||||
|
||||
$scope.seriesHandler = function(seriesData) {
|
||||
var series = new TimeSeries({
|
||||
datapoints: seriesData.datapoints,
|
||||
alias: seriesData.target,
|
||||
});
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
$scope.setColoring = function(options) {
|
||||
if (options.background) {
|
||||
$scope.panel.colorValue = false;
|
||||
$scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
|
||||
}
|
||||
else {
|
||||
$scope.panel.colorBackground = false;
|
||||
$scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
|
||||
}
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.invertColorOrder = function() {
|
||||
var tmp = $scope.panel.colors[0];
|
||||
$scope.panel.colors[0] = $scope.panel.colors[2];
|
||||
$scope.panel.colors[2] = tmp;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.getDecimalsForValue = function(value) {
|
||||
if (_.isNumber($scope.panel.decimals)) {
|
||||
return { decimals: $scope.panel.decimals, scaledDecimals: null };
|
||||
}
|
||||
|
||||
var delta = value / 2;
|
||||
var dec = -Math.floor(Math.log(delta) / Math.LN10);
|
||||
|
||||
var magn = Math.pow(10, -dec),
|
||||
norm = delta / magn, // norm is between 1.0 and 10.0
|
||||
size;
|
||||
|
||||
if (norm < 1.5) {
|
||||
size = 1;
|
||||
} else if (norm < 3) {
|
||||
size = 2;
|
||||
// special case for 2.5, requires an extra decimal
|
||||
if (norm > 2.25) {
|
||||
size = 2.5;
|
||||
++dec;
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (norm < 7.5) {
|
||||
size = 5;
|
||||
} else {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
size *= magn;
|
||||
function applyColoringThresholds(value, valueString) {
|
||||
if (!panel.colorValue) {
|
||||
return valueString;
|
||||
}
|
||||
|
||||
// reduce starting decimals if not needed
|
||||
if (Math.floor(value) === value) { dec = 0; }
|
||||
var color = getColorForValue(value);
|
||||
if (color) {
|
||||
return '<span style="color:' + color + '">'+ valueString + '</span>';
|
||||
}
|
||||
|
||||
var result = {};
|
||||
result.decimals = Math.max(0, dec);
|
||||
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
var data = {};
|
||||
|
||||
$scope.setValues(data);
|
||||
|
||||
data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
|
||||
return Number(strVale.trim());
|
||||
});
|
||||
|
||||
data.colorMap = $scope.panel.colors;
|
||||
|
||||
$scope.data = data;
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.setValues = function(data) {
|
||||
data.flotpairs = [];
|
||||
|
||||
if($scope.series.length > 1) {
|
||||
$scope.inspector.error = new Error();
|
||||
$scope.inspector.error.message = 'Multiple Series Error';
|
||||
$scope.inspector.error.data = 'Metric query returns ' + $scope.series.length +
|
||||
' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series);
|
||||
throw $scope.inspector.error;
|
||||
}
|
||||
|
||||
if ($scope.series && $scope.series.length > 0) {
|
||||
var lastPoint = _.last($scope.series[0].datapoints);
|
||||
var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
|
||||
|
||||
if (_.isString(lastValue)) {
|
||||
data.value = 0;
|
||||
data.valueFormated = lastValue;
|
||||
data.valueRounded = 0;
|
||||
} else {
|
||||
data.value = $scope.series[0].stats[$scope.panel.valueName];
|
||||
data.flotpairs = $scope.series[0].flotpairs;
|
||||
|
||||
var decimalInfo = $scope.getDecimalsForValue(data.value);
|
||||
var formatFunc = kbn.valueFormats[$scope.panel.format];
|
||||
data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
return valueString;
|
||||
}
|
||||
}
|
||||
|
||||
// check value to text mappings
|
||||
for(var i = 0; i < $scope.panel.valueMaps.length; i++) {
|
||||
var map = $scope.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
function getColorForValue(value) {
|
||||
for (var i = data.thresholds.length - 1; i >= 0 ; i--) {
|
||||
if (value >= data.thresholds[i]) {
|
||||
return data.colorMap[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSpan(className, fontSize, value) {
|
||||
value = templateSrv.replace(value);
|
||||
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
|
||||
value + '</span>';
|
||||
}
|
||||
|
||||
function getBigValueHtml() {
|
||||
var body = '<div class="singlestat-panel-value-container">';
|
||||
|
||||
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); }
|
||||
|
||||
var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
|
||||
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
|
||||
|
||||
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
|
||||
|
||||
body += '</div>';
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function addSparkline() {
|
||||
var panel = scope.panel;
|
||||
var width = elem.width() + 20;
|
||||
var height = elem.height() || 100;
|
||||
|
||||
var plotCanvas = $('<div></div>');
|
||||
var plotCss = {};
|
||||
plotCss.position = 'absolute';
|
||||
|
||||
if (panel.sparkline.full) {
|
||||
plotCss.bottom = '5px';
|
||||
plotCss.left = '-5px';
|
||||
plotCss.width = (width - 10) + 'px';
|
||||
var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5;
|
||||
plotCss.height = (height - dynamicHeightMargin) + 'px';
|
||||
}
|
||||
else {
|
||||
plotCss.bottom = "0px";
|
||||
plotCss.left = "-5px";
|
||||
plotCss.width = (width - 10) + 'px';
|
||||
plotCss.height = Math.floor(height * 0.25) + "px";
|
||||
}
|
||||
|
||||
plotCanvas.css(plotCss);
|
||||
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 1,
|
||||
lineWidth: 1,
|
||||
fillColor: panel.sparkline.fillColor,
|
||||
},
|
||||
},
|
||||
yaxes: { show: false },
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: "time",
|
||||
min: scope.range.from.valueOf(),
|
||||
max: scope.range.to.valueOf(),
|
||||
},
|
||||
grid: { hoverable: false, show: false },
|
||||
};
|
||||
|
||||
elem.append(plotCanvas);
|
||||
|
||||
var plotSeries = {
|
||||
data: data.flotpairs,
|
||||
color: panel.sparkline.lineColor
|
||||
};
|
||||
|
||||
$.plot(plotCanvas, [plotSeries], options);
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!scope.data) { return; }
|
||||
|
||||
data = scope.data;
|
||||
panel = scope.panel;
|
||||
|
||||
setElementHeight();
|
||||
|
||||
var body = getBigValueHtml();
|
||||
|
||||
if (panel.colorBackground && !isNaN(data.valueRounded)) {
|
||||
var color = getColorForValue(data.valueRounded);
|
||||
if (color) {
|
||||
$panelContainer.css('background-color', color);
|
||||
if (scope.fullscreen) {
|
||||
elem.css('background-color', color);
|
||||
} else {
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$panelContainer.css('background-color', '');
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
|
||||
elem.html(body);
|
||||
|
||||
if (panel.sparkline.show) {
|
||||
addSparkline();
|
||||
}
|
||||
|
||||
elem.toggleClass('pointer', panel.links.length > 0);
|
||||
|
||||
if (panel.links.length > 0) {
|
||||
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars);
|
||||
} else {
|
||||
linkInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
// drilldown link tooltip
|
||||
var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
|
||||
|
||||
elem.mouseleave(function() {
|
||||
if (panel.links.length === 0) { return;}
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
|
||||
elem.click(function() {
|
||||
if (!linkInfo) { return; }
|
||||
|
||||
if (linkInfo.target === '_blank') {
|
||||
var redirectWindow = window.open(linkInfo.href, '_blank');
|
||||
redirectWindow.location;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.value) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (linkInfo.href.indexOf('http') === 0) {
|
||||
window.location.href = linkInfo.href;
|
||||
} else {
|
||||
$timeout(function() {
|
||||
$location.url(linkInfo.href);
|
||||
});
|
||||
}
|
||||
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = "no value";
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
|
||||
elem.mousemove(function(e) {
|
||||
if (!linkInfo) { return;}
|
||||
|
||||
drilldownTooltip.text('click to go to: ' + linkInfo.title);
|
||||
|
||||
drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$scope.removeValueMap = function(map) {
|
||||
var index = _.indexOf($scope.panel.valueMaps, map);
|
||||
$scope.panel.valueMaps.splice(index, 1);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.addValueMap = function() {
|
||||
$scope.panel.valueMaps.push({value: '', op: '=', text: '' });
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
return {
|
||||
panel: singleStatPanel
|
||||
};
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ export class TablePanelCtrl {
|
||||
metricsEditor: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/table/options.html');
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
var panelDefaults = {
|
||||
|
@ -118,7 +118,7 @@ export function tablePanelEditor($q, uiSegmentSrv) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
templateUrl: 'app/plugins/panels/table/editor.html',
|
||||
templateUrl: 'app/plugins/panel/table/editor.html',
|
||||
controller: TablePanelEditorCtrl,
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,9 @@ import {TablePanelCtrl} from './controller';
|
||||
import {TableRenderer} from './renderer';
|
||||
import {tablePanelEditor} from './editor';
|
||||
|
||||
export function tablePanel() {
|
||||
angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);
|
||||
|
||||
function tablePanel() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
@ -102,5 +104,4 @@ export function tablePanel() {
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('grafana.directives').directive('grafanaPanelTable', tablePanel);
|
||||
angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);
|
||||
export {tablePanel as panel};
|
||||
|
@ -96,13 +96,12 @@ export class TableRenderer {
|
||||
}
|
||||
|
||||
renderCell(columnIndex, value, addWidthHack = false) {
|
||||
var value = this.formatColumnValue(columnIndex, value);
|
||||
value = this.formatColumnValue(columnIndex, value);
|
||||
var style = '';
|
||||
if (this.colorState.cell) {
|
||||
style = ' style="background-color:' + this.colorState.cell + ';color: white"';
|
||||
this.colorState.cell = null;
|
||||
}
|
||||
else if (this.colorState.value) {
|
||||
} else if (this.colorState.value) {
|
||||
style = ' style="color:' + this.colorState.value + '"';
|
||||
this.colorState.value = null;
|
||||
}
|
||||
|
@ -52,8 +52,7 @@ transformers['timeseries_to_columns'] = {
|
||||
if (!points[timeKey]) {
|
||||
points[timeKey] = {time: dp[1]};
|
||||
points[timeKey][i] = dp[0];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
points[timeKey][i] = dp[0];
|
||||
}
|
||||
}
|
||||
|
@ -10,17 +10,7 @@ function (angular, app, _, require, PanelMeta) {
|
||||
|
||||
var converter;
|
||||
|
||||
var module = angular.module('grafana.panels.text', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.directive('grafanaPanelText', function() {
|
||||
return {
|
||||
controller: 'TextPanelCtrl',
|
||||
templateUrl: 'app/plugins/panel/text/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('TextPanelCtrl', function($scope, templateSrv, $sce, panelSrv) {
|
||||
function TextPanelCtrl($scope, templateSrv, $sce, panelSrv) {
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
panelName: 'Text',
|
||||
@ -107,5 +97,16 @@ function (angular, app, _, require, PanelMeta) {
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
}
|
||||
|
||||
function textPanel() {
|
||||
return {
|
||||
controller: TextPanelCtrl,
|
||||
templateUrl: 'app/plugins/panel/text/module.html',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
panel: textPanel,
|
||||
};
|
||||
});
|
||||
|
@ -1,8 +1,52 @@
|
||||
# Plugin API
|
||||
|
||||
## Changelog
|
||||
### 3.0 changes to plugin api changes
|
||||
|
||||
2.5.1
|
||||
There has been big changes to both data source and plugin schema (plugin.json) and how
|
||||
you write the plugin main module.
|
||||
|
||||
#### Datasource plugin
|
||||
|
||||
Now data source plugins AMD/SystemJS module should return:
|
||||
|
||||
```javascript
|
||||
return {
|
||||
Datasource: ElasticDatasource,
|
||||
configView: editView.default,
|
||||
annotationsQueryEditor: annotationsQueryEditor,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
metricsQueryOptions: metricsQueryOptions,
|
||||
};
|
||||
```
|
||||
|
||||
Where ElasticDatasource is a constructor function to a javascript. The constructor
|
||||
function can take angular services and `instanceSettings` as parameters.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
function ElasticDatasource(instanceSettings, templateSrv) {
|
||||
this.instanceSettings = this.instanceSettings;
|
||||
///...
|
||||
};
|
||||
```
|
||||
|
||||
A datasource module can optionally return a configView directive function, metricsQueryEditor directive function, etc.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
function metricsQueryEditor() {
|
||||
return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'};
|
||||
}
|
||||
```
|
||||
|
||||
#### Panel plugin
|
||||
|
||||
The panel plugin AMD/SystemJS module should return an object with a property named `panel`. This needs to be
|
||||
a directive function.
|
||||
|
||||
### 2.5.1 changes
|
||||
datasource annotationQuery changed. now single options parameter with:
|
||||
- range
|
||||
- rangeRaw
|
||||
|
@ -1,51 +0,0 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'app/features/panel/panel_srv',
|
||||
'app/features/panel/panel_helper',
|
||||
'app/plugins/panel/graph/module'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('GraphCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(module('grafana.panels.graph'));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('GraphCtrl'));
|
||||
|
||||
describe('get_data with 2 series', function() {
|
||||
beforeEach(function() {
|
||||
ctx.annotationsSrv.getAnnotations = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.datasource.query = sinon.stub().returns(ctx.$q.when({
|
||||
data: [
|
||||
{ target: 'test.cpu1', datapoints: [[1, 10]]},
|
||||
{ target: 'test.cpu2', datapoints: [[1, 10]]}
|
||||
]
|
||||
}));
|
||||
ctx.scope.render = sinon.spy();
|
||||
ctx.scope.refreshData(ctx.datasource);
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
it('should send time series to render', function() {
|
||||
var data = ctx.scope.render.getCall(0).args[0];
|
||||
expect(data.length).to.be(2);
|
||||
});
|
||||
|
||||
describe('get_data failure following success', function() {
|
||||
beforeEach(function() {
|
||||
ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error'));
|
||||
ctx.scope.refreshData(ctx.datasource);
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,231 +0,0 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'angular',
|
||||
'jquery',
|
||||
'app/core/time_series',
|
||||
'app/plugins/panel/graph/graph'
|
||||
], function(helpers, angular, $, TimeSeries) {
|
||||
'use strict';
|
||||
|
||||
describe('grafanaGraph', function() {
|
||||
|
||||
beforeEach(module('grafana.directives'));
|
||||
|
||||
function graphScenario(desc, func) {
|
||||
describe(desc, function() {
|
||||
var ctx = {};
|
||||
|
||||
ctx.setup = function (setupFunc) {
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value("timeSrv", new helpers.TimeSrvStub());
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
var element = angular.element("<div style='width:500px' grafana-graph><div>");
|
||||
|
||||
scope.height = '200px';
|
||||
scope.panel = {
|
||||
legend: {},
|
||||
grid: { },
|
||||
y_formats: [],
|
||||
seriesOverrides: [],
|
||||
tooltip: {
|
||||
shared: true
|
||||
}
|
||||
};
|
||||
|
||||
scope.panelRenderingComplete = sinon.spy();
|
||||
scope.appEvent = sinon.spy();
|
||||
scope.onAppEvent = sinon.spy();
|
||||
scope.hiddenSeries = {};
|
||||
scope.dashboard = { timezone: 'browser' };
|
||||
scope.range = {
|
||||
from: new Date('2014-08-09 10:00:00'),
|
||||
to: new Date('2014-09-09 13:00:00')
|
||||
};
|
||||
ctx.data = [];
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
alias: 'series1'
|
||||
}));
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
alias: 'series2'
|
||||
}));
|
||||
|
||||
setupFunc(scope, ctx.data);
|
||||
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
$.plot = ctx.plotSpy = sinon.spy();
|
||||
|
||||
scope.$emit('render', ctx.data);
|
||||
ctx.plotData = ctx.plotSpy.getCall(0).args[1];
|
||||
ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
|
||||
}));
|
||||
};
|
||||
|
||||
func(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
graphScenario('simple lines options', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.linewidth = 3;
|
||||
scope.panel.steppedLine = true;
|
||||
});
|
||||
|
||||
it('should configure plot with correct options', function() {
|
||||
expect(ctx.plotOptions.series.lines.show).to.be(true);
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
|
||||
expect(ctx.plotOptions.series.lines.steps).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('grid thresholds 100, 200', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 100,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 200,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(100);
|
||||
expect(markings[0].yaxis.to).to.be(200);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(200);
|
||||
expect(markings[1].yaxis.to).to.be(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('inverted grid thresholds 200, 100', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 200,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 100,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(200);
|
||||
expect(markings[0].yaxis.to).to.be(100);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(100);
|
||||
expect(markings[1].yaxis.to).to.be(-Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('grid thresholds from zero', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 0,
|
||||
threshold1Color: "#111",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when logBase is log 10', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
leftMax: null,
|
||||
rightMax: null,
|
||||
leftMin: null,
|
||||
rightMin: null,
|
||||
leftLogBase: 10,
|
||||
};
|
||||
});
|
||||
|
||||
it('should apply axis transform and ticks', function() {
|
||||
var axis = ctx.plotOptions.yaxes[0];
|
||||
expect(axis.transform(100)).to.be(Math.log(100+0.1));
|
||||
expect(axis.ticks[0]).to.be(0);
|
||||
expect(axis.ticks[1]).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('should use timeStep for barWidth', function(ctx) {
|
||||
ctx.setup(function(scope, data) {
|
||||
scope.panel.bars = true;
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[1,10],[2,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set barWidth', function() {
|
||||
expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('series option overrides, fill & points', function(ctx) {
|
||||
ctx.setup(function(scope, data) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.seriesOverrides = [
|
||||
{ alias: 'test', fill: 0, points: true }
|
||||
];
|
||||
|
||||
data[1].alias = 'test';
|
||||
});
|
||||
|
||||
it('should match second series and fill zero, and enable points', function() {
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotData[1].lines.fill).to.be(0.001);
|
||||
expect(ctx.plotData[1].points.show).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('should order series order according to zindex', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
|
||||
});
|
||||
|
||||
it('should move zindex 2 last', function() {
|
||||
expect(ctx.plotData[0].alias).to.be('series2');
|
||||
expect(ctx.plotData[1].alias).to.be('series1');
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when series is hidden', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.hiddenSeries = {'series2': true};
|
||||
});
|
||||
|
||||
it('should remove datapoints and disable stack', function() {
|
||||
expect(ctx.plotData[0].alias).to.be('series1');
|
||||
expect(ctx.plotData[1].data.length).to.be(0);
|
||||
expect(ctx.plotData[1].stack).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when stack and percent', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.percentage = true;
|
||||
scope.panel.stack = true;
|
||||
});
|
||||
|
||||
it('should show percentage', function() {
|
||||
var axis = ctx.plotOptions.yaxes[0];
|
||||
expect(axis.tickFormatter(100, axis)).to.be("100%");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,171 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/plugins/panel/graph/graph.tooltip'
|
||||
], function($, GraphTooltip) {
|
||||
'use strict';
|
||||
|
||||
var scope = {
|
||||
appEvent: sinon.spy(),
|
||||
onAppEvent: sinon.spy(),
|
||||
};
|
||||
|
||||
var elem = $('<div></div>');
|
||||
var dashboard = { };
|
||||
|
||||
function describeSharedTooltip(desc, fn) {
|
||||
var ctx = {};
|
||||
ctx.scope = scope;
|
||||
ctx.scope.panel = {
|
||||
tooltip: {
|
||||
shared: true
|
||||
},
|
||||
legend: { },
|
||||
stack: false
|
||||
};
|
||||
|
||||
ctx.setup = function(setupFn) {
|
||||
ctx.setupFn = setupFn;
|
||||
};
|
||||
|
||||
describe(desc, function() {
|
||||
beforeEach(function() {
|
||||
ctx.setupFn();
|
||||
var tooltip = new GraphTooltip(elem, dashboard, scope);
|
||||
ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos);
|
||||
});
|
||||
|
||||
fn(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{ data: [[10, 15], [12, 20]], lines: {} },
|
||||
{ data: [[10, 2], [12, 3]], lines: {} }
|
||||
];
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(ctx.results.length).to.be(2);
|
||||
});
|
||||
it('should add time to results array', function() {
|
||||
expect(ctx.results.time).to.be(10);
|
||||
});
|
||||
it('should set value and hoverIndex', function() {
|
||||
expect(ctx.results[0].value).to.be(15);
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
expect(ctx.results[0].hoverIndex).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("one series is hidden", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{ data: [[10, 15], [12, 20]], },
|
||||
{ data: [] }
|
||||
];
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10,15], [12,20]],
|
||||
},
|
||||
stack: true,
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: true
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(17);
|
||||
});
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual false, series stack false", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 15], [12, 20]],
|
||||
},
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: false
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should not show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.data = [
|
||||
{
|
||||
data: [[10, 15], [12, 20]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 15], [12, 20]],
|
||||
},
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
data: [[10, 2], [12, 3]],
|
||||
lines: {},
|
||||
datapoints: {
|
||||
pointsize: 2,
|
||||
points: [[10, 2], [12, 3]],
|
||||
},
|
||||
stack: false
|
||||
}
|
||||
];
|
||||
ctx.scope.panel.stack = true;
|
||||
ctx.scope.panel.tooltip.value_type = 'individual';
|
||||
ctx.pos = { x: 11 };
|
||||
});
|
||||
|
||||
it('should not show stacked value', function() {
|
||||
expect(ctx.results[1].value).to.be(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
6
public/test/specs/helpers.d.ts
vendored
6
public/test/specs/helpers.d.ts
vendored
@ -1,6 +1,4 @@
|
||||
declare module "test/specs/helpers" {
|
||||
let helpers: any;
|
||||
export default helpers;
|
||||
}
|
||||
declare let helpers: any;
|
||||
export default helpers;
|
||||
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
define([
|
||||
'angular',
|
||||
'./helpers',
|
||||
'app/plugins/panel/singlestat/controller',
|
||||
'app/features/panel/panel_srv',
|
||||
'app/features/panel/panel_helper',
|
||||
'app/plugins/panel/singlestat/module'
|
||||
], function(helpers) {
|
||||
], function(angular, helpers, SingleStatCtrl) {
|
||||
'use strict';
|
||||
|
||||
angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl);
|
||||
|
||||
describe('SingleStatCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
@ -16,7 +19,7 @@ define([
|
||||
ctx.setup = function (setupFunc) {
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(module('grafana.panels.singlestat'));
|
||||
beforeEach(module('grafana.controllers'));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('SingleStatCtrl'));
|
||||
@ -24,7 +27,7 @@ define([
|
||||
beforeEach(function() {
|
||||
setupFunc();
|
||||
ctx.datasource.query = sinon.stub().returns(ctx.$q.when({
|
||||
data: [ { target: 'test.cpu1', datapoints: ctx.datapoints } ]
|
||||
data: [{target: 'test.cpu1', datapoints: ctx.datapoints}]
|
||||
}));
|
||||
|
||||
ctx.scope.refreshData(ctx.datasource);
|
||||
|
@ -6,18 +6,19 @@ module.exports = function(config) {
|
||||
}
|
||||
},
|
||||
options: {
|
||||
configuration: {
|
||||
rules: {
|
||||
curly: true,
|
||||
align: [true, "parameters", "statements"],
|
||||
indent: [true, "spaces"],
|
||||
"class-name": true,
|
||||
"interface-name": true,
|
||||
"semicolon": true,
|
||||
"use-strict": [false, "check-module", "check-function"],
|
||||
"whitespace": [true, "check-branch", "check-decl", "check-type"],
|
||||
}
|
||||
}
|
||||
configuration: 'tslint.json'
|
||||
// {
|
||||
// rules: {
|
||||
// curly: true,
|
||||
// align: [true, "parameters", "statements"],
|
||||
// indent: [true, "spaces"],
|
||||
// "class-name": true,
|
||||
// "interface-name": true,
|
||||
// "semicolon": true,
|
||||
// "use-strict": [false, "check-module", "check-function"],
|
||||
// "whitespace": [true, "check-branch", "check-decl", "check-type"],
|
||||
// }
|
||||
// }
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -27,15 +27,17 @@ module.exports = function(config, grunt) {
|
||||
}
|
||||
|
||||
if (/(\.ts)$/.test(filepath)) {
|
||||
newPath = filepath.replace(/^public/, 'public_gen');
|
||||
grunt.log.writeln('Copying to ' + newPath);
|
||||
grunt.file.copy(filepath, newPath);
|
||||
|
||||
// copy ts file also used by source maps
|
||||
//changes changed file source to that of the changed file
|
||||
var option = 'typescript.build.src';
|
||||
var result = filepath;
|
||||
grunt.config(option, result);
|
||||
grunt.task.run('typescript:build');
|
||||
grunt.task.run('tslint');
|
||||
// copy ts file also used by source maps
|
||||
newPath = filepath.replace(/^public/, 'public_gen');
|
||||
grunt.file.copy(filepath, newPath);
|
||||
}
|
||||
});
|
||||
|
||||
|
62
tslint.json
Normal file
62
tslint.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [true, "check-space"],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": false,
|
||||
"indent": [true, "spaces"],
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [true, 140],
|
||||
"member-access": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": true,
|
||||
"no-shadowed-variable": false,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-comma": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": false,
|
||||
"no-unused-variable": false,
|
||||
"no-unreachable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else"
|
||||
],
|
||||
"radix": false,
|
||||
"semicolon": true,
|
||||
"triple-equals": [true, "allow-null-check"],
|
||||
"typedef-whitespace": [true, {
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}],
|
||||
"variable-name": [true, "ban-keywords"],
|
||||
"whitespace": [true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user