Merge branch 'panelbase'

This commit is contained in:
Torkel Ödegaard
2016-02-01 17:11:08 +01:00
167 changed files with 2310 additions and 233724 deletions

View File

@@ -5,7 +5,7 @@ import store from 'app/core/store';
import _ from 'lodash';
import angular from 'angular';
import $ from 'jquery';
import coreModule from '../core_module';
import coreModule from 'app/core/core_module';
export class GrafanaCtrl {

View File

@@ -59,11 +59,14 @@ coreModule.filter('noXml', function() {
coreModule.filter('interpolateTemplateVars', function (templateSrv) {
var filterFunc: any = function(text, scope) {
if (scope.panel) {
return templateSrv.replaceWithText(text, scope.panel.scopedVars);
var scopedVars;
if (scope.ctrl && scope.ctrl.panel) {
scopedVars = scope.ctrl.panel.scopedVars;
} else {
return templateSrv.replaceWithText(text, scope.row.scopedVars);
scopedVars = scope.row.scopedVars;
}
return templateSrv.replaceWithText(text, scopedVars);
};
filterFunc.$stateful = true;

View File

@@ -116,36 +116,7 @@ function (angular, _, config) {
$scope.$broadcast('render');
};
$scope.removePanel = function(panel) {
$scope.appEvent('confirm-modal', {
title: 'Are you sure you want to remove this panel?',
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: function() {
$scope.row.panels = _.without($scope.row.panels, panel);
}
});
};
$scope.updatePanelSpan = function(panel, span) {
panel.span = Math.min(Math.max(Math.floor(panel.span + span), 1), 12);
};
$scope.replacePanel = function(newPanel, oldPanel) {
var row = $scope.row;
var index = _.indexOf(row.panels, oldPanel);
row.panels.splice(index, 1);
// adding it back needs to be done in next digest
$timeout(function() {
newPanel.id = oldPanel.id;
newPanel.span = oldPanel.span;
row.panels.splice(index, 0, newPanel);
});
};
$scope.init();
});
module.directive('rowHeight', function() {

View File

@@ -26,7 +26,7 @@ function (angular, _, require, config) {
$scope.modalTitle = 'Share Dashboard';
}
if (!$scope.dashboardMeta.isSnapshot) {
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({title: 'Snapshot sharing', src: 'shareSnapshot.html'});
}

View File

@@ -17,6 +17,7 @@ function (angular, _, $) {
self.state = {};
self.panelScopes = [];
self.$scope = $scope;
self.dashboard = $scope.dashboard;
$scope.exitFullscreen = function() {
if (self.state.fullscreen) {
@@ -31,6 +32,14 @@ function (angular, _, $) {
}
});
$scope.onAppEvent('panel-change-view', function(evt, payload) {
self.update(payload);
});
$scope.onAppEvent('panel-instantiated', function(evt, payload) {
self.registerPanel(payload.scope);
});
this.update(this.getQueryStringState(), true);
this.expandRowForPanel();
}
@@ -66,7 +75,7 @@ function (angular, _, $) {
DashboardViewState.prototype.update = function(state, skipUrlSync) {
_.extend(this.state, state);
this.fullscreen = this.state.fullscreen;
this.dashboard.meta.fullscreen = this.state.fullscreen;
if (!this.state.fullscreen) {
this.state.panelId = null;
@@ -84,7 +93,7 @@ function (angular, _, $) {
DashboardViewState.prototype.syncState = function() {
if (this.panelScopes.length === 0) { return; }
if (this.fullscreen) {
if (this.dashboard.meta.fullscreen) {
if (this.fullscreenPanel) {
this.leaveFullscreen(false);
}
@@ -105,23 +114,24 @@ function (angular, _, $) {
DashboardViewState.prototype.getPanelScope = function(id) {
return _.find(this.panelScopes, function(panelScope) {
return panelScope.panel.id === id;
return panelScope.ctrl.panel.id === id;
});
};
DashboardViewState.prototype.leaveFullscreen = function(render) {
var self = this;
var ctrl = self.fullscreenPanel.ctrl;
self.fullscreenPanel.editMode = false;
self.fullscreenPanel.fullscreen = false;
delete self.fullscreenPanel.height;
ctrl.editMode = false;
ctrl.fullscreen = false;
delete ctrl.height;
this.$scope.appEvent('panel-fullscreen-exit', {panelId: this.fullscreenPanel.panel.id});
this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id});
if (!render) { return false;}
$timeout(function() {
if (self.oldTimeRange !== self.fullscreenPanel.range) {
if (self.oldTimeRange !== ctrl.range) {
self.$scope.broadcastRefresh();
}
else {
@@ -135,17 +145,18 @@ function (angular, _, $) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var ctrl = panelScope.ctrl;
panelScope.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit;
panelScope.height = panelScope.editMode ? editHeight : fullscreenHeight;
ctrl.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit;
ctrl.height = ctrl.editMode ? editHeight : fullscreenHeight;
ctrl.fullscreen = true;
this.oldTimeRange = panelScope.range;
this.oldTimeRange = ctrl.range;
this.fullscreenPanel = panelScope;
$(window).scrollTop(0);
panelScope.fullscreen = true;
this.$scope.appEvent('panel-fullscreen-enter', {panelId: panelScope.panel.id});
this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id});
$timeout(function() {
panelScope.$broadcast('render');
@@ -156,8 +167,12 @@ function (angular, _, $) {
var self = this;
self.panelScopes.push(panelScope);
if (self.state.panelId === panelScope.panel.id) {
self.enterFullscreen(panelScope);
if (self.state.panelId === panelScope.ctrl.panel.id) {
if (self.state.edit) {
panelScope.ctrl.editPanel();
} else {
panelScope.ctrl.viewPanel();
}
}
panelScope.$on('$destroy', function() {

View File

@@ -6,4 +6,5 @@ define([
'./solo_panel_ctrl',
'./panel_loader',
'./query_editor',
'./panel_editor_tab',
], function () {});

View File

@@ -0,0 +1,234 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import $ from 'jquery';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import {PanelCtrl} from './panel_ctrl';
import * as rangeUtil from '../../core/utils/rangeutil';
import * as dateMath from '../../core/utils/datemath';
class MetricsPanelCtrl extends PanelCtrl {
error: boolean;
loading: boolean;
datasource: any;
$q: any;
$timeout: any;
datasourceSrv: any;
timeSrv: any;
timing: any;
range: any;
rangeRaw: any;
interval: any;
resolution: any;
timeInfo: any;
skipDataOnInit: boolean;
datasources: any[];
constructor($scope, $injector) {
super($scope, $injector);
// make metrics tab the default
this.editorTabIndex = 1;
this.$q = $injector.get('$q');
this.datasourceSrv = $injector.get('datasourceSrv');
this.timeSrv = $injector.get('timeSrv');
if (!this.panel.targets) {
this.panel.targets = [{}];
}
// hookup initial data fetch
this.$timeout(() => {
if (!this.skipDataOnInit) {
this.refresh();
}
}, 30);;
}
initEditMode() {
this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
this.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
this.datasources = this.datasourceSrv.getMetricSources();
}
refresh() {
this.getData();
}
refreshData(data) {
// null op
return this.$q.when(data);
}
loadSnapshot(data) {
// null op
return data;
}
getData() {
// ignore fetching data if another panel is in fullscreen
if (this.otherPanelInFullscreenMode()) { return; }
// if we have snapshot data use that
if (this.panel.snapshotData) {
if (this.loadSnapshot) {
this.loadSnapshot(this.panel.snapshotData);
}
return;
}
// clear loading/error state
delete this.error;
this.loading = true;
// load datasource service
this.datasourceSrv.get(this.panel.datasource).then(datasource => {
this.datasource = datasource;
return this.refreshData(this.datasource);
}).then(() => {
this.loading = false;
}).catch(err => {
console.log('Panel data error:', err);
this.loading = false;
this.error = err.message || "Timeseries data request error";
this.inspector = {error: err};
});
}
setTimeQueryStart() {
this.timing = {};
this.timing.queryStart = new Date().getTime();
}
setTimeQueryEnd() {
this.timing.queryEnd = new Date().getTime();
}
updateTimeRange() {
this.range = this.timeSrv.timeRange();
this.rangeRaw = this.timeSrv.timeRange(false);
this.applyPanelTimeOverrides();
if (this.panel.maxDataPoints) {
this.resolution = this.panel.maxDataPoints;
} else {
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
}
var panelInterval = this.panel.interval;
var datasourceInterval = (this.datasource || {}).interval;
this.interval = kbn.calculateInterval(this.range, this.resolution, panelInterval || datasourceInterval);
};
applyPanelTimeOverrides() {
this.timeInfo = '';
// check panel time overrrides
if (this.panel.timeFrom) {
var timeFromInfo = rangeUtil.describeTextRange(this.panel.timeFrom);
if (timeFromInfo.invalid) {
this.timeInfo = 'invalid time override';
return;
}
if (_.isString(this.rangeRaw.from)) {
var timeFromDate = dateMath.parse(timeFromInfo.from);
this.timeInfo = timeFromInfo.display;
this.rangeRaw.from = timeFromInfo.from;
this.rangeRaw.to = timeFromInfo.to;
this.range.from = timeFromDate;
}
}
if (this.panel.timeShift) {
var timeShiftInfo = rangeUtil.describeTextRange(this.panel.timeShift);
if (timeShiftInfo.invalid) {
this.timeInfo = 'invalid timeshift';
return;
}
var timeShift = '-' + this.panel.timeShift;
this.timeInfo += ' timeshift ' + timeShift;
this.range.from = dateMath.parseDateMath(timeShift, this.range.from, false);
this.range.to = dateMath.parseDateMath(timeShift, this.range.to, true);
this.rangeRaw = this.range;
}
if (this.panel.hideTimeOverride) {
this.timeInfo = '';
}
};
issueQueries(datasource) {
if (!this.panel.targets || this.panel.targets.length === 0) {
return this.$q.when([]);
}
this.updateTimeRange();
var metricsQuery = {
range: this.range,
rangeRaw: this.rangeRaw,
interval: this.interval,
targets: this.panel.targets,
format: this.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: this.resolution,
scopedVars: this.panel.scopedVars,
cacheTimeout: this.panel.cacheTimeout
};
this.setTimeQueryStart();
return datasource.query(metricsQuery).then(results => {
this.setTimeQueryEnd();
if (this.dashboard.snapshot) {
this.panel.snapshotData = results;
}
return results;
});
}
addDataQuery(datasource) {
this.dashboard.addDataQueryTo(this.panel, datasource);
}
removeDataQuery(query) {
this.dashboard.removeDataQuery(this.panel, query);
this.refresh();
};
duplicateDataQuery(query) {
this.dashboard.duplicateDataQuery(this.panel, query);
}
moveDataQuery(fromIndex, toIndex) {
this.dashboard.moveDataQuery(this.panel, fromIndex, toIndex);
}
setDatasource(datasource) {
// switching to mixed
if (datasource.meta.mixed) {
_.each(this.panel.targets, target => {
target.datasource = this.panel.datasource;
if (target.datasource === null) {
target.datasource = config.defaultDatasource;
}
});
} else if (this.datasource && this.datasource.meta.mixed) {
_.each(this.panel.targets, target => {
delete target.datasource;
});
}
this.panel.datasource = datasource.value;
this.datasource = null;
this.refresh();
}
}
export {MetricsPanelCtrl};

View File

@@ -0,0 +1,50 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import {PanelCtrl} from './panel_ctrl';
import {MetricsPanelCtrl} from './metrics_panel_ctrl';
export class DefaultPanelCtrl extends PanelCtrl {
constructor($scope, $injector) {
super($scope, $injector);
}
}
class PanelDirective {
template: string;
templateUrl: string;
bindToController: boolean;
scope: any;
controller: any;
controllerAs: string;
getDirective() {
if (!this.controller) {
this.controller = DefaultPanelCtrl;
}
return {
template: this.template,
templateUrl: this.templateUrl,
controller: this.controller,
controllerAs: 'ctrl',
bindToController: true,
scope: {dashboard: "=", panel: "=", row: "="},
link: (scope, elem, attrs, ctrl) => {
ctrl.init();
this.link(scope, elem, attrs, ctrl);
}
};
}
link(scope, elem, attrs, ctrl) {
return null;
}
}
export {
PanelCtrl,
MetricsPanelCtrl,
PanelDirective,
}

View File

@@ -0,0 +1,179 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
export class PanelCtrl {
panel: any;
row: any;
dashboard: any;
editorTabIndex: number;
pluginName: string;
pluginId: string;
icon: string;
editorTabs: any;
$scope: any;
$injector: any;
$timeout: any;
fullscreen: boolean;
inspector: any;
editModeInitiated: boolean;
editorHelpIndex: number;
constructor($scope, $injector) {
this.$injector = $injector;
this.$scope = $scope;
this.$timeout = $injector.get('$timeout');
this.editorTabIndex = 0;
var plugin = config.panels[this.panel.type];
if (plugin) {
this.pluginId = plugin.id;
this.pluginName = plugin.name;
}
$scope.$on("refresh", () => this.refresh());
}
init() {
this.publishAppEvent('panel-instantiated', {scope: this.$scope});
this.refresh();
}
renderingCompleted() {
this.$scope.$root.performance.panelsRendered++;
}
refresh() {
return;
}
publishAppEvent(evtName, evt) {
this.$scope.$root.appEvent(evtName, evt);
}
changeView(fullscreen, edit) {
this.publishAppEvent('panel-change-view', {
fullscreen: fullscreen, edit: edit, panelId: this.panel.id
});
}
viewPanel() {
this.changeView(true, false);
}
editPanel() {
if (!this.editModeInitiated) {
this.editorTabs = [];
this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
this.initEditMode();
}
this.changeView(true, true);
}
exitFullscreen() {
this.changeView(false, false);
}
initEditMode() {
return;
}
addEditorTab(title, directiveFn, index?) {
var editorTab = {title, directiveFn};
if (_.isString(directiveFn)) {
editorTab.directiveFn = function() {
return {templateUrl: directiveFn};
};
}
if (index) {
this.editorTabs.splice(index, 0, editorTab);
} else {
this.editorTabs.push(editorTab);
}
}
getMenu() {
let menu = [];
menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'});
menu.push({text: 'Edit', click: 'ctrl.editPanel(); dismiss();', role: 'Editor'});
menu.push({text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor' });
menu.push({text: 'Share', click: 'ctrl.sharePanel(); dismiss();'});
return menu;
}
getExtendedMenu() {
return [{text: 'Panel JSON', click: 'ctrl.editPanelJson(); dismiss();'}];
}
otherPanelInFullscreenMode() {
return this.dashboard.meta.fullscreen && !this.fullscreen;
}
broadcastRender(arg1?, arg2?) {
this.$scope.$broadcast('render', arg1, arg2);
}
toggleEditorHelp(index) {
if (this.editorHelpIndex === index) {
this.editorHelpIndex = null;
return;
}
this.editorHelpIndex = index;
}
duplicate() {
this.dashboard.duplicatePanel(this.panel, this.row);
}
updateColumnSpan(span) {
this.panel.span = Math.min(Math.max(Math.floor(this.panel.span + span), 1), 12);
this.$timeout(() => {
this.broadcastRender();
});
}
removePanel() {
this.publishAppEvent('confirm-modal', {
title: 'Are you sure you want to remove this panel?',
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: () => {
this.row.panels = _.without(this.row.panels, this.panel);
}
});
}
editPanelJson() {
this.publishAppEvent('show-json-editor', {
object: this.panel,
updateHandler: this.replacePanel.bind(this)
});
}
replacePanel(newPanel, oldPanel) {
var row = this.row;
var index = _.indexOf(this.row.panels, oldPanel);
this.row.panels.splice(index, 1);
// adding it back needs to be done in next digest
this.$timeout(() => {
newPanel.id = oldPanel.id;
newPanel.span = oldPanel.span;
this.row.panels.splice(index, 0, newPanel);
});
}
sharePanel() {
var shareScope = this.$scope.$new();
shareScope.panel = this.panel;
shareScope.dashboard = this.dashboard;
this.publishAppEvent('show-modal', {
src: './app/features/dashboard/partials/shareModal.html',
scope: shareScope
});
}
}

View File

@@ -12,12 +12,13 @@ function (angular, $) {
restrict: 'E',
templateUrl: 'app/features/panel/partials/panel.html',
transclude: true,
scope: { ctrl: "=" },
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
var ctrl = scope.ctrl;
scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() {
panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false);
});
}
};
@@ -30,6 +31,7 @@ function (angular, $) {
link: function(scope, elem) {
var resizing = false;
var lastPanel = false;
var ctrl = scope.ctrl;
var handleOffset;
var originalHeight;
var originalWidth;
@@ -40,31 +42,31 @@ function (angular, $) {
resizing = true;
handleOffset = $(e.target).offset();
originalHeight = parseInt(scope.row.height);
originalWidth = scope.panel.span;
originalHeight = parseInt(ctrl.row.height);
originalWidth = ctrl.panel.span;
maxWidth = $(document).width();
lastPanel = scope.row.panels[scope.row.panels.length - 1];
lastPanel = ctrl.row.panels[ctrl.row.panels.length - 1];
$('body').on('mousemove', moveHandler);
$('body').on('mouseup', dragEndHandler);
}
function moveHandler(e) {
scope.row.height = originalHeight + (e.pageY - handleOffset.top);
scope.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12);
scope.panel.span = Math.min(Math.max(scope.panel.span, 1), 12);
ctrl.row.height = originalHeight + (e.pageY - handleOffset.top);
ctrl.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12);
ctrl.panel.span = Math.min(Math.max(ctrl.panel.span, 1), 12);
var rowSpan = scope.dashboard.rowSpan(scope.row);
var rowSpan = ctrl.dashboard.rowSpan(ctrl.row);
// auto adjust other panels
if (Math.floor(rowSpan) < 14) {
// last panel should not push row down
if (lastPanel === scope.panel && rowSpan > 12) {
if (lastPanel === ctrl.panel && rowSpan > 12) {
lastPanel.span -= rowSpan - 12;
}
// reduce width of last panel so total in row is 12
else if (lastPanel !== scope.panel) {
else if (lastPanel !== ctrl.panel) {
lastPanel.span = lastPanel.span - (rowSpan - 12);
lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12);
}
@@ -77,7 +79,7 @@ function (angular, $) {
function dragEndHandler() {
// if close to 12
var rowSpan = scope.dashboard.rowSpan(scope.row);
var rowSpan = ctrl.dashboard.rowSpan(ctrl.row);
if (rowSpan < 12 && rowSpan > 11) {
lastPanel.span += 12 - rowSpan;
}

View File

@@ -0,0 +1,28 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import config from 'app/core/config';
var directiveModule = angular.module('grafana.directives');
/** @ngInject */
function panelEditorTab(dynamicDirectiveSrv) {
return dynamicDirectiveSrv.create({
scope: {
ctrl: "=",
editorTab: "=",
index: "=",
},
directive: scope => {
var pluginId = scope.ctrl.pluginId;
var tabIndex = scope.index;
return Promise.resolve({
name: `panel-editor-tab-${pluginId}${tabIndex}`,
fn: scope.editorTab.directiveFn,
});
}
});
}
directiveModule.directive('panelEditorTab', panelEditorTab);

View File

@@ -3,28 +3,82 @@
import angular from 'angular';
import config from 'app/core/config';
import {unknownPanelDirective} from '../../plugins/panel/unknown/module';
import {UnknownPanel} from '../../plugins/panel/unknown/module';
var directiveModule = angular.module('grafana.directives');
/** @ngInject */
function panelLoader($parse, dynamicDirectiveSrv) {
return dynamicDirectiveSrv.create({
directive: scope => {
let panelInfo = config.panels[scope.panel.type];
if (!panelInfo) {
return Promise.resolve({
name: 'panel-directive-' + scope.panel.type,
fn: unknownPanelDirective
function panelLoader($compile, dynamicDirectiveSrv, $http, $q, $injector) {
return {
restrict: 'E',
scope: {
dashboard: "=",
row: "=",
panel: "="
},
link: function(scope, elem, attrs) {
function getTemplate(directive) {
if (directive.template) {
return $q.when(directive.template);
}
return $http.get(directive.templateUrl).then(res => {
return res.data;
});
}
return System.import(panelInfo.module).then(function(panelModule) {
return {
name: 'panel-directive-' + scope.panel.type,
fn: panelModule.panel,
};
function addPanelAndCompile(name) {
var child = angular.element(document.createElement(name));
child.attr('dashboard', 'dashboard');
child.attr('panel', 'panel');
child.attr('row', 'row');
$compile(child)(scope);
elem.empty();
elem.append(child);
}
function addPanel(name, Panel) {
if (Panel.registered) {
addPanelAndCompile(name);
return;
}
if (Panel.promise) {
Panel.promise.then(() => {
addPanelAndCompile(name);
});
return;
}
var panelInstance = $injector.instantiate(Panel);
var directive = panelInstance.getDirective();
Panel.promise = getTemplate(directive).then(template => {
directive.templateUrl = null;
directive.template = `<grafana-panel ctrl="ctrl">${template}</grafana-panel>`;
directiveModule.directive(attrs.$normalize(name), function() {
return directive;
});
Panel.registered = true;
addPanelAndCompile(name);
});
}
var panelElemName = 'panel-directive-' + scope.panel.type;
let panelInfo = config.panels[scope.panel.type];
if (!panelInfo) {
addPanel(panelElemName, UnknownPanel);
return;
}
System.import(panelInfo.module).then(function(panelModule) {
addPanel(panelElemName, panelModule.Panel);
}).catch(err => {
console.log('Panel err: ', err);
});
},
});
}
};
}
angular.module('grafana.directives').directive('panelLoader', panelLoader);
directiveModule.directive('panelLoader', panelLoader);

View File

@@ -11,32 +11,32 @@ function (angular, $, _) {
.directive('panelMenu', function($compile, linkSrv) {
var linkTemplate =
'<span class="panel-title drag-handle pointer">' +
'<span class="panel-title-text drag-handle">{{panel.title | interpolateTemplateVars:this}}</span>' +
'<span class="panel-links-btn"><i class="fa fa-external-link"></i></span>' +
'<span class="panel-time-info" ng-show="panelMeta.timeInfo"><i class="fa fa-clock-o"></i> {{panelMeta.timeInfo}}</span>' +
'<span class="panel-title-text drag-handle">{{ctrl.panel.title | interpolateTemplateVars:this}}</span>' +
'<span class="panel-links-btn"><i class="fa fa-link"></i></span>' +
'<span class="panel-time-info" ng-show="ctrl.timeInfo"><i class="fa fa-clock-o"></i> {{ctrl.timeInfo}}</span>' +
'</span>';
function createExternalLinkMenu($scope) {
function createExternalLinkMenu(ctrl) {
var template = '<div class="panel-menu small">';
template += '<div class="panel-menu-row">';
if ($scope.panel.links) {
_.each($scope.panel.links, function(link) {
var info = linkSrv.getPanelLinkAnchorInfo(link, $scope.panel.scopedVars);
if (ctrl.panel.links) {
_.each(ctrl.panel.links, function(link) {
var info = linkSrv.getPanelLinkAnchorInfo(link, ctrl.panel.scopedVars);
template += '<a class="panel-menu-link" href="' + info.href + '" target="' + info.target + '">' + info.title + '</a>';
});
}
return template;
}
function createMenuTemplate($scope) {
function createMenuTemplate(ctrl) {
var template = '<div class="panel-menu small">';
if ($scope.dashboardMeta.canEdit) {
if (ctrl.dashboard.meta.canEdit) {
template += '<div class="panel-menu-inner">';
template += '<div class="panel-menu-row">';
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(-1)"><i class="fa fa-minus"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(1)"><i class="fa fa-plus"></i></a>';
template += '<a class="panel-menu-icon pull-right" ng-click="removePanel(panel)"><i class="fa fa-remove"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="ctrl.updateColumnSpan(-1)"><i class="fa fa-minus"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="ctrl.updateColumnSpan(1)"><i class="fa fa-plus"></i></a>';
template += '<a class="panel-menu-icon pull-right" ng-click="ctrl.removePanel()"><i class="fa fa-remove"></i></a>';
template += '<div class="clearfix"></div>';
template += '</div>';
}
@@ -44,9 +44,9 @@ function (angular, $, _) {
template += '<div class="panel-menu-row">';
template += '<a class="panel-menu-link" gf-dropdown="extendedMenu"><i class="fa fa-bars"></i></a>';
_.each($scope.panelMeta.menu, function(item) {
_.each(ctrl.getMenu(), function(item) {
// skip edit actions if not editor
if (item.role === 'Editor' && !$scope.dashboardMeta.canEdit) {
if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) {
return;
}
@@ -63,8 +63,8 @@ function (angular, $, _) {
return template;
}
function getExtendedMenu($scope) {
return angular.copy($scope.panelMeta.extendedMenu);
function getExtendedMenu(ctrl) {
return ctrl.getExtendedMenu();
}
return {
@@ -74,13 +74,14 @@ function (angular, $, _) {
var $panelLinksBtn = $link.find(".panel-links-btn");
var $panelContainer = elem.parents(".panel-container");
var menuScope = null;
var ctrl = $scope.ctrl;
var timeout = null;
var $menu = null;
elem.append($link);
$scope.$watchCollection('panel.links', function(newValue) {
var showIcon = (newValue ? newValue.length > 0 : false) && $scope.panel.title !== '';
$scope.$watchCollection('ctrl.panel.links', function(newValue) {
var showIcon = (newValue ? newValue.length > 0 : false) && ctrl.panel.title !== '';
$panelLinksBtn.toggle(showIcon);
});
@@ -95,7 +96,7 @@ function (angular, $, _) {
// if hovering or draging pospone close
if (force !== true) {
if ($menu.is(':hover') || $scope.dashboard.$$panelDragging) {
if ($menu.is(':hover') || $scope.ctrl.dashboard.$$panelDragging) {
dismiss(2200);
return;
}
@@ -126,7 +127,7 @@ function (angular, $, _) {
if ($(e.target).hasClass('fa-link')) {
menuTemplate = createExternalLinkMenu($scope);
} else {
menuTemplate = createMenuTemplate($scope);
menuTemplate = createMenuTemplate(ctrl);
}
$menu = $(menuTemplate);
@@ -135,7 +136,7 @@ function (angular, $, _) {
});
menuScope = $scope.$new();
menuScope.extendedMenu = getExtendedMenu($scope);
menuScope.extendedMenu = getExtendedMenu(ctrl);
menuScope.dismiss = function() {
dismiss(null, true);
};

View File

@@ -1,7 +0,0 @@
define([
'./panel_meta2',
],
function (panelMeta) {
'use strict';
return panelMeta.default;
});

View File

@@ -1,48 +0,0 @@
export default class PanelMeta {
description: any;
fullscreen: any;
editIcon: any;
panelName: any;
menu: any;
editorTabs: any;
extendedMenu: any;
constructor(options: any) {
this.description = options.description;
this.fullscreen = options.fullscreen;
this.editIcon = options.editIcon;
this.panelName = options.panelName;
this.menu = [];
this.editorTabs = [];
this.extendedMenu = [];
if (options.fullscreen) {
this.addMenuItem('View', 'icon-eye-open', 'toggleFullscreen(false); dismiss();');
}
this.addMenuItem('Edit', 'icon-cog', 'editPanel(); dismiss();', 'Editor');
this.addMenuItem('Duplicate', 'icon-copy', 'duplicatePanel()', 'Editor');
this.addMenuItem('Share', 'icon-share', 'sharePanel(); dismiss();');
this.addEditorTab('General', 'app/partials/panelgeneral.html');
if (options.metricsEditor) {
this.addEditorTab('Metrics', 'app/partials/metrics.html');
}
this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson(); dismiss();');
}
addMenuItem (text, icon, click, role?) {
this.menu.push({text: text, icon: icon, click: click, role: role});
}
addExtendedMenuItem (text, icon, click, role?) {
this.extendedMenu.push({text: text, icon: icon, click: click, role: role});
}
addEditorTab (title, src) {
this.editorTabs.push({title: title, src: src});
}
}

View File

@@ -1,12 +1,12 @@
<div class="panel-container" ng-class="{'panel-transparent': panel.transparent}">
<div class="panel-container" ng-class="{'panel-transparent': ctrl.panel.transparent}">
<div class="panel-header">
<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="panelMeta.error">
<span data-placement="top" bs-tooltip="panelMeta.error">
<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="ctrl.error">
<span data-placement="top" bs-tooltip="ctrl.error">
<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>
</span>
</span>
<span class="panel-loading" ng-show="panelMeta.loading">
<span class="panel-loading" ng-show="ctrl.loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
@@ -19,27 +19,27 @@
<panel-resizer></panel-resizer>
</div>
<div class="panel-full-edit" ng-if="editMode">
<div class="panel-full-edit" ng-if="ctrl.editMode">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i ng-class="panelMeta.editIcon"></i>
{{panelMeta.panelName}}
<i ng-class="ctrl.icon"></i>
{{ctrl.name}}
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
<div ng-model="ctrl.editorTabIndex" bs-tabs>
<div ng-repeat="tab in ctrl.editorTabs" data-title="{{tab.title}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="exitFullscreen();">
<button class="gf-box-header-close-btn" ng-click="ctrl.exitFullscreen();">
Back to dashboard
</button>
</div>
<div class="gf-box-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
<div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index">
<panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
</div>
</div>
</div>

View File

@@ -13,8 +13,8 @@
</li>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h"
empty-to-null ng-model="panel.timeFrom" valid-time-span
ng-change="get_data()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span
ng-change="ctrl.refresh()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -32,8 +32,8 @@
</li>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h"
empty-to-null ng-model="panel.timeShift" valid-time-span
ng-change="get_data()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
ng-change="ctrl.refresh()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -47,9 +47,9 @@
<strong>Hide time override info</strong>
</li>
<li class="tight-form-item last">
<input class="cr1" id="panel.hideTimeOverride" type="checkbox"
ng-model="panel.hideTimeOverride" ng-checked="panel.hideTimeOverride" ng-change="get_data()">
<label for="panel.hideTimeOverride" class="cr1"></label>
<input class="cr1" id="ctrl.panel.hideTimeOverride" type="checkbox"
ng-model="ctrl.panel.hideTimeOverride" ng-checked="ctrl.panel.hideTimeOverride" ng-change="ctrl.refresh()">
<label for="ctrl.panel.hideTimeOverride" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -2,7 +2,8 @@
<div class="row-fluid">
<div class="span12">
<div class="panel nospace" ng-if="panel" style="width: 100%">
<panel-loader type="panel.type" ng-cloak></panel-loader>
<panel-loader dashboard="dashboard" row="row" panel="panel">
</panel-loader>
</div>
</div>
</div>

View File

@@ -5,9 +5,9 @@ import angular from 'angular';
/** @ngInject */
function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
return dynamicDirectiveSrv.create({
watchPath: "panel.datasource",
watchPath: "ctrl.panel.datasource",
directive: scope => {
let datasource = scope.target.datasource || scope.panel.datasource;
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
return datasourceSrv.get(datasource).then(ds => {
scope.datasource = ds;
@@ -29,9 +29,9 @@ function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
/** @ngInject */
function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
return dynamicDirectiveSrv.create({
watchPath: "panel.datasource",
watchPath: "ctrl.panel.datasource",
directive: scope => {
return datasourceSrv.get(scope.panel.datasource).then(ds => {
return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => {
return System.import(ds.meta.module).then(dsModule => {
return {
name: 'metrics-query-options-' + ds.meta.id,

View File

@@ -39,13 +39,8 @@ function (angular, $) {
}
$scope.panel.span = 12;
$scope.dashboardViewState = {registerPanel: function() { }, state: {}};
};
if (!$scope.skipAutoInit) {
$scope.init();
}
$scope.init();
});
});

View File

@@ -5,7 +5,7 @@ declare var System: any;
// dummy modules
declare module 'app/core/config' {
var config : any;
var config: any;
export default config;
}
@@ -35,7 +35,7 @@ declare module 'app/core/utils/kbn' {
}
declare module 'app/core/store' {
var store : any;
var store: any;
export default store;
}

View File

@@ -23,7 +23,7 @@
<div class="row-text pointer" ng-click="toggleRow(row)" ng-bind="row.title | interpolateTemplateVars:this"></div>
</div>
<div class="row-open" ng-show="!row.collapse">
<div class='row-tab bgSuccess dropdown' ng-show="dashboardMeta.canEdit" ng-hide="dashboardViewState.fullscreen">
<div class='row-tab bgSuccess dropdown' ng-show="dashboardMeta.canEdit" ng-hide="dashboard.meta.fullscreen">
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bars"></i>
</span>
@@ -79,9 +79,10 @@
<div class="row-text pointer" ng-click="toggleRow(row)" ng-if="row.showTitle" ng-bind="row.title | interpolateTemplateVars:this">
</div>
<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 class="panel-margin"></panel-loader>
<div ng-repeat="panel in row.panels track by panel.id" class="panel" ui-draggable="!dashboard.meta.fullscreen" drag="panel.id"
ui-on-drop="onDrop($data, row, panel)" drag-handle-class="drag-handle" panel-width>
<panel-loader class="panel-margin" dashboard="dashboard" row="row" panel="panel">
</panel-loader>
</div>
<div panel-drop-zone class="panel panel-drop-zone" ui-on-drop="onDrop($data, row)" data-drop="true">
@@ -97,7 +98,7 @@
</div>
</div>
<div ng-show='dashboardMeta.canEdit' class="row-fluid add-row-panel-hint" ng-hide="dashboardViewState.fullscreen">
<div ng-show='dashboardMeta.canEdit' class="row-fluid add-row-panel-hint" ng-hide="dashboard.meta.fullscreen">
<div class="span12" style="text-align:right;">
<span style="margin-right: 10px;" ng-click="addRowDefault()" class="pointer btn btn-info btn-small">
<span><i class="fa fa-plus"></i> ADD ROW</span>

View File

@@ -1,25 +1,25 @@
<div class="editor-row">
<div class="tight-form-container">
<metrics-query-editor ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
<metrics-query-editor ng-repeat="target in ctrl.panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
</metrics-query-editor>
</div>
<div style="margin: 20px 0 0 0">
<button class="btn btn-inverse" ng-click="addDataQuery()" ng-hide="datasource.meta.mixed">
<button class="btn btn-inverse" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.datasource.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Query
</button>
<div class="dropdown" ng-if="datasource.meta.mixed">
<div class="dropdown" ng-if="ctrl.datasource.meta.mixed">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>&nbsp;
Query &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="addDataQuery(datasource);">{{datasource.name}}</a>
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
@@ -34,12 +34,12 @@
<div class="pull-right dropdown" style="margin-right: 10px;">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
<i class="fa fa-database"></i>&nbsp;
{{datasource.name}} &nbsp; <span class="caret"></span>
{{ctrl.datasource.name}} &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem">
<a ng-click="setDatasource(datasource);">{{datasource.name}}</a>
<li ng-repeat="datasource in ctrl.datasources" role="menuitem">
<a ng-click="ctrl.setDatasource(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>

View File

@@ -7,23 +7,23 @@
Title
</li>
<li>
<input type="text" class="input-xlarge tight-form-input" ng-model='panel.title'></input>
<input type="text" class="input-xlarge tight-form-input" ng-model='ctrl.panel.title'></input>
</li>
<li class="tight-form-item">
Span
</li>
<li>
<select class="input-mini tight-form-input" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
<select class="input-mini tight-form-input" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
</li>
<li class="tight-form-item">
Height
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model='panel.height'></input>
<input type="text" class="input-small tight-form-input" ng-model='ctrl.panel.height'></input>
</li>
<li class="tight-form-item">
<label class="checkbox-label" for="panel.transparent">Transparent</label>
<input class="cr1" id="panel.transparent" type="checkbox" ng-model="panel.transparent" ng-checked="panel.transparent">
<input class="cr1" id="panel.transparent" type="checkbox" ng-model="ctrl.panel.transparent" ng-checked="ctrl.panel.transparent">
<label for="panel.transparent" class="cr1"></label>
</li>
</ul>
@@ -38,7 +38,7 @@
Repeat Panel
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
<select class="input-small tight-form-input last" ng-model="ctrl.panel.repeat" ng-options="f.name as f.name for f in ctrl.dashboard.templating.list">
<option value=""></option>
</select>
</li>
@@ -46,7 +46,7 @@
Min span
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]">
<select class="input-small tight-form-input last" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]">
<option value=""></option>
</select>
</li>
@@ -56,6 +56,6 @@
</div>
</div>
<panel-links-editor panel="panel"></panel-links-editor>
<panel-links-editor panel="ctrl.panel"></panel-links-editor>

View File

@@ -6,14 +6,14 @@
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="ctrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -25,7 +25,7 @@
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
ng-click="target.hide = !target.hide; ctrl.refresh();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>
@@ -35,4 +35,4 @@
<div class="clearfix"></div>
</div>
<cloudwatch-query-parameter target="target" datasource="datasource" on-change="refreshMetricData()"></cloudwatch-query-parameter>
<cloudwatch-query-parameter target="target" datasource="ctrl.datasource" on-change="refreshMetricData()"></cloudwatch-query-parameter>

View File

@@ -16,7 +16,7 @@ function (angular, _) {
$scope.refreshMetricData = function() {
if (!_.isEqual($scope.oldTarget, $scope.target)) {
$scope.oldTarget = angular.copy($scope.target);
$scope.get_data();
$scope.ctrl.refresh();
}
};

View File

@@ -14,15 +14,15 @@
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="panelCtrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -33,7 +33,7 @@
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
@@ -44,13 +44,13 @@
Query
</li>
<li>
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="target.query" spellcheck='false' placeholder="Lucene query" ng-blur="get_data()">
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="target.query" spellcheck='false' placeholder="Lucene query" ng-blur="panelCtrl.refresh()">
</li>
<li class="tight-form-item query-keyword">
Alias
</li>
<li>
<input type="text" class="tight-form-input" style="width: 200px;" ng-model="target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="get_data()">
<input type="text" class="tight-form-input" style="width: 200px;" ng-model="target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="panelCtrl.refresh()">
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -8,7 +8,7 @@
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
<input type="text" class="input-medium tight-form-input" ng-model="ctrl.panel.interval" ng-blur="ctrl.refresh();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
@@ -23,7 +23,7 @@
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
@@ -34,7 +34,7 @@
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul ng-non-bindable>
<li>{{term fieldname}} = replaced with value of term group by</li>

View File

@@ -6,8 +6,9 @@ function (angular) {
var module = angular.module('grafana.controllers');
module.controller('ElasticQueryCtrl', function($scope, $timeout, uiSegmentSrv) {
module.controller('ElasticQueryCtrl', function($scope, $rootScope, $timeout, uiSegmentSrv) {
$scope.esVersion = $scope.datasource.esVersion;
$scope.panelCtrl = $scope.ctrl;
$scope.init = function() {
var target = $scope.target;
@@ -27,10 +28,10 @@ function (angular) {
var newJson = angular.toJson($scope.datasource.queryBuilder.build($scope.target), true);
if (newJson !== $scope.oldQueryRaw) {
$scope.rawQueryOld = newJson;
$scope.get_data();
$scope.panelCtrl.refresh();
}
$scope.appEvent('elastic-query-updated');
$rootScope.appEvent('elastic-query-updated');
};
$scope.handleQueryError = function(err) {

View File

@@ -5,7 +5,10 @@ function (GraphiteDatasource) {
'use strict';
function metricsQueryEditor() {
return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
return {
controller: 'GraphiteQueryCtrl',
templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'
};
}
function metricsQueryOptions() {

View File

@@ -25,19 +25,19 @@
</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a>
<a tabindex="1" ng-click="ctrl.duplicateDataQuery(target)">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a>
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a>
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index+1)">Move down</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="ctrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -48,14 +48,14 @@
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<span style="display: block; overflow: hidden;">
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.textEditor"></input>
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="panelCtrl.getData()" ng-show="target.textEditor"></input>
</span>
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">

View File

@@ -11,7 +11,7 @@
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.cacheTimeout"
ng-model="ctrl.panel.cacheTimeout"
bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
@@ -23,10 +23,10 @@
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.maxDataPoints"
ng-model="ctrl.panel.maxDataPoints"
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
data-placement="right"
ng-model-onblur ng-change="get_data()"
ng-model-onblur ng-change="ctrl.refresh()"
spellcheck='false'
placeholder="auto"></input>
</li>
@@ -39,27 +39,27 @@
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
shorter legend names
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
series as parameters
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
templating
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
max data points
</a>
</li>
@@ -71,7 +71,7 @@
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
<div class="grafana-info-box span8" ng-if="ctrl.editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<ul>
<li>alias() function to specify a custom series name</li>
@@ -81,7 +81,7 @@
</ul>
</div>
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
<div class="grafana-info-box span8" ng-if="ctrl.editorHelpIndex === 2">
<h5>Series as parameter</h5>
<ul>
<li>Some graphite functions allow you to have many series arguments</li>
@@ -99,7 +99,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 3">
<h5>Stacking</h5>
<ul>
<li>You find the stacking option under Display Styles tab</li>
@@ -107,7 +107,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 4">
<h5>Templating</h5>
<ul>
<li>You can use a template variable in place of metric names</li>
@@ -116,7 +116,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 5">
<h5>Max data points</h5>
<ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li>

View File

@@ -11,6 +11,8 @@ function (angular, _, config, gfunc, Parser) {
var module = angular.module('grafana.controllers');
module.controller('GraphiteQueryCtrl', function($scope, uiSegmentSrv, templateSrv) {
var panelCtrl = $scope.panelCtrl = $scope.ctrl;
var datasource = $scope.datasource;
$scope.init = function() {
if ($scope.target) {
@@ -125,7 +127,7 @@ function (angular, _, config, gfunc, Parser) {
}
var path = getSegmentPathUpTo(fromIndex + 1);
return $scope.datasource.metricFindQuery(path)
return datasource.metricFindQuery(path)
.then(function(segments) {
if (segments.length === 0) {
if (path !== '') {
@@ -159,7 +161,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.getAltSegments = function (index) {
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
return $scope.datasource.metricFindQuery(query).then(function(segments) {
return datasource.metricFindQuery(query).then(function(segments) {
var altSegments = _.map(segments, function(segment) {
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
});
@@ -208,7 +210,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetTextChanged = function() {
parseTarget();
$scope.get_data();
panelCtrl.refresh();
};
$scope.targetChanged = function() {
@@ -222,7 +224,7 @@ function (angular, _, config, gfunc, Parser) {
if ($scope.target.target !== oldTarget) {
if ($scope.segments[$scope.segments.length - 1].value !== 'select metric') {
$scope.$parent.get_data();
panelCtrl.refresh();
}
}
};

View File

@@ -14,13 +14,18 @@ describe('GraphiteQueryCtrl', function() {
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl'));
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
ctx.$q = $q;
ctx.scope = $rootScope.$new();
ctx.scope.ctrl = {panel: ctx.panel};
ctx.panelCtrl = ctx.scope.ctrl;
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
ctx.controller = $controller('GraphiteQueryCtrl', {$scope: ctx.scope});
}));
beforeEach(function() {
ctx.scope.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
});
describe('init', function() {
@@ -49,7 +54,7 @@ describe('GraphiteQueryCtrl', function() {
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.panelCtrl.refresh = sinon.spy();
ctx.scope.addFunction(gfunc.getFuncDef('aliasByNode'));
});
@@ -61,8 +66,8 @@ describe('GraphiteQueryCtrl', function() {
expect(ctx.scope.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
});
it('should call get_data', function() {
expect(ctx.scope.$parent.get_data.called).to.be(true);
it('should call refresh', function() {
expect(ctx.panelCtrl.refresh.called).to.be(true);
});
});
@@ -72,8 +77,6 @@ describe('GraphiteQueryCtrl', function() {
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.scope.addFunction(gfunc.getFuncDef('asPercent'));
});
@@ -88,7 +91,6 @@ describe('GraphiteQueryCtrl', function() {
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should not add select metric segment', function() {
@@ -107,7 +109,6 @@ describe('GraphiteQueryCtrl', function() {
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should add 2 segments', function() {
@@ -147,7 +148,6 @@ describe('GraphiteQueryCtrl', function() {
ctx.altSegments = results;
});
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should have no segments', function() {
@@ -162,7 +162,7 @@ describe('GraphiteQueryCtrl', function() {
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.panelCtrl.refresh = sinon.spy();
ctx.scope.target.target = '';
ctx.scope.targetChanged();
});
@@ -171,8 +171,8 @@ describe('GraphiteQueryCtrl', function() {
expect(ctx.scope.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
});
it('should call get_data', function() {
expect(ctx.scope.$parent.get_data.called).to.be(true);
it('should call panelCtrl.refresh', function() {
expect(ctx.panelCtrl.refresh.called).to.be(true);
});
});
});

View File

@@ -16,15 +16,15 @@
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="toggleQueryMode()">Switch editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="panelCtrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -35,7 +35,7 @@
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
@@ -60,7 +60,7 @@
</ul>
<div class="tight-form-flex-wrapper" ng-show="target.rawQuery">
<input type="text" class="tight-form-clear-input" ng-model="target.query" spellcheck="false" style="width: 100%;" ng-blur="get_data()"></input>
<input type="text" class="tight-form-clear-input" ng-model="target.query" spellcheck="false" style="width: 100%;" ng-blur="panelCtrl.refresh()"></input>
</div>
<div class="clearfix"></div>
@@ -88,7 +88,7 @@
<span>GROUP BY</span>
</li>
<li ng-repeat="part in queryModel.groupByParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="removeGroupByPart(part, $index)" part-updated="get_data();" get-options="getPartOptions(part)"></influx-query-part-editor>
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="removeGroupByPart(part, $index)" part-updated="panelCtrl.refresh();" get-options="getPartOptions(part)"></influx-query-part-editor>
</li>
<li>
<metric-segment segment="groupBySegment" get-options="getGroupByOptions()" on-change="groupByAction(part, $index)"></metric-segment>
@@ -104,13 +104,13 @@
ALIAS BY
</li>
<li>
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="get_data()">
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="panelCtrl.refresh()">
</li>
<li class="tight-form-item">
Format as
</li>
<li>
<select class="input-small tight-form-input" style="width: 104px" ng-model="target.resultFormat" ng-options="f.value as f.text for f in resultFormats" ng-change="get_data()"></select>
<select class="input-small tight-form-input" style="width: 104px" ng-model="target.resultFormat" ng-options="f.value as f.text for f in resultFormats" ng-change="panelCtrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -8,7 +8,7 @@
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
<input type="text" class="input-medium tight-form-input" ng-model="ctrl.panel.interval" ng-blur="ctrl.refresh();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
@@ -24,17 +24,17 @@
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
@@ -46,7 +46,7 @@
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$m = replaced with measurement name</li>
@@ -58,7 +58,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
@@ -69,7 +69,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
@@ -80,8 +80,6 @@
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>

View File

@@ -15,13 +15,16 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
queryPart = queryPart.default;
module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) {
var panelCtrl = $scope.ctrl;
var datasource = $scope.datasource;
$scope.panelCtrl = panelCtrl;
$scope.init = function() {
if (!$scope.target) { return; }
$scope.target = $scope.target;
$scope.queryModel = new InfluxQuery($scope.target);
$scope.queryBuilder = new InfluxQueryBuilder($scope.target, $scope.datasource.database);
$scope.queryBuilder = new InfluxQueryBuilder($scope.target, datasource.database);
$scope.groupBySegment = uiSegmentSrv.newPlusButton();
$scope.resultFormats = [
{text: 'Time series', value: 'time_series'},
@@ -75,7 +78,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getGroupByOptions = function() {
var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(query)
return datasource.metricFindQuery(query)
.then(function(tags) {
var options = [];
if (!$scope.queryModel.hasFill()) {
@@ -97,26 +100,26 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
var plusButton = uiSegmentSrv.newPlusButton();
$scope.groupBySegment.value = plusButton.value;
$scope.groupBySegment.html = plusButton.html;
$scope.get_data();
panelCtrl.refresh();
};
$scope.removeGroupByPart = function(part, index) {
$scope.queryModel.removeGroupByPart(part, index);
$scope.get_data();
panelCtrl.refresh();
};
$scope.addSelectPart = function(selectParts, cat, subitem) {
$scope.queryModel.addSelectPart(selectParts, subitem.value);
$scope.get_data();
panelCtrl.refresh();
};
$scope.removeSelectPart = function(selectParts, part) {
$scope.queryModel.removeSelectPart(selectParts, part);
$scope.get_data();
panelCtrl.refresh();
};
$scope.selectPartUpdated = function() {
$scope.get_data();
panelCtrl.refresh();
};
$scope.fixTagSegments = function() {
@@ -130,19 +133,19 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.measurementChanged = function() {
$scope.target.measurement = $scope.measurementSegment.value;
$scope.get_data();
panelCtrl.refresh();
};
$scope.getPolicySegments = function() {
var policiesQuery = $scope.queryBuilder.buildExploreQuery('RETENTION POLICIES');
return $scope.datasource.metricFindQuery(policiesQuery)
return datasource.metricFindQuery(policiesQuery)
.then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError);
};
$scope.policyChanged = function() {
$scope.target.policy = $scope.policySegment.value;
$scope.get_data();
panelCtrl.refresh();
};
$scope.toggleQueryMode = function () {
@@ -151,19 +154,19 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getMeasurements = function () {
var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS');
return $scope.datasource.metricFindQuery(query)
return datasource.metricFindQuery(query)
.then($scope.transformToSegments(true), $scope.handleQueryError);
};
$scope.getPartOptions = function(part) {
if (part.def.type === 'field') {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery)
return datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments(true), $scope.handleQueryError);
}
if (part.def.type === 'tag') {
var tagsQuery = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(tagsQuery)
return datasource.metricFindQuery(tagsQuery)
.then($scope.transformToSegments(true), $scope.handleQueryError);
}
};
@@ -211,7 +214,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
addTemplateVars = true;
}
return $scope.datasource.metricFindQuery(query)
return datasource.metricFindQuery(query)
.then($scope.transformToSegments(addTemplateVars))
.then(function(results) {
if (segment.type === 'key') {
@@ -224,7 +227,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getFieldSegments = function() {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery)
return datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError);
};
@@ -234,7 +237,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.setFill = function(fill) {
$scope.target.fill = fill;
$scope.get_data();
panelCtrl.refresh();
};
$scope.tagSegmentUpdated = function(segment, index) {
@@ -300,7 +303,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
});
$scope.target.tags = tags;
$scope.$parent.get_data();
panelCtrl.refresh();
};
$scope.getTagValueOperator = function(tagValue, tagOperator) {

View File

@@ -10,14 +10,20 @@ describe('InfluxDBQueryCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('InfluxQueryCtrl'));
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
ctx.$q = $q;
ctx.scope = $rootScope.$new();
ctx.scope.ctrl = {panel: ctx.panel};
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
ctx.panelCtrl = ctx.scope.ctrl;
ctx.controller = $controller('InfluxQueryCtrl', {$scope: ctx.scope});
}));
beforeEach(function() {
ctx.scope.target = {};
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
ctx.panelCtrl.refresh = sinon.spy();
});
describe('init', function() {

View File

@@ -9,15 +9,14 @@
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="toggleQueryMode()">Switch editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="panelCtrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -29,7 +28,7 @@
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
ng-click="target.hide = !target.hide; panelCtrl.refresh();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>

View File

@@ -9,6 +9,7 @@ function (angular, _, kbn) {
var module = angular.module('grafana.controllers');
module.controller('OpenTSDBQueryCtrl', function($scope) {
$scope.panelCtrl = $scope.ctrl;
$scope.init = function() {
$scope.target.errors = validateTarget($scope.target);

View File

@@ -9,15 +9,14 @@
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="toggleQueryMode()">Switch editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.duplicateDataQuery(target)">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="panelCtrl.moveDataQuery($index, $index+1)">Move down</a></li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="panelCtr.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@@ -29,7 +28,7 @@
</li>
<li>
<a class="tight-form-item"
ng-click="target.hide = !target.hide; get_data();"
ng-click="target.hide = !target.hide; panelCtrl.refresh();"
role="menuitem">
<i class="fa fa-eye"></i>
</a>

View File

@@ -8,6 +8,8 @@ function (angular, _) {
var module = angular.module('grafana.controllers');
module.controller('PrometheusQueryCtrl', function($scope, templateSrv) {
$scope.panelCtrl = $scope.ctrl;
$scope.panel = $scope.panelCtrl.panel;
$scope.init = function() {
var target = $scope.target;
@@ -29,7 +31,7 @@ function (angular, _) {
$scope.refreshMetricData = function() {
if (!_.isEqual($scope.oldTarget, $scope.target)) {
$scope.oldTarget = angular.copy($scope.target);
$scope.get_data();
$scope.paneCtrl.refresh();
}
};

View File

@@ -7,13 +7,13 @@
<strong>Mode</strong>
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.mode" ng-options="f for f in modes" ng-change="get_data()"></select>
<select class="input-small tight-form-input last" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="section" style="margin-bottom: 20px" ng-if="panel.mode === 'search'">
<div class="section" style="margin-bottom: 20px" ng-if="ctrl.panel.mode === 'search'">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px">
@@ -24,13 +24,13 @@
</li>
<li>
<input type="text" class="input-medium tight-form-input" placeholder="title query"
ng-model="panel.query" ng-change="get_data()" ng-model-onblur>
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
</li>
<li class="tight-form-item">
Tags
</li>
<li>
<bootstrap-tagsinput ng-model="panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="get_data()">
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</li>
</ul>
@@ -47,7 +47,7 @@
<strong>Limit number to</strong>
</li>
<li>
<input class="input-small tight-form-input last" type="number" ng-model="panel.limit" ng-model-onblur ng-change="get_data()">
<input class="input-small tight-form-input last" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -1,6 +1,5 @@
<grafana-panel>
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in dashList">
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in ctrl.dashList">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<span class="dashlist-title">
{{dash.title}}
@@ -10,4 +9,4 @@
</span>
</a>
</div>
</grafana-panel>
</div>

View File

@@ -1,82 +0,0 @@
define([
'angular',
'app/app',
'lodash',
'app/core/config',
'app/features/panel/panel_meta',
],
function (angular, app, _, config, PanelMeta) {
'use strict';
var module = angular.module('grafana.panels.dashlist', []);
app.useModule(module);
/** @ngInject */
function DashListPanelCtrl($scope, panelSrv, backendSrv) {
$scope.panelMeta = new PanelMeta({
panelName: 'Dashboard list',
editIcon: "fa fa-star",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/dashlist/editor.html');
var defaults = {
mode: 'starred',
query: '',
limit: 10,
tags: []
};
$scope.modes = ['starred', 'search'];
_.defaults($scope.panel, defaults);
$scope.dashList = [];
$scope.init = function() {
panelSrv.init($scope);
if ($scope.panel.tag) {
$scope.panel.tags = [$scope.panel.tag];
delete $scope.panel.tag;
}
if ($scope.isNewPanel()) {
$scope.panel.title = "Starred Dashboards";
}
};
$scope.refreshData = function() {
var params = {
limit: $scope.panel.limit
};
if ($scope.panel.mode === 'starred') {
params.starred = "true";
} else {
params.query = $scope.panel.query;
params.tag = $scope.panel.tags;
}
return backendSrv.search(params).then(function(result) {
$scope.dashList = result;
$scope.panelRenderingComplete();
});
};
$scope.init();
}
function dashListPanelDirective() {
return {
controller: DashListPanelCtrl,
templateUrl: 'app/plugins/panel/dashlist/module.html',
};
}
return {
panel: dashListPanelDirective
};
});

View File

@@ -0,0 +1,63 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import config from 'app/core/config';
import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
// Set and populate defaults
var panelDefaults = {
mode: 'starred',
query: '',
limit: 10,
tags: []
};
class DashListCtrl extends PanelCtrl {
dashList: any[];
modes: any[];
/** @ngInject */
constructor($scope, $injector, private backendSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
if (this.panel.tag) {
this.panel.tags = [$scope.panel.tag];
delete this.panel.tag;
}
}
initEditMode() {
this.modes = ['starred', 'search'];
this.icon = "fa fa-star";
this.addEditorTab('Options', () => {
return {templateUrl: 'app/plugins/panel/dashlist/editor.html'};
});
}
refresh() {
var params: any = {limit: this.panel.limit};
if (this.panel.mode === 'starred') {
params.starred = "true";
} else {
params.query = this.panel.query;
params.tag = this.panel.tags;
}
return this.backendSrv.search(params).then(result => {
this.dashList = result;
this.renderingCompleted();
});
}
}
class DashListPanel extends PanelDirective {
controller = DashListCtrl;
templateUrl = 'public/app/plugins/panel/dashlist/module.html';
}
export {
DashListCtrl,
DashListPanel as Panel
}

View File

@@ -10,22 +10,22 @@
Unit
</li>
<li class="dropdown" style="width: 140px;"
ng-model="panel.y_formats[0]"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(0, $subItem)">
ng-model="ctrl.panel.y_formats[0]"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat(0, $subItem)">
</li>
<li class="tight-form-item">
Scale type
</li>
<li>
<select class="input-small tight-form-input" style="width: 113px" ng-model="panel.grid.leftLogBase" ng-options="v as k for (k, v) in logScales" ng-change="render()"></select>
<select class="input-small tight-form-input" style="width: 113px" ng-model="ctrl.panel.grid.leftLogBase" ng-options="v as k for (k, v) in ctrl.logScales" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Label
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.leftYAxisLabel" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.leftYAxisLabel" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -40,16 +40,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto"
empty-to-null ng-model="panel.grid.leftMax"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.leftMax"
ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item" style="width: 115px; text-align: right;">
Y-Min
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto" style="width: 113px;"
empty-to-null ng-model="panel.grid.leftMin"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.leftMin"
ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -63,22 +63,22 @@
Unit
</li>
<li class="dropdown" style="width: 140px"
ng-model="panel.y_formats[1]"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(1, $subItem)">
ng-model="ctrl.panel.y_formats[1]"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat(1, $subItem)">
</li>
<li class="tight-form-item">
Scale type
</li>
<li>
<select class="input-small tight-form-input" style="width: 113px" ng-model="panel.grid.rightLogBase" ng-options="v as k for (k, v) in logScales" ng-change="render()"></select>
<select class="input-small tight-form-input" style="width: 113px" ng-model="ctrl.panel.grid.rightLogBase" ng-options="v as k for (k, v) in ctrl.logScales" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Label
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.rightYAxisLabel" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.rightYAxisLabel" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -93,16 +93,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto"
empty-to-null ng-model="panel.grid.rightMax"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.rightMax"
ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item" style="width: 115px; text-align: right;">
Y-Min
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto" style="width: 113px;"
empty-to-null ng-model="panel.grid.rightMin"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.rightMin"
ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -119,13 +119,13 @@
<li class="tight-form-item">
X-Axis&nbsp;
<input class="cr1" id="hideXAxis" type="checkbox"
ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
ng-model="ctrl.panel['x-axis']" ng-checked="ctrl.panel['x-axis']" ng-change="ctrl.render()">
<label for="hideXAxis" class="cr1"></label>
</li>
<li class="tight-form-item last">
Y-Axis&nbsp;
<input class="cr1" id="hideYAxis" type="checkbox"
ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
ng-model="ctrl.panel['y-axis']" ng-checked="ctrl.panel['y-axis']" ng-change="ctrl.render()">
<label for="hideYAxis" class="cr1"></label>
</li>
</ul>
@@ -141,23 +141,23 @@
</li>
<li>
<input type="number" class="input-small tight-form-input"
ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.grid.threshold1" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.grid.threshold1Color" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item">
Level 2
</li>
<li>
<input type="number" class="input-small tight-form-input"
ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.grid.threshold2" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.grid.threshold2Color" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<editor-checkbox text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-checkbox>
<editor-checkbox text="Line mode" model="ctrl.panel.grid.thresholdLine" change="ctrl.render()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
@@ -173,20 +173,20 @@
Legend
</li>
<li class="tight-form-item">
<editor-checkbox text="Show" model="panel.legend.show" change="get_data()"></editor-checkbox>
<editor-checkbox text="Show" model="ctrl.panel.legend.show" change="ctrl.refresh()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Table" model="panel.legend.alignAsTable" change="render()"></editor-checkbox>
<editor-checkbox text="Table" model="ctrl.panel.legend.alignAsTable" change="ctrl.render()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Right side" model="panel.legend.rightSide" change="render()"></editor-checkbox>
<editor-checkbox text="Right side" model="ctrl.panel.legend.rightSide" change="ctrl.render()"></editor-checkbox>
</li>
<li ng-if="panel.legend.rightSide" class="tight-form-item">
Side width
</li>
<li ng-if="panel.legend.rightSide" style="width: 105px">
<input type="number" class="input-small tight-form-input" placeholder="250" bs-tooltip="'Set a min-width for the legend side table/block'" data-placement="right"
ng-model="panel.legend.sideWidth" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.legend.sideWidth" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -197,10 +197,10 @@
Hide series
</li>
<li class="tight-form-item">
<editor-checkbox text="With only nulls" model="panel.legend.hideEmpty" change="render()"></editor-checkbox>
<editor-checkbox text="With only nulls" model="ctrl.panel.legend.hideEmpty" change="ctrl.render()"></editor-checkbox>
</li>
<li class="tight-form-item last">
<editor-checkbox text="With only zeroes" model="panel.legend.hideZero" change="render()"></editor-checkbox>
<editor-checkbox text="With only zeroes" model="ctrl.panel.legend.hideZero" change="ctrl.render()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
@@ -212,26 +212,26 @@
Values
</li>
<li class="tight-form-item">
<editor-checkbox text="Min" model="panel.legend.min" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Min" model="ctrl.panel.legend.min" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Max" model="panel.legend.max" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Max" model="ctrl.panel.legend.max" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Avg" model="panel.legend.avg" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Avg" model="ctrl.panel.legend.avg" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Current" model="panel.legend.current" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Current" model="ctrl.panel.legend.current" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Total" model="panel.legend.total" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Total" model="ctrl.panel.legend.total" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
Decimals
</li>
<li style="width: 105px">
<input type="number" class="input-small tight-form-input" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -24,14 +24,16 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var dashboard = scope.dashboard;
var ctrl = scope.ctrl;
var dashboard = ctrl.dashboard;
var panel = ctrl.panel;
var data, annotations;
var sortedSeries;
var graphHeight;
var legendSideLastValue = null;
scope.crosshairEmiter = false;
var rootScope = scope.$root;
scope.onAppEvent('setCrosshair', function(event, info) {
rootScope.onAppEvent('setCrosshair', function(event, info) {
// do not need to to this if event is from this panel
if (info.scope === scope) {
return;
@@ -43,20 +45,20 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
}
}
});
}, scope);
scope.onAppEvent('clearCrosshair', function() {
rootScope.onAppEvent('clearCrosshair', function() {
var plot = elem.data().plot;
if (plot) {
plot.clearCrosshair();
}
});
}, scope);
// Receive render events
scope.$on('render',function(event, renderData) {
data = renderData || data;
if (!data) {
scope.get_data();
ctrl.refresh();
return;
}
annotations = data.annotations || annotations;
@@ -64,10 +66,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
});
function getLegendHeight(panelHeight) {
if (!scope.panel.legend.show || scope.panel.legend.rightSide) {
if (!panel.legend.show || panel.legend.rightSide) {
return 0;
}
if (scope.panel.legend.alignAsTable) {
if (panel.legend.alignAsTable) {
var total = 30 + (25 * data.length);
return Math.min(total, Math.floor(panelHeight/2));
} else {
@@ -77,14 +79,13 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function setElementHeight() {
try {
graphHeight = scope.height || scope.panel.height || scope.row.height;
graphHeight = ctrl.height || panel.height || ctrl.row.height;
if (_.isString(graphHeight)) {
graphHeight = parseInt(graphHeight.replace('px', ''), 10);
}
graphHeight -= 5; // padding
graphHeight -= scope.panel.title ? 24 : 9; // subtract panel title bar
graphHeight -= panel.title ? 24 : 9; // subtract panel title bar
graphHeight = graphHeight - getLegendHeight(graphHeight); // subtract one line legend
elem.css('height', graphHeight + 'px');
@@ -100,7 +101,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
return true;
}
if ($rootScope.fullscreen && !scope.fullscreen) {
if (ctrl.otherPanelInFullscreenMode()) {
return true;
}
@@ -122,11 +123,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
for (var i = 0; i < data.length; i++) {
var series = data[i];
var axis = yaxis[series.yaxis - 1];
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
var formater = kbn.valueFormats[panel.y_formats[series.yaxis - 1]];
// decimal override
if (_.isNumber(scope.panel.decimals)) {
series.updateLegendValues(formater, scope.panel.decimals, null);
if (_.isNumber(panel.decimals)) {
series.updateLegendValues(formater, panel.decimals, null);
} else {
// auto decimals
// legend and tooltip gets one more decimal precision
@@ -135,22 +136,22 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
}
if(!scope.$$phase) { scope.$digest(); }
if(!rootScope.$$phase) { scope.$digest(); }
}
// add left axis labels
if (scope.panel.leftYAxisLabel) {
if (panel.leftYAxisLabel) {
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
.text(scope.panel.leftYAxisLabel)
.text(panel.leftYAxisLabel)
.appendTo(elem);
yaxisLabel.css("margin-top", yaxisLabel.width() / 2);
}
// add right axis labels
if (scope.panel.rightYAxisLabel) {
if (panel.rightYAxisLabel) {
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
.text(scope.panel.rightYAxisLabel)
.text(panel.rightYAxisLabel)
.appendTo(elem);
rightLabel.css("margin-top", rightLabel.width() / 2);
@@ -158,8 +159,8 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
}
function processOffsetHook(plot, gridMargin) {
if (scope.panel.leftYAxisLabel) { gridMargin.left = 20; }
if (scope.panel.rightYAxisLabel) { gridMargin.right = 20; }
if (panel.leftYAxisLabel) { gridMargin.left = 20; }
if (panel.rightYAxisLabel) { gridMargin.right = 20; }
}
// Function for rendering panel
@@ -168,7 +169,6 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
return;
}
var panel = scope.panel;
var stack = panel.stack ? true : null;
// Populate element
@@ -230,7 +230,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode, panel.y_formats);
// if hidden remove points and disable stack
if (scope.hiddenSeries[series.alias]) {
if (ctrl.hiddenSeries[series.alias]) {
series.data = [];
series.stack = false;
}
@@ -255,7 +255,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
}
if (incrementRenderCounter) {
scope.panelRenderingComplete();
ctrl.renderingCompleted();
}
}
@@ -285,12 +285,12 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function addTimeAxis(options) {
var ticks = elem.width() / 100;
var min = _.isUndefined(scope.range.from) ? null : scope.range.from.valueOf();
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.valueOf();
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
options.xaxis = {
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
show: panel['x-axis'],
mode: "time",
min: min,
max: max,
@@ -361,11 +361,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function configureAxisOptions(data, options) {
var defaults = {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.leftMin,
show: panel['y-axis'],
min: panel.grid.leftMin,
index: 1,
logBase: scope.panel.grid.leftLogBase || 1,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
logBase: panel.grid.leftLogBase || 1,
max: panel.percentage && panel.stack ? 100 : panel.grid.leftMax,
};
options.yaxes.push(defaults);
@@ -373,18 +373,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
secondY.index = 2,
secondY.logBase = scope.panel.grid.rightLogBase || 1,
secondY.logBase = panel.grid.rightLogBase || 1,
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
secondY.min = panel.grid.rightMin;
secondY.max = panel.percentage && panel.stack ? 100 : panel.grid.rightMax;
options.yaxes.push(secondY);
applyLogScale(options.yaxes[1], data);
configureAxisMode(options.yaxes[1], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[1]);
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.y_formats[1]);
}
applyLogScale(options.yaxes[0], data);
configureAxisMode(options.yaxes[0], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[0]);
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.y_formats[0]);
}
function applyLogScale(axis, data) {
@@ -466,18 +466,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
url += '&height=' + elem.css('height').replace('px', '');
url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
url += scope.panel.stack ? '&areaMode=stacked' : '';
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
url += panel.stack ? '&areaMode=stacked' : '';
url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : '';
url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : '';
url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : '';
url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : '';
url += panel['x-axis'] ? '' : '&hideAxes=true';
url += panel['y-axis'] ? '' : '&hideYAxis=true';
switch(scope.panel.y_formats[0]) {
switch(panel.y_formats[0]) {
case 'bytes':
url += '&yUnitSystem=binary';
break;
@@ -510,7 +510,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
break;
}
switch(scope.panel.nullPointMode) {
switch(panel.nullPointMode) {
case 'connected':
url += '&lineMode=connected';
break;
@@ -521,7 +521,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
break;
}
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
url += panel.steppedLine ? '&lineMode=staircase' : '';
elem.html('<img src="' + url + '"></img>');
}

View File

@@ -0,0 +1,295 @@
///<reference path="../../../headers/common.d.ts" />
import moment from 'moment';
import kbn from 'app/core/utils/kbn';
import _ from 'lodash';
import TimeSeries from '../../../core/time_series2';
import * as fileExport from '../../../core/utils/file_export';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
var panelDefaults = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
// Show/hide the x-axis
'x-axis' : true,
// Show/hide y-axis
'y-axis' : true,
// y axis formats, [left axis,right axis]
y_formats : ['short', 'short'],
// grid options
grid : {
leftLogBase: 1,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
rightLogBase: 1,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
class GraphCtrl extends MetricsPanelCtrl {
hiddenSeries: any = {};
seriesList: any = [];
logScales: any;
unitFormats: any;
annotationsPromise: any;
datapointsCount: number;
datapointsOutside: boolean;
datapointsWarning: boolean;
colors: any = [];
/** @ngInject */
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel.tooltip, panelDefaults.tooltip);
_.defaults(this.panel.grid, panelDefaults.grid);
_.defaults(this.panel.legend, panelDefaults.legend);
this.colors = $scope.$root.colors;
}
initEditMode() {
super.initEditMode();
this.icon = "fa fa-bar-chart";
this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html', 2);
this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html', 3);
this.logScales = {
'linear': 1,
'log (base 2)': 2,
'log (base 10)': 10,
'log (base 32)': 32,
'log (base 1024)': 1024
};
this.unitFormats = kbn.getUnitFormats();
}
getExtendedMenu() {
var menu = super.getExtendedMenu();
menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
menu.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'});
return menu;
}
setUnitFormat(axis, subItem) {
this.panel.y_formats[axis] = subItem.value;
this.render();
}
refreshData(datasource) {
this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
return this.issueQueries(datasource)
.then(res => this.dataHandler(res))
.catch(err => {
this.seriesList = [];
this.render([]);
throw err;
});
}
zoomOut(evt) {
this.publishAppEvent('zoom-out', evt);
}
loadSnapshot(snapshotData) {
this.updateTimeRange();
this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
this.dataHandler(snapshotData);
}
dataHandler(results) {
// png renderer returns just a url
if (_.isString(results)) {
this.render(results);
return;
}
this.datapointsWarning = false;
this.datapointsCount = 0;
this.datapointsOutside = false;
this.seriesList = _.map(results.data, (series, i) => this.seriesHandler(series, i));
this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
this.annotationsPromise.then(annotations => {
this.loading = false;
this.seriesList.annotations = annotations;
this.render(this.seriesList);
}, () => {
this.loading = false;
this.render(this.seriesList);
});
};
seriesHandler(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var colorIndex = index % this.colors.length;
var color = this.panel.aliasColors[alias] || this.colors[colorIndex];
var series = new TimeSeries({
datapoints: datapoints,
alias: alias,
color: color,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1]);
var from = moment.utc(this.range.from);
if (last - from < -10000) {
this.datapointsOutside = true;
}
this.datapointsCount += datapoints.length;
}
return series;
}
render(data?: any) {
this.broadcastRender(data);
}
changeSeriesColor(series, color) {
series.color = color;
this.panel.aliasColors[series.alias] = series.color;
this.render();
}
toggleSeries(serie, event) {
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.hiddenSeries[serie.alias]) {
delete this.hiddenSeries[serie.alias];
} else {
this.hiddenSeries[serie.alias] = true;
}
} else {
this.toggleSeriesExclusiveMode(serie);
}
this.render();
}
toggleSeriesExclusiveMode (serie) {
var hidden = this.hiddenSeries;
if (hidden[serie.alias]) {
delete hidden[serie.alias];
}
// check if every other series is hidden
var alreadyExclusive = _.every(this.seriesList, value => {
if (value.alias === serie.alias) {
return true;
}
return hidden[value.alias];
});
if (alreadyExclusive) {
// remove all hidden series
_.each(this.seriesList, value => {
delete this.hiddenSeries[value.alias];
});
} else {
// hide all but this serie
_.each(this.seriesList, value => {
if (value.alias === serie.alias) {
return;
}
this.hiddenSeries[value.alias] = true;
});
}
}
toggleYAxis(info) {
var override = _.findWhere(this.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
this.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
this.render();
};
addSeriesOverride(override) {
this.panel.seriesOverrides.push(override || {});
}
removeSeriesOverride(override) {
this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);
this.render();
}
// Called from panel menu
toggleLegend() {
this.panel.legend.show = !this.panel.legend.show;
this.refresh();
}
legendValuesOptionChanged() {
var legend = this.panel.legend;
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
this.render();
}
exportCsv() {
fileExport.exportSeriesListToCsv(this.seriesList);
}
}
export {GraphCtrl}

View File

@@ -6,6 +6,8 @@ function ($) {
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
var self = this;
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var $tooltip = $('<div id="tooltip">');
@@ -47,12 +49,12 @@ function ($) {
for (i = 0; i < seriesList.length; i++) {
series = seriesList[i];
if (!series.data.length || (scope.panel.legend.hideEmpty && series.allIsNull)) {
if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
results.push({ hidden: true });
continue;
}
if (!series.data.length || (scope.panel.legend.hideZero && series.allIsZero)) {
if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
results.push({ hidden: true });
continue;
}
@@ -61,7 +63,7 @@ function ($) {
results.time = series.data[hoverIndex][0];
if (series.stack) {
if (scope.panel.tooltip.value_type === 'individual') {
if (panel.tooltip.value_type === 'individual') {
value = series.data[hoverIndex][1];
} else if (!series.stack) {
value = series.data[hoverIndex][1];
@@ -89,7 +91,7 @@ function ($) {
};
elem.mouseleave(function () {
if (scope.panel.tooltip.shared) {
if (panel.tooltip.shared) {
var plot = elem.data().plot;
if (plot) {
$tooltip.detach();
@@ -98,7 +100,7 @@ function ($) {
}
if (dashboard.sharedCrosshair) {
scope.appEvent('clearCrosshair');
ctrl.publishAppEvent('clearCrosshair');
}
});
@@ -108,15 +110,15 @@ function ($) {
var seriesList = getSeriesFn();
var group, value, absoluteTime, relativeTime, hoverInfo, i, series, seriesHtml;
if(dashboard.sharedCrosshair){
scope.appEvent('setCrosshair', { pos: pos, scope: scope });
if (dashboard.sharedCrosshair) {
ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
}
if (seriesList.length === 0) {
return;
}
if (scope.panel.tooltip.shared) {
if (panel.tooltip.shared) {
plot.unhighlight();
var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos);
@@ -151,7 +153,7 @@ function ($) {
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
if (panel.stack && panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];
}
else {

View File

@@ -8,7 +8,7 @@ define([
function (angular, _, $) {
'use strict';
var module = angular.module('grafana.panels.graph');
var module = angular.module('grafana.directives');
module.directive('graphLegend', function(popoverSrv) {
@@ -16,13 +16,14 @@ function (angular, _, $) {
link: function(scope, elem) {
var $container = $('<section class="graph-legend"></section>');
var firstRender = true;
var panel = scope.panel;
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var data;
var seriesList;
var i;
scope.$on('render', function() {
data = scope.seriesList;
data = ctrl.seriesList;
if (data) {
render();
}
@@ -54,7 +55,7 @@ function (angular, _, $) {
var el = $(e.currentTarget);
var index = getSeriesIndexForElement(el);
var seriesInfo = seriesList[index];
scope.toggleSeries(seriesInfo, e);
ctrl.toggleSeries(seriesInfo, e);
}
function sortLegend(e) {
@@ -148,7 +149,7 @@ function (angular, _, $) {
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' pull-right'; }
if (scope.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';

View File

@@ -3,12 +3,12 @@
<div class="editor-row small" style="padding-bottom: 0;">
<label>Axis:</label>
<button ng-click="toggleYAxis(series);dismiss();"
<button ng-click="ctrl.toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series);dismiss();"
<button ng-click="ctrl.toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right
@@ -16,10 +16,10 @@
</div>
<div class="editor-row">
<i ng-repeat="color in colors" class="pointer"
<i ng-repeat="color in ctrl.colors" class="pointer"
ng-class="{'fa fa-circle-o': color === series.color,'fa fa-circle': color !== series.color}"
ng-style="{color:color}"
ng-click="changeSeriesColor(series, color);dismiss();">&nbsp;</i>
ng-click="ctrl.changeSeriesColor(series, color);dismiss();">&nbsp;</i>
</div>
</div>

View File

@@ -1,3 +0,0 @@
declare var panel: any;
declare var GraphCtrl: any;
export {panel, GraphCtrl};

View File

@@ -1,25 +1,22 @@
<grafana-panel>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
No datapoints <tip>No datapoints returned from metric query</tip>
</span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="histogram-chart" ng-dblclick="zoomOut()">
</div>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
No datapoints <tip>No datapoints returned from metric query</tip>
</span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="histogram-chart" ng-dblclick="ctrl.zoomOut()">
</div>
<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
</div>
<div class="clearfix"></div>
<div class="graph-legend-wrapper" ng-if="ctrl.panel.legend.show" graph-legend></div>
</div>
<div class="clearfix"></div>
</grafana-panel>

View File

@@ -1,303 +0,0 @@
define([
'angular',
'lodash',
'moment',
'app/core/utils/kbn',
'app/core/utils/file_export',
'app/core/time_series',
'app/features/panel/panel_meta',
'./seriesOverridesCtrl',
'./graph',
'./legend',
],
function (angular, _, moment, kbn, fileExport, TimeSeries, PanelMeta) {
'use strict';
/** @ngInject */
function GraphCtrl($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) {
$scope.panelMeta = new PanelMeta({
panelName: 'Graph',
editIcon: "fa fa-bar-chart",
fullscreen: true,
metricsEditor: true,
});
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panel/graph/axisEditor.html');
$scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panel/graph/styleEditor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
// Set and populate defaults
var _d = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
// Show/hide the x-axis
'x-axis' : true,
// Show/hide y-axis
'y-axis' : true,
// y axis formats, [left axis,right axis]
y_formats : ['short', 'short'],
// grid options
grid : {
leftLogBase: 1,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
rightLogBase: 1,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip, _d.tooltip);
_.defaults($scope.panel.annotate, _d.annotate);
_.defaults($scope.panel.grid, _d.grid);
_.defaults($scope.panel.legend, _d.legend);
$scope.logScales = {'linear': 1, 'log (base 2)': 2, 'log (base 10)': 10, 'log (base 32)': 32, 'log (base 1024)': 1024};
$scope.hiddenSeries = {};
$scope.seriesList = [];
$scope.unitFormats = kbn.getUnitFormats();
$scope.setUnitFormat = function(axis, subItem) {
$scope.panel.y_formats[axis] = subItem.value;
$scope.render();
};
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.seriesList = [];
$scope.render([]);
throw err;
});
};
$scope.zoomOut = function(evt) {
$scope.appEvent('zoom-out', evt);
};
$scope.loadSnapshot = function(snapshotData) {
panelHelper.updateTimeRange($scope);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
$scope.dataHandler(snapshotData);
};
$scope.dataHandler = function(results) {
// png renderer returns just a url
if (_.isString(results)) {
$scope.render(results);
return;
}
$scope.datapointsWarning = false;
$scope.datapointsCount = 0;
$scope.datapointsOutside = false;
$scope.seriesList = _.map(results.data, $scope.seriesHandler);
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
$scope.annotationsPromise
.then(function(annotations) {
$scope.panelMeta.loading = false;
$scope.seriesList.annotations = annotations;
$scope.render($scope.seriesList);
}, function() {
$scope.panelMeta.loading = false;
$scope.render($scope.seriesList);
});
};
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var colorIndex = index % $rootScope.colors.length;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex];
var series = new TimeSeries({
datapoints: datapoints,
alias: alias,
color: color,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1]);
var from = moment.utc($scope.range.from);
if (last - from < -10000) {
$scope.datapointsOutside = true;
}
$scope.datapointsCount += datapoints.length;
}
return series;
};
$scope.render = function(data) {
panelHelper.broadcastRender($scope, data);
};
$scope.changeSeriesColor = function(series, color) {
series.color = color;
$scope.panel.aliasColors[series.alias] = series.color;
$scope.render();
};
$scope.toggleSeries = function(serie, event) {
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if ($scope.hiddenSeries[serie.alias]) {
delete $scope.hiddenSeries[serie.alias];
}
else {
$scope.hiddenSeries[serie.alias] = true;
}
} else {
$scope.toggleSeriesExclusiveMode(serie);
}
$scope.render();
};
$scope.toggleSeriesExclusiveMode = function(serie) {
var hidden = $scope.hiddenSeries;
if (hidden[serie.alias]) {
delete hidden[serie.alias];
}
// check if every other series is hidden
var alreadyExclusive = _.every($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return true;
}
return hidden[value.alias];
});
if (alreadyExclusive) {
// remove all hidden series
_.each($scope.seriesList, function(value) {
delete $scope.hiddenSeries[value.alias];
});
}
else {
// hide all but this serie
_.each($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return;
}
$scope.hiddenSeries[value.alias] = true;
});
}
};
$scope.toggleYAxis = function(info) {
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
$scope.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
$scope.addSeriesOverride = function(override) {
$scope.panel.seriesOverrides.push(override || {});
};
$scope.removeSeriesOverride = function(override) {
$scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
$scope.render();
};
// Called from panel menu
$scope.toggleLegend = function() {
$scope.panel.legend.show = !$scope.panel.legend.show;
$scope.get_data();
};
$scope.legendValuesOptionChanged = function() {
var legend = $scope.panel.legend;
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
$scope.render();
};
$scope.exportCsv = function() {
fileExport.exportSeriesListToCsv($scope.seriesList);
};
panelSrv.init($scope);
}
function graphPanelDirective() {
return {
controller: GraphCtrl,
templateUrl: 'app/plugins/panel/graph/module.html',
};
}
return {
GraphCtrl: GraphCtrl,
panel: graphPanelDirective,
};
});

View File

@@ -0,0 +1,17 @@
import {PanelDirective} from '../../../features/panel/panel';
import {GraphCtrl} from './graph_ctrl';
import './graph';
import './legend';
import './seriesOverridesCtrl';
class GraphPanel extends PanelDirective {
controller = GraphCtrl;
templateUrl = 'public/app/plugins/panel/graph/module.html';
}
export {
GraphPanel,
GraphPanel as Panel
}

View File

@@ -1,13 +1,11 @@
define([
'angular',
'jquery',
'app/app',
'lodash',
], function(angular, jquery, app, _) {
], function(angular, jquery, _) {
'use strict';
var module = angular.module('grafana.panels.graph', []);
app.useModule(module);
var module = angular.module('grafana.controllers');
module.controller('SeriesOverridesCtrl', function($scope, $element, popoverSrv) {
$scope.overrideMenu = [];
@@ -45,18 +43,19 @@ define([
}
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.colorSelected = function(color) {
$scope.override['color'] = color;
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.openColorSelector = function() {
var popoverScope = $scope.$new();
popoverScope.colorSelected = $scope.colorSelected;
popoverScope.colors = $scope.ctrl.colors;
popoverSrv.show({
element: $element.find(".dropdown"),
@@ -69,11 +68,11 @@ define([
$scope.removeOverride = function(option) {
delete $scope.override[option.propertyName];
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.getSeriesNames = function() {
return _.map($scope.seriesList, function(series) {
return _.map($scope.ctrl.seriesList, function(series) {
return series.alias;
});
};
@@ -107,7 +106,5 @@ define([
$scope.addOverrideOption('Transform', 'transform', ['negative-Y']);
$scope.addOverrideOption('Legend', 'legend', [true, false]);
$scope.updateCurrentOverrides();
});
});

View File

@@ -2,15 +2,10 @@
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 {GraphCtrl} from '../graph_ctrl';
import helpers from '../../../../../test/specs/helpers';
angular.module('grafana.controllers').controller('GraphCtrl', GraphCtrl);
describe('GraphCtrl', function() {
var ctx = new helpers.ControllerTestContext();
@@ -18,7 +13,7 @@ describe('GraphCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('GraphCtrl'));
beforeEach(ctx.createPanelController(GraphCtrl));
describe('get_data with 2 series', function() {
beforeEach(function() {
@@ -29,25 +24,23 @@ describe('GraphCtrl', function() {
{ target: 'test.cpu2', datapoints: [[1, 10]]}
]
}));
ctx.scope.render = sinon.spy();
ctx.scope.refreshData(ctx.datasource);
ctx.ctrl.render = sinon.spy();
ctx.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
});
it('should send time series to render', function() {
var data = ctx.scope.render.getCall(0).args[0];
var data = ctx.ctrl.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.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
});
});
});
});

View File

@@ -24,11 +24,13 @@ describe('grafanaGraph', function() {
}));
beforeEach(angularMocks.inject(function($rootScope, $compile) {
var ctrl: any = {};
var scope = $rootScope.$new();
scope.ctrl = ctrl;
var element = angular.element("<div style='width:" + elementWidth + "px' grafana-graph><div>");
scope.height = '200px';
scope.panel = {
ctrl.height = '200px';
ctrl.panel = {
legend: {},
grid: { },
y_formats: [],
@@ -38,14 +40,14 @@ describe('grafanaGraph', function() {
}
};
scope.panelRenderingComplete = sinon.spy();
scope.appEvent = sinon.spy();
scope.onAppEvent = sinon.spy();
scope.hiddenSeries = {};
scope.dashboard = { timezone: 'browser' };
scope.range = {
$rootScope.onAppEvent = sinon.spy();
ctrl.otherPanelInFullscreenMode = sinon.spy();
ctrl.renderingCompleted = sinon.spy();
ctrl.hiddenSeries = {};
ctrl.dashboard = { timezone: 'browser' };
ctrl.range = {
from: moment([2015, 1, 1, 10]),
to: moment([2015, 1, 1, 22])
to: moment([2015, 1, 1, 22]),
};
ctx.data = [];
ctx.data.push(new TimeSeries({
@@ -57,7 +59,7 @@ describe('grafanaGraph', function() {
alias: 'series2'
}));
setupFunc(scope, ctx.data);
setupFunc(ctrl, ctx.data);
$compile(element)(scope);
scope.$digest();
@@ -74,11 +76,11 @@ describe('grafanaGraph', function() {
}
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;
ctx.setup(function(ctrl) {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.linewidth = 3;
ctrl.panel.steppedLine = true;
});
it('should configure plot with correct options', function() {
@@ -90,8 +92,8 @@ describe('grafanaGraph', function() {
});
graphScenario('grid thresholds 100, 200', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 100,
threshold1Color: "#111",
threshold2: 200,
@@ -110,8 +112,8 @@ describe('grafanaGraph', function() {
});
graphScenario('inverted grid thresholds 200, 100', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 200,
threshold1Color: "#111",
threshold2: 100,
@@ -130,8 +132,8 @@ describe('grafanaGraph', function() {
});
graphScenario('grid thresholds from zero', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 0,
threshold1Color: "#111",
};
@@ -144,8 +146,8 @@ describe('grafanaGraph', function() {
});
graphScenario('when logBase is log 10', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
leftMax: null,
rightMax: null,
leftMin: null,
@@ -163,8 +165,8 @@ describe('grafanaGraph', function() {
});
graphScenario('should use timeStep for barWidth', function(ctx) {
ctx.setup(function(scope, data) {
scope.panel.bars = true;
ctx.setup(function(ctrl, data) {
ctrl.panel.bars = true;
data[0] = new TimeSeries({
datapoints: [[1,10],[2,20]],
alias: 'series1',
@@ -177,10 +179,10 @@ describe('grafanaGraph', function() {
});
graphScenario('series option overrides, fill & points', function(ctx) {
ctx.setup(function(scope, data) {
scope.panel.lines = true;
scope.panel.fill = 5;
scope.panel.seriesOverrides = [
ctx.setup(function(ctrl, data) {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.seriesOverrides = [
{ alias: 'test', fill: 0, points: true }
];
@@ -195,8 +197,8 @@ describe('grafanaGraph', function() {
});
graphScenario('should order series order according to zindex', function(ctx) {
ctx.setup(function(scope) {
scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
ctx.setup(function(ctrl) {
ctrl.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
});
it('should move zindex 2 last', function() {
@@ -206,8 +208,8 @@ describe('grafanaGraph', function() {
});
graphScenario('when series is hidden', function(ctx) {
ctx.setup(function(scope) {
scope.hiddenSeries = {'series2': true};
ctx.setup(function(ctrl) {
ctrl.hiddenSeries = {'series2': true};
});
it('should remove datapoints and disable stack', function() {
@@ -218,9 +220,9 @@ describe('grafanaGraph', function() {
});
graphScenario('when stack and percent', function(ctx) {
ctx.setup(function(scope) {
scope.panel.percentage = true;
scope.panel.stack = true;
ctx.setup(function(ctrl) {
ctrl.panel.percentage = true;
ctrl.panel.stack = true;
});
it('should show percentage', function() {
@@ -231,9 +233,9 @@ describe('grafanaGraph', function() {
graphScenario('when panel too narrow to show x-axis dates in same granularity as wide panels', function(ctx) {
describe('and the range is less than 24 hours', function() {
ctx.setup(function(scope) {
scope.range.from = moment([2015, 1, 1, 10]);
scope.range.to = moment([2015, 1, 1, 22]);
ctx.setup(function(ctrl) {
ctrl.range.from = moment([2015, 1, 1, 10]);
ctrl.range.to = moment([2015, 1, 1, 22]);
});
it('should format dates as hours minutes', function() {

View File

@@ -8,6 +8,7 @@ import GraphTooltip from '../graph_tooltip';
var scope = {
appEvent: sinon.spy(),
onAppEvent: sinon.spy(),
ctrl: {}
};
var elem = $('<div></div>');
@@ -15,8 +16,8 @@ var dashboard = { };
function describeSharedTooltip(desc, fn) {
var ctx: any = {};
ctx.scope = scope;
ctx.scope.panel = {
ctx.ctrl = scope.ctrl;
ctx.ctrl.panel = {
tooltip: {
shared: true
},
@@ -51,9 +52,11 @@ describeSharedTooltip("steppedLine false, stack false", function(ctx) {
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);
@@ -93,7 +96,7 @@ describeSharedTooltip("steppedLine false, stack true, individual false", functio
stack: true
}
];
ctx.scope.panel.stack = true;
ctx.ctrl.panel.stack = true;
ctx.pos = { x: 11 };
});
@@ -124,7 +127,7 @@ describeSharedTooltip("steppedLine false, stack true, individual false, series s
stack: false
}
];
ctx.scope.panel.stack = true;
ctx.ctrl.panel.stack = true;
ctx.pos = { x: 11 };
});
@@ -156,15 +159,14 @@ describeSharedTooltip("steppedLine false, stack true, individual true", function
stack: false
}
];
ctx.scope.panel.stack = true;
ctx.scope.panel.tooltip.value_type = 'individual';
ctx.ctrl.panel.stack = true;
ctx.ctrl.panel.tooltip.value_type = 'individual';
ctx.pos = { x: 11 };
});
it('should not show stacked value', function() {
expect(ctx.results[1].value).to.be(2);
});
});

View File

@@ -1,60 +1,60 @@
<div class="editor-row">
<div class="section">
<h5>Chart Options</h5>
<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
<editor-opt-bool text="Bars" model="ctrl.panel.bars" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Lines" model="ctrl.panel.lines" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Points" model="ctrl.panel.points" change="ctrl.render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Line options</h5>
<div class="editor-option" ng-show="panel.lines">
<div class="editor-option" ng-show="ctrl.panel.lines">
<label class="small">Line Fill</label>
<select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option" ng-show="panel.lines">
<div class="editor-option" ng-show="ctrl.panel.lines">
<label class="small">Line Width</label>
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option" ng-show="panel.points">
<div class="editor-option" ng-show="ctrl.panel.points">
<label class="small">Point Radius</label>
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option">
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
<select class="input-medium" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
</div>
<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
<editor-opt-bool text="Staircase line" model="ctrl.panel.steppedLine" change="ctrl.render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Multiple Series</h5>
<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
<editor-opt-bool text="Stack" model="ctrl.panel.stack" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Percent" model="ctrl.panel.percentage" change="ctrl.render()" tip="Stack as a percentage of total"></editor-opt-bool>
</div>
<div class="section">
<h5>Rendering</h5>
<div class="editor-option">
<label class="small">Flot <tip>client side</tip></label>
<input type="radio" class="input-small" ng-model="panel.renderer" value="flot" ng-change="get_data()" />
<input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="flot" ng-change="ctrl.refresh()" />
</div>
<div class="editor-option">
<label class="small">Graphite PNG <tip>server side</tip></label>
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
<input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="png" ng-change="ctr.refresh()" />
</div>
</div>
<div class="section">
<h5>Tooltip</h5>
<editor-opt-bool
text="All series" model="panel.tooltip.shared" change="render()"
text="All series" model="ctrl.panel.tooltip.shared" change="ctrl.render()"
tip="Show all series on same tooltip and a x croshair to help follow all series">
</editor-opt-bool>
<div class="editor-option" ng-show="panel.stack">
<div class="editor-option" ng-show="ctrl.panel.stack">
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
<select class="input-small" ng-model="ctrl.panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
@@ -64,10 +64,10 @@
<div class="section">
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
<div class="tight-form-container">
<div class="tight-form" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<div class="tight-form" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer" ng-click="removeSeriesOverride(override)"></i>
<i class="fa fa-remove pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
</li>
<li class="tight-form-item">
@@ -75,7 +75,7 @@
</li>
<li>
<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="ctrl.render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
</li>
<li class="tight-form-item" ng-repeat="option in currentOverrides">
@@ -95,7 +95,7 @@
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addSeriesOverride()">
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="ctrl.addSeriesOverride()">
Add series specific option
</button>
</div>

View File

@@ -3,240 +3,231 @@
import angular from 'angular';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import PanelMeta from 'app/features/panel/panel_meta2';
import TimeSeries from '../../../core/time_series2';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
export class SingleStatCtrl {
// Set and populate defaults
var panelDefaults = {
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)',
}
};
export class SingleStatCtrl extends MetricsPanelCtrl {
series: any[];
data: any[];
fontSizes: any[];
unitFormats: any[];
/** @ngInject */
constructor($scope, panelSrv, panelHelper) {
$scope.panelMeta = new PanelMeta({
panelName: 'Singlestat',
editIcon: "fa fa-dashboard",
fullscreen: true,
metricsEditor: true
constructor($scope, $injector) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
}
initEditMode() {
super.initEditMode();
this.icon = "fa fa-dashboard";
this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2);
this.unitFormats = kbn.getUnitFormats();
}
setUnitFormat(subItem) {
this.panel.format = subItem.value;
this.render();
}
refreshData(datasource) {
return this.issueQueries(datasource)
.then(this.dataHandler.bind(this))
.catch(err => {
this.series = [];
this.render();
throw err;
});
}
loadSnapshot(snapshotData) {
this.updateTimeRange();
this.dataHandler(snapshotData);
}
dataHandler(results) {
this.series = _.map(results.data, this.seriesHandler.bind(this));
this.render();
}
seriesHandler(seriesData) {
var series = new TimeSeries({
datapoints: seriesData.datapoints,
alias: seriesData.target,
});
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
return series;
}
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
setColoring(options) {
if (options.background) {
this.panel.colorValue = false;
this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
} else {
this.panel.colorBackground = false;
this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
}
this.render();
}
// 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)',
invertColorOrder() {
var tmp = this.panel.colors[0];
this.panel.colors[0] = this.panel.colors[2];
this.panel.colors[2] = tmp;
this.render();
}
getDecimalsForValue(value) {
if (_.isNumber(this.panel.decimals)) {
return {decimals: this.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;
}
_.defaults($scope.panel, _d);
size *= magn;
$scope.unitFormats = kbn.getUnitFormats();
// reduce starting decimals if not needed
if (Math.floor(value) === value) { dec = 0; }
$scope.setUnitFormat = function(subItem) {
$scope.panel.format = subItem.value;
$scope.render();
};
var result: any = {};
result.decimals = Math.max(0, dec);
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
$scope.init = function() {
panelSrv.init($scope);
};
return result;
}
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
render() {
var data: any = {};
this.setValues(data);
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.series = [];
$scope.render();
throw err;
});
};
data.thresholds = this.panel.thresholds.split(',').map(function(strVale) {
return Number(strVale.trim());
});
$scope.loadSnapshot = function(snapshotData) {
panelHelper.updateTimeRange($scope);
$scope.dataHandler(snapshotData);
};
data.colorMap = this.panel.colors;
$scope.dataHandler = function(results) {
$scope.series = _.map(results.data, $scope.seriesHandler);
$scope.render();
};
this.data = data;
this.broadcastRender();
}
$scope.seriesHandler = function(seriesData) {
var series = new TimeSeries({
datapoints: seriesData.datapoints,
alias: seriesData.target,
});
setValues(data) {
data.flotpairs = [];
series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
if (this.series.length > 1) {
this.inspector.error = new Error();
this.inspector.error.message = 'Multiple Series Error';
this.inspector.error.data = 'Metric query returns ' + this.series.length +
' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify(this.series);
throw this.inspector.error;
}
return series;
};
if (this.series && this.series.length > 0) {
var lastPoint = _.last(this.series[0].datapoints);
var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
$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)'];
if (_.isString(lastValue)) {
data.value = 0;
data.valueFormated = lastValue;
data.valueRounded = 0;
} 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)'];
data.value = this.series[0].stats[this.panel.valueName];
data.flotpairs = this.series[0].flotpairs;
var decimalInfo = this.getDecimalsForValue(data.value);
var formatFunc = kbn.valueFormats[this.panel.format];
data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
$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: any = {};
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: any = {};
$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) {
// check value to text mappings
for (var i = 0; i < this.panel.valueMaps.length; i++) {
var map = this.panel.valueMaps[i];
// special null case
if (map.value === 'null') {
if (data.value === null || data.value === void 0) {
data.valueFormated = map.text;
return;
}
continue;
}
if (data.value === null || data.value === void 0) {
data.valueFormated = "no value";
// value/number to text mapping
var value = parseFloat(map.value);
if (value === data.value) {
data.valueFormated = map.text;
return;
}
};
}
$scope.removeValueMap = function(map) {
var index = _.indexOf($scope.panel.valueMaps, map);
$scope.panel.valueMaps.splice(index, 1);
$scope.render();
};
if (data.value === null || data.value === void 0) {
data.valueFormated = "no value";
}
};
$scope.addValueMap = function() {
$scope.panel.valueMaps.push({value: '', op: '=', text: '' });
};
$scope.init();
removeValueMap(map) {
var index = _.indexOf(this.panel.valueMaps, map);
this.panel.valueMaps.splice(index, 1);
this.render();
};
addValueMap() {
this.panel.valueMaps.push({value: '', op: '=', text: '' });
}
}

View File

@@ -10,20 +10,20 @@
</li>
<li>
<input type="text" class="input-small tight-form-input"
ng-model="panel.prefix" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
Value
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Postfix
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.postfix" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -37,19 +37,19 @@
Prefix
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Value
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.valueFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Postfix
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input last" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
</ul>
<div class="clearfix"></div>
@@ -60,16 +60,16 @@
<strong>Unit</strong>
</li>
<li class="dropdown" style="width: 266px;"
ng-model="panel.format"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat($subItem)">
ng-model="ctrl.panel.format"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat($subItem)">
</li>
<li class="tight-form-item">
Decimals
</li>
<li>
<input type="number" class="input-small tight-form-input last" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@@ -86,32 +86,32 @@
</li>
<li class="tight-form-item">
Background&nbsp;
<input class="cr1" id="panel.colorBackground" type="checkbox"
ng-model="panel.colorBackground" ng-checked="panel.colorBackground" ng-change="render()">
<label for="panel.colorBackground" class="cr1"></label>
<input class="cr1" id="ctrl.panel.colorBackground" type="checkbox"
ng-model="ctrl.panel.colorBackground" ng-checked="ctrl.panel.colorBackground" ng-change="ctrl.render()">
<label for="ctrl.panel.colorBackground" class="cr1"></label>
</li>
<li class="tight-form-item">
Value&nbsp;
<input class="cr1" id="panel.colorValue" type="checkbox"
ng-model="panel.colorValue" ng-checked="panel.colorValue" ng-change="render()">
<label for="panel.colorValue" class="cr1"></label>
<input class="cr1" id="ctrl.panel.colorValue" type="checkbox"
ng-model="ctrl.panel.colorValue" ng-checked="ctrl.panel.colorValue" ng-change="ctrl.render()">
<label for="ctrl.panel.colorValue" class="cr1"></label>
</li>
<li class="tight-form-item">
Thresholds<tip>Define two threshold values&lt;br /&gt; 50,80 will produce: &lt;50 = Green, 50:80 = Yellow, &gt;80 = Red</tip>
</li>
<li>
<input type="text" class="input-large tight-form-input" ng-model="panel.thresholds" ng-blur="render()" placeholder="50,80"></input>
<input type="text" class="input-large tight-form-input" ng-model="ctrl.panel.thresholds" ng-blur="ctrl.render()" placeholder="50,80"></input>
</li>
<li class="tight-form-item">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[0]" ng-change="ctrl.render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[1]" ng-change="ctrl.render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[2]" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
<a class="pointer" ng-click="ctrl.invertColorOrder()">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
@@ -128,27 +128,27 @@
</li>
<li class="tight-form-item">
Show&nbsp;
<input class="cr1" id="panel.sparkline.show" type="checkbox"
ng-model="panel.sparkline.show" ng-checked="panel.sparkline.show" ng-change="render()">
<label for="panel.sparkline.show" class="cr1"></label>
<input class="cr1" id="ctrl.panel.sparkline.show" type="checkbox"
ng-model="ctrl.panel.sparkline.show" ng-checked="ctrl.panel.sparkline.show" ng-change="ctrl.render()">
<label for="ctrl.panel.sparkline.show" class="cr1"></label>
</li>
<li class="tight-form-item">
Background mode&nbsp;
<input class="cr1" id="panel.sparkline.full" type="checkbox"
ng-model="panel.sparkline.full" ng-checked="panel.sparkline.full" ng-change="render()">
<label for="panel.sparkline.full" class="cr1"></label>
<input class="cr1" id="ctrl.panel.sparkline.full" type="checkbox"
ng-model="ctrl.panel.sparkline.full" ng-checked="ctrl.panel.sparkline.full" ng-change="ctrl.render()">
<label for="ctrl.panel.sparkline.full" class="cr1"></label>
</li>
<li class="tight-form-item">
Line Color
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.sparkline.lineColor" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item">
Fill Color
</li>
<li class="tight-form-item last">
<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.sparkline.fillColor" ng-change="ctrl.render()" ></spectrum-picker>
</li>
</ul>
<div class="clearfix"></div>
@@ -163,21 +163,21 @@
<li class="tight-form-item">
<strong>Value to text mapping</strong>
</li>
<li class="tight-form-item" ng-repeat-start="map in panel.valueMaps">
<i class="fa fa-remove pointer" ng-click="removeValueMap(map)"></i>
<li class="tight-form-item" ng-repeat-start="map in ctrl.panel.valueMaps">
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
</li>
<li>
<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="render()">
<input type="text" ng-model="ctrl.map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li class="tight-form-item">
<i class="fa fa-arrow-right"></i>
</li>
<li ng-repeat-end>
<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="render()">
<input type="text" placeholder="text" ng-model="ctrl.map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li>
<a class="pointer tight-form-item last" ng-click="addValueMap();">
<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
<i class="fa fa-plus"></i>
</a>
</li>

View File

@@ -1,4 +1,4 @@
<grafana-panel>
<div class="singlestat-panel"></div>
<div class="clearfix"></div>
</grafana-panel>
<div class="singlestat-panel">
</div>
<div class="clearfix"></div>

View File

@@ -2,222 +2,225 @@
import _ from 'lodash';
import $ from 'jquery';
import angular from 'angular';
import 'jquery.flot';
import {SingleStatCtrl} from './controller';
import {PanelDirective} from '../../../features/panel/panel';
angular.module('grafana.directives').directive('singleStatPanel', singleStatPanel);
class SingleStatPanel extends PanelDirective {
templateUrl = 'app/plugins/panel/singlestat/module.html';
controller = SingleStatCtrl;
/** @ngInject */
function singleStatPanel($location, linkSrv, $timeout, templateSrv) {
'use strict';
return {
controller: SingleStatCtrl,
templateUrl: 'app/plugins/panel/singlestat/module.html',
link: function(scope, elem) {
var data, panel, linkInfo, $panelContainer;
var firstRender = true;
/** @ngInject */
constructor(private $location, private linkSrv, private $timeout, private templateSrv) {
super();
}
scope.$on('render', function() {
if (firstRender) {
var inner = elem.find('.singlestat-panel');
if (inner.length) {
elem = inner;
$panelContainer = elem.parents('.panel-container');
firstRender = false;
hookupDrilldownLinkTooltip();
}
}
link(scope, elem, attrs, ctrl) {
var $location = this.$location;
var linkSrv = this.linkSrv;
var $timeout = this.$timeout;
var panel = ctrl.panel;
var templateSrv = this.templateSrv;
var data, linkInfo, $panelContainer;
var firstRender = true;
render();
scope.panelRenderingComplete();
});
function setElementHeight() {
try {
var height = scope.height || panel.height || scope.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
height -= 5; // padding
height -= panel.title ? 24 : 9; // subtract panel title bar
elem.css('height', height + 'px');
return true;
} catch (e) { // IE throws errors sometimes
return false;
scope.$on('render', function() {
if (firstRender) {
var inner = elem.find('.singlestat-panel');
if (inner.length) {
elem = inner;
$panelContainer = elem.parents('.panel-container');
firstRender = false;
hookupDrilldownLinkTooltip();
}
}
function applyColoringThresholds(value, valueString) {
if (!panel.colorValue) {
return valueString;
render();
ctrl.renderingCompleted();
});
function setElementHeight() {
try {
var height = scope.height || panel.height || ctrl.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
var color = getColorForValue(data, value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
height -= 5; // padding
height -= panel.title ? 24 : 9; // subtract panel title bar
elem.css('height', height + 'px');
return true;
} catch (e) { // IE throws errors sometimes
return false;
}
}
function applyColoringThresholds(value, valueString) {
if (!panel.colorValue) {
return valueString;
}
function getSpan(className, fontSize, value) {
value = templateSrv.replace(value);
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
value + '</span>';
var color = getColorForValue(data, value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
function getBigValueHtml() {
var body = '<div class="singlestat-panel-value-container">';
return valueString;
}
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); }
function getSpan(className, fontSize, value) {
value = templateSrv.replace(value);
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
value + '</span>';
}
var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
function getBigValueHtml() {
var body = '<div class="singlestat-panel-value-container">';
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.prefix); }
body += '</div>';
var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
return body;
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
body += '</div>';
return body;
}
function addSparkline() {
var width = elem.width() + 20;
var height = elem.height() || 100;
var plotCanvas = $('<div></div>');
var plotCss: any = {};
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";
}
function addSparkline() {
var panel = scope.panel;
var width = elem.width() + 20;
var height = elem.height() || 100;
plotCanvas.css(plotCss);
var plotCanvas = $('<div></div>');
var plotCss: any = {};
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,
},
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 },
};
},
yaxes: { show: false },
xaxis: {
show: false,
mode: "time",
min: ctrl.range.from.valueOf(),
max: ctrl.range.to.valueOf(),
},
grid: { hoverable: false, show: false },
};
elem.append(plotCanvas);
elem.append(plotCanvas);
var plotSeries = {
data: data.flotpairs,
color: panel.sparkline.lineColor
};
var plotSeries = {
data: data.flotpairs,
color: panel.sparkline.lineColor
};
$.plot(plotCanvas, [plotSeries], options);
}
$.plot(plotCanvas, [plotSeries], options);
}
function render() {
if (!scope.data) { return; }
function render() {
if (!ctrl.data) { return; }
data = scope.data;
panel = scope.panel;
data = ctrl.data;
setElementHeight();
setElementHeight();
var body = getBigValueHtml();
var body = getBigValueHtml();
if (panel.colorBackground && !isNaN(data.valueRounded)) {
var color = getColorForValue(data, 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;
}
}
function hookupDrilldownLinkTooltip() {
// 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(evt) {
if (!linkInfo) { return; }
// ignore title clicks in title
if ($(evt).parents('.panel-header').length > 0) { return; }
if (linkInfo.target === '_blank') {
var redirectWindow = window.open(linkInfo.href, '_blank');
redirectWindow.location;
return;
}
if (linkInfo.href.indexOf('http') === 0) {
window.location.href = linkInfo.href;
if (panel.colorBackground && !isNaN(data.valueRounded)) {
var color = getColorForValue(data, data.valueRounded);
if (color) {
$panelContainer.css('background-color', color);
if (scope.fullscreen) {
elem.css('background-color', color);
} else {
$timeout(function() {
$location.url(linkInfo.href);
});
elem.css('background-color', '');
}
}
} else {
$panelContainer.css('background-color', '');
elem.css('background-color', '');
}
drilldownTooltip.detach();
});
elem.html(body);
elem.mousemove(function(e) {
if (!linkInfo) { return;}
if (panel.sparkline.show) {
addSparkline();
}
drilldownTooltip.text('click to go to: ' + linkInfo.title);
drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
});
elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) {
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars);
} else {
linkInfo = null;
}
}
};
function hookupDrilldownLinkTooltip() {
// 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(evt) {
if (!linkInfo) { return; }
// ignore title clicks in title
if ($(evt).parents('.panel-header').length > 0) { return; }
if (linkInfo.target === '_blank') {
var redirectWindow = window.open(linkInfo.href, '_blank');
redirectWindow.location;
return;
}
if (linkInfo.href.indexOf('http') === 0) {
window.location.href = linkInfo.href;
} else {
$timeout(function() {
$location.url(linkInfo.href);
});
}
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);
});
}
}
}
function getColorForValue(data, value) {
@@ -226,8 +229,11 @@ function getColorForValue(data, value) {
return data.colorMap[i];
}
}
return _.first(data.colorMap);
}
export {singleStatPanel as panel, getColorForValue};
export {
SingleStatPanel,
SingleStatPanel as Panel,
getColorForValue
};

View File

@@ -9,9 +9,6 @@ import angular from 'angular';
import helpers from '../../../../../test/specs/helpers';
import {SingleStatCtrl} from '../controller';
angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl);
describe('SingleStatCtrl', function() {
var ctx = new helpers.ControllerTestContext();
@@ -25,7 +22,7 @@ describe('SingleStatCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('SingleStatCtrl'));
beforeEach(ctx.createPanelController(SingleStatCtrl));
beforeEach(function() {
setupFunc();
@@ -33,9 +30,9 @@ describe('SingleStatCtrl', function() {
data: [{target: 'test.cpu1', datapoints: ctx.datapoints}]
}));
ctx.scope.refreshData(ctx.datasource);
ctx.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
ctx.data = ctx.scope.data;
ctx.data = ctx.ctrl.data;
});
};
@@ -76,7 +73,7 @@ describe('SingleStatCtrl', function() {
singleStatScenario('When value to text mapping is specified', function(ctx) {
ctx.setup(function() {
ctx.datapoints = [[10,1]];
ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}];
ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}];
});
it('Should replace value with text', function() {

View File

@@ -4,132 +4,130 @@ import angular from 'angular';
import _ from 'lodash';
import moment from 'moment';
import * as FileExport from 'app/core/utils/file_export';
import PanelMeta from 'app/features/panel/panel_meta2';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor';
export class TablePanelCtrl {
var panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
export class TablePanelCtrl extends MetricsPanelCtrl {
pageIndex: number;
dataRaw: any;
table: any;
/** @ngInject */
constructor($scope, $rootScope, $q, panelSrv, panelHelper, annotationsSrv) {
$scope.ctrl = this;
$scope.pageIndex = 0;
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
this.pageIndex = 0;
$scope.panelMeta = new PanelMeta({
panelName: 'Table',
editIcon: "fa fa-table",
fullscreen: true,
metricsEditor: true,
});
if (this.panel.styles === void 0) {
this.panel.styles = this.panel.columns;
this.panel.columns = this.panel.fields;
delete this.panel.columns;
delete this.panel.fields;
}
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
_.defaults(this.panel, panelDefaults);
}
var panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
initEditMode() {
super.initEditMode();
this.addEditorTab('Options', tablePanelEditor, 1);
}
$scope.init = function() {
if ($scope.panel.styles === void 0) {
$scope.panel.styles = $scope.panel.columns;
$scope.panel.columns = $scope.panel.fields;
delete $scope.panel.columns;
delete $scope.panel.fields;
}
getExtendedMenu() {
var menu = super.getExtendedMenu();
menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
return menu;
}
_.defaults($scope.panel, panelDefaults);
panelSrv.init($scope);
};
refreshData(datasource) {
this.pageIndex = 0;
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
$scope.pageIndex = 0;
if ($scope.panel.transform === 'annotations') {
return annotationsSrv.getAnnotations($scope.dashboard).then(annotations => {
$scope.dataRaw = annotations;
$scope.render();
});
}
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.render();
throw err;
if (this.panel.transform === 'annotations') {
return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => {
this.dataRaw = annotations;
this.render();
});
};
}
$scope.toggleColumnSort = function(col, colIndex) {
if ($scope.panel.sort.col === colIndex) {
if ($scope.panel.sort.desc) {
$scope.panel.sort.desc = false;
} else {
$scope.panel.sort.col = null;
}
return this.issueQueries(datasource)
.then(this.dataHandler.bind(this))
.catch(err => {
this.render();
throw err;
});
}
toggleColumnSort(col, colIndex) {
if (this.panel.sort.col === colIndex) {
if (this.panel.sort.desc) {
this.panel.sort.desc = false;
} else {
$scope.panel.sort.col = colIndex;
$scope.panel.sort.desc = true;
this.panel.sort.col = null;
}
} else {
this.panel.sort.col = colIndex;
this.panel.sort.desc = true;
}
$scope.render();
};
this.render();
}
$scope.dataHandler = function(results) {
$scope.dataRaw = results.data;
$scope.pageIndex = 0;
$scope.render();
};
dataHandler(results) {
this.dataRaw = results.data;
this.pageIndex = 0;
this.render();
}
$scope.render = function() {
// automatically correct transform mode
// based on data
if ($scope.dataRaw && $scope.dataRaw.length) {
if ($scope.dataRaw[0].type === 'table') {
$scope.panel.transform = 'table';
render() {
// automatically correct transform mode
// based on data
if (this.dataRaw && this.dataRaw.length) {
if (this.dataRaw[0].type === 'table') {
this.panel.transform = 'table';
} else {
if (this.dataRaw[0].type === 'docs') {
this.panel.transform = 'json';
} else {
if ($scope.dataRaw[0].type === 'docs') {
$scope.panel.transform = 'json';
} else {
if ($scope.panel.transform === 'table' || $scope.panel.transform === 'json') {
$scope.panel.transform = 'timeseries_to_rows';
}
if (this.panel.transform === 'table' || this.panel.transform === 'json') {
this.panel.transform = 'timeseries_to_rows';
}
}
}
}
$scope.table = transformDataToTable($scope.dataRaw, $scope.panel);
$scope.table.sort($scope.panel.sort);
panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
};
this.table = transformDataToTable(this.dataRaw, this.panel);
this.table.sort(this.panel.sort);
this.broadcastRender(this.table);
}
$scope.exportCsv = function() {
FileExport.exportTableDataToCsv($scope.table);
};
$scope.init();
exportCsv() {
FileExport.exportTableDataToCsv(this.table);
}
}

View File

@@ -9,9 +9,9 @@
</li>
<li>
<select class="input-large tight-form-input"
ng-model="panel.transform"
ng-options="k as v.description for (k, v) in transformers"
ng-change="transformChanged()"></select>
ng-model="editor.panel.transform"
ng-options="k as v.description for (k, v) in editor.transformers"
ng-change="editor.transformChanged()"></select>
</li>
</ul>
<div class="clearfix"></div>
@@ -21,14 +21,14 @@
<li class="tight-form-item" style="width: 140px">
Columns
</li>
<li class="tight-form-item" ng-repeat="column in panel.columns">
<i class="pointer fa fa-remove" ng-click="removeColumn(column)"></i>
<li class="tight-form-item" ng-repeat="column in editor.panel.columns">
<i class="pointer fa fa-remove" ng-click="editor.removeColumn(column)"></i>
<span>
{{column.text}}
</span>
</li>
<li>
<metric-segment segment="addColumnSegment" get-options="getColumnOptions()" on-change="addColumn()"></metric-segment>
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
</li>
</ul>
<div class="clearfix"></div>
@@ -46,16 +46,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="100"
empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
empty-to-null ng-model="editor.panel.pageSize" ng-change="editor.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<editor-checkbox text="Scroll" model="panel.scroll" change="render()"></editor-checkbox>
<editor-checkbox text="Scroll" model="editor.panel.scroll" change="editor.render()"></editor-checkbox>
</li>
<li class="tight-form-item">
Font size
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.fontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="editor.panel.fontSize" ng-options="f for f in editor.fontSizes" ng-change="editor.render()"></select>
</li>
</ul>
<div class="clearfix"></div>
@@ -68,11 +68,11 @@
<h5>Column Styles</h5>
<div class="tight-form-container">
<div ng-repeat="style in panel.styles">
<div ng-repeat="style in editor.panel.styles">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item last">
<i class="fa fa-remove pointer" ng-click="removeColumnStyle(style)"></i>
<i class="fa fa-remove pointer" ng-click="editor.removeColumnStyle(style)"></i>
</li>
</ul>
@@ -81,7 +81,7 @@
Name or regex
</li>
<li>
<input type="text" ng-model="style.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
<input type="text" ng-model="style.pattern" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
</li>
<li class="tight-form-item" style="width: 86px">
Type
@@ -89,8 +89,8 @@
<li>
<select class="input-small tight-form-input"
ng-model="style.type"
ng-options="c.value as c.text for c in columnTypes"
ng-change="render()"
ng-options="c.value as c.text for c in editor.columnTypes"
ng-change="editor.render()"
style="width: 150px"
></select>
</li>
@@ -100,7 +100,7 @@
Format
</li>
<li>
<metric-segment-model property="style.dateFormat" options="dateFormats" on-change="render()" custom="true"></metric-segment-model>
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
@@ -113,8 +113,8 @@
<li>
<select class="input-small tight-form-input"
ng-model="style.colorMode"
ng-options="c.value as c.text for c in colorModes"
ng-change="render()"
ng-options="c.value as c.text for c in editor.colorModes"
ng-change="editor.render()"
style="width: 150px"
></select>
</li>
@@ -122,18 +122,18 @@
Thresholds<tip>Comma seperated values</tip>
</li>
<li>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="style.thresholds" ng-blur="render()" placeholder="0,50,80" array-join></input>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="style.thresholds" ng-blur="editor.render()" placeholder="0,50,80" array-join></input>
</li>
<li class="tight-form-item" style="width: 60px">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="style.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[2]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder($index)">invert order</a>
<a class="pointer" ng-click="editor.invertColorOrder($index)">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
@@ -145,8 +145,8 @@
</li>
<li class="dropdown" style="width: 150px"
ng-model="style.unit"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(style, $subItem)">
dropdown-typeahead="editor.unitFormats"
dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)">
</li>
<li class="tight-form-item" style="width: 86px">
Decimals
@@ -161,7 +161,7 @@
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="editor.addColumnStyle()">
Add column style rule
</button>
</div>

View File

@@ -10,105 +10,123 @@ import {transformers} from './transformers';
import kbn from 'app/core/utils/kbn';
export class TablePanelEditorCtrl {
panel: any;
panelCtrl: any;
transformers: any;
colorModes: any;
columnStyles: any;
columnTypes: any;
fontSizes: any;
dateFormats: any;
addColumnSegment: any;
unitFormats: any;
getColumnNames: any;
/** @ngInject */
constructor($scope, $q, uiSegmentSrv) {
$scope.transformers = transformers;
$scope.unitFormats = kbn.getUnitFormats();
$scope.colorModes = [
constructor($scope, private $q, private uiSegmentSrv) {
$scope.editor = this;
this.panelCtrl = $scope.ctrl;
this.panel = this.panelCtrl.panel;
this.transformers = transformers;
this.unitFormats = kbn.getUnitFormats();
this.colorModes = [
{text: 'Disabled', value: null},
{text: 'Cell', value: 'cell'},
{text: 'Value', value: 'value'},
{text: 'Row', value: 'row'},
];
$scope.columnTypes = [
this.columnTypes = [
{text: 'Number', value: 'number'},
{text: 'String', value: 'string'},
{text: 'Date', value: 'date'},
];
$scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
$scope.dateFormats = [
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
this.dateFormats = [
{text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'},
{text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'},
{text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'},
];
$scope.addColumnSegment = uiSegmentSrv.newPlusButton();
this.addColumnSegment = uiSegmentSrv.newPlusButton();
$scope.getColumnOptions = function() {
if (!$scope.dataRaw) {
return $q.when([]);
}
var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw);
var segments = _.map(columns, (c: any) => uiSegmentSrv.newSegment({value: c.text}));
return $q.when(segments);
};
$scope.addColumn = function() {
var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw);
var column = _.findWhere(columns, {text: $scope.addColumnSegment.value});
if (column) {
$scope.panel.columns.push(column);
$scope.render();
}
var plusButton = uiSegmentSrv.newPlusButton();
$scope.addColumnSegment.html = plusButton.html;
$scope.addColumnSegment.value = plusButton.value;
};
$scope.transformChanged = function() {
$scope.panel.columns = [];
$scope.render();
};
$scope.removeColumn = function(column) {
$scope.panel.columns = _.without($scope.panel.columns, column);
$scope.render();
};
$scope.setUnitFormat = function(column, subItem) {
column.unit = subItem.value;
$scope.render();
};
$scope.addColumnStyle = function() {
var columnStyleDefaults = {
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
};
$scope.panel.styles.push(angular.copy(columnStyleDefaults));
};
$scope.removeColumnStyle = function(style) {
$scope.panel.styles = _.without($scope.panel.styles, style);
};
$scope.getColumnNames = function() {
if (!$scope.table) {
// this is used from bs-typeahead and needs to be instance bound
this.getColumnNames = () => {
if (!this.panelCtrl.table) {
return [];
}
return _.map($scope.table.columns, function(col: any) {
return _.map(this.panelCtrl.table.columns, function(col: any) {
return col.text;
});
};
}
$scope.invertColorOrder = function(index) {
var ref = $scope.panel.styles[index].colors;
var copy = ref[0];
ref[0] = ref[2];
ref[2] = copy;
$scope.render();
getColumnOptions() {
if (!this.panelCtrl.dataRaw) {
return this.$q.when([]);
}
var columns = this.transformers[this.panel.transform].getColumns(this.panelCtrl.dataRaw);
var segments = _.map(columns, (c: any) => this.uiSegmentSrv.newSegment({value: c.text}));
return this.$q.when(segments);
}
addColumn() {
var columns = transformers[this.panel.transform].getColumns(this.panelCtrl.dataRaw);
var column = _.findWhere(columns, {text: this.addColumnSegment.value});
if (column) {
this.panel.columns.push(column);
this.render();
}
var plusButton = this.uiSegmentSrv.newPlusButton();
this.addColumnSegment.html = plusButton.html;
this.addColumnSegment.value = plusButton.value;
}
transformChanged() {
this.panel.columns = [];
this.render();
}
render() {
this.panelCtrl.render();
}
removeColumn(column) {
this.panel.columns = _.without(this.panel.columns, column);
this.panelCtrl.render();
}
setUnitFormat(column, subItem) {
column.unit = subItem.value;
this.panelCtrl.render();
};
addColumnStyle() {
var columnStyleDefaults = {
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
};
this.panel.styles.push(angular.copy(columnStyleDefaults));
}
removeColumnStyle(style) {
this.panel.styles = _.without(this.panel.styles, style);
}
invertColorOrder(index) {
var ref = this.panel.styles[index].colors;
var copy = ref[0];
ref[0] = ref[2];
ref[2] = copy;
this.panelCtrl.render();
}
}

View File

@@ -1,28 +1,24 @@
<div class="table-panel-wrapper">
<grafana-panel>
<div class="table-panel-container">
<div class="table-panel-header-bg"></div>
<div class="table-panel-scroll">
<table class="table-panel-table">
<thead>
<tr>
<th ng-repeat="col in table.columns">
<div class="table-panel-table-header-inner pointer" ng-click="toggleColumnSort(col, $index)">
{{col.text}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
<i class="fa fa-caret-down" ng-show="col.desc"></i>
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="table-panel-footer">
</div>
</grafana-panel>
<div class="table-panel-container">
<div class="table-panel-header-bg"></div>
<div class="table-panel-scroll">
<table class="table-panel-table">
<thead>
<tr>
<th ng-repeat="col in ctrl.table.columns">
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
{{col.text}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
<i class="fa fa-caret-down" ng-show="col.desc"></i>
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="table-panel-footer">
</div>

View File

@@ -5,103 +5,98 @@ import kbn = require('app/core/utils/kbn');
import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import angular from 'angular';
import {PanelDirective} from '../../../features/panel/panel';
import {TablePanelCtrl} from './controller';
import {TableRenderer} from './renderer';
import {tablePanelEditor} from './editor';
angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);
class TablePanel extends PanelDirective {
templateUrl = 'app/plugins/panel/table/module.html';
controller = TablePanelCtrl;
function tablePanel() {
'use strict';
return {
restrict: 'E',
templateUrl: 'app/plugins/panel/table/module.html',
controller: TablePanelCtrl,
link: function(scope, elem) {
var data;
var panel = scope.panel;
var pageCount = 0;
var formaters = [];
link(scope, elem, attrs, ctrl) {
var data;
var panel = ctrl.panel;
var pageCount = 0;
var formaters = [];
function getTableHeight() {
var panelHeight = scope.height || scope.panel.height || scope.row.height;
if (_.isString(panelHeight)) {
panelHeight = parseInt(panelHeight.replace('px', ''), 10);
}
if (pageCount > 1) {
panelHeight -= 28;
}
return (panelHeight - 60) + 'px';
function getTableHeight() {
var panelHeight = ctrl.height || ctrl.panel.height || ctrl.row.height;
if (_.isString(panelHeight)) {
panelHeight = parseInt(panelHeight.replace('px', ''), 10);
}
if (pageCount > 1) {
panelHeight -= 28;
}
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, scope.dashboard.timezone);
tbodyElem.empty();
tbodyElem.html(renderer.render(scope.pageIndex));
}
function switchPage(e) {
var el = $(e.currentTarget);
scope.pageIndex = (parseInt(el.text(), 10)-1);
renderPanel();
}
function appendPaginationControls(footerElem) {
footerElem.empty();
var pageSize = panel.pageSize || 100;
pageCount = Math.ceil(data.rows.length / pageSize);
if (pageCount === 1) {
return;
}
var startPage = Math.max(scope.pageIndex - 3, 0);
var endPage = Math.min(pageCount, startPage + 9);
var paginationList = $('<ul></ul>');
for (var i = startPage; i < endPage; i++) {
var activeClass = i === scope.pageIndex ? 'active' : '';
var pageLinkElem = $('<li><a class="table-panel-page-link pointer ' + activeClass + '">' + (i+1) + '</a></li>');
paginationList.append(pageLinkElem);
}
footerElem.append(paginationList);
}
function renderPanel() {
var container = elem.find('.table-panel-container');
var rootElem = elem.find('.table-panel-scroll');
var tbodyElem = elem.find('tbody');
var footerElem = elem.find('.table-panel-footer');
appendTableRows(tbodyElem);
container.css({'font-size': panel.fontSize});
appendPaginationControls(footerElem);
rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' });
}
elem.on('click', '.table-panel-page-link', switchPage);
scope.$on('$destroy', function() {
elem.off('click', '.table-panel-page-link');
});
scope.$on('render', function(event, renderData) {
data = renderData || data;
if (!data) {
scope.get_data();
return;
}
renderPanel();
});
return (panelHeight - 60) + 'px';
}
};
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, ctrl.dashboard.timezone);
tbodyElem.empty();
tbodyElem.html(renderer.render(ctrl.pageIndex));
}
function switchPage(e) {
var el = $(e.currentTarget);
ctrl.pageIndex = (parseInt(el.text(), 10)-1);
renderPanel();
}
function appendPaginationControls(footerElem) {
footerElem.empty();
var pageSize = panel.pageSize || 100;
pageCount = Math.ceil(data.rows.length / pageSize);
if (pageCount === 1) {
return;
}
var startPage = Math.max(ctrl.pageIndex - 3, 0);
var endPage = Math.min(pageCount, startPage + 9);
var paginationList = $('<ul></ul>');
for (var i = startPage; i < endPage; i++) {
var activeClass = i === ctrl.pageIndex ? 'active' : '';
var pageLinkElem = $('<li><a class="table-panel-page-link pointer ' + activeClass + '">' + (i+1) + '</a></li>');
paginationList.append(pageLinkElem);
}
footerElem.append(paginationList);
}
function renderPanel() {
var panelElem = elem.parents('.panel');
var rootElem = elem.find('.table-panel-scroll');
var tbodyElem = elem.find('tbody');
var footerElem = elem.find('.table-panel-footer');
elem.css({'font-size': panel.fontSize});
panelElem.addClass('table-panel-wrapper');
appendTableRows(tbodyElem);
appendPaginationControls(footerElem);
rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' });
}
elem.on('click', '.table-panel-page-link', switchPage);
scope.$on('$destroy', function() {
elem.off('click', '.table-panel-page-link');
});
scope.$on('render', function(event, renderData) {
data = renderData || data;
if (data) {
renderPanel();
}
});
}
}
export {tablePanel as panel};
export {
TablePanel,
TablePanel as Panel
};

View File

@@ -1,17 +1,17 @@
<div>
<div class="row-fluid">
<div class="span4">
<label class="small">Mode</label> <select class="input-medium" ng-model="panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
<label class="small">Mode</label> <select class="input-medium" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
</div>
<div class="span2" ng-show="panel.mode == 'text'">
<label class="small">Font Size</label> <select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
<div class="span2" ng-show="ctrl.panel.mode == 'text'">
<label class="small">Font Size</label> <select class="input-mini" ng-model="ctrl.panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
</div>
</div>
<label class=small>Content
<span ng-show="panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
<span ng-show="ctrl.panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
</label>
<textarea ng-model="panel.content" rows="20" style="width:95%" ng-change="render()" ng-model-onblur>
<textarea ng-model="ctrl.panel.content" rows="20" style="width:95%" ng-change="ctrl.render()" ng-model-onblur>
</textarea>
</div>

View File

@@ -1,3 +1 @@
<grafana-panel>
<p ng-bind-html="content" ng-show="content"></p>
</grafana-panel>
<p ng-bind-html="ctrl.content" ng-show="ctrl.content"></p>

View File

@@ -1,113 +0,0 @@
define([
'angular',
'app/app',
'lodash',
'require',
'app/features/panel/panel_meta',
],
function (angular, app, _, require, PanelMeta) {
'use strict';
var converter;
/** @ngInject */
function TextPanelCtrl($scope, templateSrv, $sce, panelSrv) {
$scope.panelMeta = new PanelMeta({
panelName: 'Text',
editIcon: "fa fa-text-width",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panel/text/editor.html');
// Set and populate defaults
var _d = {
title : 'default title',
mode : "markdown", // 'html', 'markdown', 'text'
content : "",
style: {},
};
_.defaults($scope.panel, _d);
$scope.init = function() {
panelSrv.init($scope);
$scope.ready = false;
$scope.render();
};
$scope.refreshData = function() {
$scope.panelMeta.loading = false;
$scope.render();
};
$scope.render = function() {
if ($scope.panel.mode === 'markdown') {
$scope.renderMarkdown($scope.panel.content);
}
else if ($scope.panel.mode === 'html') {
$scope.updateContent($scope.panel.content);
}
else if ($scope.panel.mode === 'text') {
$scope.renderText($scope.panel.content);
}
$scope.panelRenderingComplete();
};
$scope.renderText = function(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
$scope.updateContent(content);
};
$scope.renderMarkdown = function(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (converter) {
$scope.updateContent(converter.makeHtml(text));
}
else {
require(['vendor/showdown'], function (Showdown) {
converter = new Showdown.converter();
$scope.updateContent(converter.makeHtml(text));
});
}
};
$scope.updateContent = function(html) {
try {
$scope.content = $sce.trustAsHtml(templateSrv.replace(html, $scope.panel.scopedVars));
} catch(e) {
console.log('Text panel error: ', e);
$scope.content = $sce.trustAsHtml(html);
}
if(!$scope.$$phase) {
$scope.$digest();
}
};
$scope.openEditor = function() {
};
$scope.init();
}
function textPanel() {
return {
controller: TextPanelCtrl,
templateUrl: 'app/plugins/panel/text/module.html',
};
}
return {
panel: textPanel,
};
});

View File

@@ -0,0 +1,85 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
// Set and populate defaults
var panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
export class TextPanelCtrl extends PanelCtrl {
converter: any;
content: string;
/** @ngInject */
constructor($scope, $injector, private templateSrv, private $sce) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
}
initEditMode() {
this.icon = 'fa fa-text-width';
this.addEditorTab('Options', 'public/app/plugins/panel/text/editor.html');
}
refresh() {
this.render();
}
render() {
if (this.panel.mode === 'markdown') {
this.renderMarkdown(this.panel.content);
} else if (this.panel.mode === 'html') {
this.updateContent(this.panel.content);
} else if (this.panel.mode === 'text') {
this.renderText(this.panel.content);
}
this.renderingCompleted();
}
renderText(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
this.updateContent(content);
}
renderMarkdown(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (this.converter) {
this.updateContent(this.converter.makeHtml(text));
} else {
System.import('vendor/showdown').then(Showdown => {
this.converter = new Showdown.converter();
this.$scope.$apply(() => {
this.updateContent(this.converter.makeHtml(text));
});
});
}
}
updateContent(html) {
try {
this.content = this.$sce.trustAsHtml(this.templateSrv.replace(html, this.panel.scopedVars));
} catch (e) {
console.log('Text panel error: ', e);
this.content = this.$sce.trustAsHtml(html);
}
}
}
class TextPanel extends PanelDirective {
templateUrl = `app/plugins/panel/text/module.html`;
controller = TextPanelCtrl;
}
export {TextPanel as Panel}

View File

@@ -0,0 +1,5 @@
<div class="text-center" style="padding-top: 2rem">
Unknown panel type: <strong>{{ctrl.panel.type}}</strong>
</div

View File

@@ -1,15 +1,14 @@
///<reference path="../../../headers/common.d.ts" />
export function unknownPanelDirective() {
return {
restrict: 'E',
template: `
<grafana-panel>
<div class="text-center" style="padding-top: 2rem">
Unknown panel type: <strong>{{panel.type}}</strong>
</div>
</grafana-panel>
`,
};
import {PanelDirective} from '../../../features/panel/panel';
class UnknownPanel extends PanelDirective {
templateUrl = 'public/app/plugins/panel/unknown/module.html';
}
export {
UnknownPanel,
UnknownPanel as Panel
}