mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into feature/influxdb-add-where-conditions
Conflicts: src/app/partials/influxdb/editor.html src/app/services/influxdb/influxdbDatasource.js
This commit is contained in:
commit
f98db943a5
66
CHANGELOG.md
66
CHANGELOG.md
@ -1,5 +1,45 @@
|
||||
# 1.5.0 (2013-03-09)
|
||||
###New Features and improvements
|
||||
# vNext
|
||||
- InfluxDB enhancement: support for multiple hosts (with retries) and raw queries (Issue #318, thx @toddboom)
|
||||
- Added rounding for graphites from and to time range filters
|
||||
for very short absolute ranges (Issue #320)
|
||||
|
||||
# 1.5.3 (2014-04-17)
|
||||
- Add support for async scripted dashboards (Issue #274)
|
||||
- Text panel now accepts html (for links to other dashboards, etc) (Issue #236)
|
||||
- Fix for Text panel, now changes take effect directly (Issue #251)
|
||||
- Fix when adding functions without params that did not cause graph to update (Issue #267)
|
||||
- Graphite errors are now much easier to see and troubleshoot with the new inspector (Issue #265)
|
||||
- Use influxdb aliases to distinguish between multiple columns (Issue #283)
|
||||
- Correction to ms axis formater, now formats days correctly. (Issue #189)
|
||||
- Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode (Issue #106)
|
||||
- Browser page title is now Grafana - {{dashboard title}} (Issue #294)
|
||||
- Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative (Issue #282)
|
||||
- More graphite functions
|
||||
|
||||
# 1.5.2 (2014-03-24)
|
||||
### New Features and improvements
|
||||
- Support for second optional params for functions like aliasByNode (Issue #167). Read the wiki on the [Function Editor](https://github.com/torkelo/grafana/wiki/Graphite-Function-Editor) for more info.
|
||||
- More functions added to InfluxDB query editor (Issue #218)
|
||||
- Filters can now be used inside other filters (templated segments) (Issue #128)
|
||||
- More graphite functions added
|
||||
|
||||
### Fixes
|
||||
- Float arguments now work for functions like scale (Issue #223)
|
||||
- Fix for graphite function editor, the graph & target was not updated after adding a function and leaving default params as is #191
|
||||
|
||||
The zip files now contains a sub folder with project name and version prefix. (Issue #209)
|
||||
|
||||
# 1.5.1 (2014-03-10)
|
||||
### Fixes
|
||||
- maxDataPoints must be an integer #184 (thanks @frejsoya for fixing this)
|
||||
|
||||
For people who are find Grafana slow for large time spans or high resolution metrics. This is most likely due to graphite returning a large number of datapoints. The maxDataPoints parameter solves this issue. For maxDataPoints to work you need to run the latest graphite-web (some builds of 0.9.12 does not include this feature).
|
||||
|
||||
Read this for more info:
|
||||
[Performance for large time spans](https://github.com/torkelo/grafana/wiki/Performance-for-large-time-spans)
|
||||
|
||||
# 1.5.0 (2014-03-09)
|
||||
### New Features and improvements
|
||||
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178)
|
||||
- Links to function documentation from function editor (Issue #3)
|
||||
- Reorder functions (Issue #130)
|
||||
@ -17,8 +57,8 @@
|
||||
- Basic Auth fix (Issue #152)
|
||||
- Fix to annotations with graphite source & null values (Issue #138)
|
||||
|
||||
# 1.4.0 (2013-02-21)
|
||||
###New Features
|
||||
# 1.4.0 (2014-02-21)
|
||||
### New Features
|
||||
- #44 Annotations! Required a lot of work to get right. Read wiki article for more info. Supported annotations data sources are graphite metrics and graphite events. Support for more will be added in the future!
|
||||
- #35 Support for multiple graphite servers! (Read wiki article for more)
|
||||
- #116 Back to dashboard link in top menu to easily exist full screen / edit mode.
|
||||
@ -35,7 +75,7 @@
|
||||
- #104 Improvement to graphite target editor, select wildcard now gives you a "select metric" link for the next node.
|
||||
- #105 Added zero as a possible node value in groupByAlias function
|
||||
|
||||
# 1.3.0 (2013-02-13)
|
||||
# 1.3.0 (2014-02-13)
|
||||
### New features or improvements
|
||||
- #86 Dashboard tags and search (see wiki article for details)
|
||||
- #54 Enhancement to filter / template. "Include All" improvement
|
||||
@ -48,7 +88,7 @@
|
||||
- #85 Added all parameters to summarize function
|
||||
- #83 Stack as percent should now work a lot better!
|
||||
|
||||
# 1.2.0 (2013-02-10)
|
||||
# 1.2.0 (2014-02-10)
|
||||
### New features
|
||||
- #70 Grid Thresholds (warning and error regions or lines in graph)
|
||||
- #72 Added an example of a scripted dashboard and a short wiki article documenting scripted dashboards.
|
||||
@ -62,7 +102,7 @@
|
||||
- #67 Allow decimal input for scale function
|
||||
- #68 Bug when trying to open dashboard while in edit mode
|
||||
|
||||
# 1.1.0 (2013-02-06)
|
||||
# 1.1.0 (2014-02-06)
|
||||
### New features:
|
||||
|
||||
- #22 Support for native graphite png renderer, does not support click and select zoom yet
|
||||
@ -80,24 +120,24 @@
|
||||
|
||||
Thanks to everyone who contributed fixes and provided feedback :+1:
|
||||
|
||||
# 1.0.4 (2013-01-24)
|
||||
# 1.0.4 (2014-01-24)
|
||||
- Fixes #28 - Relative time range caused 500 graphite error in some cases (thx rsommer for the fix)
|
||||
|
||||
# 1.0.3 (2013-01-23)
|
||||
# 1.0.3 (2014-01-23)
|
||||
- #9 Add Y-axis format for milliseconds
|
||||
- #16 Add support for Basic Auth (use http://username:password@yourgraphitedomain.com)
|
||||
- #13 Relative time ranges now uses relative time ranges when issuing graphite query
|
||||
|
||||
# 1.0.2 (2013-01-21)
|
||||
# 1.0.2 (2014-01-21)
|
||||
- Fixes #12, should now work ok without ElasticSearch
|
||||
|
||||
# 1.0.1 (2013-01-21)
|
||||
# 1.0.1 (2014-01-21)
|
||||
- Resize fix
|
||||
- Improvements to drag & drop
|
||||
- Added a few graphite function definitions
|
||||
- Fixed duplicate panel bug
|
||||
- Updated default dashboard with welcome message and randomWalk graph
|
||||
|
||||
# 1.0.0 (2013-01-19)
|
||||
# 1.0.0 (2014-01-19)
|
||||
|
||||
First public release
|
||||
First public release
|
||||
|
10
README.md
10
README.md
@ -46,7 +46,7 @@ Grafana is very easy to install. It is a client side web app with no backend. An
|
||||
|
||||
# Installation
|
||||
- Download and extract the [latest release](https://github.com/torkelo/grafana/releases).
|
||||
- Edit config.js, then change graphiteUrl and elasticsearch to point to the correct urls. The urls entered here must be reachable by your browser.
|
||||
- Rename `config.sample.js` to `config.js`, then change `graphiteUrl` and `elasticsearch` to point to the correct urls. The urls entered here must be reachable by your browser.
|
||||
- Point your browser to the installation.
|
||||
|
||||
To run from master:
|
||||
@ -54,7 +54,9 @@ To run from master:
|
||||
- Start a web server in src folder
|
||||
- Or create a optimized & minified build:
|
||||
- npm install (requires nodejs)
|
||||
- grunt build
|
||||
- grunt build (requires grunt-cli)
|
||||
|
||||
If you use ansible for provisioning and deployment [ansible-grafana](https://github.com/bobrik/ansible-grafana) should get you started.
|
||||
|
||||
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 and/or watch [this video](https://www.youtube.com/watch?v=OUvJamHeMpw) for a guide in creating a new dashboard and for creating
|
||||
@ -67,6 +69,7 @@ Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Methods "GET, OPTIONS"
|
||||
Header set Access-Control-Allow-Headers "origin, authorization, accept"
|
||||
```
|
||||
Note that using "\*" leaves your graphite instance quite open so you might want to consider using "http://my.graphite-dom.ain" in place of "\*"
|
||||
|
||||
If your Graphite web is proteced by basic authentication, you have to enable the HTTP verb OPTIONS, origin
|
||||
(no wildcards are allowed in this case) and add Access-Control-Allow-Credentials. This looks like the following for Apache:
|
||||
@ -92,7 +95,6 @@ Header set Access-Control-Allow-Credentials true
|
||||
- Use elasticsearch to search for metrics
|
||||
- Improve template support
|
||||
- Annotate graph by querying ElasticSearch for events (or other event sources)
|
||||
- Add support for other time series databases like InfluxDB
|
||||
|
||||
# Contribute
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about!
|
||||
@ -106,4 +108,4 @@ Clone repository:
|
||||
This software is based on the great log dashboard [kibana](https://github.com/elasticsearch/kibana).
|
||||
|
||||
# License
|
||||
Grafana is distributed under Apache 2.0 License.
|
||||
Grafana is distributed under Apache 2.0 License.
|
||||
|
@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.3",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
@ -13,13 +13,12 @@
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
"grunt": "~0.4.0",
|
||||
"grunt-ngmin": "0.0.3",
|
||||
"grunt-contrib": "~0.8.0",
|
||||
"grunt-contrib-less": "~0.7.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-git-describe": "~2.3.2",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-cssmin": "~0.6.1",
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-string-replace": "~0.2.4",
|
||||
"grunt-contrib-htmlmin": "~0.1.3",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
@ -44,7 +43,8 @@
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-mocha": "~0.1.1",
|
||||
"karma-expect": "~1.0.0",
|
||||
"grunt-cli": "~0.1.13"
|
||||
"grunt-cli": "~0.1.13",
|
||||
"jshint-stylish": "~0.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "0.10.x",
|
||||
|
@ -430,28 +430,75 @@ function($, _, moment) {
|
||||
ext = " B";
|
||||
break;
|
||||
case 1:
|
||||
ext = " KB";
|
||||
ext = " KiB";
|
||||
break;
|
||||
case 2:
|
||||
ext = " MB";
|
||||
ext = " MiB";
|
||||
break;
|
||||
case 3:
|
||||
ext = " GB";
|
||||
ext = " GiB";
|
||||
break;
|
||||
case 4:
|
||||
ext = " TB";
|
||||
ext = " TiB";
|
||||
break;
|
||||
case 5:
|
||||
ext = " PB";
|
||||
ext = " PiB";
|
||||
break;
|
||||
case 6:
|
||||
ext = " EB";
|
||||
ext = " EiB";
|
||||
break;
|
||||
case 7:
|
||||
ext = " ZB";
|
||||
ext = " ZiB";
|
||||
break;
|
||||
case 8:
|
||||
ext = " YB";
|
||||
ext = " YiB";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.bitFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " b";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kib";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mib";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gib";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tib";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pib";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Eib";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zib";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Yib";
|
||||
break;
|
||||
}
|
||||
|
||||
@ -515,6 +562,10 @@ function($, _, moment) {
|
||||
return function(val) {
|
||||
return kbn.byteFormat(val, decimals);
|
||||
};
|
||||
case 'bits':
|
||||
return function(val) {
|
||||
return kbn.bitFormat(val, decimals);
|
||||
};
|
||||
case 'ms':
|
||||
return function(val) {
|
||||
return kbn.msFormat(val, decimals);
|
||||
@ -546,18 +597,12 @@ function($, _, moment) {
|
||||
else if (size < 86400000) {
|
||||
return (size / 3600000).toFixed(decimals) + " hour";
|
||||
}
|
||||
// Less than one week, devide in days
|
||||
else if (size < 604800000) {
|
||||
// Less than one year, devide in days
|
||||
else if (size < 31536000000) {
|
||||
return (size / 86400000).toFixed(decimals) + " day";
|
||||
}
|
||||
// Less than one month, devide in weeks
|
||||
else if (size < 2.62974e9) {
|
||||
return (size / 604800000).toFixed(decimals) + " week";
|
||||
}
|
||||
// Less than one year, devide in weeks
|
||||
else if (size < 3.15569e10) {
|
||||
return (size / 2.62974e9).toFixed(decimals) + " year";
|
||||
}
|
||||
|
||||
return (size / 31536000000).toFixed(decimals) + " year";
|
||||
};
|
||||
|
||||
kbn.microsFormat = function(size, decimals) {
|
||||
|
@ -49,6 +49,11 @@ function (_, crypto) {
|
||||
return datasource;
|
||||
};
|
||||
|
||||
var parseMultipleHosts = function(datasource) {
|
||||
datasource.urls = _.map(datasource.url.split(","), function (url) { return url.trim(); });
|
||||
return datasource;
|
||||
};
|
||||
|
||||
if (options.graphiteUrl) {
|
||||
settings.datasources = {
|
||||
graphite: {
|
||||
@ -62,6 +67,7 @@ function (_, crypto) {
|
||||
_.each(settings.datasources, function(datasource, key) {
|
||||
datasource.name = key;
|
||||
parseBasicAuth(datasource);
|
||||
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
|
||||
});
|
||||
|
||||
var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
|
||||
|
@ -10,4 +10,5 @@ define([
|
||||
'./graphiteImport',
|
||||
'./influxTargetCtrl',
|
||||
'./playlistCtrl',
|
||||
], function () {});
|
||||
'./inspectCtrl',
|
||||
], function () {});
|
||||
|
@ -49,7 +49,7 @@ function (angular, _, moment) {
|
||||
|
||||
$scope.set_default = function() {
|
||||
if(dashboard.set_default($location.path())) {
|
||||
alertSrv.set('Home Set','This page has been set as your default Kibana dashboard','success',5000);
|
||||
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
}
|
||||
@ -57,7 +57,7 @@ function (angular, _, moment) {
|
||||
|
||||
$scope.purge_default = function() {
|
||||
if(dashboard.purge_default()) {
|
||||
alertSrv.set('Local Default Clear','Your Kibana default dashboard has been reset to the default',
|
||||
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default',
|
||||
'success',5000);
|
||||
} else {
|
||||
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
|
||||
|
@ -12,12 +12,25 @@ function (angular, app, _) {
|
||||
|
||||
$scope.init = function() {
|
||||
console.log('hej!');
|
||||
$scope.datasources = datasourceSrv.listOptions();
|
||||
$scope.setDatasource(null);
|
||||
};
|
||||
|
||||
|
||||
$scope.setDatasource = function(datasource) {
|
||||
$scope.datasource = datasourceSrv.get(datasource);
|
||||
|
||||
if (!$scope.datasource) {
|
||||
$scope.error = "Cannot find datasource " + datasource;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.listAll = function(query) {
|
||||
delete $scope.error;
|
||||
|
||||
datasourceSrv.default.listDashboards(query)
|
||||
$scope.datasource.listDashboards(query)
|
||||
.then(function(results) {
|
||||
$scope.dashboards = results;
|
||||
})
|
||||
@ -29,20 +42,20 @@ function (angular, app, _) {
|
||||
$scope.import = function(dashName) {
|
||||
delete $scope.error;
|
||||
|
||||
datasourceSrv.default.loadDashboard(dashName)
|
||||
$scope.datasource.loadDashboard(dashName)
|
||||
.then(function(results) {
|
||||
if (!results.data || !results.data.state) {
|
||||
throw { message: 'no dashboard state received from graphite' };
|
||||
}
|
||||
|
||||
graphiteToGrafanaTranslator(results.data.state);
|
||||
graphiteToGrafanaTranslator(results.data.state, $scope.datasource.name);
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.error = err.message || 'Failed to import dashboard';
|
||||
});
|
||||
};
|
||||
|
||||
function graphiteToGrafanaTranslator(state) {
|
||||
function graphiteToGrafanaTranslator(state, datasource) {
|
||||
var graphsPerRow = 2;
|
||||
var rowHeight = 300;
|
||||
var rowTemplate;
|
||||
@ -72,7 +85,8 @@ function (angular, app, _) {
|
||||
type: 'graphite',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: []
|
||||
targets: [],
|
||||
datasource: datasource
|
||||
};
|
||||
|
||||
_.each(graph[1].target, function(target) {
|
||||
|
@ -234,7 +234,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
$scope.moveAliasFuncLast();
|
||||
$scope.smartlyHandleNewAliasByNode(newFunc);
|
||||
|
||||
if (!funcDef.params && newFunc.added) {
|
||||
if (!funcDef.params.length && newFunc.added) {
|
||||
$scope.targetChanged();
|
||||
}
|
||||
};
|
||||
|
@ -15,12 +15,23 @@ function (angular) {
|
||||
$scope.target.function = 'mean';
|
||||
}
|
||||
|
||||
$scope.rawQuery = false;
|
||||
|
||||
$scope.functions = ['count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median', 'derivative', 'stddev', 'first', 'last'];
|
||||
$scope.oldSeries = $scope.target.series;
|
||||
$scope.$on('typeahead-updated', function(){
|
||||
$timeout($scope.get_data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showQuery = function () {
|
||||
$scope.target.rawQuery = true;
|
||||
};
|
||||
|
||||
$scope.hideQuery = function () {
|
||||
$scope.target.rawQuery = false;
|
||||
};
|
||||
|
||||
// Cannot use typeahead and ng-change on blur at the same time
|
||||
$scope.seriesBlur = function() {
|
||||
if ($scope.oldSeries !== $scope.target.series) {
|
||||
|
75
src/app/controllers/inspectCtrl.js
Normal file
75
src/app/controllers/inspectCtrl.js
Normal file
@ -0,0 +1,75 @@
|
||||
define([
|
||||
'angular'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('InspectCtrl', function($scope) {
|
||||
var model = $scope.inspector;
|
||||
|
||||
function getParametersFromQueryString(queryString) {
|
||||
var result = [];
|
||||
var parameters = queryString.split("&");
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
var keyValue = parameters[i].split("=");
|
||||
if (keyValue[1].length > 0) {
|
||||
result.push({ key: keyValue[0], value: window.unescape(keyValue[1]) });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.editor = { index: 0 };
|
||||
|
||||
if (!model.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.error.stack) {
|
||||
$scope.editor.index = 2;
|
||||
$scope.stack_trace = model.error.stack;
|
||||
$scope.message = model.error.message;
|
||||
}
|
||||
else if (model.error.config && model.error.config.data) {
|
||||
$scope.editor.index = 1;
|
||||
|
||||
$scope.request_parameters = getParametersFromQueryString(model.error.config.data);
|
||||
|
||||
if (model.error.data.indexOf('DOCTYPE') !== -1) {
|
||||
$scope.response_html = model.error.data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('iframeContent', function($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem, attrs) {
|
||||
var getter = $parse(attrs.iframeContent), value = getter($scope);
|
||||
|
||||
$scope.$on("$destroy",function() {
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.width = '100%';
|
||||
iframe.height = '400px';
|
||||
iframe.style.border = 'none';
|
||||
iframe.src = 'about:blank';
|
||||
elem.append(iframe);
|
||||
|
||||
iframe.contentWindow.document.open('text/html', 'replace');
|
||||
iframe.contentWindow.document.write(value);
|
||||
iframe.contentWindow.document.close();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
134
src/app/controllers/panelBaseCtrl.js
Normal file
134
src/app/controllers/panelBaseCtrl.js
Normal file
@ -0,0 +1,134 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery'
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
// This function needs $inject annotations, update below
|
||||
// when changing arguments to this function
|
||||
function PanelBaseCtrl($scope, $rootScope, $timeout) {
|
||||
|
||||
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.inspector = {};
|
||||
$scope.panelMeta.menu = _.where(menu, { condition: true });
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
});
|
||||
};
|
||||
|
||||
$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() {
|
||||
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;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({edit: true});
|
||||
};
|
||||
|
||||
$scope.toggleFullscreen = function() {
|
||||
if ($scope.fullscreen && !$scope.editMode) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({ edit: false });
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
PanelBaseCtrl['$inject'] = ['$scope', '$rootScope', '$timeout'];
|
||||
|
||||
return PanelBaseCtrl;
|
||||
|
||||
});
|
@ -124,6 +124,18 @@ function (angular, app, _) {
|
||||
*/
|
||||
type : type
|
||||
};
|
||||
|
||||
function fixRowHeight(height) {
|
||||
if (!height) {
|
||||
return '200px';
|
||||
}
|
||||
if (!_.isString(height)) {
|
||||
return height + 'px';
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
$scope.row.height = fixRowHeight($scope.row.height);
|
||||
};
|
||||
|
||||
/** @scratch /panels/2
|
||||
|
@ -5,18 +5,25 @@
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
* Return a dashboard object, or a function
|
||||
*
|
||||
* For async scripts, return a function, this function must take a single callback function as argument,
|
||||
* call this callback function with the dashboard object (look at scripted_async.js for an example)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// accessable variables in this scope
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, _d_timespan;
|
||||
var dashboard, timspan;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
_d_timespan = '1d';
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
@ -28,7 +35,7 @@ dashboard = {
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-"+(ARGS.from || _d_timespan),
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
}
|
||||
};
|
||||
@ -67,8 +74,7 @@ for (var i = 0; i < rows; i++) {
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Now return the object and we're good!
|
||||
|
||||
return dashboard;
|
81
src/app/dashboards/scripted_async.js
Normal file
81
src/app/dashboards/scripted_async.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* global _ */
|
||||
|
||||
/*
|
||||
* Complex scripted dashboard
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
* Global accessable variables
|
||||
* window, document, $, jQuery, ARGS, moment
|
||||
*
|
||||
* Return a dashboard object, or a function
|
||||
*
|
||||
* For async scripts, return a function, this function must take a single callback function,
|
||||
* call this function with the dasboard object
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// accessable variables in this scope
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
return function(callback) {
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
}
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
var seriesName = 'argName';
|
||||
|
||||
if(!_.isUndefined(ARGS.rows)) {
|
||||
rows = parseInt(ARGS.rows, 10);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ARGS.name)) {
|
||||
seriesName = ARGS.name;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: '/'
|
||||
})
|
||||
.done(function(result) {
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Chart',
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Async dashboard test',
|
||||
type: 'text',
|
||||
span: 12,
|
||||
fill: 1,
|
||||
content: '# Async test'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// when dashboard is composed call the callback
|
||||
// function and pass the dashboard
|
||||
callback(dashboard);
|
||||
|
||||
});
|
||||
}
|
@ -45,21 +45,45 @@ function (angular, $, kbn, moment, _) {
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
elem.css({ height: scope.height || scope.panel.height || scope.row.height });
|
||||
var height = scope.height || scope.panel.height || scope.row.height;
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height = height - 32; // subtract panel title bar
|
||||
|
||||
if (scope.panel.legend.show) {
|
||||
height = height - 21; // subtract one line legend
|
||||
}
|
||||
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
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; }
|
||||
function shouldAbortRender() {
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($rootScope.fullscreen && !scope.fullscreen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!setElementHeight()) { return true; }
|
||||
|
||||
if (_.isString(data)) {
|
||||
render_panel_as_graphite_png(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (shouldAbortRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -248,10 +272,7 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format === 'bytes') {
|
||||
axis.mode = 'byte';
|
||||
}
|
||||
else if (format !== 'none') {
|
||||
if (format !== 'none') {
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
}
|
||||
}
|
||||
@ -326,9 +347,9 @@ function (angular, $, kbn, moment, _) {
|
||||
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.legend.show ? '&hideLegend=false' : '&hideLegend=true';
|
||||
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
|
||||
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
|
||||
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
@ -336,6 +357,9 @@ function (angular, $, kbn, moment, _) {
|
||||
case 'bytes':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'bits':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'short':
|
||||
url += '&yUnitSystem=si';
|
||||
break;
|
||||
|
@ -29,6 +29,8 @@ function (angular, _, $) {
|
||||
var $funcControls = $(funcControlsTemplate);
|
||||
var func = $scope.func;
|
||||
var funcDef = func.def;
|
||||
var scheduledRelink = false;
|
||||
var paramCountAtLink = 0;
|
||||
|
||||
function clickFuncParam(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
@ -51,17 +53,33 @@ function (angular, _, $) {
|
||||
}
|
||||
}
|
||||
|
||||
function scheduledRelinkIfNeeded() {
|
||||
if (paramCountAtLink === func.params.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scheduledRelink) {
|
||||
scheduledRelink = true;
|
||||
setTimeout(function() {
|
||||
relink();
|
||||
scheduledRelink = false;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function inputBlur(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $input = $(this);
|
||||
var $link = $input.prev();
|
||||
|
||||
if ($input.val() !== '') {
|
||||
if ($input.val() !== '' || func.def.params[paramIndex].optional) {
|
||||
$link.text($input.val());
|
||||
|
||||
|
||||
func.updateParam($input.val(), paramIndex);
|
||||
$scope.$apply($scope.targetChanged);
|
||||
scheduledRelinkIfNeeded();
|
||||
|
||||
$scope.$apply($scope.targetChanged);
|
||||
}
|
||||
|
||||
$input.hide();
|
||||
@ -129,9 +147,19 @@ function (angular, _, $) {
|
||||
$funcLink.appendTo(elem);
|
||||
|
||||
_.each(funcDef.params, function(param, index) {
|
||||
if (param.optional && !func.params[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
$('<span>, </span>').appendTo(elem);
|
||||
}
|
||||
|
||||
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
|
||||
var $input = $(paramTemplate);
|
||||
|
||||
paramCountAtLink++;
|
||||
|
||||
$paramLink.appendTo(elem);
|
||||
$input.appendTo(elem);
|
||||
|
||||
@ -140,10 +168,6 @@ function (angular, _, $) {
|
||||
$input.keypress(_.partial(inputKeyPress, index));
|
||||
$paramLink.click(_.partial(clickFuncParam, index));
|
||||
|
||||
if (index !== funcDef.params.length - 1) {
|
||||
$('<span>, </span>').appendTo(elem);
|
||||
}
|
||||
|
||||
if (funcDef.params[index].options) {
|
||||
addTypeahead($input, index);
|
||||
}
|
||||
@ -200,10 +224,16 @@ function (angular, _, $) {
|
||||
});
|
||||
}
|
||||
|
||||
addElementsAndCompile();
|
||||
ifJustAddedFocusFistParam();
|
||||
registerFuncControlsToggle();
|
||||
registerFuncControlsActions();
|
||||
function relink() {
|
||||
elem.children().remove();
|
||||
|
||||
addElementsAndCompile();
|
||||
ifJustAddedFocusFistParam();
|
||||
registerFuncControlsToggle();
|
||||
registerFuncControlsActions();
|
||||
}
|
||||
|
||||
relink();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'underscore'
|
||||
'underscore',
|
||||
'../controllers/panelBaseCtrl'
|
||||
],
|
||||
function (angular, $, _) {
|
||||
function (angular, $, _, PanelBaseCtrl) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('kibanaPanel', function($compile, $timeout, $rootScope) {
|
||||
.directive('kibanaPanel', function($compile, $timeout, $rootScope, $injector) {
|
||||
|
||||
var container = '<div class="panel-container"></div>';
|
||||
var content = '<div class="panel-content"></div>';
|
||||
@ -16,9 +17,10 @@ function (angular, $, _) {
|
||||
var panelHeader =
|
||||
'<div class="panel-header">'+
|
||||
'<div class="row-fluid">' +
|
||||
'<div class="span12 alert-error panel-error" ng-hide="!panel.error">' +
|
||||
'<div class="span12 alert-error panel-error small" ng-show="panel.error">' +
|
||||
'<a class="close" ng-click="panel.error=false">×</a>' +
|
||||
'<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}' +
|
||||
'<span><i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}} </span>' +
|
||||
'<span class="pointer panel-error-inspector-link" config-modal="app/partials/inspector.html">View details</span>' +
|
||||
'</div>' +
|
||||
'</div>\n' +
|
||||
|
||||
@ -73,11 +75,15 @@ function (angular, $, _) {
|
||||
elem.removeClass("ng-cloak");
|
||||
}
|
||||
|
||||
newScope.$on('$destroy',function(){
|
||||
newScope.$on('$destroy',function() {
|
||||
elem.unbind();
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
newScope.initBaseController = function(self, scope) {
|
||||
$injector.invoke(PanelBaseCtrl, self, { $scope: scope });
|
||||
};
|
||||
|
||||
$scope.$watch(attr.type, function (name) {
|
||||
elem.addClass("ng-cloak");
|
||||
// load the panels module file, then render it in the dom.
|
||||
@ -106,124 +112,6 @@ 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 });
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div class='filtering-container'>
|
||||
|
||||
<div ng-repeat="filter in filterList" class="small filter-panel-filter">
|
||||
<div ng-repeat="filter in filterSrv.list" class="small filter-panel-filter">
|
||||
<div>
|
||||
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(filter)"></i>
|
||||
<i class="filter-action pointer icon-edit" ng-hide="filter.editing" bs-tooltip="'Edit'" ng-click="filter.editing = true"></i>
|
||||
|
@ -27,7 +27,7 @@ function (angular, app, _) {
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.filterList = filterSrv.list;
|
||||
$scope.filterSrv = filterSrv;
|
||||
};
|
||||
|
||||
$scope.remove = function(filter) {
|
||||
@ -40,7 +40,7 @@ function (angular, app, _) {
|
||||
};
|
||||
|
||||
$scope.applyFilterToOtherFilters = function(updatedFilter) {
|
||||
_.each($scope.filterList, function(filter) {
|
||||
_.each(filterSrv.list, function(filter) {
|
||||
if (filter === updatedFilter) {
|
||||
return;
|
||||
}
|
||||
|
@ -10,11 +10,11 @@
|
||||
</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[0]" ng-options="f for f in ['none','short','bytes', 'ms', 'µs']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'ms', 'µs']" 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[1]" ng-options="f for f in ['none','short','bytes', 'ms', 'µs']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'ms', 'µs']" ng-change="render()"></select>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
@ -102,4 +102,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@
|
||||
<div ng-if="panel.legend" class="grafana-legend-container">
|
||||
<div ng-include="'app/panels/graphite/legend.html'"></div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="panel-full-edit-tabs" ng-if="editMode">
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
|
@ -84,7 +84,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_formats :: 'none','bytes','short', 'ms'
|
||||
* y_formats :: 'none','bytes','bits','short', 'ms'
|
||||
*/
|
||||
y_formats : ['short', 'short'],
|
||||
/** @scratch /panels/histogram/5
|
||||
@ -140,7 +140,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
*/
|
||||
stack : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legond
|
||||
* legend:: Display the legend
|
||||
*/
|
||||
legend: {
|
||||
show: true, // disable/enable legend
|
||||
@ -199,7 +199,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
}
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.initPanel($scope);
|
||||
$scope.initBaseController(this, $scope);
|
||||
|
||||
$scope.fullscreen = false;
|
||||
$scope.editor = { index: 1 };
|
||||
@ -261,7 +261,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
return $scope.datasource.query(graphiteQuery)
|
||||
.then($scope.dataHandler)
|
||||
.then(null, function(err) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.panel.error = err.message || "Graphite HTTP Request Error";
|
||||
$scope.inspector.error = err;
|
||||
$scope.render([]);
|
||||
});
|
||||
};
|
||||
|
||||
@ -318,9 +321,9 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
if (last - from < -10000) {
|
||||
$scope.datapointsOutside = true;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.datapointsCount += datapoints.length;
|
||||
$scope.datapointsCount += datapoints.length;
|
||||
}
|
||||
|
||||
return series;
|
||||
};
|
||||
|
@ -8,9 +8,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class=small>Content
|
||||
<label class=small>Content
|
||||
<span ng-show="panel.mode == 'html'">(This area uses HTML sanitized via AngularJS's <a href='http://docs.angularjs.org/api/ngSanitize.$sanitize'>$sanitize</a> service)</span>
|
||||
<span ng-show="panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
|
||||
</label>
|
||||
<textarea ng-model="panel.content" rows="6" style="width:95%"></textarea>
|
||||
|
||||
<textarea ng-model="panel.content" rows="6" style="width:95%" ng-change="render()" ng-model-onblur>
|
||||
</textarea>
|
||||
</div>
|
@ -1,10 +1,10 @@
|
||||
<div ng-controller='text' ng-init="init()">
|
||||
<div ng-controller='text' ng-init="init()" style="min-height:{{panel.height || row.height}}" ng-dblclick="openEditor()">
|
||||
<!--<p ng-style="panel.style" ng-bind-html-unsafe="panel.content | striphtml | newlines"></p>-->
|
||||
<markdown ng-show="ready && panel.mode == 'markdown'">
|
||||
{{panel.content}}
|
||||
</markdown>
|
||||
<p ng-show="panel.mode == 'text'" ng-style='panel.style' ng-bind-html="panel.content | striphtml | newlines">
|
||||
<p ng-show="panel.mode == 'text'" ng-style='panel.style' ng-bind-html-unsafe="panel.content | striphtml | newlines">
|
||||
</p>
|
||||
<p ng-show="panel.mode == 'html'" ng-bind-html="panel.content">
|
||||
<p ng-show="panel.mode == 'html'" ng-bind-html-unsafe="panel.content">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,31 +23,35 @@ function (angular, app, _, require) {
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('text', function($scope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/text/5
|
||||
* === Parameters
|
||||
*
|
||||
* mode:: `html', `markdown' or `text'
|
||||
*/
|
||||
mode : "markdown", // 'html','markdown','text'
|
||||
/** @scratch /panels/text/5
|
||||
* content:: The content of your panel, written in the mark up specified in +mode+
|
||||
*/
|
||||
mode : "markdown", // 'html', 'markdown', 'text'
|
||||
content : "",
|
||||
style: {},
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.initPanel($scope);
|
||||
$scope.initBaseController(this, $scope);
|
||||
|
||||
$scope.ready = false;
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.openEditor = function() {
|
||||
//$scope.$emit('open-modal','paneleditor');
|
||||
console.log('scope id', $scope.$id);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.directive('markdown', function() {
|
||||
|
@ -45,7 +45,7 @@
|
||||
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
|
||||
</li>
|
||||
<li ng-show="dashboard.current.loader.save_local">
|
||||
<a class="link" ng-click="dashboard.to_file()">Export schema</a>
|
||||
<a class="link" ng-click="dashboard.to_file()">Export dashboard</a>
|
||||
</li>
|
||||
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
|
||||
|
||||
|
@ -156,5 +156,5 @@
|
||||
</div>
|
||||
|
||||
<button ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-success" ng-show="editor.index == 1">Create Row</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Close</button>
|
||||
<button type="button" class="btn btn-info" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Close</button>
|
||||
</div>
|
@ -1,8 +1,17 @@
|
||||
<div ng-controller="GraphiteImportCtrl" ng-init="init()">
|
||||
<div ng-controller="GraphiteImportCtrl" ng-init="init()" style="height: 400px">
|
||||
<h5>Import dashboards from graphite web</h5>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="btn-group">
|
||||
<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>
|
||||
<button ng-click="listAll()" class="btn btn-primary">List all dashboards</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,19 +11,18 @@
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-target-controls">
|
||||
<li class="dropdown">
|
||||
<a class="pointer dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
tabindex="1">
|
||||
<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>
|
||||
<a tabindex="1" ng-click="duplicate()">Duplicate</a>
|
||||
<a tabindex="2" ng-click="showQuery()" ng-hide="target.rawQuery">Show Query</a>
|
||||
<a tabindex="2" ng-click="hideQuery()" ng-show="target.rawQuery">Hide Query</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" tabindex="1" ng-click="removeTarget(target)">
|
||||
@ -34,19 +33,29 @@
|
||||
|
||||
<ul class="grafana-target-controls-left">
|
||||
<li>
|
||||
<a class="grafana-target-segment"
|
||||
ng-click="target.hide = !target.hide; get_data();"
|
||||
role="menuitem">
|
||||
<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">
|
||||
<li ng-show="target.rawQuery">
|
||||
<input type="text"
|
||||
class="input-large grafana-target-segment-input span10"
|
||||
ng-model="target.query"
|
||||
placeholder="select ..."
|
||||
data-min-length=0 data-items=100
|
||||
ng-blur="get_data()">
|
||||
</li>
|
||||
<li class="grafana-target-segment" ng-hide="target.rawQuery">
|
||||
from series
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<li ng-hide="target.rawQuery">
|
||||
<input type="text"
|
||||
class="input-medium grafana-target-segment-input"
|
||||
ng-model="target.series"
|
||||
@ -54,13 +63,14 @@
|
||||
bs-typeahead="listSeries"
|
||||
placeholder="series name"
|
||||
data-min-length=0 data-items=100
|
||||
ng-blur="seriesBlur()"
|
||||
>
|
||||
ng-blur="seriesBlur()">
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
|
||||
<li class="grafana-target-segment" ng-hide="target.rawQuery">
|
||||
select
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<li ng-hide="target.rawQuery">
|
||||
<input type="text"
|
||||
class="input-medium grafana-target-segment-input"
|
||||
ng-model="target.column"
|
||||
@ -70,10 +80,11 @@
|
||||
data-min-length=0
|
||||
ng-blur="get_data()">
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
|
||||
<li class="grafana-target-segment" ng-hide="target.rawQuery">
|
||||
function
|
||||
</li>
|
||||
<li>
|
||||
<li ng-hide="target.rawQuery">
|
||||
<select class="input-medium grafana-target-segment-input"
|
||||
ng-change="get_data()"
|
||||
ng-model="target.function"
|
||||
@ -92,10 +103,12 @@
|
||||
data-min-length=0
|
||||
ng-blur="get_data()">
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
|
||||
<li class="grafana-target-segment" ng-hide="target.rawQuery">
|
||||
group by time
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<li ng-hide="target.rawQuery">
|
||||
<input type="text"
|
||||
class="input-mini grafana-target-segment-input"
|
||||
ng-model="target.interval"
|
||||
|
@ -1,15 +1,68 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Last Elasticsearch Query</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" ng-controller="InspectCtrl" ng-init="init()">
|
||||
<div class="pull-right editor-title">Inspector</div>
|
||||
|
||||
<div>
|
||||
<pre>curl -XGET '{{config.elasticsearch}}/{{dashboard.indices|stringify}}/_search?pretty' -d '{{inspector}}'
|
||||
</pre>
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
<div ng-repeat="tab in ['Request', 'Response', 'JS Error']" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="editor.index == 0">
|
||||
<h5>Request details</h5>
|
||||
<table class="table table-striped small inspector-request-table">
|
||||
<tr>
|
||||
<td>Url</td>
|
||||
<td>{{inspector.error.config.url}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Method</td>
|
||||
<td>{{inspector.error.config.method}}</td>
|
||||
</tr>
|
||||
<tr ng-repeat="(key, value) in inspector.error.config.headers">
|
||||
<td>
|
||||
{{key}}
|
||||
</td>
|
||||
<td>
|
||||
{{value}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h5>Request parameters</h5>
|
||||
<table class="table table-striped small inspector-request-table">
|
||||
<tr ng-repeat="param in request_parameters">
|
||||
<td>
|
||||
{{param.key}}
|
||||
</td>
|
||||
<td>
|
||||
{{param.value}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 1">
|
||||
|
||||
<div ng-if="response_html">
|
||||
<div iframe-content="response_html"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 2">
|
||||
|
||||
<label>Message:</label>
|
||||
<pre>
|
||||
{{message}}
|
||||
</pre>
|
||||
|
||||
<label>Stack trace:</label>
|
||||
<pre>
|
||||
{{stack_trace}}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" ng-click="dismiss()">Close</button>
|
||||
<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
|
||||
</div>
|
@ -19,5 +19,5 @@
|
||||
|
||||
<div class="modal-footer">
|
||||
<!-- close_edit() is provided here to allow for a scope to perform action on dismiss -->
|
||||
<button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss()">Cancel</button>
|
||||
<button type="button" class="btn btn-info" ng-click="editor.index=0;dismiss()">Close</button>
|
||||
</div>
|
@ -7,14 +7,5 @@
|
||||
<div class="editor-option" ng-hide="panel.sizeable == false">
|
||||
<label class="small">Span</label> <select class="input-mini" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Editable</label><input type="checkbox" ng-model="panel.editable" ng-checked="panel.editable">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="!_.isUndefined(panel.spyable)">
|
||||
<label class="small">
|
||||
Inspect <i class="icon-question-sign" bs-tooltip="'Allow query reveal via <i class=icon-eye-open></i>'"></i>
|
||||
</label>
|
||||
<input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -62,5 +62,5 @@
|
||||
<div class="modal-footer">
|
||||
<button ng-show="editor.index == 1" ng-click="editor.index = 2;" class="btn btn-success" ng-disabled="panel.loadingEditor">Add Panel</button>
|
||||
<button ng-show="panel.type && editor.index == 2" ng-click="add_panel(row,panel); reset_panel(); editor.index = 1;" class="btn btn-success" ng-disabled="panel.loadingEditor">Add Panel</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();close_edit()">Close</button>
|
||||
<button type="button" class="btn btn-info" ng-click="editor.index=0;dismiss();reset_panel();close_edit()">Close</button>
|
||||
</div>
|
@ -135,7 +135,7 @@ define([
|
||||
}
|
||||
tooltip += '<i>' + moment(options.time).format('YYYY-MM-DD HH:mm:ss') + '</i><br/>';
|
||||
if (options.data) {
|
||||
tooltip += options.data;
|
||||
tooltip += options.data.replace(/\n/g, '<br/>');
|
||||
}
|
||||
tooltip += "</small>";
|
||||
|
||||
|
@ -15,7 +15,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
|
||||
module.service('dashboard', function(
|
||||
$routeParams, $http, $rootScope, $injector, $location, $timeout,
|
||||
ejsResource, timer, alertSrv
|
||||
ejsResource, timer, alertSrv, $q
|
||||
) {
|
||||
// A hash of defaults to use when loading a dashboard
|
||||
|
||||
@ -152,6 +152,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
// Make sure the dashboard being loaded has everything required
|
||||
dashboard = dash_defaults(dashboard);
|
||||
|
||||
window.document.title = 'Grafana - ' + dashboard.title;
|
||||
|
||||
// Set the current dashboard
|
||||
self.current = angular.copy(dashboard);
|
||||
|
||||
@ -330,13 +332,27 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
this.script_load = function(file) {
|
||||
return $http({
|
||||
url: "app/dashboards/"+file.replace(/\.(?!js)/,"/"),
|
||||
method: "GET",
|
||||
transformResponse: function(response) {
|
||||
/*jshint -W054 */
|
||||
var _f = new Function('ARGS','kbn','_','moment','window','document','angular','require','define','$','jQuery',response);
|
||||
return _f($routeParams,kbn,_,moment);
|
||||
method: "GET"
|
||||
})
|
||||
.then(function(result) {
|
||||
/*jshint -W054 */
|
||||
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
|
||||
var script_result = script_func($routeParams,kbn,_,moment, window, document, $, $);
|
||||
|
||||
// Handle async dashboard scripts
|
||||
if (_.isFunction(script_result)) {
|
||||
var deferred = $q.defer();
|
||||
script_result(function(dashboard) {
|
||||
$rootScope.$apply(function() {
|
||||
deferred.resolve({ data: dashboard });
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
}).then(function(result) {
|
||||
|
||||
return { data: script_result };
|
||||
})
|
||||
.then(function(result) {
|
||||
if(!result) {
|
||||
return false;
|
||||
}
|
||||
|
@ -87,6 +87,16 @@ define([
|
||||
this.setTime = function(time) {
|
||||
_.extend(self.time, time);
|
||||
|
||||
// disable refresh if we have an absolute time
|
||||
if (time.to !== 'now') {
|
||||
self.old_refresh = dashboard.current.refresh;
|
||||
dashboard.set_interval(false);
|
||||
}
|
||||
else if (self.old_refresh && self.old_refresh !== dashboard.current.refresh) {
|
||||
dashboard.set_interval(self.old_refresh);
|
||||
self.old_refresh = null;
|
||||
}
|
||||
|
||||
$timeout(function(){
|
||||
dashboard.refresh();
|
||||
},0);
|
||||
|
@ -69,6 +69,11 @@ function (_) {
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'rangeOfSeries',
|
||||
category: categories.Combine
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'percentileOfSeries',
|
||||
category: categories.Combine,
|
||||
@ -83,6 +88,18 @@ function (_) {
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'maxSeries',
|
||||
shortName: 'max',
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'minSeries',
|
||||
shortName: 'min',
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'averageSeriesWithWildcards',
|
||||
category: categories.Combine,
|
||||
@ -111,6 +128,19 @@ function (_) {
|
||||
defaultParams: ['stacked']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "consolidateBy",
|
||||
category: categories.Special,
|
||||
params: [
|
||||
{
|
||||
name: 'function',
|
||||
type: 'string',
|
||||
options: ['sum', 'average', 'min', 'max']
|
||||
}
|
||||
],
|
||||
defaultParams: ['max']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "groupByNode",
|
||||
category: categories.Special,
|
||||
@ -132,15 +162,43 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'aliasByNode',
|
||||
category: categories.Special,
|
||||
params: [ { name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] } ],
|
||||
params: [
|
||||
{ name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] },
|
||||
{ name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
|
||||
],
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'substr',
|
||||
category: categories.Special,
|
||||
params: [
|
||||
{ name: "start", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
|
||||
{ name: "stop", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
|
||||
],
|
||||
defaultParams: [0, 0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByName',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByMaxima',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByMinima',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByTotal',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'aliasByMetric',
|
||||
category: categories.Special,
|
||||
@ -189,6 +247,13 @@ function (_) {
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'transformNull',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "amount", type: "int", } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'integral',
|
||||
category: categories.Transform,
|
||||
@ -220,6 +285,13 @@ function (_) {
|
||||
defaultParams: ['1h', 'sum']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'smartSummarize',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
|
||||
defaultParams: ['1h', 'sum']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'absolute',
|
||||
category: categories.Transform,
|
||||
@ -268,6 +340,41 @@ function (_) {
|
||||
defaultParams: [25]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'maximumAbove',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "value", type: "int" } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'maximumBelow',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "value", type: "int" } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'minimumAbove',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "value", type: "int" } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'limit',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'mostDeviant',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "exclude",
|
||||
category: categories.Filter,
|
||||
@ -303,6 +410,20 @@ function (_) {
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'movingMedian',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "windowSize", type: "select", options: ['1min', '5min', '15min', '30min', '1hour'] } ],
|
||||
defaultParams: ['1min']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'stdev',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" }, { name: "tolerance", type: "int" } ],
|
||||
defaultParams: [5,0.1]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'highestAverage',
|
||||
category: categories.Filter,
|
||||
@ -317,6 +438,34 @@ function (_) {
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'removeAbovePercentile',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'removeAboveValue',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'removeBelowPercentile',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'removeBelowValue',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
_.each(categories, function(funcList, catName) {
|
||||
categories[catName] = _.sortBy(funcList, 'name');
|
||||
});
|
||||
@ -340,9 +489,29 @@ function (_) {
|
||||
return str + parameters.join(',') + ')';
|
||||
};
|
||||
|
||||
FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) {
|
||||
if (strValue.indexOf(',') === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.def.params[index + 1] && this.def.params[index + 1].optional;
|
||||
};
|
||||
|
||||
FuncInstance.prototype.updateParam = function(strValue, index) {
|
||||
if (this.def.params[index].type === 'int') {
|
||||
this.params[index] = parseInt(strValue, 10);
|
||||
// handle optional parameters
|
||||
// if string contains ',' and next param is optional, split and update both
|
||||
if (this._hasMultipleParamsInString(strValue, index)) {
|
||||
_.each(strValue.split(','), function(partVal, idx) {
|
||||
this.updateParam(partVal.trim(), idx);
|
||||
}, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strValue === '' && this.def.params[index].optional) {
|
||||
this.params.splice(index, 1);
|
||||
}
|
||||
else if (this.def.params[index].type === 'int') {
|
||||
this.params[index] = parseFloat(strValue, 10);
|
||||
}
|
||||
else {
|
||||
this.params[index] = strValue;
|
||||
@ -359,6 +528,10 @@ function (_) {
|
||||
|
||||
var text = this.def.name + '(';
|
||||
_.each(this.def.params, function(param, index) {
|
||||
if (param.optional && this.params[index] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
text += this.params[index] + ', ';
|
||||
}, this);
|
||||
text = text.substring(0, text.length - 2);
|
||||
|
@ -24,8 +24,8 @@ function (angular, _, $, config, kbn, moment) {
|
||||
GraphiteDatasource.prototype.query = function(options) {
|
||||
try {
|
||||
var graphOptions = {
|
||||
from: this.translateTime(options.range.from),
|
||||
until: this.translateTime(options.range.to),
|
||||
from: this.translateTime(options.range.from, 'round-down'),
|
||||
until: this.translateTime(options.range.to, 'round-up'),
|
||||
targets: options.targets,
|
||||
format: options.format,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
@ -68,7 +68,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
}
|
||||
};
|
||||
|
||||
GraphiteDatasource.prototype.translateTime = function(date) {
|
||||
GraphiteDatasource.prototype.translateTime = function(date, rounding) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
return 'now';
|
||||
@ -85,6 +85,21 @@ function (angular, _, $, config, kbn, moment) {
|
||||
|
||||
date = moment.utc(date);
|
||||
|
||||
if (rounding === 'round-up') {
|
||||
if (date.get('s')) {
|
||||
date.add('m', 1);
|
||||
}
|
||||
}
|
||||
else if (rounding === 'round-down') {
|
||||
// graphite' s from filter is exclusive
|
||||
// here we step back one minute in order
|
||||
// to guarantee that we get all the data that
|
||||
// exists for the specified range
|
||||
if (date.get('s')) {
|
||||
date.subtract('m', 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (dashboard.current.timezone === 'browser') {
|
||||
date = date.local();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ function (angular, _, kbn) {
|
||||
function InfluxDatasource(datasource) {
|
||||
this.type = 'influxDB';
|
||||
this.editorSrc = 'app/partials/influxdb/editor.html';
|
||||
this.url = datasource.url;
|
||||
this.urls = datasource.urls;
|
||||
this.username = datasource.username;
|
||||
this.password = datasource.password;
|
||||
this.name = datasource.name;
|
||||
@ -26,15 +26,12 @@ function (angular, _, kbn) {
|
||||
InfluxDatasource.prototype.query = function(options) {
|
||||
|
||||
var promises = _.map(options.targets, function(target) {
|
||||
if (!target.series || !target.column || target.hide) {
|
||||
var query;
|
||||
|
||||
if (target.hide || !((target.series && target.column) || target.query)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// var template = "select [[func]]([[column]]) as [[column]]_[[func]] from [[series]] where [[timeFilter]] group by time([[interval]]) order asc";
|
||||
var template = "select [[func]]([[column]]) from [[series]] where [[condition]] [[timeFilter]] group by time([[interval]]) order asc";
|
||||
|
||||
target.condition_joined = (target.condition !== undefined ? target.condition + ' AND ' : '');
|
||||
|
||||
var templateData = {
|
||||
series: target.series,
|
||||
column: target.column,
|
||||
@ -44,60 +41,122 @@ function (angular, _, kbn) {
|
||||
interval: target.interval || options.interval
|
||||
};
|
||||
|
||||
var query = _.template(template, templateData, this.templateSettings);
|
||||
var timeFilter = getTimeFilter(options);
|
||||
|
||||
if (target.rawQuery) {
|
||||
query = target.query;
|
||||
query = query.replace(";", "");
|
||||
var queryElements = query.split(" ");
|
||||
var lowerCaseQueryElements = query.toLowerCase().split(" ");
|
||||
var whereIndex = lowerCaseQueryElements.indexOf("where");
|
||||
var groupByIndex = lowerCaseQueryElements.indexOf("group");
|
||||
var orderIndex = lowerCaseQueryElements.indexOf("order");
|
||||
|
||||
if (whereIndex !== -1) {
|
||||
queryElements.splice(whereIndex+1, 0, timeFilter, "and");
|
||||
}
|
||||
else {
|
||||
if (groupByIndex !== -1) {
|
||||
queryElements.splice(groupByIndex, 0, "where", timeFilter);
|
||||
}
|
||||
else if (orderIndex !== -1) {
|
||||
queryElements.splice(orderIndex, 0, "where", timeFilter);
|
||||
}
|
||||
else {
|
||||
queryElements.push("where");
|
||||
queryElements.push(timeFilter);
|
||||
}
|
||||
}
|
||||
|
||||
query = queryElements.join(" ");
|
||||
}
|
||||
else {
|
||||
var template = "select [[func]]([[column]]) as [[column]]_[[func]] from [[series]] " +
|
||||
"where [[condition]] [[timeFilter]]" +
|
||||
" group by time([[interval]]) order asc";
|
||||
|
||||
target.condition_joined = (target.condition !== undefined ? target.condition + ' AND ' : '');
|
||||
|
||||
var templateData = {
|
||||
series: target.series,
|
||||
column: target.column,
|
||||
func: target.function,
|
||||
timeFilter: timeFilter,
|
||||
interval: target.interval || options.interval
|
||||
};
|
||||
|
||||
query = _.template(template, templateData, this.templateSettings);
|
||||
target.query = query;
|
||||
}
|
||||
|
||||
return this.doInfluxRequest(query).then(handleInfluxQueryResponse);
|
||||
|
||||
}, this);
|
||||
|
||||
return $q.all(promises).then(function(results) {
|
||||
|
||||
return { data: _.flatten(results) };
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.listColumns = function(seriesName) {
|
||||
return this.doInfluxRequest('select * from ' + seriesName + ' limit 1').then(function(results) {
|
||||
console.log('response!');
|
||||
if (!results.data) {
|
||||
return this.doInfluxRequest('select * from ' + seriesName + ' limit 1').then(function(data) {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return results.data[0].columns;
|
||||
return data[0].columns;
|
||||
});
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.listSeries = function() {
|
||||
return this.doInfluxRequest('list series').then(function(results) {
|
||||
if (!results.data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _.map(results.data, function(series) {
|
||||
return this.doInfluxRequest('list series').then(function(data) {
|
||||
return _.map(data, function(series) {
|
||||
return series.name;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function retry(deferred, callback, delay) {
|
||||
return callback().then(undefined, function(reason) {
|
||||
if (reason.status !== 0) {
|
||||
deferred.reject(reason);
|
||||
}
|
||||
setTimeout(function() {
|
||||
return retry(deferred, callback, Math.min(delay * 2, 30000));
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
InfluxDatasource.prototype.doInfluxRequest = function(query) {
|
||||
var params = {
|
||||
u: this.username,
|
||||
p: this.password,
|
||||
q: query
|
||||
};
|
||||
var _this = this;
|
||||
var deferred = $q.defer();
|
||||
|
||||
var options = {
|
||||
method: 'GET',
|
||||
url: this.url + '/series',
|
||||
params: params,
|
||||
};
|
||||
retry(deferred, function() {
|
||||
var currentUrl = _this.urls.shift();
|
||||
_this.urls.push(currentUrl);
|
||||
|
||||
console.log(query);
|
||||
return $http(options);
|
||||
var params = {
|
||||
u: _this.username,
|
||||
p: _this.password,
|
||||
q: query
|
||||
};
|
||||
|
||||
var options = {
|
||||
method: 'GET',
|
||||
url: currentUrl + '/series',
|
||||
params: params,
|
||||
};
|
||||
|
||||
return $http(options).success(function (data) {
|
||||
deferred.resolve(data);
|
||||
});
|
||||
}, 10);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function handleInfluxQueryResponse(results) {
|
||||
function handleInfluxQueryResponse(data) {
|
||||
var output = [];
|
||||
|
||||
var getKey = function (str) {
|
||||
@ -106,7 +165,7 @@ function (angular, _, kbn) {
|
||||
return (key2[0] !== key1[1] ? '.' + key2[0] : '');
|
||||
}
|
||||
|
||||
_.each(results.data, function(series) {
|
||||
_.each(data, function(series) {
|
||||
var timeCol = series.columns.indexOf('time');
|
||||
|
||||
_.each(series.columns, function(column, index) {
|
||||
|
@ -67,6 +67,7 @@ function (angular, _, kbn) {
|
||||
|
||||
timerInstance = setInterval(function() {
|
||||
$rootScope.$apply(function() {
|
||||
angular.element(window).unbind('resize');
|
||||
$location.path(dashboards[index % dashboards.length].url);
|
||||
index++;
|
||||
});
|
||||
@ -82,4 +83,4 @@ function (angular, _, kbn) {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -36,9 +36,16 @@ function (Settings) {
|
||||
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)
|
||||
* If you experiance problems with zoom, it is probably caused by timezone diff between
|
||||
* your browser and the graphite-web application. timezoneOffset setting can be used to have Grafana
|
||||
* translate absolute time ranges to the graphite-web timezone.
|
||||
* Example:
|
||||
* If TIME_ZONE in graphite-web config file local_settings.py is set to America/New_York, then set
|
||||
* timezoneOffset to "-0500" (for UTC - 5 hours)
|
||||
* Example:
|
||||
* If TIME_ZONE is set to UTC, set this to "0000"
|
||||
*/
|
||||
|
||||
timezoneOffset: null,
|
||||
|
||||
grafana_index: "grafana-dash",
|
||||
|
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
@ -130,7 +130,7 @@
|
||||
}
|
||||
|
||||
.panel-fullscreen {
|
||||
z-index: 1500;
|
||||
z-index: 100;
|
||||
display: block !important;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
@ -147,7 +147,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dashboard-fullscreen .container-fluid.main {
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
@ -158,8 +157,10 @@
|
||||
// Graphite Graph Legends
|
||||
|
||||
.grafana-legend-container {
|
||||
margin: 4px 15px;
|
||||
margin: 0 15px;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.grafana-legend-container .popover-content {
|
||||
|
@ -77,7 +77,7 @@ code, pre {
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 0px 10px 10px 10px;
|
||||
padding: 0px 10px 0 10px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
@ -112,7 +112,12 @@ code, pre {
|
||||
|
||||
.panel-error {
|
||||
color: @white;
|
||||
padding: 3px 10px 0px 10px;
|
||||
padding: 5px 10px 0px 10px;
|
||||
}
|
||||
|
||||
.panel-error-inspector-link {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
@ -583,3 +588,21 @@ div.flot-text {
|
||||
.save-dashboard-dropdown-save-form {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
// inspector
|
||||
.inspector-request-table {
|
||||
td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// pre
|
||||
code, pre {
|
||||
background-color: @kibanaPanelBackground;
|
||||
color: @textColor;
|
||||
}
|
38
src/test/.jshintrc
Normal file
38
src/test/.jshintrc
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"browser": true,
|
||||
"bitwise":false,
|
||||
"curly": true,
|
||||
"eqnull": true,
|
||||
"globalstrict": true,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"forin": false,
|
||||
"immed": true,
|
||||
"supernew": true,
|
||||
"expr": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"trailing": false,
|
||||
"laxbreak": true,
|
||||
"laxcomma": true,
|
||||
"sub": true,
|
||||
"unused": true,
|
||||
"maxlen": 140,
|
||||
|
||||
"globals": {
|
||||
"expect": true,
|
||||
"it": true,
|
||||
"describe": true,
|
||||
"define": true,
|
||||
"module": true,
|
||||
"beforeEach": true,
|
||||
"inject": true,
|
||||
"require": true,
|
||||
"setImmediate": true
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
module.exports = function(config) {
|
||||
'use strict';
|
||||
|
||||
config.set({
|
||||
basePath: '../',
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
define([],
|
||||
function() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
create: function() {
|
||||
return {
|
||||
refresh: function() {},
|
||||
set_interval: function(value) { this.current.refresh = value; },
|
||||
|
||||
current: {
|
||||
title: "",
|
||||
@ -32,9 +34,9 @@ define([],
|
||||
load_local: false,
|
||||
hide: false
|
||||
},
|
||||
refresh: false
|
||||
refresh: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -3,13 +3,16 @@ define([
|
||||
'underscore',
|
||||
'services/filterSrv'
|
||||
], function(dashboardMock, _) {
|
||||
'use strict';
|
||||
|
||||
describe('filterSrv', function() {
|
||||
var _filterSrv;
|
||||
var _dashboard;
|
||||
|
||||
beforeEach(module('kibana.services'));
|
||||
beforeEach(module(function($provide){
|
||||
$provide.value('dashboard', dashboardMock.create());
|
||||
_dashboard = dashboardMock.create();
|
||||
$provide.value('dashboard', _dashboard);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(filterSrv) {
|
||||
@ -55,6 +58,23 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTime', function() {
|
||||
it('should return disable refresh for absolute times', function() {
|
||||
_dashboard.current.refresh = true;
|
||||
|
||||
_filterSrv.setTime({from: '2011-01-01', to: '2015-01-01' });
|
||||
expect(_dashboard.current.refresh).to.be(false);
|
||||
});
|
||||
|
||||
it('should restore refresh after relative time range is set', function() {
|
||||
_dashboard.current.refresh = true;
|
||||
_filterSrv.setTime({from: '2011-01-01', to: '2015-01-01' });
|
||||
expect(_dashboard.current.refresh).to.be(false);
|
||||
_filterSrv.setTime({from: '2011-01-01', to: 'now' });
|
||||
expect(_dashboard.current.refresh).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
define([
|
||||
'services/graphite/gfunc'
|
||||
], function(gfunc) {
|
||||
'use strict';
|
||||
|
||||
describe('when creating func instance from func names', function() {
|
||||
|
||||
@ -20,8 +21,8 @@ define([
|
||||
|
||||
it('should return func instance from funcDef', function() {
|
||||
var func = gfunc.createFuncInstance('sum');
|
||||
var func = gfunc.createFuncInstance(func.def);
|
||||
expect(func).to.be.ok();
|
||||
var func2 = gfunc.createFuncInstance(func.def);
|
||||
expect(func2).to.be.ok();
|
||||
});
|
||||
|
||||
it('func instance should have text representation', function() {
|
||||
@ -57,12 +58,51 @@ define([
|
||||
});
|
||||
|
||||
describe('when requesting function categories', function() {
|
||||
|
||||
it('should return function categories', function() {
|
||||
var catIndex = gfunc.getCategories();
|
||||
expect(catIndex.Special.length).to.be.greaterThan(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updating func param', function() {
|
||||
it('should update param value and update text representation', function() {
|
||||
var func = gfunc.createFuncInstance('summarize');
|
||||
func.updateParam('1h', 0);
|
||||
expect(func.params[0]).to.be('1h');
|
||||
expect(func.text).to.be('summarize(1h, sum)');
|
||||
});
|
||||
|
||||
it('should parse numbers as float', function() {
|
||||
var func = gfunc.createFuncInstance('scale');
|
||||
func.updateParam('0.001', 0);
|
||||
expect(func.params[0]).to.be(0.001);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updating func param with optional second parameter', function() {
|
||||
it('should update value and text', function() {
|
||||
var func = gfunc.createFuncInstance('aliasByNode');
|
||||
func.updateParam('1', 0);
|
||||
expect(func.params[0]).to.be(1);
|
||||
});
|
||||
|
||||
it('should slit text and put value in second param', function() {
|
||||
var func = gfunc.createFuncInstance('aliasByNode');
|
||||
func.updateParam('4,-5', 0);
|
||||
expect(func.params[0]).to.be(4);
|
||||
expect(func.params[1]).to.be(-5);
|
||||
expect(func.text).to.be('aliasByNode(4, -5)');
|
||||
});
|
||||
|
||||
it('should remove second param when empty string is set', function() {
|
||||
var func = gfunc.createFuncInstance('aliasByNode');
|
||||
func.updateParam('4,-5', 0);
|
||||
func.updateParam('', 1);
|
||||
expect(func.params[0]).to.be(4);
|
||||
expect(func.params[1]).to.be(undefined);
|
||||
expect(func.text).to.be('aliasByNode(4)');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
define([
|
||||
'mocks/dashboard-mock',
|
||||
'underscore',
|
||||
'services/filterSrv'
|
||||
], function(dashboardMock, _) {
|
||||
], function() {
|
||||
'use strict';
|
||||
|
||||
describe('graphiteTargetCtrl', function() {
|
||||
var _filterSrv;
|
||||
var _targetCtrl;
|
||||
|
||||
beforeEach(module('kibana.services'));
|
||||
beforeEach(module(function($provide){
|
||||
@ -20,8 +18,6 @@ define([
|
||||
|
||||
describe('init', function() {
|
||||
beforeEach(function() {
|
||||
_filterSrv.add({ name: 'test', current: { value: 'oogle' } });
|
||||
_filterSrv.init();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
24
src/test/specs/kbn-format-specs.js
Normal file
24
src/test/specs/kbn-format-specs.js
Normal file
@ -0,0 +1,24 @@
|
||||
define([
|
||||
'kbn'
|
||||
], function(kbn) {
|
||||
'use strict';
|
||||
|
||||
describe('millisecond formating', function() {
|
||||
|
||||
it('should translate 4378634603 as 1.67 years', function() {
|
||||
var str = kbn.msFormat(4378634603, 2);
|
||||
expect(str).to.be('50.68 day');
|
||||
});
|
||||
|
||||
it('should translate 3654454 as 1.02 hour', function() {
|
||||
var str = kbn.msFormat(3654454, 2);
|
||||
expect(str).to.be('1.02 hour');
|
||||
});
|
||||
|
||||
it('should translate 365445 as 6.09 min', function() {
|
||||
var str = kbn.msFormat(365445, 2);
|
||||
expect(str).to.be('6.09 min');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -1,6 +1,7 @@
|
||||
define([
|
||||
'services/graphite/lexer'
|
||||
], function(Lexer) {
|
||||
'use strict';
|
||||
|
||||
describe('when lexing graphite expression', function() {
|
||||
|
||||
@ -88,6 +89,12 @@ define([
|
||||
expect(tokens[4].pos).to.be(20);
|
||||
});
|
||||
|
||||
it('should handle float parameters', function() {
|
||||
var lexer = new Lexer("alias(metric, 0.002)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[4].value).to.be('0.002');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
define([
|
||||
'services/graphite/parser'
|
||||
], function(Parser) {
|
||||
'use strict';
|
||||
|
||||
describe('when parsing', function() {
|
||||
|
||||
@ -103,7 +104,7 @@ define([
|
||||
var parser = new Parser("sum(test.[[server]].count)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be(undefined)
|
||||
expect(rootNode.message).to.be(undefined);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].segments[1].type).to.be('template');
|
||||
expect(rootNode.params[0].segments[1].value).to.be('server');
|
||||
@ -139,6 +140,13 @@ define([
|
||||
expect(rootNode.type).to.be('function');
|
||||
});
|
||||
|
||||
it('handle float function arguments', function() {
|
||||
var parser = new Parser('scale(test, 0.002)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
expect(rootNode.params[1].value).to.be(0.002);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,6 @@ require.config({
|
||||
kbn: 'components/kbn',
|
||||
|
||||
settings: 'components/settings',
|
||||
crypto: '../vendor/crypto.min',
|
||||
underscore: 'components/underscore.extended',
|
||||
'underscore-src': '../vendor/underscore',
|
||||
|
||||
@ -29,7 +28,6 @@ require.config({
|
||||
jquery: '../vendor/jquery/jquery-1.8.0',
|
||||
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
|
||||
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
|
||||
|
||||
@ -106,6 +104,7 @@ require([
|
||||
'angular',
|
||||
'angularMocks',
|
||||
], function(angular) {
|
||||
'use strict';
|
||||
|
||||
angular.module('kibana', []);
|
||||
angular.module('kibana.services', []);
|
||||
@ -115,6 +114,7 @@ require([
|
||||
'specs/parser-specs',
|
||||
'specs/gfunc-specs',
|
||||
'specs/filterSrv-specs',
|
||||
'specs/kbn-format-specs',
|
||||
], function () {
|
||||
window.__karma__.start();
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ module.exports = function(grunt) {
|
||||
// Concat and Minify the src directory into dist
|
||||
grunt.registerTask('build', [
|
||||
'jshint:source',
|
||||
'jshint:tests',
|
||||
'clean:on_start',
|
||||
'less:src',
|
||||
'copy:everything_but_less_to_temp',
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Lint and build CSS
|
||||
module.exports = function(grunt) {
|
||||
grunt.registerTask('default', ['jshint:source', 'less:src']);
|
||||
grunt.registerTask('default', ['jshint:source', 'jshint:tests', 'less:src']);
|
||||
grunt.registerTask('test', ['default', 'karma:test']);
|
||||
};
|
@ -9,9 +9,11 @@ module.exports = function(config) {
|
||||
expand: true,
|
||||
cwd: '<%= destDir %>',
|
||||
src: ['**/*'],
|
||||
dest: '<%= pkg.name %>/',
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
dest: '<%= pkg.name %>/',
|
||||
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
|
||||
}
|
||||
]
|
||||
@ -25,10 +27,12 @@ module.exports = function(config) {
|
||||
expand: true,
|
||||
cwd: '<%= destDir %>',
|
||||
src: ['**/*'],
|
||||
dest: '<%= pkg.name %>/',
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
|
||||
dest: '<%= pkg.name %>/',
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -41,10 +45,12 @@ module.exports = function(config) {
|
||||
expand: true,
|
||||
cwd: '<%= destDir %>',
|
||||
src: ['**/*'],
|
||||
dest: '<%= pkg.name %>-<%= pkg.version %>/',
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
|
||||
dest: '<%= pkg.name %>-<%= pkg.version %>/',
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -57,10 +63,12 @@ module.exports = function(config) {
|
||||
expand: true,
|
||||
cwd: '<%= destDir %>',
|
||||
src: ['**/*'],
|
||||
dest: '<%= pkg.name %>-<%= pkg.version %>/',
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
|
||||
dest: '<%= pkg.name %>-<%= pkg.version %>/',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ module.exports = function(config) {
|
||||
dev: {
|
||||
options: {
|
||||
port: 5601,
|
||||
hostname: '*',
|
||||
base: config.srcDir,
|
||||
keepalive: true
|
||||
}
|
||||
|
@ -1,19 +1,25 @@
|
||||
module.exports = function(config) {
|
||||
return {
|
||||
// just lint the source dir
|
||||
source: {
|
||||
files: {
|
||||
src: ['Gruntfile.js', '<%= srcDir %>/app/**/*.js']
|
||||
src: ['Gruntfile.js', '<%= srcDir %>/app/**/*.js'],
|
||||
}
|
||||
},
|
||||
tests: {
|
||||
files: {
|
||||
src: ['<%= srcDir %>/test/**/*.js'],
|
||||
}
|
||||
},
|
||||
options: {
|
||||
jshintrc: '<%= baseDir %>/.jshintrc',
|
||||
jshintrc: true,
|
||||
reporter: require('jshint-stylish'),
|
||||
ignores: [
|
||||
'node_modules/*',
|
||||
'dist/*',
|
||||
'sample/*',
|
||||
'<%= srcDir %>/vendor/*',
|
||||
'<%= srcDir %>/app/panels/*/{lib,leaflet}/*'
|
||||
'<%= srcDir %>/app/panels/*/{lib,leaflet}/*',
|
||||
'<%= srcDir %>/app/dashboards/*'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user