mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into panel_edit_menu_poc
This commit is contained in:
commit
6003fee33f
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,5 +1,14 @@
|
||||
# 1.8.0 (unreleased)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #802](https://github.com/grafana/grafana/issues/802). Annotations: Fix when using InfluxDB datasource
|
||||
- [Issue #795](https://github.com/grafana/grafana/issues/795). Chrome: Fix for display issue in chrome beta & chrome canary when entering edit mode
|
||||
- [Issue #818](https://github.com/grafana/grafana/issues/818). Graph: Added percent y-axis format
|
||||
- [Issue #828](https://github.com/grafana/grafana/issues/828). Elasticsearch: saving new dashboard with title equal to slugified url would cause it to deleted.
|
||||
- [Issue #830](https://github.com/grafana/grafana/issues/830). Annotations: Fix for elasticsearch annotations and mapping nested fields
|
||||
|
||||
# 1.8.0-RC1 (2014-09-12)
|
||||
|
||||
**UI polish / changes**
|
||||
- [Issue #725](https://github.com/grafana/grafana/issues/725). UI: All modal editors are removed and replaced by an edit pane under menu. The look of editors is also updated and polished. Search dropdown is also shown as pane under menu and has seen some UI polish.
|
||||
|
||||
@ -13,6 +22,8 @@
|
||||
- [Issue #262](https://github.com/grafana/grafana/issues/262). Templating: Ability to use template variables for function parameters via custom variable type, can be used as parameter for movingAverage or scaleToSeconds for example
|
||||
- [Issue #312](https://github.com/grafana/grafana/issues/312). Templating: Can now use template variables in panel titles
|
||||
- [Issue #613](https://github.com/grafana/grafana/issues/613). Templating: Full support for InfluxDB, filter by part of series names, extract series substrings, nested queries, multipe where clauses!
|
||||
- Template variables can be initialized from url, with var-my_varname=value, breaking change, before it was just my_varname.
|
||||
- Templating and url state sync has some issues that are not solved for this release, see [Issue #772](https://github.com/grafana/grafana/issues/772) for more details.
|
||||
|
||||
**InfluxDB Breaking changes**
|
||||
- To better support templating, fill(0) and group by time low limit some changes has been made to the editor and query model schema
|
||||
@ -41,6 +52,9 @@
|
||||
- [Issue #425](https://github.com/grafana/grafana/issues/425). Graph: New section in 'Display Styles' tab to override any display setting on per series bases (mix and match lines, bars, points, fill, stack, line width etc)
|
||||
- [Issue #634](https://github.com/grafana/grafana/issues/634). Dashboard: Dashboard tags now in different colors (from fixed palette) determined by tag name.
|
||||
- [Issue #685](https://github.com/grafana/grafana/issues/685). Dashboard: New config.js option to change/remove window title prefix.
|
||||
- [Issue #781](https://github.com/grafana/grafana/issues/781). Dashboard: Title URL is now slugified for greater URL readability, works with both ES & InfluxDB storage, is backward compatible
|
||||
- [Issue #785](https://github.com/grafana/grafana/issues/785). Elasticsearch: Support for full elasticsearch lucene search grammar when searching for dashboards, better async search
|
||||
- [Issue #787](https://github.com/grafana/grafana/issues/787). Dashboard: time range can now be read from URL parameters, will override dashboard saved time range
|
||||
|
||||
**Fixes**
|
||||
- [Issue #696](https://github.com/grafana/grafana/issues/696). Graph: Fix for y-axis format 'none' when values are in scientific notation (ex 2.3e-13)
|
||||
@ -234,7 +248,7 @@ Read this for more info:
|
||||
- More graphite function definitions
|
||||
- Make "ms" axis format include hour, day, weeks, month and year ([Issue #149](https://github.com/grafana/grafana/issues/149))
|
||||
- Microsecond axis format ([Issue #146](https://github.com/grafana/grafana/issues/146))
|
||||
- Specify template paramaters in URL ([Issue #123](https://github.com/grafana/grafana/issues/123))
|
||||
- Specify template parameters in URL ([Issue #123](https://github.com/grafana/grafana/issues/123))
|
||||
|
||||
### Fixes
|
||||
- Basic Auth fix ([Issue #152](https://github.com/grafana/grafana/issues/152))
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "1.7.0",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.7.0"
|
||||
"version": "1.8.0-rc1",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.0-rc1"
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.0-rc1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
|
@ -531,6 +531,10 @@ function($, _, moment) {
|
||||
return function(val) {
|
||||
return kbn.nanosFormat(val, decimals);
|
||||
};
|
||||
case 'percent':
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
|
||||
};
|
||||
default:
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
|
||||
@ -563,8 +567,8 @@ function($, _, moment) {
|
||||
|
||||
kbn.msFormat = function(size, decimals) {
|
||||
// Less than 1 milli, downscale to micro
|
||||
if (Math.abs(size) < 1) {
|
||||
return kbn.microsFormat(size * 1000,decimals);
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.microsFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(decimals) + " ms";
|
||||
@ -591,7 +595,7 @@ function($, _, moment) {
|
||||
|
||||
kbn.sFormat = function(size, decimals) {
|
||||
// Less than 1 sec, downscale to milli
|
||||
if (Math.abs(size) < 1) {
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.msFormat(size * 1000, decimals);
|
||||
}
|
||||
// Less than 10 min, use seconds
|
||||
@ -620,7 +624,7 @@ function($, _, moment) {
|
||||
|
||||
kbn.microsFormat = function(size, decimals) {
|
||||
// Less than 1 micro, downscale to nano
|
||||
if (Math.abs(size) < 1) {
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.nanosFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
@ -655,6 +659,13 @@ function($, _, moment) {
|
||||
}
|
||||
};
|
||||
|
||||
kbn.slugifyForUrl = function(str) {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace(/[^\w ]+/g,'')
|
||||
.replace(/ +/g,'-');
|
||||
};
|
||||
|
||||
kbn.stringToJsRegex = function(str) {
|
||||
if (str[0] !== '/') {
|
||||
return new RegExp(str);
|
||||
|
@ -44,14 +44,14 @@ function (angular, $, config, _) {
|
||||
$scope.setupDashboard = function(event, dashboardData) {
|
||||
$rootScope.performance.dashboardLoadStart = new Date().getTime();
|
||||
$rootScope.performance.panelsInitialized = 0;
|
||||
$rootScope.performance.panelsRendered= 0;
|
||||
$rootScope.performance.panelsRendered = 0;
|
||||
|
||||
$scope.dashboard = dashboardSrv.create(dashboardData);
|
||||
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
|
||||
|
||||
// init services
|
||||
timeSrv.init($scope.dashboard);
|
||||
templateValuesSrv.init($scope.dashboard);
|
||||
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
|
||||
panelMoveSrv.init($scope.dashboard, $scope);
|
||||
|
||||
$scope.checkFeatureToggles();
|
||||
@ -93,18 +93,18 @@ function (angular, $, config, _) {
|
||||
};
|
||||
};
|
||||
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
$scope.edit_path = function(type) {
|
||||
var p = $scope.panel_path(type);
|
||||
if(p) {
|
||||
return p+'/editor.html';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.edit_path = function(type) {
|
||||
var p = $scope.panel_path(type);
|
||||
if(p) {
|
||||
return p+'/editor.html';
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ function (angular, _, moment, config, store) {
|
||||
var clone = angular.copy($scope.dashboard);
|
||||
$scope.db.saveDashboard(clone)
|
||||
.then(function(result) {
|
||||
alertSrv.set('Dashboard Saved', 'Dashboard has been saved as "' + result.title + '"','success', 5000);
|
||||
alertSrv.set('Dashboard Saved', 'Saved as "' + result.title + '"','success', 3000);
|
||||
|
||||
if (result.url !== $location.path()) {
|
||||
$location.search({});
|
||||
@ -88,7 +88,7 @@ function (angular, _, moment, config, store) {
|
||||
$rootScope.$emit('dashboard-saved', $scope.dashboard);
|
||||
|
||||
}, function(err) {
|
||||
alertSrv.set('Save failed', err, 'error',5000);
|
||||
alertSrv.set('Save failed', err, 'error', 5000);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.target.target = $scope.target.target || '';
|
||||
$scope.targetLetter = targetLetters[$scope.$index];
|
||||
$scope.targetLetters = targetLetters;
|
||||
|
||||
parseTarget();
|
||||
};
|
||||
@ -90,7 +90,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
break;
|
||||
case 'metric':
|
||||
if ($scope.segments.length > 0) {
|
||||
if ($scope.segments[0].length !== 1) {
|
||||
if (astNode.segments.length !== 1) {
|
||||
throw { message: 'Multiple metric params not supported, use text editor.' };
|
||||
}
|
||||
addFunctionParameter(func, astNode.segments[0].value, index, true);
|
||||
|
@ -17,6 +17,7 @@ function (angular, _, config, $) {
|
||||
$scope.results = {dashboards: [], tags: [], metrics: []};
|
||||
$scope.query = { query: 'title:' };
|
||||
$scope.db = datasourceSrv.getGrafanaDB();
|
||||
$scope.currentSearchId = 0;
|
||||
|
||||
$timeout(function() {
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
@ -75,8 +76,18 @@ function (angular, _, config, $) {
|
||||
};
|
||||
|
||||
$scope.searchDashboards = function(queryString) {
|
||||
// bookeeping for determining stale search requests
|
||||
var searchId = $scope.currentSearchId + 1;
|
||||
$scope.currentSearchId = searchId > $scope.currentSearchId ? searchId : $scope.currentSearchId;
|
||||
|
||||
return $scope.db.searchDashboards(queryString)
|
||||
.then(function(results) {
|
||||
// since searches are async, it's possible that these results are not for the latest search. throw
|
||||
// them away if so
|
||||
if (searchId < $scope.currentSearchId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tagsOnly = results.tagsOnly;
|
||||
$scope.results.dashboards = results.dashboards;
|
||||
$scope.results.tags = results.tags;
|
||||
@ -108,9 +119,10 @@ function (angular, _, config, $) {
|
||||
$scope.searchDashboards($scope.query.query);
|
||||
};
|
||||
|
||||
$scope.deleteDashboard = function(id, evt) {
|
||||
$scope.deleteDashboard = function(dash, evt) {
|
||||
evt.stopPropagation();
|
||||
$scope.emitAppEvent('delete-dashboard', { id: id });
|
||||
$scope.emitAppEvent('delete-dashboard', { id: dash.id });
|
||||
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
|
||||
};
|
||||
|
||||
$scope.addMetricToCurrentDashboard = function (metricId) {
|
||||
|
@ -9,14 +9,15 @@
|
||||
"title": "New row",
|
||||
"height": "150px",
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"id": 1,
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"http://grafana.org/assets/img/logo_transparent_200x75.png\"> \n</div>",
|
||||
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"//grafana.org/assets/img/logo_transparent_200x75.png\"> \n</div>",
|
||||
"style": {},
|
||||
"title": "Welcome to"
|
||||
}
|
||||
@ -26,22 +27,20 @@
|
||||
"title": "Welcome to Grafana",
|
||||
"height": "210px",
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"id": 2,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "html",
|
||||
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
|
||||
"style": {},
|
||||
"title": "Documentation Links"
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"id": 3,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
|
||||
@ -53,11 +52,12 @@
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"panels": [
|
||||
{
|
||||
"id": 4,
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
|
@ -35,11 +35,9 @@ return function(callback) {
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
}
|
||||
dashboard.time = {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
@ -78,4 +76,4 @@ return function(callback) {
|
||||
callback(dashboard);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
264
src/app/dashboards/template_vars.json
Normal file
264
src/app/dashboards/template_vars.json
Normal file
@ -0,0 +1,264 @@
|
||||
{
|
||||
"id": null,
|
||||
"title": "Templated Graphs Nested",
|
||||
"originalTitle": "Templated Graphs Nested",
|
||||
"tags": [
|
||||
"showcase",
|
||||
"templated"
|
||||
],
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"hideControls": false,
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)",
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(apps.$app.$server.counters.requests.count, 2)",
|
||||
"function": "mean",
|
||||
"column": "value"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"highres.test": "#1F78C1",
|
||||
"scale(highres.test,3)": "#6ED0E0",
|
||||
"mobile": "#6ED0E0",
|
||||
"tablet": "#EAB839"
|
||||
},
|
||||
"title": "Traffic [[period]]",
|
||||
"id": 1,
|
||||
"seriesOverrides": []
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)",
|
||||
"leftMax": null,
|
||||
"rightMax": null,
|
||||
"leftMin": null,
|
||||
"rightMin": null
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(apps.$app.$server.counters.requests.count, 2)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"highres.test": "#1F78C1",
|
||||
"scale(highres.test,3)": "#6ED0E0",
|
||||
"mobile": "#6ED0E0",
|
||||
"tablet": "#EAB839"
|
||||
},
|
||||
"title": "Second pannel",
|
||||
"id": 2,
|
||||
"seriesOverrides": []
|
||||
}
|
||||
],
|
||||
"notice": 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
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-15m",
|
||||
"to": "now"
|
||||
},
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"type": "query",
|
||||
"name": "app",
|
||||
"query": "apps.*",
|
||||
"includeAll": true,
|
||||
"options": [],
|
||||
"current": {
|
||||
"text": "All",
|
||||
"value": "*"
|
||||
},
|
||||
"datasource": null,
|
||||
"allFormat": "wildcard",
|
||||
"refresh": true
|
||||
},
|
||||
{
|
||||
"type": "query",
|
||||
"name": "server",
|
||||
"query": "apps.$app.*",
|
||||
"includeAll": true,
|
||||
"options": [],
|
||||
"current": {
|
||||
"text": "All",
|
||||
"value": "*"
|
||||
},
|
||||
"datasource": null,
|
||||
"allFormat": "Glob",
|
||||
"refresh": false
|
||||
},
|
||||
{
|
||||
"type": "query",
|
||||
"datasource": null,
|
||||
"refresh_on_load": false,
|
||||
"name": "metric",
|
||||
"options": [],
|
||||
"includeAll": true,
|
||||
"allFormat": "glob",
|
||||
"query": "apps.$app.$server.*",
|
||||
"current": {
|
||||
"text": "counters",
|
||||
"value": "counters"
|
||||
}
|
||||
}
|
||||
],
|
||||
"enable": true
|
||||
},
|
||||
"annotations": {
|
||||
"enable": false
|
||||
},
|
||||
"refresh": false,
|
||||
"version": 6
|
||||
}
|
@ -15,7 +15,7 @@ function (angular, $, kbn, moment, _) {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot, annotations;
|
||||
var data, annotations;
|
||||
var hiddenData = {};
|
||||
var dashboard = scope.dashboard;
|
||||
var legendSideLastValue = null;
|
||||
@ -82,6 +82,10 @@ function (angular, $, kbn, moment, _) {
|
||||
render_panel_as_graphite_png(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (elem.width() === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
@ -165,18 +169,22 @@ function (angular, $, kbn, moment, _) {
|
||||
|
||||
var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
// if legend is to the right delay plot draw a few milliseconds
|
||||
// so the legend width calculation can be done
|
||||
function callPlot() {
|
||||
try {
|
||||
$.plot(elem, sortedSeries, options);
|
||||
} catch (e) {
|
||||
console.log('flotcharts error', e);
|
||||
}
|
||||
|
||||
addAxisLabels();
|
||||
}
|
||||
|
||||
if (shouldDelayDraw(panel)) {
|
||||
setTimeout(callPlot, 50);
|
||||
legendSideLastValue = panel.legend.rightSide;
|
||||
setTimeout(function() {
|
||||
plot = $.plot(elem, sortedSeries, options);
|
||||
addAxisLabels();
|
||||
}, 50);
|
||||
}
|
||||
else {
|
||||
plot = $.plot(elem, sortedSeries, options);
|
||||
addAxisLabels();
|
||||
callPlot();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ function (angular, app, _, $) {
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({ source: $scope.source, minLength: 0, items: 100, updater: $scope.updater });
|
||||
$input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater });
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
|
@ -11,10 +11,10 @@ function (angular, kbn) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
var _t = '<i class="icon-'+(attrs.icon||'question-sign')+'" bs-tooltip="\''+
|
||||
var _t = '<i class="grafana-tip icon-'+(attrs.icon||'question-sign')+'" bs-tooltip="\''+
|
||||
kbn.addslashes(elem.text())+'\'"></i>';
|
||||
elem.replaceWith($compile(angular.element(_t))(scope));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -57,7 +57,7 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen
|
||||
|
||||
module.filter('interpolateTemplateVars', function(templateSrv) {
|
||||
return function(text) {
|
||||
return templateSrv.replace(text);
|
||||
return templateSrv.replaceWithText(text);
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<h5>Left Y Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">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', 'bits', 'bps', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
|
||||
@ -23,7 +23,7 @@
|
||||
<h5>Right Y Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">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', 'bits', 'bps', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
|
||||
|
@ -70,7 +70,7 @@ define([
|
||||
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
|
||||
$scope.addOverrideOption('Points', 'points', [true, false]);
|
||||
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
|
||||
$scope.addOverrideOption('Stack', 'stack', [true, false]);
|
||||
$scope.addOverrideOption('Stack', 'stack', [true, false, 2, 3, 4, 5]);
|
||||
$scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
|
||||
$scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
|
||||
$scope.updateCurrentOverrides();
|
||||
|
@ -27,7 +27,7 @@
|
||||
<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>
|
||||
<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">
|
||||
|
@ -9,10 +9,9 @@
|
||||
</div>
|
||||
|
||||
<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%" ng-change="render()" ng-model-onblur>
|
||||
<textarea ng-model="panel.content" rows="20" style="width:95%" ng-change="render()" ng-model-onblur>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,6 +16,9 @@
|
||||
<div class="dashboard-editor-body">
|
||||
<div class="editor-row row" ng-if="editor.index == 0">
|
||||
<div class="span6">
|
||||
<div ng-if="variables.length === 0">
|
||||
<em>No annotations defined</em>
|
||||
</div>
|
||||
<table class="grafana-options-table">
|
||||
<tr ng-repeat="annotation in annotations">
|
||||
<td style="width:90%">
|
||||
|
@ -1,7 +1,10 @@
|
||||
<div class="navbar navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<span class="brand"><img src="img/small.png" bs-tooltip="'Grafana'" data-placement="bottom"> {{dashboard.title}}</span>
|
||||
<span class="brand">
|
||||
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
|
||||
<span class="page-title">{{dashboard.title}}</span>
|
||||
</span>
|
||||
<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
|
||||
|
||||
<li ng-show="dashboardViewState.fullscreen">
|
||||
|
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body" style="height: 500px">
|
||||
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 90%; color: white"></textarea>
|
||||
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 90%;"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-footer">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="editor-row" style="margin-top: 10px;">
|
||||
<div class="editor-row">
|
||||
|
||||
<div ng-repeat="target in panel.targets"
|
||||
class="grafana-target"
|
||||
@ -30,13 +30,6 @@
|
||||
ng-click="duplicate()">
|
||||
Duplicate
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1"
|
||||
ng-click="toggleMetricOptions()">
|
||||
Toggle request options
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
@ -48,7 +41,7 @@
|
||||
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment" style="min-width: 15px; text-align: center">
|
||||
{{targetLetter}}
|
||||
{{targetLetters[$index]}}
|
||||
</li>
|
||||
<li>
|
||||
<a class="grafana-target-segment"
|
||||
@ -80,24 +73,109 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grafana-target grafana-metric-options" ng-if="panel.metricOptionsEnabled">
|
||||
</div>
|
||||
|
||||
<section class="grafana-metric-options">
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment grafana-target-segment-icon">
|
||||
<i class="icon-wrench"></i>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
cacheTimeout <tip>Graphite parameter to overwride memcache default timeout (unit is seconds)</tip>
|
||||
cacheTimeout
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-large grafana-target-segment-input"
|
||||
ng-model="panel.cacheTimeout"
|
||||
spellcheck='false'
|
||||
placeholder="60">
|
||||
class="input-mini grafana-target-segment-input"
|
||||
ng-model="panel.cacheTimeout"
|
||||
bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
placeholder="60">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment grafana-target-segment-icon">
|
||||
<i class="icon-info-sign"></i>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
shorter legend names
|
||||
</a>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
series as parameters
|
||||
</a>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
stacking
|
||||
</a>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
templating
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="pull-left" style="margin-top: 30px;">
|
||||
|
||||
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
|
||||
<h5>Shorter legend names</h5>
|
||||
<ul>
|
||||
<li>alias() function to specify a custom series name</li>
|
||||
<li>aliasByNode(2) to alias by a specific part of your metric path</li>
|
||||
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
|
||||
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
|
||||
<h5>Series as parameter</h5>
|
||||
<ul>
|
||||
<li>Some graphite functions allow you to have many series arguments</li>
|
||||
<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
|
||||
<li>
|
||||
Examples:
|
||||
<ul>
|
||||
<li>asPercent(#A, #B)</li>
|
||||
<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
|
||||
<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
|
||||
<li>divideSeries(#A, #B)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
|
||||
<h5>Stacking</h5>
|
||||
<ul>
|
||||
<li>You find the stacking option under Display Styles tab</li>
|
||||
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
|
||||
<h5>Templating</h5>
|
||||
<ul>
|
||||
<li>You can use a template variable in place of metric names</li>
|
||||
<li>You can use a template variable in place of function parameters</li>
|
||||
<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -181,8 +181,8 @@
|
||||
<div class="grafana-target">
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment">
|
||||
<i class="icon-cogs"></i>
|
||||
<li class="grafana-target-segment grafana-target-segment-icon">
|
||||
<i class="icon-wrench"></i>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
group by time
|
||||
@ -227,8 +227,8 @@
|
||||
<div class="editor-row">
|
||||
<div class="pull-left" style="margin-top: 30px;">
|
||||
|
||||
<div class="span6" ng-if="editorHelpIndex === 1">
|
||||
Alias patterns:
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
|
||||
<h5>Alias patterns</h5>
|
||||
<ul>
|
||||
<li>$s = series name</li>
|
||||
<li>$g = group by</li>
|
||||
@ -236,8 +236,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="span6" ng-if="editorHelpIndex === 2">
|
||||
Stacking and fill:
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
|
||||
<h5>Stacking and fill</h5>
|
||||
<ul>
|
||||
<li>When stacking is enabled it important that points align</li>
|
||||
<li>If there are missing points for one series it can cause gaps or missing bars</li>
|
||||
@ -247,8 +247,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="span6" ng-if="editorHelpIndex === 3">
|
||||
Group by time:
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
|
||||
<h5>Group by time</h5>
|
||||
<ul>
|
||||
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
|
||||
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
|
||||
|
@ -30,7 +30,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-target-segment-list">
|
||||
<ul class="grafana-segment-list">
|
||||
<li>
|
||||
<a class="grafana-target-segment"
|
||||
ng-click="target.hide = !target.hide; get_data();"
|
||||
|
@ -1,6 +0,0 @@
|
||||
<div ng-include="'app/partials/panelgeneral.html'"></div>
|
||||
<div ng-if="!panelMeta.fullEditorTabs" ng-include="edit_path(panel.type)"></div>
|
||||
<div ng-repeat="tab in panelMeta.editorTabs">
|
||||
<h5>{{tab.title}}</h5>
|
||||
<div ng-include="tab.src"></div>
|
||||
</div>
|
@ -38,7 +38,7 @@
|
||||
<div class="editor-option">
|
||||
<div class="span4">
|
||||
<span><i class="icon-question-sign"></i>
|
||||
dashboards available in the playlist are only the once marked as favorites (stored in local browser storage).
|
||||
dashboards available in the playlist are only the ones marked as favorites (stored in local browser storage).
|
||||
to mark a dashboard as favorite, use save icon in the menu and in the dropdown select mark as favorite
|
||||
<br/><br/>
|
||||
</span>
|
||||
|
@ -54,7 +54,7 @@
|
||||
<a ng-click="shareDashboard(row.id, row.id, $event)" config-modal="app/partials/dashLoaderShare.html">
|
||||
<i class="icon-share"></i> share
|
||||
</a>
|
||||
<a ng-click="deleteDashboard(row.id, $event)">
|
||||
<a ng-click="deleteDashboard(row, $event)">
|
||||
<i class="icon-remove"></i> delete
|
||||
</a>
|
||||
</div>
|
||||
|
@ -57,7 +57,8 @@ define([
|
||||
|
||||
function errorHandler(err) {
|
||||
console.log('Annotation error: ', err);
|
||||
alertSrv.set('Annotations','Could not fetch annotations','error');
|
||||
var message = err.message || "Aannotation query failed";
|
||||
alertSrv.set('Annotations error', message,'error');
|
||||
}
|
||||
|
||||
function addAnnotation(options) {
|
||||
|
@ -18,8 +18,8 @@ function(angular, $) {
|
||||
keyboardManager.unbind('ctrl+s');
|
||||
keyboardManager.unbind('ctrl+r');
|
||||
keyboardManager.unbind('ctrl+z');
|
||||
keyboardManager.unbind('esc');
|
||||
});
|
||||
keyboardManager.unbind('esc');
|
||||
|
||||
keyboardManager.bind('ctrl+f', function() {
|
||||
scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
|
@ -131,7 +131,7 @@ function (angular, $, kbn, _, moment) {
|
||||
if (old.services) {
|
||||
if (old.services.filter) {
|
||||
this.time = old.services.filter.time;
|
||||
this.templating.list = old.services.filter.list;
|
||||
this.templating.list = old.services.filter.list || [];
|
||||
}
|
||||
delete this.services;
|
||||
}
|
||||
|
@ -14,9 +14,12 @@ function (angular, _, $) {
|
||||
// like fullscreen panel & edit
|
||||
function DashboardViewState($scope) {
|
||||
var self = this;
|
||||
self.state = {};
|
||||
self.panelScopes = [];
|
||||
self.$scope = $scope;
|
||||
|
||||
$scope.exitFullscreen = function() {
|
||||
if (self.fullscreen) {
|
||||
if (self.state.fullscreen) {
|
||||
self.update({ fullscreen: false });
|
||||
}
|
||||
};
|
||||
@ -28,42 +31,48 @@ function (angular, _, $) {
|
||||
}
|
||||
});
|
||||
|
||||
this.panelScopes = [];
|
||||
this.$scope = $scope;
|
||||
|
||||
this.update(this.getQueryStringState(), true);
|
||||
}
|
||||
|
||||
DashboardViewState.prototype.needsSync = function(urlState) {
|
||||
if (urlState.fullscreen !== this.fullscreen) { return true; }
|
||||
if (urlState.edit !== this.edit) { return true; }
|
||||
if (urlState.panelId !== this.panelId) { return true; }
|
||||
return false;
|
||||
return _.isEqual(this.state, urlState) === false;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.getQueryStringState = function() {
|
||||
var queryParams = $location.search();
|
||||
return {
|
||||
var urlState = {
|
||||
panelId: parseInt(queryParams.panelId) || null,
|
||||
fullscreen: queryParams.fullscreen ? true : false,
|
||||
edit: queryParams.edit ? true : false
|
||||
edit: queryParams.edit ? true : false,
|
||||
};
|
||||
|
||||
_.each(queryParams, function(value, key) {
|
||||
if (key.indexOf('var-') !== 0) { return; }
|
||||
urlState[key] = value;
|
||||
});
|
||||
|
||||
return urlState;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.serializeToUrl = function() {
|
||||
var urlState = _.clone(this.state);
|
||||
urlState.fullscreen = this.state.fullscreen ? true : null,
|
||||
urlState.edit = this.state.edit ? true : null;
|
||||
|
||||
return urlState;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.update = function(state, skipUrlSync) {
|
||||
_.extend(this, state);
|
||||
_.extend(this.state, state);
|
||||
this.fullscreen = this.state.fullscreen;
|
||||
|
||||
if (!this.fullscreen) {
|
||||
this.panelId = null;
|
||||
this.edit = false;
|
||||
if (!this.state.fullscreen) {
|
||||
this.state.panelId = null;
|
||||
this.state.edit = false;
|
||||
}
|
||||
|
||||
if (!skipUrlSync) {
|
||||
$location.search({
|
||||
fullscreen: this.fullscreen ? true : null,
|
||||
panelId: this.panelId,
|
||||
edit: this.edit ? true : null
|
||||
});
|
||||
$location.search(this.serializeToUrl());
|
||||
}
|
||||
|
||||
this.syncState();
|
||||
@ -76,7 +85,7 @@ function (angular, _, $) {
|
||||
if (this.fullscreenPanel) {
|
||||
this.leaveFullscreen(false);
|
||||
}
|
||||
var panelScope = this.getPanelScope(this.panelId);
|
||||
var panelScope = this.getPanelScope(this.state.panelId);
|
||||
this.enterFullscreen(panelScope);
|
||||
return;
|
||||
}
|
||||
@ -118,8 +127,8 @@ function (angular, _, $) {
|
||||
var fullscreenHeight = Math.floor(docHeight * 0.7);
|
||||
this.oldTimeRange = panelScope.range;
|
||||
|
||||
panelScope.height = this.edit ? editHeight : fullscreenHeight;
|
||||
panelScope.editMode = this.edit;
|
||||
panelScope.height = this.state.edit ? editHeight : fullscreenHeight;
|
||||
panelScope.editMode = this.state.edit;
|
||||
this.fullscreenPanel = panelScope;
|
||||
|
||||
$(window).scrollTop(0);
|
||||
@ -135,7 +144,7 @@ function (angular, _, $) {
|
||||
var self = this;
|
||||
self.panelScopes.push(panelScope);
|
||||
|
||||
if (self.panelId === panelScope.panel.id) {
|
||||
if (self.state.panelId === panelScope.panel.id) {
|
||||
self.enterFullscreen(panelScope);
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ function (angular, _, config) {
|
||||
if (!name) { return this.default; }
|
||||
if (datasources[name]) { return datasources[name]; }
|
||||
|
||||
throw "Unable to find datasource: " + name;
|
||||
return this.default;
|
||||
};
|
||||
|
||||
this.getAnnotationSources = function() {
|
||||
|
@ -1,12 +1,11 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'config',
|
||||
'kbn',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, $, config, kbn, moment) {
|
||||
function (angular, _, config, kbn, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
@ -38,6 +37,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
};
|
||||
|
||||
if (this.basicAuth) {
|
||||
options.withCredentials = true;
|
||||
options.headers = {
|
||||
"Authorization": "Basic " + this.basicAuth
|
||||
};
|
||||
@ -76,57 +76,78 @@ function (angular, _, $, config, kbn, moment) {
|
||||
var queryInterpolated = templateSrv.replace(queryString);
|
||||
var filter = { "bool": { "must": [{ "range": range }] } };
|
||||
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
|
||||
var data = { "query" : { "filtered": { "query" : query, "filter": filter } }, "size": 100 };
|
||||
var data = {
|
||||
"fields": [timeField, "_source"],
|
||||
"query" : { "filtered": { "query" : query, "filter": filter } },
|
||||
"size": 100
|
||||
};
|
||||
|
||||
return this._request('POST', '/_search', annotation.index, data).then(function(results) {
|
||||
var list = [];
|
||||
var hits = results.data.hits.hits;
|
||||
|
||||
var getFieldFromSource = function(source, fieldName) {
|
||||
if (!fieldName) { return; }
|
||||
|
||||
var fieldNames = fieldName.split('.');
|
||||
var fieldValue = source;
|
||||
|
||||
for (var i = 0; i < fieldNames.length; i++) {
|
||||
fieldValue = fieldValue[fieldNames[i]];
|
||||
}
|
||||
|
||||
if (_.isArray(fieldValue)) {
|
||||
fieldValue = fieldValue.join(', ');
|
||||
}
|
||||
return fieldValue;
|
||||
};
|
||||
|
||||
for (var i = 0; i < hits.length; i++) {
|
||||
var source = hits[i]._source;
|
||||
var fields = hits[i].fields;
|
||||
var time = source[timeField];
|
||||
|
||||
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
|
||||
time = fields[timeField];
|
||||
}
|
||||
|
||||
var event = {
|
||||
annotation: annotation,
|
||||
time: moment.utc(source[timeField]).valueOf(),
|
||||
title: source[titleField],
|
||||
time: moment.utc(time).valueOf(),
|
||||
title: getFieldFromSource(source, titleField),
|
||||
tags: getFieldFromSource(source, tagsField),
|
||||
text: getFieldFromSource(source, textField)
|
||||
};
|
||||
|
||||
if (source[tagsField]) {
|
||||
if (_.isArray(source[tagsField])) {
|
||||
event.tags = source[tagsField].join(', ');
|
||||
}
|
||||
else {
|
||||
event.tags = source[tagsField];
|
||||
}
|
||||
}
|
||||
if (textField && source[textField]) {
|
||||
event.text = source[textField];
|
||||
}
|
||||
|
||||
list.push(event);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
|
||||
return this._get('/dashboard/' + kbn.slugifyForUrl(id))
|
||||
.then(function(result) {
|
||||
return angular.fromJson(result._source.dashboard);
|
||||
}, function() {
|
||||
throw "Dashboard not found";
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
|
||||
var url = '/dashboard/' + id;
|
||||
if (isTemp) { url = '/temp/' + id; }
|
||||
|
||||
if (isTemp) {
|
||||
url = '/temp/' + id;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this._get(url)
|
||||
.then(function(result) {
|
||||
if (result._source && result._source.dashboard) {
|
||||
return angular.fromJson(result._source.dashboard);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return angular.fromJson(result._source.dashboard);
|
||||
}, function(data) {
|
||||
if(data.status === 0) {
|
||||
throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
|
||||
} else {
|
||||
throw "Could not find dashboard " + id;
|
||||
// backward compatible fallback
|
||||
return self._getDashboardWithSlug(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -148,15 +169,30 @@ function (angular, _, $, config, kbn, moment) {
|
||||
return this._saveTempDashboard(data);
|
||||
}
|
||||
else {
|
||||
return this._request('PUT', '/dashboard/' + encodeURIComponent(title), this.index, data)
|
||||
.then(function() {
|
||||
return { title: title, url: '/dashboard/db/' + title };
|
||||
}, function(err) {
|
||||
throw 'Failed to save to elasticsearch ' + err.data;
|
||||
|
||||
var id = encodeURIComponent(kbn.slugifyForUrl(title));
|
||||
var self = this;
|
||||
|
||||
return this._request('PUT', '/dashboard/' + id, this.index, data)
|
||||
.then(function(results) {
|
||||
self._removeUnslugifiedDashboard(results, title, id);
|
||||
return { title: title, url: '/dashboard/db/' + id };
|
||||
}, function() {
|
||||
throw 'Failed to save to elasticsearch';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title, id) {
|
||||
if (saveResult.statusText !== 'Created') { return; }
|
||||
if (title === id) { return; }
|
||||
|
||||
var self = this;
|
||||
this._get('/dashboard/' + title).then(function() {
|
||||
self.deleteDashboard(title);
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._saveTempDashboard = function(data) {
|
||||
return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
|
||||
.then(function(result) {
|
||||
@ -181,7 +217,21 @@ function (angular, _, $, config, kbn, moment) {
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.searchDashboards = function(queryString) {
|
||||
queryString = queryString.toLowerCase().replace(' and ', ' AND ');
|
||||
var endsInOpen = function(string, opener, closer) {
|
||||
var character;
|
||||
var count = 0;
|
||||
for (var i=0; i<string.length; i++) {
|
||||
character = string[i];
|
||||
|
||||
if (character === opener) {
|
||||
count++;
|
||||
} else if (character === closer) {
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
|
||||
var tagsOnly = queryString.indexOf('tags!:') === 0;
|
||||
if (tagsOnly) {
|
||||
@ -193,7 +243,21 @@ function (angular, _, $, config, kbn, moment) {
|
||||
queryString = 'title:';
|
||||
}
|
||||
|
||||
if (queryString[queryString.length - 1] !== '*') {
|
||||
// make this a partial search if we're not in some reserved portion of the language, comments on conditionals, in order:
|
||||
// 1. ends in reserved character, boosting, boolean operator ( -foo)
|
||||
// 2. typing a reserved word like AND, OR, NOT
|
||||
// 3. open parens (groupiing)
|
||||
// 4. open " (term phrase)
|
||||
// 5. open [ (range)
|
||||
// 6. open { (range)
|
||||
// see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax
|
||||
if (!queryString.match(/(\*|\]|}|~|\)|"|^\d+|\s[\-+]\w+)$/) &&
|
||||
!queryString.match(/[A-Z]$/) &&
|
||||
!endsInOpen(queryString, '(', ')') &&
|
||||
!endsInOpen(queryString, '"', '"') &&
|
||||
!endsInOpen(queryString, '[', ']') && !endsInOpen(queryString, '[', '}') &&
|
||||
!endsInOpen(queryString, '{', ']') && !endsInOpen(queryString, '{', '}')
|
||||
){
|
||||
queryString += '*';
|
||||
}
|
||||
}
|
||||
@ -216,7 +280,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
for (var i = 0; i < results.hits.hits.length; i++) {
|
||||
hits.dashboards.push({
|
||||
id: results.hits.hits[i]._id,
|
||||
title: results.hits.hits[i]._id,
|
||||
title: results.hits.hits[i]._source.title,
|
||||
tags: results.hits.hits[i]._source.tags
|
||||
});
|
||||
}
|
||||
|
@ -68,7 +68,14 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'diffSeries',
|
||||
params: optionalSeriesRefArgs,
|
||||
defaultParams: ['#B'],
|
||||
defaultParams: ['#A'],
|
||||
category: categories.Calculate,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'divideSeries',
|
||||
params: optionalSeriesRefArgs,
|
||||
defaultParams: ['#A'],
|
||||
category: categories.Calculate,
|
||||
});
|
||||
|
||||
@ -79,6 +86,13 @@ function (_) {
|
||||
category: categories.Calculate,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'group',
|
||||
params: optionalSeriesRefArgs,
|
||||
defaultParams: ['#A', '#B'],
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sumSeries',
|
||||
shortName: 'sum',
|
||||
|
@ -19,6 +19,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
this.username = datasource.username;
|
||||
this.password = datasource.password;
|
||||
this.name = datasource.name;
|
||||
this.basicAuth = datasource.basicAuth;
|
||||
|
||||
this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
|
||||
this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;
|
||||
@ -63,7 +64,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
|
||||
var timeFilter = getTimeFilter({ range: rangeUnparsed });
|
||||
var query = annotation.query.replace('$timeFilter', timeFilter);
|
||||
query = templateSrv.replace(annotation.query);
|
||||
query = templateSrv.replace(query);
|
||||
|
||||
return this._seriesQuery(query).then(function(results) {
|
||||
return new InfluxSeries({ seriesList: results, annotation: annotation }).getAnnotations();
|
||||
@ -170,6 +171,11 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
inspect: { type: 'influxdb' },
|
||||
};
|
||||
|
||||
options.headers = options.headers || {};
|
||||
if (_this.basicAuth) {
|
||||
options.headers.Authorization = 'Basic ' + _this.basicAuth;
|
||||
}
|
||||
|
||||
return $http(options).success(function (data) {
|
||||
deferred.resolve(data);
|
||||
});
|
||||
@ -182,34 +188,46 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
var tags = dashboard.tags.join(',');
|
||||
var title = dashboard.title;
|
||||
var temp = dashboard.temp;
|
||||
var id = kbn.slugifyForUrl(title);
|
||||
if (temp) { delete dashboard.temp; }
|
||||
|
||||
var data = [{
|
||||
name: 'grafana.dashboard_' + btoa(title),
|
||||
columns: ['time', 'sequence_number', 'title', 'tags', 'dashboard'],
|
||||
points: [[1000000000000, 1, title, tags, angular.toJson(dashboard)]]
|
||||
name: 'grafana.dashboard_' + btoa(id),
|
||||
columns: ['time', 'sequence_number', 'title', 'tags', 'dashboard', 'id'],
|
||||
points: [[1000000000000, 1, title, tags, angular.toJson(dashboard), id]]
|
||||
}];
|
||||
|
||||
if (temp) {
|
||||
return this._saveDashboardTemp(data, title);
|
||||
return this._saveDashboardTemp(data, title, id);
|
||||
}
|
||||
else {
|
||||
var self = this;
|
||||
return this._influxRequest('POST', '/series', data).then(function() {
|
||||
return { title: title, url: '/dashboard/db/' + title };
|
||||
self._removeUnslugifiedDashboard(title, false);
|
||||
return { title: title, url: '/dashboard/db/' + id };
|
||||
}, function(err) {
|
||||
throw 'Failed to save dashboard to InfluxDB: ' + err.data;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype._saveDashboardTemp = function(data, title) {
|
||||
data[0].name = 'grafana.temp_dashboard_' + btoa(title);
|
||||
InfluxDatasource.prototype._removeUnslugifiedDashboard = function(id, isTemp) {
|
||||
var self = this;
|
||||
self._getDashboardInternal(id, isTemp).then(function(dashboard) {
|
||||
if (dashboard !== null) {
|
||||
self.deleteDashboard(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype._saveDashboardTemp = function(data, title, id) {
|
||||
data[0].name = 'grafana.temp_dashboard_' + btoa(id);
|
||||
data[0].columns.push('expires');
|
||||
data[0].points[0].push(this._getTempDashboardExpiresDate());
|
||||
|
||||
return this._influxRequest('POST', '/series', data).then(function() {
|
||||
var baseUrl = window.location.href.replace(window.location.hash,'');
|
||||
var url = baseUrl + "#dashboard/temp/" + title;
|
||||
var url = baseUrl + "#dashboard/temp/" + id;
|
||||
return { title: title, url: url };
|
||||
}, function(err) {
|
||||
throw 'Failed to save shared dashboard to InfluxDB: ' + err.data;
|
||||
@ -236,7 +254,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
return expires;
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.getDashboard = function(id, isTemp) {
|
||||
InfluxDatasource.prototype._getDashboardInternal = function(id, isTemp) {
|
||||
var queryString = 'select dashboard from "grafana.dashboard_' + btoa(id) + '"';
|
||||
|
||||
if (isTemp) {
|
||||
@ -245,15 +263,34 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
|
||||
return this._seriesQuery(queryString).then(function(results) {
|
||||
if (!results || !results.length) {
|
||||
throw "Dashboard not found";
|
||||
return null;
|
||||
}
|
||||
|
||||
var dashCol = _.indexOf(results[0].columns, 'dashboard');
|
||||
var dashJson = results[0].points[0][dashCol];
|
||||
|
||||
return angular.fromJson(dashJson);
|
||||
}, function() {
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.getDashboard = function(id, isTemp) {
|
||||
var self = this;
|
||||
return this._getDashboardInternal(id, isTemp).then(function(dashboard) {
|
||||
if (dashboard !== null) {
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
// backward compatible load for unslugified ids
|
||||
var slug = kbn.slugifyForUrl(id);
|
||||
if (slug !== id) {
|
||||
return self.getDashboard(slug, isTemp);
|
||||
}
|
||||
|
||||
throw "Dashboard not found";
|
||||
}, function(err) {
|
||||
return "Could not load dashboard, " + err.data;
|
||||
throw "Could not load dashboard, " + err.data;
|
||||
});
|
||||
};
|
||||
|
||||
@ -264,12 +301,12 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
}
|
||||
return id;
|
||||
}, function(err) {
|
||||
return "Could not delete dashboard, " + err.data;
|
||||
throw "Could not delete dashboard, " + err.data;
|
||||
});
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.searchDashboards = function(queryString) {
|
||||
var influxQuery = 'select title, tags from /grafana.dashboard_.*/ where ';
|
||||
var influxQuery = 'select * from /grafana.dashboard_.*/ where ';
|
||||
|
||||
var tagsOnly = queryString.indexOf('tags!:') === 0;
|
||||
if (tagsOnly) {
|
||||
@ -294,15 +331,21 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
return hits;
|
||||
}
|
||||
|
||||
var dashCol = _.indexOf(results[0].columns, 'title');
|
||||
var tagsCol = _.indexOf(results[0].columns, 'tags');
|
||||
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var dashCol = _.indexOf(results[i].columns, 'title');
|
||||
var tagsCol = _.indexOf(results[i].columns, 'tags');
|
||||
var idCol = _.indexOf(results[i].columns, 'id');
|
||||
|
||||
var hit = {
|
||||
id: results[i].points[0][dashCol],
|
||||
title: results[i].points[0][dashCol],
|
||||
tags: results[i].points[0][tagsCol].split(",")
|
||||
};
|
||||
|
||||
if (idCol !== -1) {
|
||||
hit.id = results[i].points[0][idCol];
|
||||
}
|
||||
|
||||
hit.tags = hit.tags[0] ? hit.tags : [];
|
||||
hits.dashboards.push(hit);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ function (angular, _, kbn) {
|
||||
}
|
||||
|
||||
// Called once per panel (graph)
|
||||
OpenTSDBDatasource.prototype.query = function(filterSrv, options) {
|
||||
OpenTSDBDatasource.prototype.query = function(options) {
|
||||
var start = convertToTSDBTime(options.range.from);
|
||||
var end = convertToTSDBTime(options.range.to);
|
||||
var queries = _.compact(_.map(options.targets, convertTargetToQuery));
|
||||
|
@ -7,37 +7,29 @@ function (angular, _) {
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('templateSrv', function($q, $routeParams) {
|
||||
module.service('templateSrv', function() {
|
||||
var self = this;
|
||||
|
||||
this._regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g;
|
||||
this._templateData = {};
|
||||
this._values = {};
|
||||
this._texts = {};
|
||||
this._grafanaVariables = {};
|
||||
|
||||
this.init = function(variables) {
|
||||
this.variables = variables;
|
||||
this.updateTemplateData(true);
|
||||
this.updateTemplateData();
|
||||
};
|
||||
|
||||
this.updateTemplateData = function(initial) {
|
||||
var data = {};
|
||||
this.updateTemplateData = function() {
|
||||
this._values = {};
|
||||
this._texts = {};
|
||||
|
||||
_.each(this.variables, function(variable) {
|
||||
if (initial) {
|
||||
var urlValue = $routeParams[ variable.name ];
|
||||
if (urlValue) {
|
||||
variable.current = { text: urlValue, value: urlValue };
|
||||
}
|
||||
}
|
||||
if (!variable.current || !variable.current.value) { return; }
|
||||
|
||||
if (!variable.current || !variable.current.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data[variable.name] = variable.current.value;
|
||||
});
|
||||
|
||||
this._templateData = data;
|
||||
this._values[variable.name] = variable.current.value;
|
||||
this._texts[variable.name] = variable.current.text;
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.setGrafanaVariable = function (name, value) {
|
||||
@ -47,7 +39,11 @@ function (angular, _) {
|
||||
this.variableExists = function(expression) {
|
||||
this._regex.lastIndex = 0;
|
||||
var match = this._regex.exec(expression);
|
||||
return match && (self._templateData[match[1] || match[2]] !== void 0);
|
||||
return match && (self._values[match[1] || match[2]] !== void 0);
|
||||
};
|
||||
|
||||
this.containsVariable = function(str, variableName) {
|
||||
return str.indexOf('$' + variableName) !== -1 || str.indexOf('[[' + variableName + ']]') !== -1;
|
||||
};
|
||||
|
||||
this.highlightVariablesAsHtml = function(str) {
|
||||
@ -55,7 +51,7 @@ function (angular, _) {
|
||||
|
||||
this._regex.lastIndex = 0;
|
||||
return str.replace(this._regex, function(match, g1, g2) {
|
||||
if (self._templateData[g1 || g2]) {
|
||||
if (self._values[g1 || g2]) {
|
||||
return '<span class="template-variable">' + match + '</span>';
|
||||
}
|
||||
return match;
|
||||
@ -69,13 +65,29 @@ function (angular, _) {
|
||||
this._regex.lastIndex = 0;
|
||||
|
||||
return target.replace(this._regex, function(match, g1, g2) {
|
||||
value = self._templateData[g1 || g2];
|
||||
value = self._values[g1 || g2];
|
||||
if (!value) { return match; }
|
||||
|
||||
return self._grafanaVariables[value] || value;
|
||||
});
|
||||
};
|
||||
|
||||
this.replaceWithText = function(target) {
|
||||
if (!target) { return; }
|
||||
|
||||
var value;
|
||||
var text;
|
||||
this._regex.lastIndex = 0;
|
||||
|
||||
return target.replace(this._regex, function(match, g1, g2) {
|
||||
value = self._values[g1 || g2];
|
||||
text = self._texts[g1 || g2];
|
||||
if (!value) { return match; }
|
||||
|
||||
return self._grafanaVariables[value] || text;
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -18,17 +18,24 @@ function (angular, _, kbn) {
|
||||
}
|
||||
});
|
||||
|
||||
this.init = function(dashboard) {
|
||||
this.init = function(dashboard, viewstate) {
|
||||
this.variables = dashboard.templating.list;
|
||||
this.viewstate = viewstate;
|
||||
templateSrv.init(this.variables);
|
||||
|
||||
for (var i = 0; i < this.variables.length; i++) {
|
||||
var param = this.variables[i];
|
||||
if (param.refresh) {
|
||||
this.updateOptions(param);
|
||||
var variable = this.variables[i];
|
||||
var urlValue = viewstate.state['var-' + variable.name];
|
||||
if (urlValue !== void 0) {
|
||||
var option = _.findWhere(variable.options, { text: urlValue });
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
this.setVariableValue(variable, option, true);
|
||||
}
|
||||
else if (param.type === 'interval') {
|
||||
this.updateAutoInterval(param);
|
||||
else if (variable.refresh) {
|
||||
this.updateOptions(variable);
|
||||
}
|
||||
else if (variable.type === 'interval') {
|
||||
this.updateAutoInterval(variable);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -63,7 +70,7 @@ function (angular, _, kbn) {
|
||||
if (otherVariable === updatedVariable) {
|
||||
return;
|
||||
}
|
||||
if (otherVariable.query.indexOf('[[' + updatedVariable.name + ']]') !== -1) {
|
||||
if (templateSrv.containsVariable(otherVariable.query, updatedVariable.name)) {
|
||||
return self.updateOptions(otherVariable);
|
||||
}
|
||||
});
|
||||
@ -92,7 +99,6 @@ function (angular, _, kbn) {
|
||||
var datasource = datasourceSrv.get(variable.datasource);
|
||||
return datasource.metricFindQuery(variable.query)
|
||||
.then(function (results) {
|
||||
|
||||
variable.options = self.metricNamesToVariableValues(variable, results);
|
||||
|
||||
if (variable.includeAll) {
|
||||
@ -102,9 +108,9 @@ function (angular, _, kbn) {
|
||||
// if parameter has current value
|
||||
// if it exists in options array keep value
|
||||
if (variable.current) {
|
||||
var currentExists = _.findWhere(variable.options, { value: variable.current.value });
|
||||
if (currentExists) {
|
||||
return self.setVariableValue(variable, variable.current, true);
|
||||
var currentOption = _.findWhere(variable.options, { text: variable.current.text });
|
||||
if (currentOption) {
|
||||
return self.setVariableValue(variable, currentOption, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,14 @@ define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'config',
|
||||
'kbn'
|
||||
], function (angular, _, config, kbn) {
|
||||
'kbn',
|
||||
'moment'
|
||||
], function (angular, _, config, kbn, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('timeSrv', function($rootScope, $timeout, timer) {
|
||||
module.service('timeSrv', function($rootScope, $timeout, $routeParams, timer) {
|
||||
var self = this;
|
||||
|
||||
this.init = function(dashboard) {
|
||||
@ -17,11 +18,40 @@ define([
|
||||
this.dashboard = dashboard;
|
||||
this.time = dashboard.time;
|
||||
|
||||
this._initTimeFromUrl();
|
||||
|
||||
if(this.dashboard.refresh) {
|
||||
this.set_interval(this.dashboard.refresh);
|
||||
}
|
||||
};
|
||||
|
||||
this._parseUrlParam = function(value) {
|
||||
if (value.indexOf('now') !== -1) {
|
||||
return value;
|
||||
}
|
||||
if (value.length === 8) {
|
||||
return moment.utc(value, 'YYYYMMDD').toDate();
|
||||
}
|
||||
if (value.length === 15) {
|
||||
return moment.utc(value, 'YYYYMMDDTHHmmss').toDate();
|
||||
}
|
||||
var epoch = parseInt(value);
|
||||
if (!_.isNaN(epoch)) {
|
||||
return new Date(epoch);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
this._initTimeFromUrl = function() {
|
||||
if ($routeParams.from) {
|
||||
this.time.from = this._parseUrlParam($routeParams.from) || this.time.from;
|
||||
}
|
||||
if ($routeParams.to) {
|
||||
this.time.to = this._parseUrlParam($routeParams.to) || this.time.to;
|
||||
}
|
||||
};
|
||||
|
||||
this.set_interval = function (interval) {
|
||||
this.dashboard.refresh = interval;
|
||||
if (interval) {
|
||||
|
@ -83,6 +83,14 @@ function(angular, _, config) {
|
||||
current.time = original.time = {};
|
||||
current.refresh = original.refresh;
|
||||
|
||||
// ignore template variable values
|
||||
_.each(current.templating.list, function(value, index) {
|
||||
value.current = null;
|
||||
value.options = null;
|
||||
original.templating.list[index].current = null;
|
||||
original.templating.list[index].options = null;
|
||||
});
|
||||
|
||||
var currentTimepicker = _.findWhere(current.nav, { type: 'timepicker' });
|
||||
var originalTimepicker = _.findWhere(original.nav, { type: 'timepicker' });
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
///// @scratch /configuration/config.js/1
|
||||
// == Configuration
|
||||
// config.js is where you will find the core Grafana configuration. This file contains parameter that
|
||||
// must be set before Grafana is run for the first time.
|
||||
///
|
||||
// == Configuration
|
||||
// config.js is where you will find the core Grafana configuration. This file contains parameter that
|
||||
// must be set before Grafana is run for the first time.
|
||||
|
||||
define(['settings'],
|
||||
function (Settings) {
|
||||
"use strict";
|
||||
@ -97,7 +96,7 @@ function (Settings) {
|
||||
// Change window title prefix from 'Grafana - <dashboard title>'
|
||||
window_title_prefix: 'Grafana - ',
|
||||
|
||||
// Add your own custom pannels
|
||||
// Add your own custom panels
|
||||
plugins: {
|
||||
// list of plugin panels
|
||||
panels: [],
|
||||
|
@ -56,7 +56,7 @@ hr {
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding: 15px 20px 15px;
|
||||
padding: 0px 15px;
|
||||
color: @grayLighter;
|
||||
font-weight: normal;
|
||||
text-shadow: none;
|
||||
|
@ -59,6 +59,7 @@ a.text-success:hover { color: darken(@green, 10%); }
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding: 0px 15px;
|
||||
|
||||
&:hover {
|
||||
color: @navbarLinkColorHover;
|
||||
@ -461,7 +462,6 @@ legend {
|
||||
// -----------------------------------------------------
|
||||
|
||||
.alert {
|
||||
.border-radius(0);
|
||||
text-shadow: none;
|
||||
|
||||
&-heading, h1, h2, h3, h4, h5, h6 {
|
||||
|
@ -35,6 +35,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 24px;
|
||||
padding: 13px 11px 0 0;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
padding: 15px 0;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.row-button {
|
||||
width: 24px;
|
||||
}
|
||||
@ -87,7 +100,7 @@
|
||||
|
||||
.panel-fullscreen {
|
||||
z-index: 100;
|
||||
display: block !important;
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
@ -103,11 +116,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-fullscreen .main-view-container {
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
position: fixed;
|
||||
right: -10000px;
|
||||
.dashboard-fullscreen {
|
||||
.row-control-inner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.histogram-chart {
|
||||
@ -423,6 +435,9 @@ select.grafana-target-segment-input {
|
||||
background-color: rgb(58, 57, 57);
|
||||
border-radius: 5px;
|
||||
z-index: 9999;
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltip.in {
|
||||
@ -485,3 +500,26 @@ select.grafana-target-segment-input {
|
||||
color: @variable;
|
||||
}
|
||||
|
||||
.grafana-info-box:before {
|
||||
content: "\f05a";
|
||||
font-family:'FontAwesome';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
font-size: 20px;
|
||||
color: @blue;
|
||||
}
|
||||
|
||||
.grafana-info-box {
|
||||
position: relative;
|
||||
padding: 5px 15px;
|
||||
background-color: @grafanaTargetBackground;
|
||||
border: 1px solid @grafanaTargetBorder;
|
||||
h5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.grafana-tip {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
@ -317,13 +317,38 @@ div.flot-text {
|
||||
color: @textColor !important;
|
||||
}
|
||||
|
||||
.dashboard-notice {
|
||||
.page-alert-list {
|
||||
z-index:8000;
|
||||
margin-left:0px;
|
||||
padding:3px 0px 3px 0px;
|
||||
width:100%;
|
||||
padding-left:20px;
|
||||
color: @white;
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 56px;
|
||||
|
||||
.alert {
|
||||
color: @white;
|
||||
padding-bottom: 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.alert-close {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -2px;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
padding: 0;
|
||||
background: @grayLighter;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
font-size: 1.1rem;
|
||||
color: @grayDarker;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: bold;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -516,6 +541,11 @@ div.flot-text {
|
||||
}
|
||||
}
|
||||
|
||||
// typeahead max height
|
||||
.typeahead {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// Labels & Badges
|
||||
.label-tag {
|
||||
|
@ -257,7 +257,7 @@
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
@warningText: darken(#c09853, 10%);
|
||||
@warningBackground: @grayLighter;
|
||||
@warningBackground: @orange;
|
||||
@warningBorder: transparent;
|
||||
|
||||
@errorText: #b94a48;
|
||||
|
BIN
src/img/fav16.png
Normal file
BIN
src/img/fav16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/img/fav32.png
Normal file
BIN
src/img/fav32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/img/fav_dark_16.png
Normal file
BIN
src/img/fav_dark_16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
src/img/fav_dark_32.png
Normal file
BIN
src/img/fav_dark_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
@ -5,9 +5,11 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="google" value="notranslate">
|
||||
|
||||
<title>Grafana</title>
|
||||
<link rel="stylesheet" href="css/grafana.dark.min.css" title="Dark">
|
||||
<link rel="icon" type="image/png" href="img/fav32.png">
|
||||
|
||||
<!-- build:js app/app.js -->
|
||||
<script src="vendor/require/require.js"></script>
|
||||
@ -22,12 +24,17 @@
|
||||
|
||||
<link rel="stylesheet" href="css/grafana.light.min.css" ng-if="grafana.style === 'light'">
|
||||
|
||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
|
||||
<button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">×</button>
|
||||
<strong>{{alert.title}}</strong> <span ng-bind-html='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
|
||||
</div>
|
||||
<div class="page-alert-list">
|
||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
|
||||
<button type="button" class="alert-close" ng-click="dashAlerts.clear(alert)">
|
||||
<i class="icon-remove-sign"></i>
|
||||
</button>
|
||||
<div class="alert-title">{{alert.title}}</div>
|
||||
<div ng-bind-html='alert.text'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-view></div>
|
||||
<div ng-view></div>
|
||||
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -20,6 +20,7 @@ define([
|
||||
viewState.update(updateState);
|
||||
expect(location.search()).to.eql(updateState);
|
||||
expect(viewState.fullscreen).to.be(true);
|
||||
expect(viewState.state.fullscreen).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,6 +30,7 @@ define([
|
||||
viewState.update({fullscreen: false});
|
||||
expect(location.search()).to.eql({});
|
||||
expect(viewState.fullscreen).to.be(false);
|
||||
expect(viewState.state.fullscreen).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,6 +99,25 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('when initializing a target with single param func using variable', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'movingAverage(prod.count, $var)';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
ctx.scope.$parent = { get_data: sinon.spy() };
|
||||
});
|
||||
|
||||
it('should add 2 segments', function() {
|
||||
expect(ctx.scope.segments.length).to.be(2);
|
||||
});
|
||||
|
||||
it('should add function param', function() {
|
||||
expect(ctx.scope.functions[0].params.length).to.be(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when initalizing target without metric expression and function with series-ref', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'asPercent(metric.node.count, #A)';
|
||||
|
@ -51,6 +51,7 @@ define([
|
||||
self.templateSrv = new TemplateSrvStub();
|
||||
self.timeSrv = new TimeSrvStub();
|
||||
self.datasourceSrv = {};
|
||||
self.$routeParams = {};
|
||||
|
||||
this.providePhase = function(mocks) {
|
||||
return module(function($provide) {
|
||||
@ -103,6 +104,7 @@ define([
|
||||
this.replace = function(text) {
|
||||
return _.template(text, this.data, this.templateSettings);
|
||||
};
|
||||
this.init = function() {};
|
||||
this.updateTemplateData = function() { };
|
||||
this.variableExists = function() { return false; };
|
||||
this.highlightVariablesAsHtml = function(str) { return str; };
|
||||
|
@ -8,7 +8,7 @@ define([
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.providePhase(['templateSrv']));
|
||||
beforeEach(ctx.createService('InfluxDatasource'));
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
|
||||
@ -70,6 +70,30 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('When issuing annotation query', function() {
|
||||
var results;
|
||||
var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+
|
||||
"+where+time+%3E+now()+-+1h&time_precision=s";
|
||||
|
||||
var range = { from: 'now-1h', to: 'now' };
|
||||
var annotation = { query: 'select title from events.$server where $timeFilter' };
|
||||
var response = [];
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.templateSrv.replace = function(str) {
|
||||
return str.replace('$server', 'backend_01');
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
ctx.ds.annotationQuery(annotation, range).then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should generate the correct query', function() {
|
||||
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -15,6 +15,12 @@ define([
|
||||
expect(str).to.be('1.02 hour');
|
||||
});
|
||||
|
||||
it('should not downscale when value is zero', function() {
|
||||
var str = kbn.msFormat(0, 2);
|
||||
expect(str).to.be('0.00 ms');
|
||||
});
|
||||
|
||||
|
||||
it('should translate 365445 as 6.09 min', function() {
|
||||
var str = kbn.msFormat(365445, 2);
|
||||
expect(str).to.be('6.09 min');
|
||||
|
@ -62,6 +62,24 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('when checking if a string contains a variable', function() {
|
||||
beforeEach(function() {
|
||||
_templateSrv.init([{ name: 'test', current: { value: 'muuuu' } }]);
|
||||
_templateSrv.updateTemplateData();
|
||||
});
|
||||
|
||||
it('should find it with $var syntax', function() {
|
||||
var contains = _templateSrv.containsVariable('this.$test.filters', 'test');
|
||||
expect(contains).to.be(true);
|
||||
});
|
||||
|
||||
it('should find it with [[var]] syntax', function() {
|
||||
var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test');
|
||||
expect(contains).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('updateTemplateData with simple value', function() {
|
||||
beforeEach(function() {
|
||||
_templateSrv.init([{ name: 'test', current: { value: 'muuuu' } }]);
|
||||
@ -74,6 +92,23 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceWithText', function() {
|
||||
beforeEach(function() {
|
||||
_templateSrv.init([
|
||||
{ name: 'server', current: { value: '{asd,asd2}', text: 'All' } },
|
||||
{ name: 'period', current: { value: '$__auto_interval', text: 'auto' } }
|
||||
]);
|
||||
_templateSrv.setGrafanaVariable('$__auto_interval', '13m');
|
||||
_templateSrv.updateTemplateData();
|
||||
});
|
||||
|
||||
it('should replace with text except for grafanaVariables', function() {
|
||||
var target = _templateSrv.replaceWithText('Server: $server, period: $period');
|
||||
expect(target).to.be('Server: All, period: 13m');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ define([
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv']));
|
||||
beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', "$routeParams"]));
|
||||
beforeEach(ctx.createService('templateValuesSrv'));
|
||||
|
||||
describe('update interval variable options', function() {
|
||||
@ -125,12 +125,12 @@ define([
|
||||
describeUpdateVariable('and existing value still exists in options', function(scenario) {
|
||||
scenario.setup(function() {
|
||||
scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
|
||||
scenario.variable.current = { value: 'backend2'};
|
||||
scenario.variable.current = { text: 'backend2'};
|
||||
scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}];
|
||||
});
|
||||
|
||||
it('should keep variable value', function() {
|
||||
expect(scenario.variable.current.value).to.be('backend2');
|
||||
expect(scenario.variable.current.text).to.be('backend2');
|
||||
});
|
||||
});
|
||||
|
||||
@ -182,18 +182,6 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describeUpdateVariable('and existing value still exists in options', function(scenario) {
|
||||
scenario.setup(function() {
|
||||
scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
|
||||
scenario.variable.current = { value: 'backend2'};
|
||||
scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}];
|
||||
});
|
||||
|
||||
it('should keep variable value', function() {
|
||||
expect(scenario.variable.current.value).to.be('backend2');
|
||||
});
|
||||
});
|
||||
|
||||
describeUpdateVariable('with include All glob syntax', function(scenario) {
|
||||
scenario.setup(function() {
|
||||
scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' };
|
||||
|
@ -11,7 +11,7 @@ define([
|
||||
var _dashboard;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.providePhase(['$routeParams']));
|
||||
beforeEach(ctx.createService('timeSrv'));
|
||||
|
||||
beforeEach(function() {
|
||||
@ -35,6 +35,45 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('init time from url', function() {
|
||||
it('should handle relative times', function() {
|
||||
ctx.$routeParams.from = 'now-2d';
|
||||
ctx.$routeParams.to = 'now';
|
||||
ctx.service.init(_dashboard);
|
||||
var time = ctx.service.timeRange(false);
|
||||
expect(time.from).to.be('now-2d');
|
||||
expect(time.to).to.be('now');
|
||||
});
|
||||
|
||||
it('should handle formated dates', function() {
|
||||
ctx.$routeParams.from = '20140410T052010';
|
||||
ctx.$routeParams.to = '20140520T031022';
|
||||
ctx.service.init(_dashboard);
|
||||
var time = ctx.service.timeRange(true);
|
||||
expect(time.from.getTime()).to.equal(new Date("2014-04-10T05:20:10Z").getTime());
|
||||
expect(time.to.getTime()).to.equal(new Date("2014-05-20T03:10:22Z").getTime());
|
||||
});
|
||||
|
||||
it('should handle formated dates without time', function() {
|
||||
ctx.$routeParams.from = '20140410';
|
||||
ctx.$routeParams.to = '20140520';
|
||||
ctx.service.init(_dashboard);
|
||||
var time = ctx.service.timeRange(true);
|
||||
expect(time.from.getTime()).to.equal(new Date("2014-04-10T00:00:00Z").getTime());
|
||||
expect(time.to.getTime()).to.equal(new Date("2014-05-20T00:00:00Z").getTime());
|
||||
});
|
||||
|
||||
it('should handle epochs', function() {
|
||||
ctx.$routeParams.from = '1410337646373';
|
||||
ctx.$routeParams.to = '1410337665699';
|
||||
ctx.service.init(_dashboard);
|
||||
var time = ctx.service.timeRange(true);
|
||||
expect(time.from.getTime()).to.equal(1410337646373);
|
||||
expect(time.to.getTime()).to.equal(1410337665699);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('setTime', function() {
|
||||
it('should return disable refresh for absolute times', function() {
|
||||
_dashboard.refresh = false;
|
||||
|
@ -1,4 +1,6 @@
|
||||
module.exports = function(config,grunt) {
|
||||
'use strict';
|
||||
|
||||
var _c = {
|
||||
build: {
|
||||
options: {
|
||||
@ -59,12 +61,15 @@ module.exports = function(config,grunt) {
|
||||
'directives/all',
|
||||
'jquery.flot.pie',
|
||||
'angular-dragdrop',
|
||||
'controllers/all',
|
||||
'routes/all',
|
||||
'components/partials',
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var fs = require('fs');
|
||||
var panelPath = config.srcDir+'/app/panels'
|
||||
var panelPath = config.srcDir+'/app/panels';
|
||||
|
||||
// create a module for each directory in src/app/panels/
|
||||
fs.readdirSync(panelPath).forEach(function (panelName) {
|
||||
|
Loading…
Reference in New Issue
Block a user