Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0015f81645 | ||
|
|
ed76335c96 | ||
|
|
e0a35a3958 | ||
|
|
d9ec9ed1ef | ||
|
|
0032f934c9 | ||
|
|
8816e18027 | ||
|
|
e8be950752 | ||
|
|
aea4b37760 | ||
|
|
1aff1a0000 | ||
|
|
df54c17dca | ||
|
|
73c5883f0d | ||
|
|
e3561ce555 | ||
|
|
a5feb639d9 | ||
|
|
ac33286c8c | ||
|
|
19d5d32229 | ||
|
|
d90f2537a4 | ||
|
|
487aa1f2c3 | ||
|
|
be9ab15c4e | ||
|
|
7d4f0d24d5 | ||
|
|
bafa334b94 | ||
|
|
80c5f99f3b | ||
|
|
212bf5df98 | ||
|
|
32189afc67 | ||
|
|
0fa6b34f1a | ||
|
|
4d6bb8b6be | ||
|
|
82da75bc0c | ||
|
|
beda378314 | ||
|
|
6734864acd | ||
|
|
9f548870d0 | ||
|
|
d2bae43d26 | ||
|
|
e77e43faab | ||
|
|
224d2f92c3 | ||
|
|
250e354659 | ||
|
|
2e59587c8e | ||
|
|
6f4520254b | ||
|
|
b939c02da0 | ||
|
|
9e900d5885 | ||
|
|
38d3450160 | ||
|
|
2c5bfec089 | ||
|
|
a56a2b0057 | ||
|
|
89224401a9 | ||
|
|
1c17c8661a | ||
|
|
4ed65891c2 | ||
|
|
49e131ce21 | ||
|
|
06f4b017d6 | ||
|
|
f52450aef4 | ||
|
|
6476843e95 | ||
|
|
67d11dffb9 | ||
|
|
58dbb01e76 | ||
|
|
81e9a483bc | ||
|
|
5688f792cb | ||
|
|
3ffc04be4d | ||
|
|
55e586c2c6 | ||
|
|
76b535a2e4 | ||
|
|
6e27f97bc9 | ||
|
|
d9ada8d94e | ||
|
|
b3d67c3ed4 | ||
|
|
459b0dc5bd | ||
|
|
aefd95890e | ||
|
|
36d66494d3 |
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_script:
|
||||
- npm install -g grunt-cli
|
||||
13
README.md
13
README.md
@@ -1,7 +1,9 @@
|
||||
# Grafana - Graphite Dashboard
|
||||
|
||||
[Grafana](http://grafana.org) [](https://travis-ci.org/torkelo/grafana)
|
||||
=================
|
||||
A beautiful, easy to use and feature rich Graphite dashboard replacement and graph editor. Visit [grafana.org](http://grafana.org) for screenshots and an overview.
|
||||
|
||||

|
||||
|
||||
# Features
|
||||
## Graphite Target Editor
|
||||
- Graphite target expression parser
|
||||
@@ -45,8 +47,11 @@ To run from master:
|
||||
- Clone this repository
|
||||
- Start a web server in src folder
|
||||
- Or create a optimized & minified build:
|
||||
-- npm install (requires nodejs)
|
||||
-- grunt build
|
||||
- npm install (requires nodejs)
|
||||
- grunt build
|
||||
|
||||
When you have Grafana up an running, read the [Getting started](https://github.com/torkelo/grafana/wiki/Getting-started) guide for
|
||||
an introduction on how to use Grafana.
|
||||
|
||||
# Graphite server config
|
||||
If you haven't used an alternative dashboard for graphite before you need to enable cross-domain origin request. For Apache 2.x:
|
||||
|
||||
12
package.json
12
package.json
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
@@ -43,7 +43,15 @@
|
||||
"karma": "~0.10.9",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-mocha": "~0.1.1",
|
||||
"karma-expect": "~1.0.0"
|
||||
"karma-expect": "~1.0.0",
|
||||
"grunt-cli": "~0.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": "0.10.x",
|
||||
"npm": "1.2.x"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt"
|
||||
},
|
||||
"license": "Apache License"
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ function (_, crypto) {
|
||||
* @type {Object}
|
||||
*/
|
||||
var defaults = {
|
||||
elasticsearch : "http://"+window.location.hostname+":9200",
|
||||
graphiteUrl : "http://"+window.location.hostname+":8080",
|
||||
panel_names : [],
|
||||
default_route : '/dashboard/file/default.json',
|
||||
grafana_index : 'grafana-dash'
|
||||
elasticsearch : "http://"+window.location.hostname+":9200",
|
||||
graphiteUrl : "http://"+window.location.hostname+":8080",
|
||||
panel_names : [],
|
||||
default_route : '/dashboard/file/default.json',
|
||||
grafana_index : 'grafana-dash',
|
||||
elasticsearch_all_disabled : false,
|
||||
timezoneOffset : null,
|
||||
};
|
||||
|
||||
// This initializes a new hash on purpose, to avoid adding parameters to
|
||||
@@ -27,16 +29,19 @@ function (_, crypto) {
|
||||
settings[key] = typeof options[key] !== 'undefined' ? options[key] : defaults[key];
|
||||
});
|
||||
|
||||
var url = settings.graphiteUrl;
|
||||
var passwordAt = url.indexOf('@');
|
||||
if (passwordAt > 0) {
|
||||
var userStart = url.indexOf('//') + 2;
|
||||
var userAndPassword = url.substring(userStart, passwordAt);
|
||||
var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
var base64 = crypto.util.bytesToBase64(bytes);
|
||||
settings.graphiteBasicAuth = base64;
|
||||
}
|
||||
var basicAuth = function(url) {
|
||||
var passwordAt = url.indexOf('@');
|
||||
if (passwordAt > 0) {
|
||||
var userStart = url.indexOf('//') + 2;
|
||||
var userAndPassword = url.substring(userStart, passwordAt);
|
||||
var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
var base64 = crypto.util.bytesToBase64(bytes);
|
||||
return base64;
|
||||
}
|
||||
};
|
||||
|
||||
settings.graphiteBasicAuth = basicAuth(settings.graphiteUrl);
|
||||
settings.elasticsearchBasicAuth = basicAuth(settings.elasticsearch);
|
||||
return settings;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ function (angular, $, config, _) {
|
||||
|
||||
$scope.reset_row();
|
||||
|
||||
$scope.ejs = ejsResource(config.elasticsearch);
|
||||
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
|
||||
|
||||
$scope.bindKeyboardShortcuts();
|
||||
};
|
||||
@@ -72,6 +72,12 @@ function (angular, $, config, _) {
|
||||
$rootScope.fullscreen = false;
|
||||
});
|
||||
|
||||
$rootScope.$on('dashboard-saved', function() {
|
||||
if ($rootScope.fullscreen) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
}
|
||||
});
|
||||
|
||||
keyboardManager.bind('ctrl+f', function(evt) {
|
||||
$rootScope.$emit('open-search', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
@@ -65,17 +65,18 @@ function (angular, _, moment) {
|
||||
type,
|
||||
($scope.elasticsearch.title || dashboard.current.title),
|
||||
($scope.loader.save_temp_ttl_enable ? ttl : false)
|
||||
).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result._id)) {
|
||||
alertSrv.set('Dashboard Saved','This dashboard has been saved to Elasticsearch as "' +
|
||||
result._id + '"','success',5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
} else {
|
||||
).then(function(result) {
|
||||
if(_.isUndefined(result._id)) {
|
||||
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
|
||||
return;
|
||||
}
|
||||
|
||||
alertSrv.set('Dashboard Saved', 'This dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
|
||||
$rootScope.$emit('dashboard-saved');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -65,11 +65,13 @@ function (angular, app, _) {
|
||||
_.each(state.graphs, function(graph) {
|
||||
if (currentRow.panels.length === graphsPerRow) {
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
newDashboard.rows.push(currentRow);
|
||||
}
|
||||
|
||||
panel = {
|
||||
type: 'graphite',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: []
|
||||
};
|
||||
|
||||
@@ -87,4 +89,4 @@ function (angular, app, _) {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,13 @@ function (angular, _, config) {
|
||||
|
||||
module.controller('MetricKeysCtrl', function($scope, $http, $q) {
|
||||
var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/';
|
||||
|
||||
var httpOptions = {};
|
||||
if (config.elasticsearchBasicAuth) {
|
||||
httpOptions.withCredentials = true;
|
||||
httpOptions.headers = {
|
||||
"Authorization": "Basic " + config.elasticsearchBasicAuth
|
||||
};
|
||||
}
|
||||
$scope.init = function () {
|
||||
$scope.metricPath = "prod.apps.api.boobarella.*";
|
||||
$scope.metricCounter = 0;
|
||||
@@ -77,7 +83,7 @@ function (angular, _, config) {
|
||||
function deleteIndex()
|
||||
{
|
||||
var deferred = $q.defer();
|
||||
$http.delete(elasticSearchUrlForMetricIndex)
|
||||
$http.delete(elasticSearchUrlForMetricIndex, httpOptions)
|
||||
.success(function() {
|
||||
deferred.resolve('ok');
|
||||
})
|
||||
@@ -124,7 +130,7 @@ function (angular, _, config) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, httpOptions);
|
||||
}
|
||||
|
||||
function receiveMetric(result) {
|
||||
|
||||
@@ -51,12 +51,6 @@ function (angular, _, config, $) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleImport = function ($event) {
|
||||
$event.stopPropagation();
|
||||
|
||||
$scope.showImport = !$scope.showImport;
|
||||
};
|
||||
|
||||
$scope.elasticsearch_dblist = function(queryStr) {
|
||||
$scope.showImport = false;
|
||||
$scope.selectedIndex = -1;
|
||||
@@ -120,6 +114,15 @@ function (angular, _, config, $) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleImport = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.showImport = !$scope.showImport;
|
||||
};
|
||||
|
||||
$scope.newDashboard = function() {
|
||||
$location.url('/dashboard/file/empty.json');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"title": "New Dashboard",
|
||||
"title": "Welcome to Grafana!",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
@@ -24,7 +24,7 @@
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "####Thank you for trying out Grafana! \n\nGeneral documentation is found in the readme and in the wiki section of the [Github Probject](https://github.com/torkelo/grafana). If you encounter any problem or have an idea for an improvement do not hesitate to open a github issue. \n\nTips: \n\n- Ctrl+S saves the current dashboard\n- Ctrl+F Opens the dashboard finder (searches elastic search)\n- Ctrl+H Hide/show row controls \n- Click and drag graph title to move panel (only works when row controls are enabled)\n\nIf you do not see a graph in the panel bellow the browser cannot access your graphite installation. Please make sure that the graphiteUrl property in config.js is correctly set and accessible.",
|
||||
"content": "####Thank you for trying out Grafana! \n\nGeneral documentation is found in the readme and in the wiki section of the [Github Project](https://github.com/torkelo/grafana). If you encounter any problem or have an idea for an improvement do not hesitate to open a github issue. \n\nTips: \n\n- Ctrl+S saves the current dashboard\n- Ctrl+F Opens the dashboard finder (searches elastic search)\n- Ctrl+H Hide/show row controls \n- Click and drag graph title to move panel (only works when row controls are enabled)\n\nIf you do not see a graph in the panel bellow the browser cannot access your graphite installation. Please make sure that the graphiteUrl property in config.js is correctly set and accessible.",
|
||||
"style": {},
|
||||
"title": "Welcome to Grafana"
|
||||
}
|
||||
@@ -46,39 +46,12 @@
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_format": "none",
|
||||
"y2_format": "none",
|
||||
"y_formats": ["short", "short"],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false,
|
||||
"query": "*",
|
||||
"size": 20,
|
||||
"field": "_type",
|
||||
"sort": [
|
||||
"_score",
|
||||
"desc"
|
||||
]
|
||||
},
|
||||
"auto_int": true,
|
||||
"resolution": 100,
|
||||
"interval": "5m",
|
||||
"intervals": [
|
||||
"auto",
|
||||
"1s",
|
||||
"1m",
|
||||
"5m",
|
||||
"10m",
|
||||
"30m",
|
||||
"1h",
|
||||
"3h",
|
||||
"12h",
|
||||
"1d",
|
||||
"1w",
|
||||
"1y"
|
||||
],
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
@@ -87,7 +60,6 @@
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"zoomlinks": false,
|
||||
"options": false,
|
||||
"legend": true,
|
||||
"interactive": true,
|
||||
|
||||
83
src/app/dashboards/empty.json
Normal file
83
src/app/dashboards/empty.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"title": "New Dashboard",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false
|
||||
}
|
||||
@@ -8,5 +8,6 @@ define([
|
||||
'./ngModelOnBlur',
|
||||
'./tip',
|
||||
'./confirmClick',
|
||||
'./configModal'
|
||||
'./configModal',
|
||||
'./grafanaGraph'
|
||||
], function () {});
|
||||
323
src/app/directives/grafanaGraph.js
Normal file
323
src/app/directives/grafanaGraph.js
Normal file
@@ -0,0 +1,323 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.directives');
|
||||
|
||||
module.directive('grafanaGraph', function(filterSrv, $rootScope, dashboard) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot;
|
||||
var hiddenData = {};
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, alias) {
|
||||
if (hiddenData[alias]) {
|
||||
data.push(hiddenData[alias]);
|
||||
delete hiddenData[alias];
|
||||
}
|
||||
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, d) {
|
||||
data = d || data;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
elem.css({ height: scope.height || scope.panel.height || scope.row.height });
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (!data) { return; }
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
if (!setElementHeight()) { return; }
|
||||
|
||||
if (_.isString(data)) {
|
||||
render_panel_as_graphite_png(data);
|
||||
return;
|
||||
}
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.interval);
|
||||
|
||||
var stack = scope.panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: scope.panel.stack ? scope.panel.percentage : false,
|
||||
stack: scope.panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: scope.panel.lines,
|
||||
// Silly, but fixes bug in stacked percentages
|
||||
fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
|
||||
lineWidth: scope.panel.linewidth,
|
||||
steps: scope.panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: scope.panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: scope.panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: scope.panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {
|
||||
timezone: dashboard.current.timezone,
|
||||
show: scope.panel['x-axis'],
|
||||
mode: "time",
|
||||
min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
|
||||
max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
|
||||
timeformat: time_format(scope.interval),
|
||||
label: "Datetime",
|
||||
ticks: elem.width()/100
|
||||
},
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
},
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
}
|
||||
};
|
||||
|
||||
addAnnotations(options);
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].getFlotPairs(scope.panel.nullPointMode);
|
||||
data[i].data = _d;
|
||||
}
|
||||
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
plot = $.plot(elem, data, options);
|
||||
|
||||
addAxisLabels();
|
||||
}
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
|
||||
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 ? '' : '&hideLegend=true';
|
||||
url += scope.panel.grid.min ? '&yMin=' + scope.panel.grid.min : '';
|
||||
url += scope.panel.grid.max ? '&yMax=' + scope.panel.grid.max : '';
|
||||
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
switch(scope.panel.y_formats[0]) {
|
||||
case 'bytes':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'short':
|
||||
url += '&yUnitSystem=si';
|
||||
break;
|
||||
case 'none':
|
||||
url += '&yUnitSystem=none';
|
||||
break;
|
||||
}
|
||||
|
||||
switch(scope.panel.nullPointMode) {
|
||||
case 'connected':
|
||||
url += '&lineMode=connected';
|
||||
break;
|
||||
case 'null':
|
||||
break; // graphite default lineMode
|
||||
case 'null as zero':
|
||||
url += "&drawNullAsZero=true";
|
||||
break;
|
||||
}
|
||||
|
||||
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
|
||||
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
|
||||
function addAnnotations(options) {
|
||||
if(scope.panel.annotate.enable) {
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: scope.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
icon: {
|
||||
icon: "icon-tag icon-flip-vertical",
|
||||
size: 20,
|
||||
color: "#222",
|
||||
outline: "#bbb"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function addAxisLabels() {
|
||||
if (scope.panel.leftYAxisLabel) {
|
||||
elem.css('margin-left', '10px');
|
||||
var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
|
||||
.text(scope.panel.leftYAxisLabel)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
|
||||
} else if (elem.css('margin-left')) {
|
||||
elem.css('margin-left', '');
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.min,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max,
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.findWhere(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.position = 'right';
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
|
||||
}
|
||||
|
||||
configureAxisMode(options.yaxes[0], scope.panel.y_formats[0]);
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format === 'bytes') {
|
||||
axis.mode = "byte";
|
||||
}
|
||||
if (format === 'short') {
|
||||
axis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val,0);
|
||||
};
|
||||
}
|
||||
if (format === 'ms') {
|
||||
axis.tickFormatter = kbn.msFormat;
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval) {
|
||||
var _int = kbn.interval_to_seconds(interval);
|
||||
if(_int >= 2628000) {
|
||||
return "%Y-%m";
|
||||
}
|
||||
if(_int >= 10000) {
|
||||
return "%m/%d";
|
||||
}
|
||||
if(_int >= 3600) {
|
||||
return "%m/%d %H:%M";
|
||||
}
|
||||
if(_int >= 700) {
|
||||
return "%a %H:%M";
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp;
|
||||
if (item) {
|
||||
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(item.series.info.alias || item.series.info.query)+
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
|
||||
item.datapoint[1] - item.datapoint[2] :
|
||||
item.datapoint[1];
|
||||
if(item.series.info.y_format === 'bytes') {
|
||||
value = kbn.byteFormat(value, 2);
|
||||
}
|
||||
if(item.series.info.y_format === 'short') {
|
||||
value = kbn.shortFormat(value, 2);
|
||||
}
|
||||
if(item.series.info.y_format === 'ms') {
|
||||
value = kbn.msFormat(value);
|
||||
}
|
||||
timestamp = dashboard.current.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
filterSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -40,7 +40,7 @@ function (angular, $) {
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="row.panels" ' +
|
||||
'>' +
|
||||
'{{panel.title}}' +
|
||||
'{{panel.title || "No title"}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
|
||||
@@ -1,14 +1,82 @@
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Axes</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Left y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-large" ng-model="panel.leftYAxisLabel">
|
||||
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-large" ng-model="panel.rightYAxisLabel">
|
||||
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Left Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[2]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Left Y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right Y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('min')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.min" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ng-click="toggleGridMinMax('max')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.max" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Legend</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-if="panel.legend.values">
|
||||
<h5>Legend values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<span ng-show="panel.legend"
|
||||
<span ng-show="panel.legend.show"
|
||||
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
|
||||
ng-repeat='series in legend'
|
||||
class="histogram-legend">
|
||||
@@ -11,6 +11,23 @@
|
||||
<a ng-click="toggleSeries(series)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
|
||||
{{series.alias}}
|
||||
</a>
|
||||
<span ng-if="panel.legend.values">
|
||||
<span ng-show="panel.legend.current">
|
||||
Current: {{series.current}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.min">
|
||||
Min: {{series.min}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.max">
|
||||
Max: {{series.max}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.total">
|
||||
Total: {{series.total}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.avg">
|
||||
Avg: {{series.avg}}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<center><img ng-show='panel.loading && _.isUndefined(data)' src="img/load_big.gif"></center>
|
||||
|
||||
<div histogram-chart class="pointer histogram-chart" params="{{panel}}">
|
||||
<div grafana-graph class="pointer histogram-chart">
|
||||
</div>
|
||||
|
||||
<div ng-if="panel.legend" class="grafana-legend-container">
|
||||
@@ -24,9 +24,11 @@
|
||||
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" ng-show="editorTabs[editor.index] == 'General'">
|
||||
<div ng-include src="'app/partials/panelgeneral.html'"></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>
|
||||
|
||||
@@ -46,19 +46,19 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
src:'app/panels/graphite/editor.html'
|
||||
},
|
||||
{
|
||||
title:'Axis labels',
|
||||
title:'Axes & Grid',
|
||||
src:'app/panels/graphite/axisEditor.html'
|
||||
},
|
||||
{
|
||||
title:'Style',
|
||||
title:'Display Styles',
|
||||
src:'app/panels/graphite/styleEditor.html'
|
||||
}
|
||||
],
|
||||
|
||||
menuItems: [
|
||||
{ text: 'View fullscreen', click: 'toggleFullscreen()' },
|
||||
{ text: 'Edit', click: 'openConfigureModal()' },
|
||||
{ text: 'Duplicate', click: 'duplicate()' },
|
||||
{ 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)' },
|
||||
@@ -82,6 +82,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/histogram/3
|
||||
* renderer:: sets client side (flot) or native graphite png renderer (png)
|
||||
*/
|
||||
renderer: 'flot',
|
||||
/** @scratch /panels/histogram/3
|
||||
* x-axis:: Show the x-axis
|
||||
*/
|
||||
@@ -95,53 +99,28 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_format:: 'none','bytes','short '
|
||||
* y_formats :: 'none','bytes','short', 'ms'
|
||||
*/
|
||||
y_format : 'none',
|
||||
y2_format : 'none',
|
||||
y_formats : ['short', 'short'],
|
||||
/** @scratch /panels/histogram/5
|
||||
* grid object:: Min and max y-axis values
|
||||
* grid.min::: Minimum y-axis value
|
||||
* grid.max::: Maximum y-axis value
|
||||
* grid.ma1::: Maximum y-axis value
|
||||
*/
|
||||
grid : {
|
||||
max: null,
|
||||
min: 0
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Annotations
|
||||
* annotate object:: A query can be specified, the results of which will be displayed as markers on
|
||||
* the chart. For example, for noting code deploys.
|
||||
* annotate.enable::: Should annotations, aka markers, be shown?
|
||||
* annotate.query::: Lucene query_string syntax query to use for markers.
|
||||
* annotate.size::: Max number of markers to show
|
||||
* annotate.field::: Field from documents to show
|
||||
* annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
|
||||
*/
|
||||
|
||||
annotate : {
|
||||
enable : false,
|
||||
query : "*",
|
||||
size : 20,
|
||||
field : '_type',
|
||||
sort : ['_score','desc']
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Interval options
|
||||
* auto_int:: Automatically scale intervals?
|
||||
*/
|
||||
auto_int : true,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* resolution:: If auto_int is true, shoot for this many bars.
|
||||
*/
|
||||
resolution : 100,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: If auto_int is set to false, use this as the interval.
|
||||
*/
|
||||
interval : '5m',
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
|
||||
*/
|
||||
intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Drawing options
|
||||
* lines:: Show line chart
|
||||
@@ -171,35 +150,20 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
* stack:: Stack multiple series
|
||||
*/
|
||||
stack : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* spyable:: Show inspect icon
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zoomlinks:: Show `Zoom Out' link
|
||||
*/
|
||||
zoomlinks : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* options:: Show quick view options section
|
||||
*/
|
||||
options : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legond
|
||||
*/
|
||||
legend : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interactive:: Enable click-and-drag to zoom functionality
|
||||
*/
|
||||
interactive : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend_counts:: Show counts in legend
|
||||
*/
|
||||
legend_counts : true,
|
||||
legend: {
|
||||
show: true, // disable/enable legend
|
||||
values: false, // disable/enable legend values
|
||||
min: false,
|
||||
max: false,
|
||||
current: false,
|
||||
total: false,
|
||||
avg: false
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Transformations
|
||||
* timezone:: Correct for browser timezone?. Valid values: browser, utc
|
||||
*/
|
||||
timezone : 'browser', // browser or utc
|
||||
/** @scratch /panels/histogram/3
|
||||
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
* queries
|
||||
@@ -211,6 +175,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
zerofill : true,
|
||||
|
||||
nullPointMode : 'connected',
|
||||
|
||||
steppedLine: false,
|
||||
|
||||
tooltip : {
|
||||
@@ -225,13 +190,26 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
};
|
||||
|
||||
_.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.tooltip, _d.tooltip);
|
||||
_.defaults($scope.panel.annotate, _d.annotate);
|
||||
_.defaults($scope.panel.grid, _d.grid);
|
||||
|
||||
// backward compatible stuff
|
||||
if (_.isBoolean($scope.panel.legend)) {
|
||||
$scope.panel.legend = { show: $scope.panel.legend };
|
||||
_.defaults($scope.panel.legend, _d.legend);
|
||||
}
|
||||
|
||||
if ($scope.panel.y_format) {
|
||||
$scope.panel.y_formats[0] = $scope.panel.y_format;
|
||||
delete $scope.panel.y_format;
|
||||
}
|
||||
if ($scope.panel.y2_format) {
|
||||
$scope.panel.y_formats[1] = $scope.panel.y2_format;
|
||||
delete $scope.panel.y2_format;
|
||||
}
|
||||
|
||||
$scope.init = function() {
|
||||
|
||||
// Hide view options by default
|
||||
$scope.fullscreen = false;
|
||||
$scope.options = false;
|
||||
@@ -246,19 +224,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
};
|
||||
|
||||
$scope.set_interval = function(interval) {
|
||||
if(interval !== 'auto') {
|
||||
$scope.panel.auto_int = false;
|
||||
$scope.panel.interval = interval;
|
||||
} else {
|
||||
$scope.panel.auto_int = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.typeAheadSource = function () {
|
||||
return ["test", "asd", "testing2"];
|
||||
};
|
||||
|
||||
$scope.remove_panel_from_row = function(row, panel) {
|
||||
if ($scope.fullscreen) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
@@ -273,24 +238,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.interval_label = function(interval) {
|
||||
return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
|
||||
};
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
var range = filterSrv.timeRange();
|
||||
var interval = filterSrv.timeRange();
|
||||
$scope.range = filterSrv.timeRange();
|
||||
$scope.interval = '10m';
|
||||
|
||||
if ($scope.panel.auto_int) {
|
||||
if (range) {
|
||||
interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
if ($scope.range) {
|
||||
$scope.interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.panel.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
|
||||
$scope.interval = $scope.panel.interval = interval || '10m';
|
||||
$scope.range = range;
|
||||
};
|
||||
|
||||
$scope.colors = [
|
||||
@@ -322,6 +278,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var graphiteQuery = {
|
||||
range: filterSrv.timeRange(false),
|
||||
targets: $scope.panel.targets,
|
||||
renderer: $scope.panel.renderer,
|
||||
maxDataPoints: $scope.panel.span * 50
|
||||
};
|
||||
|
||||
@@ -334,9 +291,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
$scope.receiveGraphiteData = function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.legend = [];
|
||||
|
||||
// png renderer returns just a url
|
||||
if (_.isString(results)) {
|
||||
$scope.render(results);
|
||||
return;
|
||||
}
|
||||
|
||||
results = results.data;
|
||||
$scope.legend = [];
|
||||
var data = [];
|
||||
|
||||
_.each(results, function(targetData) {
|
||||
@@ -344,34 +307,21 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
|
||||
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
|
||||
|
||||
var tsOpts = {
|
||||
interval: $scope.interval,
|
||||
start_date: $scope.range && $scope.range.from,
|
||||
end_date: $scope.range && $scope.range.to,
|
||||
};
|
||||
|
||||
var time_series = new timeSeries.ZeroFilled(tsOpts);
|
||||
|
||||
_.each(targetData.datapoints, function(valueArray) {
|
||||
if (valueArray[0]) {
|
||||
time_series.addValue(valueArray[1] * 1000, valueArray[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var seriesInfo = {
|
||||
alias: alias,
|
||||
color: color,
|
||||
enable: true,
|
||||
yaxis: yaxis,
|
||||
y_format: $scope.panel.y_formats[yaxis - 1]
|
||||
};
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
|
||||
data.push({
|
||||
var series = new timeSeries.ZeroFilled({
|
||||
datapoints: targetData.datapoints,
|
||||
info: seriesInfo,
|
||||
time_series: time_series,
|
||||
});
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
data.push(series);
|
||||
});
|
||||
|
||||
$scope.render(data);
|
||||
@@ -425,6 +375,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.enterFullscreenMode({edit: true});
|
||||
};
|
||||
|
||||
$scope.otherPanelInFullscreenMode = function() {
|
||||
return $rootScope.fullscreen && !$scope.fullscreen;
|
||||
};
|
||||
|
||||
$scope.render = function(data) {
|
||||
$scope.$emit('render', data);
|
||||
};
|
||||
@@ -482,6 +436,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleGridMinMax = function(key) {
|
||||
$scope.panel.grid[key] = _.toggle($scope.panel.grid[key], null, 0);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
$timeout($scope.render);
|
||||
@@ -489,275 +448,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
});
|
||||
|
||||
module.directive('histogramChart', function(filterSrv, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot;
|
||||
var hiddenData = {};
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if ($rootScope.fullscreen && !scope.fullscreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, alias) {
|
||||
if (hiddenData[alias]) {
|
||||
data.push(hiddenData[alias]);
|
||||
delete hiddenData[alias];
|
||||
}
|
||||
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, d) {
|
||||
data = d || data;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
elem.css({ height: scope.height || scope.panel.height || scope.row.height });
|
||||
} catch(e) { return; }
|
||||
|
||||
_.each(data, function(series) {
|
||||
series.label = series.info.alias;
|
||||
series.color = series.info.color;
|
||||
});
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.panel.interval);
|
||||
|
||||
var stack = scope.panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: scope.panel.stack ? scope.panel.percentage : false,
|
||||
stack: scope.panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: scope.panel.lines,
|
||||
// Silly, but fixes bug in stacked percentages
|
||||
fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
|
||||
lineWidth: scope.panel.linewidth,
|
||||
steps: scope.panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: scope.panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: scope.panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: scope.panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {
|
||||
timezone: scope.panel.timezone,
|
||||
show: scope.panel['x-axis'],
|
||||
mode: "time",
|
||||
min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
|
||||
max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
|
||||
timeformat: time_format(scope.panel.interval),
|
||||
label: "Datetime",
|
||||
ticks: elem.width()/100
|
||||
},
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
}
|
||||
};
|
||||
|
||||
if(scope.panel.annotate.enable) {
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: scope.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
icon: {
|
||||
icon: "icon-tag icon-flip-vertical",
|
||||
size: 20,
|
||||
color: "#222",
|
||||
outline: "#bbb"
|
||||
}
|
||||
}
|
||||
}
|
||||
//xaxis: int // the x axis to attach events to
|
||||
};
|
||||
}
|
||||
|
||||
if(scope.panel.interactive) {
|
||||
options.selection = { mode: "x", color: '#666' };
|
||||
}
|
||||
|
||||
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
|
||||
// so that the stacking happens in the proper order
|
||||
var required_times = [];
|
||||
if (data.length > 1) {
|
||||
required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
|
||||
return query.time_series.getOrderedTimes();
|
||||
}));
|
||||
required_times = _.uniq(required_times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a-b;
|
||||
}), true);
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].time_series.getFlotPairs(required_times, scope.panel.nullPointMode);
|
||||
data[i].yaxis = data[i].info.yaxis;
|
||||
data[i].data = _d;
|
||||
data[i].info.y_format = data[i].yaxis === 1 ? scope.panel.y_format : scope.panel.y2_format;
|
||||
}
|
||||
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
plot = $.plot(elem, data, options);
|
||||
|
||||
if (scope.panel.leftYAxisLabel) {
|
||||
elem.css('margin-left', '10px');
|
||||
var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
|
||||
.text(scope.panel.leftYAxisLabel)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
|
||||
} else if (elem.css('margin-left')) {
|
||||
elem.css('margin-left', '');
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options)
|
||||
{
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.min,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.findWhere(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.position = 'right';
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], scope.panel.y2_format);
|
||||
}
|
||||
|
||||
configureAxisMode(options.yaxes[0], scope.panel.y_format);
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format === 'bytes') {
|
||||
axis.mode = "byte";
|
||||
}
|
||||
if (format === 'short') {
|
||||
axis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val,0);
|
||||
};
|
||||
}
|
||||
if (format === 'ms') {
|
||||
axis.tickFormatter = kbn.msFormat;
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval) {
|
||||
var _int = kbn.interval_to_seconds(interval);
|
||||
if(_int >= 2628000) {
|
||||
return "%Y-%m";
|
||||
}
|
||||
if(_int >= 10000) {
|
||||
return "%Y-%m-%d";
|
||||
}
|
||||
if(_int >= 60) {
|
||||
return "%H:%M<br>%m-%d";
|
||||
}
|
||||
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp;
|
||||
if (item) {
|
||||
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(item.series.info.alias || item.series.info.query)+
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
|
||||
item.datapoint[1] - item.datapoint[2] :
|
||||
item.datapoint[1];
|
||||
if(item.series.info.y_format === 'bytes') {
|
||||
value = kbn.byteFormat(value,2);
|
||||
}
|
||||
if(item.series.info.y_format === 'short') {
|
||||
value = kbn.shortFormat(value,2);
|
||||
}
|
||||
if(item.series.info.y_format === 'ms') {
|
||||
value = kbn.msFormat(value);
|
||||
}
|
||||
timestamp = scope.panel.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
filterSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
@@ -10,31 +12,8 @@
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Selectable</label><input type="checkbox" ng-model="panel.interactive" ng-checked="panel.interactive">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">xAxis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">yAxis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="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>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Left Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_format" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y2_format" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Line options</h5>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
@@ -45,12 +24,16 @@
|
||||
<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>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stepped lines</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
@@ -67,32 +50,17 @@
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Legend<h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Legend</label><input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Query <tip>If no alias is set, show the query in the legend</tip></label><input type="checkbox" ng-model="panel.show_query" ng-checked="panel.show_query">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Counts</label><input type="checkbox" ng-model="panel.legend_counts" ng-checked="panel.legend_counts">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid<h5>
|
||||
<h5>Rendering</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a href='' ng-click="panel.grid.min = _.toggle(panel.grid.min,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.min"/>
|
||||
<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()" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ref='' ng-click="panel.grid.max = _.toggle(panel.grid.max,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.max"/>
|
||||
<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()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,229 +1,61 @@
|
||||
define([
|
||||
'underscore',
|
||||
'./interval'
|
||||
'underscore'
|
||||
],
|
||||
function (_, Interval) {
|
||||
function (_) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
// map compatable parseInt
|
||||
function base10Int(val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
// trim the ms off of a time, but return it with empty ms.
|
||||
function getDatesTime(date) {
|
||||
return Math.floor(date.getTime() / 1000)*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain graphs require 0 entries to be specified for them to render
|
||||
* properly (like the line graph). So with this we will caluclate all of
|
||||
* the expected time measurements, and fill the missing ones in with 0
|
||||
* @param {object} opts An object specifying some/all of the options
|
||||
*
|
||||
* OPTIONS:
|
||||
* @opt {string} interval The interval notion describing the expected spacing between
|
||||
* each data point.
|
||||
* @opt {date} start_date (optional) The start point for the time series, setting this and the
|
||||
* end_date will ensure that the series streches to resemble the entire
|
||||
* expected result
|
||||
* @opt {date} end_date (optional) The end point for the time series, see start_date
|
||||
*/
|
||||
ts.ZeroFilled = function (opts) {
|
||||
opts = _.defaults(opts, {
|
||||
interval: '10m',
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
});
|
||||
|
||||
// the expected differenece between readings.
|
||||
this.interval = new Interval(opts.interval);
|
||||
|
||||
// will keep all values here, keyed by their time
|
||||
this._data = {};
|
||||
this.start_time = opts.start_date && getDatesTime(opts.start_date);
|
||||
this.end_time = opts.end_date && getDatesTime(opts.end_date);
|
||||
this.opts = opts;
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
this.color = opts.info.color;
|
||||
this.yaxis = opts.info.yaxis;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a row
|
||||
* @param {int} time The time for the value, in
|
||||
* @param {any} value The value at this time
|
||||
*/
|
||||
ts.ZeroFilled.prototype.addValue = function (time, value) {
|
||||
if (time instanceof Date) {
|
||||
time = getDatesTime(time);
|
||||
} else {
|
||||
time = base10Int(time);
|
||||
}
|
||||
if (!isNaN(time)) {
|
||||
this._data[time] = (_.isUndefined(value) ? 0 : value);
|
||||
}
|
||||
this._cached_times = null;
|
||||
};
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle) {
|
||||
var result = [];
|
||||
|
||||
/**
|
||||
* Get an array of the times that have been explicitly set in the series
|
||||
* @param {array} include (optional) list of timestamps to include in the response
|
||||
* @return {array} An array of integer times.
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
|
||||
var times = _.map(_.keys(this._data), base10Int);
|
||||
if (_.isArray(include)) {
|
||||
times = times.concat(include);
|
||||
}
|
||||
return _.uniq(times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a - b;
|
||||
}), true);
|
||||
};
|
||||
this.info.total = 0;
|
||||
this.info.max = null;
|
||||
this.info.min = 212312321312;
|
||||
|
||||
/**
|
||||
* return the rows in the format:
|
||||
* [ [time, value], [time, value], ... ]
|
||||
*
|
||||
* Heavy lifting is done by _get(Min|Default|All)FlotPairs()
|
||||
* @param {array} required_times An array of timestamps that must be in the resulting pairs
|
||||
* @return {array}
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (required_times, fillStyle) {
|
||||
var times = this.getOrderedTimes(required_times),
|
||||
strategy,
|
||||
pairs;
|
||||
|
||||
if(fillStyle === 'null as zero') {
|
||||
strategy = this._getAllFlotPairs;
|
||||
} else if(fillStyle === 'null') {
|
||||
strategy = this._getNullFlotPairs;
|
||||
} else if(fillStyle === 'connected') {
|
||||
strategy = this._getNoZeroFlotPairs;
|
||||
} else {
|
||||
strategy = this._getMinFlotPairs;
|
||||
}
|
||||
|
||||
pairs = _.reduce(
|
||||
times, // what
|
||||
strategy, // how
|
||||
[], // where
|
||||
this // context
|
||||
);
|
||||
|
||||
// if the first or last pair is inside either the start or end time,
|
||||
// add those times to the series with null values so the graph will stretch to contain them.
|
||||
// Removing, flot 0.8.1's max/min params satisfy this
|
||||
/*
|
||||
if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
|
||||
pairs.unshift([this.start_time, null]);
|
||||
}
|
||||
if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
|
||||
pairs.push([this.end_time, null]);
|
||||
}
|
||||
*/
|
||||
|
||||
return pairs;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's on either side of the current time, unless there is already a measurement there or
|
||||
* we are looking at an edge.
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, 0]);
|
||||
_.each(this.datapoints, function(valueArray) {
|
||||
var currentTime = valueArray[1];
|
||||
var currentValue = valueArray[0];
|
||||
if (currentValue === null) {
|
||||
if (fillStyle === 'connected') {
|
||||
return;
|
||||
}
|
||||
if (fillStyle === 'null as zero') {
|
||||
currentValue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || 0]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, 0]);
|
||||
if (_.isNumber(currentValue)) {
|
||||
this.info.total += currentValue;
|
||||
}
|
||||
|
||||
if (currentValue > this.info.max) {
|
||||
this.info.max = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue < this.info.min) {
|
||||
this.info.min = currentValue;
|
||||
}
|
||||
|
||||
result.push([currentTime * 1000, currentValue]);
|
||||
}, this);
|
||||
|
||||
if (result.length) {
|
||||
this.info.avg = (this.info.total / result.length).toFixed(2);
|
||||
this.info.current = result[result.length-1][1];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
|
||||
* last measurement
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next;
|
||||
|
||||
result.push([ times[i], this._data[times[i]] || 0 ]);
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Same as min, but fills with nulls
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, null]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || null]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, null]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Not fill zero's on either side of the current time, only the current time
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNoZeroFlotPairs = function (result, time) {
|
||||
|
||||
// add the current time
|
||||
if(this._data[time]){
|
||||
result.push([ time, this._data[time]]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return ts;
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="pull-right editor-title">Dashboard settings</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
|
||||
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Metrics', 'Import']" data-title="{{tab}}">
|
||||
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Import']" data-title="{{tab}}">
|
||||
</div>
|
||||
<div ng-repeat="tab in dashboard.current.nav" data-title="{{tab.type}}">
|
||||
</div>
|
||||
@@ -18,7 +18,8 @@
|
||||
<label class="small">Style</label><select class="input-small" ng-model="dashboard.current.style" ng-options="f for f in ['dark','light']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Editable </label><input type="checkbox" ng-model="dashboard.current.editable" ng-checked="dashboard.current.editable" />
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="dashboard.current.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Hints <tip>Show 'Add panel' hints in empty spaces</tip></label><input type="checkbox" ng-model="dashboard.current.panel_hints" ng-checked="dashboard.current.panel_hints" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div ng-controller="GraphiteImportCtrl" ng-init="init()">
|
||||
<h5>Import dashboards from graphite webb</h5>
|
||||
<h5>Import dashboards from graphite web</h5>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
|
||||
@@ -19,12 +19,24 @@
|
||||
<ul class="dropdown-menu" id="grafana-search">
|
||||
<li ng-if="!showImport">
|
||||
<div class="grafana-search-panel">
|
||||
<input type="text"
|
||||
placeholder="search dashboards, metrics, or graphs"
|
||||
xng-focus="giveSearchFocus"
|
||||
ng-keydown="keyDown($event)"
|
||||
ng-model="elasticsearch.query"
|
||||
ng-change="elasticsearch_dblist(elasticsearch.query)" />
|
||||
<div class="search-field-wrapper">
|
||||
<button class="btn btn-success pull-right" ng-click="toggleImport($event)">
|
||||
<i class="icon-download-alt"></i>
|
||||
Import
|
||||
</button>
|
||||
<button class="btn btn-success pull-right" ng-click="newDashboard()">
|
||||
<i class="icon-th-large"></i>
|
||||
New
|
||||
</button>
|
||||
<span>
|
||||
<input type="text"
|
||||
placeholder="search dashboards, metrics, or graphs"
|
||||
xng-focus="giveSearchFocus"
|
||||
ng-keydown="keyDown($event)"
|
||||
ng-model="elasticsearch.query"
|
||||
ng-change="elasticsearch_dblist(elasticsearch.query)" />
|
||||
</span>
|
||||
</div>
|
||||
<h6 ng-hide="search_results.dashboards.length || search_results.metrics.length">No dashboards or metrics matching your query found</h6>
|
||||
<table class="table table-condensed table-striped">
|
||||
<tr bindonce ng-repeat="row in search_results.metrics"
|
||||
@@ -52,9 +64,6 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="pull-right" style="margin: 5px;">
|
||||
<a ng-click="toggleImport($event)">Import</a>
|
||||
</li>
|
||||
<!-- ng-show="dashboard.current.loader.load_gist || dashboard.current.loader.load_local" -->
|
||||
<li ng-if="showImport" style="margin: 20px;">
|
||||
<div class="editor-row">
|
||||
|
||||
@@ -22,6 +22,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
var _dash = {
|
||||
title: "",
|
||||
style: "dark",
|
||||
timezone: 'browser',
|
||||
editable: true,
|
||||
failover: false,
|
||||
panel_hints: true,
|
||||
@@ -47,7 +48,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
};
|
||||
|
||||
// An elasticJS client to use
|
||||
var ejs = ejsResource(config.elasticsearch);
|
||||
var ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
|
||||
var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
|
||||
// Store a reference to this
|
||||
@@ -101,8 +102,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
$location.path(config.default_route);
|
||||
alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+
|
||||
' Click <a href="#/dashboard/local/deprecated">here</a> to load your old dashboard anyway.');
|
||||
} else if(!(_.isUndefined(window.localStorage.kibanaDashboardDefault))) {
|
||||
$location.path(window.localStorage.kibanaDashboardDefault);
|
||||
} else if(!(_.isUndefined(window.localStorage.grafanaDashboardDefault))) {
|
||||
$location.path(window.localStorage.grafanaDashboardDefault);
|
||||
} else {
|
||||
$location.path(config.default_route);
|
||||
}
|
||||
@@ -196,7 +197,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
if(!_.isUndefined(window.localStorage['dashboard'])) {
|
||||
delete window.localStorage['dashboard'];
|
||||
}
|
||||
window.localStorage.kibanaDashboardDefault = route;
|
||||
window.localStorage.grafanaDashboardDefault = route;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -210,7 +211,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
|
||||
delete window.localStorage['dashboard'];
|
||||
}
|
||||
delete window.localStorage.kibanaDashboardDefault;
|
||||
delete window.localStorage.grafanaDashboardDefault;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -286,13 +287,21 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
};
|
||||
|
||||
this.elasticsearch_load = function(type,id) {
|
||||
return $http({
|
||||
var options = {
|
||||
url: config.elasticsearch + "/" + config.grafana_index + "/"+type+"/"+id+'?' + new Date().getTime(),
|
||||
method: "GET",
|
||||
transformResponse: function(response) {
|
||||
return renderTemplate(angular.fromJson(response)._source.dashboard, $routeParams);
|
||||
}
|
||||
}).error(function(data, status) {
|
||||
};
|
||||
if (config.elasticsearchBasicAuth) {
|
||||
options.withCredentials = true;
|
||||
options.headers = {
|
||||
"Authorization": "Basic " + config.elasticsearchBasicAuth
|
||||
};
|
||||
}
|
||||
return $http(options)
|
||||
.error(function(data, status) {
|
||||
if(status === 0) {
|
||||
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
||||
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
||||
@@ -377,10 +386,14 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
);
|
||||
};
|
||||
|
||||
this.elasticsearch_list = function(query,count) {
|
||||
this.elasticsearch_list = function(query, count) {
|
||||
var request = ejs.Request().indices(config.grafana_index).types('dashboard');
|
||||
// if elasticsearch has disabled _all field we need
|
||||
// need to specifiy field here
|
||||
var q = 'title:' + (query || '*');
|
||||
|
||||
return request.query(
|
||||
ejs.QueryStringQuery(query || '*')
|
||||
ejs.QueryStringQuery(q)
|
||||
).size(count).doSearch(
|
||||
// Success
|
||||
function(result) {
|
||||
|
||||
@@ -101,6 +101,11 @@ function (_) {
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByName',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'aliasByMetric',
|
||||
category: categories.Special,
|
||||
@@ -138,10 +143,17 @@ function (_) {
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'derivate',
|
||||
name: 'derivative',
|
||||
category: categories.Transform,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'nonNegativeDerivative',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "max value or 0", type: "int", } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'timeShift',
|
||||
category: categories.Transform,
|
||||
@@ -175,6 +187,13 @@ function (_) {
|
||||
defaultParams: [25]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'highestCurrent',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "count", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
_.each(categories, function(funcList, catName) {
|
||||
categories[catName] = _.sortBy(funcList, 'name');
|
||||
});
|
||||
@@ -229,4 +248,4 @@ function (_) {
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,14 +3,16 @@ define([
|
||||
'underscore',
|
||||
'jquery',
|
||||
'config',
|
||||
'kbn'
|
||||
'kbn',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, $, config, kbn) {
|
||||
function (angular, _, $, config, kbn, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.services');
|
||||
|
||||
module.service('graphiteSrv', function($http, $q, filterSrv) {
|
||||
var graphiteRenderUrl = config.graphiteUrl + "/render";
|
||||
|
||||
this.query = function(options) {
|
||||
try {
|
||||
@@ -18,14 +20,19 @@ function (angular, _, $, config, kbn) {
|
||||
from: this.translateTime(options.range.from),
|
||||
until: this.translateTime(options.range.to),
|
||||
targets: options.targets,
|
||||
renderer: options.renderer,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
};
|
||||
|
||||
var params = buildGraphitePostParams(graphOptions);
|
||||
var params = buildGraphiteParams(graphOptions);
|
||||
|
||||
if (options.renderer === 'png') {
|
||||
return $q.when(graphiteRenderUrl + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
return doGraphiteRequest({
|
||||
method: 'POST',
|
||||
url: config.graphiteUrl + '/render/',
|
||||
url: graphiteRenderUrl,
|
||||
data: params.join('&'),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
@@ -42,7 +49,7 @@ function (angular, _, $, config, kbn) {
|
||||
if (date === 'now') {
|
||||
return 'now';
|
||||
}
|
||||
else if (date.indexOf('now') > 0) {
|
||||
else if (date.indexOf('now') >= 0) {
|
||||
date = date.substring(3);
|
||||
date = date.replace('m', 'min');
|
||||
date = date.replace('M', 'mon');
|
||||
@@ -52,7 +59,13 @@ function (angular, _, $, config, kbn) {
|
||||
date = kbn.parseDate(date);
|
||||
}
|
||||
|
||||
return $.plot.formatDate(date, '%H%:%M_%Y%m%d');
|
||||
date = moment.utc(date).local();
|
||||
|
||||
if (config.timezoneOffset) {
|
||||
date = date.zone(config.timezoneOffset);
|
||||
}
|
||||
|
||||
return date.format('HH:mm_YYYYMMDD');
|
||||
};
|
||||
|
||||
this.match = function(targets, graphiteTargetStr) {
|
||||
@@ -115,11 +128,13 @@ function (angular, _, $, config, kbn) {
|
||||
return $http(options);
|
||||
}
|
||||
|
||||
function buildGraphitePostParams(options) {
|
||||
function buildGraphiteParams(options) {
|
||||
var clean_options = [];
|
||||
var graphite_options = ['target', 'targets', 'from', 'until', 'rawData', 'format', 'maxDataPoints'];
|
||||
|
||||
options['format'] = 'json';
|
||||
if (options.renderer !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
$.each(options, function (key, value) {
|
||||
if ($.inArray(key, graphite_options) === -1) {
|
||||
@@ -143,4 +158,4 @@ function (angular, _, $, config, kbn) {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,6 +116,7 @@ define([
|
||||
|
||||
for (var i = 0; i < 128; i++) {
|
||||
identifierStartTable[i] =
|
||||
i >= 48 && i <= 57 || // 0-9
|
||||
i === 36 || // $
|
||||
i >= 65 && i <= 90 || // A-Z
|
||||
i === 95 || // _
|
||||
@@ -183,10 +184,10 @@ define([
|
||||
}
|
||||
|
||||
match =
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence() ||
|
||||
this.scanPunctuator() ||
|
||||
this.scanNumericLiteral();
|
||||
this.scanNumericLiteral() ||
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence();
|
||||
|
||||
if (match) {
|
||||
this.skip(match.value.length);
|
||||
@@ -402,7 +403,6 @@ define([
|
||||
}
|
||||
|
||||
// Numbers must start either with a decimal digit or a point.
|
||||
|
||||
if (char !== "." && !isDecimalDigit(char)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ define([
|
||||
return curly;
|
||||
}
|
||||
|
||||
if (this.match('identifier')) {
|
||||
if (this.match('identifier') || this.match('number')) {
|
||||
return {
|
||||
type: 'segment',
|
||||
value: this.consumeToken().value
|
||||
@@ -97,7 +97,7 @@ define([
|
||||
},
|
||||
|
||||
metricExpression: function() {
|
||||
if (!this.match('templateStart') && !this.match('identifier')) {
|
||||
if (!this.match('templateStart') && !this.match('identifier') && !this.match('number')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -153,8 +153,8 @@ define([
|
||||
|
||||
var param =
|
||||
this.functionCall() ||
|
||||
this.metricExpression() ||
|
||||
this.numericLiteral() ||
|
||||
this.metricExpression() ||
|
||||
this.stringLiteral();
|
||||
|
||||
if (!this.match(',')) {
|
||||
|
||||
@@ -9,17 +9,29 @@ function (Settings) {
|
||||
|
||||
return new Settings({
|
||||
|
||||
/**
|
||||
* elasticsearch url:
|
||||
* For Basic authentication use: http://username:password@domain.com:9200
|
||||
*/
|
||||
elasticsearch: "http://"+window.location.hostname+":9200",
|
||||
|
||||
/**
|
||||
* graphite-web url:
|
||||
* For Basic authentication use: http://username:password@domain.com
|
||||
* Basic authentication requires special nginx or apache2 headers for cross origin comain to work
|
||||
* Basic authentication requires special HTTP headers to be configured
|
||||
* in nginx or apache for cross origin domain sharing to work (CORS).
|
||||
* Check install documentation on github
|
||||
*/
|
||||
graphiteUrl: "http://"+window.location.hostname+":8080",
|
||||
|
||||
default_route: '/dashboard/file/default.json',
|
||||
|
||||
/**
|
||||
* If your graphite server has another timezone than you & users browsers specify the offset here
|
||||
* Example: "-0500" (for UTC - 5 hours)
|
||||
*/
|
||||
timezoneOffset: null,
|
||||
|
||||
grafana_index: "grafana-dash",
|
||||
|
||||
panel_names: [
|
||||
|
||||
2
src/css/bootstrap.dark.min.css
vendored
2
src/css/bootstrap.dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
src/css/bootstrap.light.min.css
vendored
2
src/css/bootstrap.light.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -31,21 +31,25 @@ define([
|
||||
expect(tokens[6].value).to.be('second');
|
||||
});
|
||||
|
||||
|
||||
it('should tokenize functions and args', function() {
|
||||
var lexer = new Lexer("sum(metric.test, 12, 'test')");
|
||||
it('should tokenize metric expression with number segments', function() {
|
||||
var lexer = new Lexer("metric.10.12_10.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('sum');
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[1].value).to.be('(');
|
||||
expect(tokens[1].type).to.be('(');
|
||||
expect(tokens[5].type).to.be(',');
|
||||
expect(tokens[5].value).to.be(',');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('10');
|
||||
expect(tokens[4].value).to.be('12_10');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize func call with numbered metric and number arg', function() {
|
||||
var lexer = new Lexer("scale(metric.10, 15)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[4].value).to.be('10');
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
expect(tokens[6].value).to.be('12');
|
||||
expect(tokens[8].type).to.be('string');
|
||||
expect(tokens[8].value).to.be('test');
|
||||
expect(tokens[tokens.length - 1].value).to.be(')');
|
||||
});
|
||||
|
||||
it('should tokenize metric with template parameter', function() {
|
||||
|
||||
@@ -13,6 +13,17 @@ define([
|
||||
expect(rootNode.segments[0].value).to.be('metric');
|
||||
});
|
||||
|
||||
it('simple metric expression with numbers in segments', function() {
|
||||
var parser = new Parser('metric.10.15_20.5');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(4);
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('15_20');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces', function() {
|
||||
var parser = new Parser('metric.se1-{count, max}');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
17
src/vendor/bootstrap/less/grafana.less
vendored
17
src/vendor/bootstrap/less/grafana.less
vendored
@@ -12,10 +12,19 @@
|
||||
|
||||
.grafana-search-panel {
|
||||
padding: 6px 10px;
|
||||
input {
|
||||
width: 100%;
|
||||
.box-sizing(border-box);
|
||||
padding: 15px;
|
||||
|
||||
.search-field-wrapper {
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
margin: 0 2px 0 0;
|
||||
}
|
||||
> span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.selected td, tr.selected:nth-child(odd)>td {
|
||||
|
||||
4
src/vendor/bootstrap/less/overrides.less
vendored
4
src/vendor/bootstrap/less/overrides.less
vendored
@@ -103,7 +103,7 @@ code, pre {
|
||||
|
||||
.panel div.panel-extra {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.panel div.panel-extra .extra {
|
||||
@@ -195,7 +195,7 @@ form input.ng-invalid {
|
||||
}
|
||||
|
||||
.kibana-row {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.row-tab {
|
||||
|
||||
@@ -13,7 +13,7 @@ be injected into your angular controllers.
|
||||
angular.module('elasticjs.service', [])
|
||||
.factory('ejsResource', ['$http', function ($http) {
|
||||
|
||||
return function (config) {
|
||||
return function (config, basicAuth) {
|
||||
|
||||
var
|
||||
|
||||
@@ -43,6 +43,12 @@ angular.module('elasticjs.service', [])
|
||||
config.server = '';
|
||||
}
|
||||
|
||||
// set authentication header
|
||||
if (basicAuth || config.basicAuth) {
|
||||
config.headers = angular.extend( config.headers||{}, {
|
||||
"Authorization": "Basic " + (basicAuth||config.basicAuth)
|
||||
});
|
||||
}
|
||||
/* implement the elastic.js client interface for angular */
|
||||
ejs.client = {
|
||||
server: function (s) {
|
||||
|
||||
1194
src/vendor/moment.js
vendored
1194
src/vendor/moment.js
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user