Merge branch 'influxdb' Issue #103, influxdb support ready for testing and feedback

This commit is contained in:
Torkel Ödegaard
2014-03-02 13:26:46 +01:00
26 changed files with 712 additions and 363 deletions

View File

@@ -52,14 +52,17 @@ function (_, crypto) {
if (options.graphiteUrl) {
settings.datasources = {
graphite: {
name: 'default',
type: 'graphite',
url: options.graphiteUrl,
default: true
}
};
}
_.map(settings.datasources, parseBasicAuth);
_.each(settings.datasources, function(datasource, key) {
datasource.name = key;
parseBasicAuth(datasource);
});
var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
settings.elasticsearchBasicAuth = elasticParsed.basicAuth;

View File

@@ -8,4 +8,5 @@ define([
'./metricKeys',
'./graphiteTarget',
'./graphiteImport',
'./influxTargetCtrl',
], function () {});

View File

@@ -0,0 +1,24 @@
define([
'angular'
],
function (angular) {
'use strict';
var module = angular.module('kibana.controllers');
module.controller('InfluxTargetCtrl', function($scope) {
$scope.init = function() {
if (!$scope.target.function) {
$scope.target.function = 'mean';
}
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
};
});
});

View File

@@ -63,6 +63,27 @@ function (angular, app, _) {
}
};
$scope.duplicatePanel = function(panel, row) {
row = row || $scope.row;
var currentRowSpan = $scope.rowSpan(row);
if (currentRowSpan <= 9) {
row.panels.push(angular.copy(panel));
}
else {
var rowsList = $scope.dashboard.current.rows;
var rowIndex = _.indexOf(rowsList, row);
if (rowIndex === rowsList.length - 1) {
var newRow = angular.copy($scope.row);
newRow.panels = [];
$scope.dashboard.current.rows.push(newRow);
$scope.duplicatePanel(panel, newRow);
}
else {
$scope.duplicatePanel(panel, rowsList[rowIndex+1]);
}
}
};
/** @scratch /panels/0
* [[panels]]
* = Panels
@@ -111,7 +132,6 @@ function (angular, app, _) {
$scope.init();
}
);
});
});

View File

@@ -69,8 +69,7 @@
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
"value_type": "cumulative"
},
"targets": [
{

View File

@@ -32,5 +32,4 @@ function (angular, app, _) {
}
};
});
});

View File

@@ -82,4 +82,52 @@ function (angular, $) {
}
};
});
angular
.module('kibana.directives')
.directive('gfDropdown', function ($parse, $compile, $timeout) {
function buildTemplate(items, ul) {
if (!ul) {
ul = [
'<ul class="dropdown-menu" role="menu" aria-labelledby="drop1">',
'</ul>'
];
}
angular.forEach(items, function (item, index) {
if (item.divider) {
return ul.splice(index + 1, 0, '<li class="divider"></li>');
}
var li = '<li' + (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') + '>' +
'<a tabindex="-1" ng-href="' + (item.href || '') + '"' + (item.click ? '" ng-click="' + item.click + '"' : '') +
(item.target ? '" target="' + item.target + '"' : '') + (item.method ? '" data-method="' + item.method + '"' : '') +
(item.configModal ? ' config-modal="' + item.configModal + '"' : "") +
'>' + (item.text || '') + '</a>';
if (item.submenu && item.submenu.length) {
li += buildTemplate(item.submenu).join('\n');
}
li += '</li>';
ul.splice(index + 1, 0, li);
});
return ul;
}
return {
restrict: 'EA',
scope: true,
link: function postLink(scope, iElement, iAttrs) {
var getter = $parse(iAttrs.gfDropdown), items = getter(scope);
$timeout(function () {
var dropdown = angular.element(buildTemplate(items).join(''));
dropdown.insertAfter(iElement);
$compile(iElement.next('ul.dropdown-menu'))(scope);
});
iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
}
};
});
});

View File

@@ -75,9 +75,6 @@ function (angular, $, kbn, moment, _) {
}
});
// Set barwidth based on specified interval
var barwidth = kbn.interval_to_ms(scope.interval);
var stack = panel.stack ? true : null;
// Populate element
@@ -96,7 +93,7 @@ function (angular, $, kbn, moment, _) {
bars: {
show: panel.bars,
fill: 1,
barWidth: barwidth/1.5,
barWidth: 1,
zero: false,
lineWidth: 0
},
@@ -128,6 +125,10 @@ function (angular, $, kbn, moment, _) {
data[i].data = _d;
}
if (panel.bars && data.length && data[0].info.timeStep) {
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
}
addTimeAxis(options);
addGridThresholds(options, panel);
addAnnotations(options);
@@ -289,7 +290,7 @@ function (angular, $, kbn, moment, _) {
seriesInfo = item.series.info;
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
if (seriesInfo.alias || scope.panel.tooltip.query_as_alias) {
if (seriesInfo.alias) {
group = '<small style="font-size:0.9em;">' +
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
(seriesInfo.alias || seriesInfo.query)+

View File

@@ -1,13 +1,14 @@
define([
'angular',
'jquery'
'jquery',
'underscore'
],
function (angular, $) {
function (angular, $, _) {
'use strict';
angular
.module('kibana.directives')
.directive('kibanaPanel', function($compile) {
.directive('kibanaPanel', function($compile, $timeout, $rootScope) {
var container = '<div class="panel-container"></div>';
var content = '<div class="panel-content"></div>';
@@ -28,8 +29,8 @@ function (angular, $) {
'<i class="icon-spinner icon-spin icon-large"></i>' +
'</span>' +
'<span ng-if="panelMeta.menuItems" class="dropdown">' +
'<span class="panel-text panel-title pointer" bs-dropdown="panelMeta.menuItems" tabindex="1" ' +
'<span class="dropdown">' +
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
' jqyoui-draggable="'+
'{'+
@@ -44,11 +45,6 @@ function (angular, $) {
'</span>' +
'</span>'+
'<span ng-if="!panelMeta.menuItems" config-modal="./app/partials/paneleditor.html" ' +
' class="panel-text panel-title pointer" >' +
'{{panel.title}}' +
'</span>'+
'</div>'+
'</div>\n'+
'</div>';
@@ -88,8 +84,7 @@ function (angular, $) {
var nameAsPath = name.replace(".", "/");
$scope.require([
'jquery',
'text!panels/'+nameAsPath+'/module.html',
'text!panels/'+nameAsPath+'/editor.html'
'text!panels/'+nameAsPath+'/module.html'
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
// top level controllers
@@ -101,9 +96,7 @@ function (angular, $) {
$controllers.first().prepend(panelHeader);
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
$scope.require([
'panels/'+nameAsPath+'/module'
], function() {
$scope.require(['panels/' + nameAsPath + '/module'], function() {
loadModule($module);
});
} else {
@@ -111,6 +104,126 @@ function (angular, $) {
}
});
});
/*
/* Panel base functionality
/* */
newScope.initPanel = function(scope) {
scope.updateColumnSpan = function(span) {
scope.panel.span = span;
$timeout(function() {
scope.$emit('render');
});
};
function enterFullscreenMode(options) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var oldTimeRange = scope.range;
scope.height = options.edit ? editHeight : fullscreenHeight;
scope.editMode = options.edit;
if (!scope.fullscreen) {
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
scope.editMode = false;
scope.fullscreen = false;
delete scope.height;
closeEditMode();
$timeout(function() {
if (oldTimeRange !== $scope.range) {
scope.dashboard.refresh();
}
else {
scope.$emit('render');
}
});
});
}
$(window).scrollTop(0);
scope.fullscreen = true;
$rootScope.$emit('panel-fullscreen-enter');
$timeout(function() {
scope.$emit('render');
});
}
scope.toggleFullscreenEdit = function() {
if (scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
enterFullscreenMode({edit: true});
};
$scope.toggleFullscreen = function() {
if (scope.fullscreen && !scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
enterFullscreenMode({ edit: false });
};
var menu = [
{
text: 'Edit',
configModal: "app/partials/paneleditor.html",
condition: !scope.panelMeta.fullscreenEdit
},
{
text: 'Edit',
click: "toggleFullscreenEdit()",
condition: scope.panelMeta.fullscreenEdit
},
{
text: "Fullscreen",
click: 'toggleFullscreen()',
condition: scope.panelMeta.fullscreenView
},
{
text: 'Duplicate',
click: 'duplicatePanel(panel)',
condition: true
},
{
text: 'Span',
submenu: [
{ text: '1', click: 'updateColumnSpan(1)' },
{ text: '2', click: 'updateColumnSpan(2)' },
{ text: '3', click: 'updateColumnSpan(3)' },
{ text: '4', click: 'updateColumnSpan(4)' },
{ text: '5', click: 'updateColumnSpan(5)' },
{ text: '6', click: 'updateColumnSpan(6)' },
{ text: '7', click: 'updateColumnSpan(7)' },
{ text: '8', click: 'updateColumnSpan(8)' },
{ text: '9', click: 'updateColumnSpan(9)' },
{ text: '10', click: 'updateColumnSpan(10)' },
{ text: '11', click: 'updateColumnSpan(11)' },
{ text: '12', click: 'updateColumnSpan(12)' },
],
condition: true
},
{
text: 'Remove',
click: 'remove_panel_from_row(row, panel)',
condition: true
}
];
scope.panelMeta.menu = _.where(menu, { condition: true });
};
}
};
});

View File

@@ -2,13 +2,6 @@
ng-init="init()"
style="min-height:{{panel.height || row.height}}"
ng-class="{'panel-fullscreen': fullscreen}">
<div>
<span ng-show='panel.options'>
<a class="link underline small" ng-show='panel.options' ng-click="options=!options">
<i ng-show="!options" class="icon-caret-right"></i><i ng-show="options" class="icon-caret-down"></i> View
</a> |&nbsp
</span>
</div>
<div style="position: relative">
@@ -32,22 +25,8 @@
</div>
</div>
<div class="tab-content" ng-show="editorTabs[editor.index] == 'General'">
<div ng-include src="'app/partials/panelgeneral.html'"></div>
<div class="editor-row" ng-show="datasources.length > 0">
<div class="section">
<div class="editor-option">
<label class="small">Datasource</label>
<select class="input-large" ng-options="obj.value as obj.name for obj in datasources" ng-model="panel.datasource" ng-change="datasourceChanged()"></select>
</div>
</div>
</div>
</div>
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>

View File

@@ -39,11 +39,14 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.panelMeta = {
modals : [],
editorTabs: [],
fullEditorTabs : [
{
title:'Targets',
src:'app/panels/graphite/editor.html'
title: 'General',
src:'app/partials/panelgeneral.html'
},
{
title: 'Metrics',
src:'app/partials/metrics.html'
},
{
title:'Axes & Grid',
@@ -54,30 +57,9 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
src:'app/panels/graphite/styleEditor.html'
}
],
menuItems: [
{ text: 'Edit', click: 'openConfigureModal()' },
{ text: 'Fullscreen', click: 'toggleFullscreen()' },
{ text: 'Duplicate', click: 'duplicate()' },
{ text: 'Span', submenu: [
{ text: '1', click: 'updateColumnSpan(1)' },
{ text: '2', click: 'updateColumnSpan(2)' },
{ text: '3', click: 'updateColumnSpan(3)' },
{ text: '4', click: 'updateColumnSpan(4)' },
{ text: '5', click: 'updateColumnSpan(5)' },
{ text: '6', click: 'updateColumnSpan(6)' },
{ text: '7', click: 'updateColumnSpan(7)' },
{ text: '8', click: 'updateColumnSpan(8)' },
{ text: '9', click: 'updateColumnSpan(9)' },
{ text: '10', click: 'updateColumnSpan(10)' },
{ text: '11', click: 'updateColumnSpan(11)' },
{ text: '12', click: 'updateColumnSpan(12)' },
]},
{ text: 'Remove', click: 'remove_panel_from_row(row, panel)' }
],
status : "Unstable",
description : "Graphite graphing panel <br /><br />"
fullscreenEdit: true,
fullscreenView: true,
description : "Graphing"
};
// Set and populate defaults
@@ -217,35 +199,27 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}
$scope.init = function() {
// Hide view options by default
$scope.initPanel($scope);
$scope.fullscreen = false;
$scope.options = false;
$scope.editor = { index: 1 };
$scope.editorTabs = _.union(['General'],_.pluck($scope.panelMeta.fullEditorTabs,'title'));
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
$scope.hiddenSeries = {};
$scope.datasources = datasourceSrv.listOptions();
$scope.datasource = datasourceSrv.get($scope.panel.datasource);
// Always show the query if an alias isn't set. Users can set an alias if the query is too
// long
$scope.panel.tooltip.query_as_alias = true;
$scope.get_data();
$scope.setDatasource($scope.panel.datasource);
};
$scope.datasourceChanged = function() {
$scope.datasource = datasourceSrv.get($scope.panel.datasource);
$scope.get_data();
};
$scope.setDatasource = function(datasource) {
$scope.panel.datasource = datasource;
$scope.datasource = datasourceSrv.get(datasource);
$scope.remove_panel_from_row = function(row, panel) {
if ($scope.fullscreen) {
$rootScope.$emit('panel-fullscreen-exit');
}
else {
$scope.$parent.remove_panel_from_row(row, panel);
if (!$scope.datasource) {
$scope.panel.error = "Cannot find datasource " + datasource;
return;
}
$scope.get_data();
};
$scope.removeTarget = function (target) {
@@ -256,25 +230,17 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.updateTimeRange = function () {
$scope.range = filterSrv.timeRange();
$scope.rangeUnparsed = filterSrv.timeRange(false);
$scope.resolution = ($(window).width() / ($scope.panel.span / 12)) / 2;
$scope.interval = '10m';
if ($scope.range) {
$scope.interval = kbn.secondsToHms(
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.panel.resolution, 0) / 1000
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
);
}
};
/**
* Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
* need to be consulted (like timestamped logstash indicies)
*
* The results of this function are stored on the scope's data property. This property will be an
* array of objects with the properties info, time_series, and hits. These objects are used in the
* render_panel function to create the historgram.
*
*/
$scope.get_data = function() {
delete $scope.panel.error;
@@ -284,22 +250,23 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
var graphiteQuery = {
range: $scope.rangeUnparsed,
interval: $scope.interval,
targets: $scope.panel.targets,
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: $scope.panel.span * 50,
maxDataPoints: $scope.resolution,
datasource: $scope.panel.datasource
};
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed);
return $scope.datasource.query(graphiteQuery)
.then($scope.receiveGraphiteData)
.then($scope.dataHandler)
.then(null, function(err) {
$scope.panel.error = err.message || "Graphite HTTP Request Error";
});
};
$scope.receiveGraphiteData = function(results) {
$scope.dataHandler = function(results) {
$scope.panelMeta.loading = false;
$scope.legend = [];
@@ -309,44 +276,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
return;
}
results = results.data;
var data = [];
$scope.datapointsWarning = false;
$scope.datapointsCount = 0;
$scope.datapointsOutside = false;
_.each(results, function(targetData) {
var datapoints = targetData.datapoints;
var alias = targetData.target;
var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
var seriesInfo = {
alias: alias,
color: color,
enable: true,
yaxis: yaxis
};
var series = new timeSeries.ZeroFilled({
datapoints: datapoints,
info: seriesInfo,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
var from = moment.utc($scope.range.from);
if (last - from < -1000) {
$scope.datapointsOutside = true;
}
}
$scope.datapointsCount += datapoints.length;
$scope.legend.push(seriesInfo);
data.push(series);
});
var data = _.map(results.data, $scope.seriesHandler);
$scope.datapointsWarning = $scope.datapointsCount || !$scope.datapointsOutside;
@@ -359,54 +293,43 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
});
};
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var color = $scope.panel.aliasColors[alias] || $scope.colors[index];
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
var seriesInfo = {
alias: alias,
color: color,
enable: true,
yaxis: yaxis
};
$scope.legend.push(seriesInfo);
var series = new timeSeries.ZeroFilled({
datapoints: datapoints,
info: seriesInfo,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
var from = moment.utc($scope.range.from);
if (last - from < -10000) {
$scope.datapointsOutside = true;
}
}
$scope.datapointsCount += datapoints.length;
return series;
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
};
$scope.enterFullscreenMode = function(options) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var oldTimeRange = $scope.range;
$scope.height = options.edit ? editHeight : fullscreenHeight;
$scope.editMode = options.edit;
if (!$scope.fullscreen) {
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
$scope.editMode = false;
$scope.fullscreen = false;
delete $scope.height;
closeEditMode();
$timeout(function() {
$scope.$emit('render');
if (oldTimeRange !== $scope.range) {
$scope.dashboard.refresh();
}
});
});
}
$(window).scrollTop(0);
$scope.fullscreen = true;
$rootScope.$emit('panel-fullscreen-enter');
$timeout($scope.render);
};
$scope.openConfigureModal = function() {
if ($scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
$scope.enterFullscreenMode({edit: true});
};
$scope.otherPanelInFullscreenMode = function() {
return $rootScope.fullscreen && !$scope.fullscreen;
};
@@ -421,36 +344,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
$scope.duplicate = function(addToRow) {
addToRow = addToRow || $scope.row;
var currentRowSpan = $scope.rowSpan(addToRow);
if (currentRowSpan <= 9) {
addToRow.panels.push(angular.copy($scope.panel));
}
else {
var rowsList = $scope.dashboard.current.rows;
var rowIndex = _.indexOf(rowsList, addToRow);
if (rowIndex === rowsList.length - 1) {
var newRow = angular.copy($scope.row);
newRow.panels = [];
$scope.dashboard.current.rows.push(newRow);
$scope.duplicate(newRow);
}
else {
$scope.duplicate(rowsList[rowIndex+1]);
}
}
};
$scope.toggleFullscreen = function() {
if ($scope.fullscreen && !$scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
$scope.enterFullscreenMode({edit: false});
};
$scope.toggleSeries = function(info) {
if ($scope.hiddenSeries[info.alias]) {
delete $scope.hiddenSeries[info.alias];
@@ -473,11 +366,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
$scope.updateColumnSpan = function(span) {
$scope.panel.span = span;
$timeout($scope.render);
};
});

View File

@@ -50,7 +50,12 @@ function (_, kbn) {
result.push([currentTime * 1000, currentValue]);
}, this);
if (result.length > 2) {
this.info.timeStep = result[1][0] - result[0][0];
}
if (result.length) {
this.info.avg = (this.info.total / result.length);
this.info.current = result[result.length-1][1];

View File

@@ -24,7 +24,6 @@ function (angular, app, _, require) {
module.controller('text', function($scope) {
$scope.panelMeta = {
status : "Stable",
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
};
@@ -45,6 +44,7 @@ function (angular, app, _, require) {
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.initPanel($scope);
$scope.ready = false;
};

View File

@@ -1,4 +1,5 @@
<div class="editor-row">
<div class="editor-row" style="margin-top: 10px;">
<div ng-repeat="target in panel.targets"
class="grafana-target"
@@ -52,7 +53,7 @@
</ul>
<input type="text"
class="grafana-target-text-input"
class="grafana-target-text-input span12"
ng-model="target.target"
focus-me="showTextEditor"
spellcheck='false'
@@ -75,7 +76,7 @@
</ul>
</li>
<li ng-repeat="func in functions">
<a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/panels/graphite/funcEditor.html'" data-placement="bottom">
<a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/partials/graphite/funcEditor.html'" data-placement="bottom">
{{func.text}}
</a>
</li>
@@ -111,6 +112,3 @@
</div>
</div>
<div class="editor-row" style="margin-top: 20px" ng-show="editor.index == 1">
<button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add target</button>
</div>

View File

@@ -0,0 +1,93 @@
<div class="editor-row" style="margin-top: 10px;">
<div ng-repeat="target in panel.targets"
class="grafana-target"
ng-class="{'grafana-target-hidden': target.hide}"
ng-controller="InfluxTargetCtrl"
ng-init="init()">
<div class="grafana-target-inner-wrapper">
<div class="grafana-target-inner">
<ul class="grafana-target-controls">
<li class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="icon-cog"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeTarget(target)">
<i class="icon-remove"></i>
</a>
</li>
</ul>
<ul class="grafana-target-controls-left">
<li>
<a class="grafana-target-segment"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="icon-eye-open"></i>
</a>
</li>
</ul>
<ul class="grafana-segment-list" role="menu">
<li class="grafana-target-segment">
from series
</li>
<li>
<input type="text"
class="input-medium grafana-target-segment-input"
ng-model="target.series"
spellcheck='false'
placeholder="series name"
ng-model-onblur ng-change="get_data()" >
</li>
<li class="grafana-target-segment">
select
</li>
<li>
<input type="text"
class="input-medium grafana-target-segment-input"
ng-model="target.column"
placeholder="value column"
spellcheck='false'
ng-model-onblur ng-change="get_data()" >
</li>
<li class="grafana-target-segment">
function
</li>
<li>
<select class="input-medium grafana-target-segment-input" ng-change="get_data()" ng-model="target.function" ng-options="f for f in ['mean', 'sum', 'min', 'max', 'median', 'derivative', 'stddev']" ></select>
</li>
<li class="grafana-target-segment">
group by time
</li>
<li>
<input type="text"
class="input-mini grafana-target-segment-input"
ng-model="target.interval"
placeholder="{{interval}}"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
spellcheck='false'
ng-model-onblur ng-change="get_data()" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<div ng-include src="datasource.editorSrc"></div>
<div class="editor-row" style="margin-top: 20px">
<button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add query</button>
<div class="btn-group pull-right" style="margin-right: 10px;">
<button class="btn btn-info dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">{{datasource.name}} <span class="caret"></span></button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem">
<a ng-click="setDatasource(datasource.value);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div ng-include="'app/partials/panelgeneral.html'"></div>
<div ng-include="edit_path(panel.type)"></div>
<div ng-if="!panelMeta.fullEditorTabs" ng-include="edit_path(panel.type)"></div>
<div ng-repeat="tab in panelMeta.editorTabs">
<h5>{{tab.title}}</h5>
<div ng-include="tab.src"></div>

View File

@@ -1,10 +1,6 @@
<div class="editor-row">
<div class="section">
<strong>{{panelMeta.status}}</strong> // <span ng-bind-html="panelMeta.description"></span>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>General options</h5>
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-medium" ng-model='panel.title'></input>
</div>

View File

@@ -7,6 +7,5 @@ define([
'./datasourceSrv',
'./keyboardManager',
'./annotationsSrv',
'./graphite/graphiteDatasource',
],
function () {});

View File

@@ -1,25 +1,35 @@
define([
'angular',
'underscore',
'config'
'config',
'./graphite/graphiteDatasource',
'./influxdb/influxdbDatasource',
],
function (angular, _, config) {
'use strict';
var module = angular.module('kibana.services');
module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource) {
module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource) {
var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true } );
this.default = new GraphiteDatasource(defaultDatasource);
this.get = function(name) {
if (!name) {
return this.default;
if (!name) { return this.default; }
var ds = config.datasources[name];
if (!ds) {
return null;
}
return new GraphiteDatasource(config.datasources[name]);
switch(ds.type) {
case 'graphite':
return new GraphiteDatasource(ds);
case 'influxdb':
return new InfluxDatasource(ds);
}
};
this.listOptions = function() {

View File

@@ -17,6 +17,8 @@ function (angular, _, $, config, kbn, moment) {
this.type = 'graphite';
this.basicAuth = datasource.basicAuth;
this.url = datasource.url;
this.editorSrc = 'app/partials/graphite/editor.html';
this.name = datasource.name;
}
GraphiteDatasource.prototype.query = function(options) {

View File

@@ -0,0 +1,137 @@
define([
'angular',
'underscore',
'kbn'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('kibana.services');
module.factory('InfluxDatasource', function($q, $http) {
function InfluxDatasource(datasource) {
this.type = 'influxDB';
this.editorSrc = 'app/partials/influxDB/editor.html';
this.url = datasource.url;
this.username = datasource.username;
this.password = datasource.password;
this.name = datasource.name;
this.templateSettings = {
interpolate : /\[\[([\s\S]+?)\]\]/g,
};
}
InfluxDatasource.prototype.query = function(options) {
var promises = _.map(options.targets, function(target) {
if (!target.series || !target.column || target.hide) {
return [];
}
var template = "select [[func]]([[column]]) from [[series]] where [[timeFilter]] group by time([[interval]]) order asc";
var templateData = {
series: target.series,
column: target.column,
func: target.function,
timeFilter: getTimeFilter(options),
interval: target.interval || options.interval
};
var query = _.template(template, templateData, this.templateSettings);
console.log(query);
return this.doInfluxRequest(query).then(handleInfluxQueryResponse);
}, this);
return $q.all(promises).then(function(results) {
return { data: _.flatten(results) };
});
};
InfluxDatasource.prototype.doInfluxRequest = function(query) {
var params = {
u: this.username,
p: this.password,
q: query
};
var options = {
method: 'GET',
url: this.url + '/series',
params: params,
};
return $http(options);
};
function handleInfluxQueryResponse(results) {
var output = [];
_.each(results.data, function(series) {
var timeCol = series.columns.indexOf('time');
_.each(series.columns, function(column, index) {
if (column === "time" || column === "sequence_number") {
return;
}
console.log("series:"+series.name + ": "+series.points.length + " points");
var target = series.name + "." + column;
var datapoints = [];
for(var i = 0; i < series.points.length; i++) {
var t = Math.floor(series.points[i][timeCol] / 1000);
var v = series.points[i][index];
datapoints[i] = [v,t];
}
output.push({ target:target, datapoints:datapoints });
});
});
return output;
}
function getTimeFilter(options) {
var from = getInfluxTime(options.range.from);
var until = getInfluxTime(options.range.to);
if (until === 'now()') {
return 'time > now() - ' + from;
}
return 'time > ' + from + ' and time < ' + until;
}
function getInfluxTime(date) {
if (_.isString(date)) {
if (date === 'now') {
return 'now()';
}
else if (date.indexOf('now') >= 0) {
return date.substring(4);
}
date = kbn.parseDate(date);
}
return to_utc_epoch_seconds(date);
}
function to_utc_epoch_seconds(date) {
return (date.getTime() / 1000).toFixed(0) + 's';
}
return InfluxDatasource;
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -318,15 +318,32 @@
input[type=text].grafana-target-text-input {
padding: 5px 7px;
border: 1px solid @grafanaTargetSegmentBorder;
border: none;
margin: 0px;
background: transparent;
width: 80%;
float: left;
color: @grafanaTargetColor;
border-radius: 0;
}
input[type=text].grafana-target-segment-input {
border: none;
border-right: 1px solid @grafanaTargetSegmentBorder;
margin: 0px;
border-radius: 0;
height: 22px;
line-height: 22px;
}
select.grafana-target-segment-input {
border: none;
border-right: 1px solid @grafanaTargetSegmentBorder;
margin: 0px;
border-radius: 0;
height: 30px;
line-height: 30px;
}
.grafana-target .dropdown {
padding: 0; margin: 0;
}