Merge branch 'master' into pro

This commit is contained in:
Torkel Ödegaard 2015-01-26 15:56:39 +01:00
commit d2f21bc93e
51 changed files with 725 additions and 382 deletions

View File

@ -2,6 +2,8 @@
**New features**
- [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data
- [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
**Enhancements**
- [Issue #1297](https://github.com/grafana/grafana/issues/1297). Graphite: Added cumulative and minimumBelow graphite functions
@ -13,6 +15,9 @@
- [Issue #1298](https://github.com/grafana/grafana/issues/1298). InfluxDB: Fix handling of empty array in templating variable query
- [Issue #1309](https://github.com/grafana/grafana/issues/1309). Graph: Fixed issue when using zero as a grid threshold
- [Issue #1345](https://github.com/grafana/grafana/issues/1345). UI: Fixed position of confirm modal when scrolled down
- [Issue #1372](https://github.com/grafana/grafana/issues/1372). Graphite: Fix for nested complex queries, where a query references a query that references another query (ie the #[A-Z] syntax)
- [Issue #1363](https://github.com/grafana/grafana/issues/1363). Templating: Fix to allow custom template variables to contain white space, now only splits on ','
- [Issue #1359](https://github.com/grafana/grafana/issues/1359). Graph: Fix for all series tooltip showing series with all null values when ``Hide Empty`` option is enabled
**Tech**
- [Issue #1311](https://github.com/grafana/grafana/issues/1311). Tech: Updated Font-Awesome from 3.2 to 4.2

View File

@ -7,5 +7,6 @@ define([
'./inspectCtrl',
'./jsonEditorCtrl',
'./loginCtrl',
'./sidemenuCtrl',
'./errorCtrl',
], function () {});

View File

@ -8,15 +8,27 @@ function (angular, config) {
var module = angular.module('grafana.controllers');
module.controller('LoginCtrl', function($scope, backendSrv, $location, $routeParams, alertSrv) {
$scope.loginModel = {
$scope.formModel = {
user: '',
password: ''
email: '',
password: '',
};
$scope.newUser = {};
$scope.grafana.sidemenu = false;
$scope.mode = 'login';
$scope.loginMode = true;
$scope.submitBtnClass = 'btn-inverse';
$scope.submitBtnText = 'Log in';
$scope.strengthClass = '';
$scope.init = function() {
if ($routeParams.logout) {
$scope.logout();
}
$scope.$watch("loginMode", $scope.loginModeChanged);
$scope.passwordChanged();
};
// build info view model
$scope.buildInfo = {
@ -26,30 +38,44 @@ function (angular, config) {
};
$scope.submit = function() {
if ($scope.mode === 'login') {
if ($scope.loginMode) {
$scope.login();
} else {
$scope.signUp();
}
};
$scope.init = function() {
if ($routeParams.logout) {
$scope.logout();
}
$scope.loginModeChanged = function(newValue) {
$scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
};
$scope.signUp = function() {
if ($scope.mode === 'login') {
$scope.mode = 'signup';
$scope.passwordChanged = function(newValue) {
if (!newValue) {
$scope.strengthText = "";
$scope.strengthClass = "hidden";
return;
}
if (newValue.length < 4) {
$scope.strengthText = "strength: weak sauce.";
$scope.strengthClass = "password-strength-bad";
return;
}
if (newValue.length <= 6) {
$scope.strengthText = "strength: you can do better.";
$scope.strengthClass = "password-strength-ok";
return;
}
$scope.strengthText = "strength: strong like a bull.";
$scope.strengthClass = "password-strength-good";
};
$scope.signUp = function() {
if (!$scope.loginForm.$valid) {
return;
}
backendSrv.put('/api/user/signup', $scope.newUser).then(function() {
backendSrv.post('/api/user/signup', $scope.formModel).then(function() {
window.location.href = config.appSubUrl + '/';
});
};
@ -63,17 +89,13 @@ function (angular, config) {
};
$scope.login = function() {
if ($scope.mode === 'signup') {
$scope.mode = 'login';
return;
}
delete $scope.loginError;
if (!$scope.loginForm.$valid) {
return;
}
backendSrv.post('/login', $scope.loginModel).then(function() {
backendSrv.post('/login', $scope.formModel).then(function() {
window.location.href = config.appSubUrl + '/';
});
};

View File

@ -0,0 +1,104 @@
define([
'angular',
'lodash',
'jquery',
'config',
],
function (angular, _, $, config) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('SideMenuCtrl', function($scope, $location) {
$scope.getUrl = function(url) {
return config.appSubUrl + url;
};
$scope.menu = [
{
text: "Dashbord",
href: $scope.getUrl("/"),
startsWith: config.appSubUrl + '/dashboard/',
icon: "fa fa-th-large",
links: [
{ text: 'Settings', editview: 'settings', icon: "fa fa-cogs" },
{ text: 'Templating', editview: 'templating', icon: "fa fa-cogs" },
{ text: 'Annotations', editview: 'annotations', icon: "fa fa-bolt" },
{ text: 'Export', href:"", icon: "fa fa-bolt" },
{ text: 'JSON', href:"", icon: "fa fa-bolt" },
]
},
{
text: "Account", href: $scope.getUrl("/account"),
icon: "fa fa-shield",
links: [
{ text: 'Info', href: $scope.getUrl("/account"), icon: "fa fa-sitemap" },
{ text: 'Data sources', href: $scope.getUrl("/account/datasources"), icon: "fa fa-sitemap" },
{ text: 'Users', href: $scope.getUrl("/account/users"), icon: "fa fa-users" },
{ text: 'API Keys', href: $scope.getUrl("/account/apikeys"), icon: "fa fa-key" },
]
},
{
text: "Profile", href: $scope.getUrl("/profile"),
icon: "fa fa-user",
links: [
{ text: 'Info', href: $scope.getUrl("/profile"), icon: "fa fa-sitemap" },
{ text: 'Password', href:"", icon: "fa fa-lock" },
]
}
];
$scope.onAppEvent('$routeUpdate', function() {
$scope.updateState();
});
$scope.onAppEvent('$routeChangeSuccess', function() {
$scope.updateState();
});
$scope.updateState = function() {
var currentPath = config.appSubUrl + $location.path();
var search = $location.search();
_.each($scope.menu, function(item) {
item.active = false;
if (item.href === currentPath) {
item.active = true;
}
if (item.startsWith) {
if (currentPath.indexOf(item.startsWith) === 0) {
item.active = true;
item.href = currentPath;
}
}
_.each(item.links, function(link) {
link.active = false;
if (link.editview) {
var params = {};
_.each(search, function(value, key) {
if (value !== null) { params[key] = value; }
});
params.editview = link.editview;
link.href = currentPath + '?' + $.param(params);
}
if (link.href === currentPath) {
item.active = true;
link.active = true;
}
});
});
};
$scope.init = function() {
$scope.updateState();
};
});
});

View File

@ -5,6 +5,12 @@ define([
function (angular, $) {
'use strict';
var editViewMap = {
'settings': 'app/partials/dasheditor.html',
'annotations': 'app/features/annotations/partials/editor.html',
'templating': 'app/partials/templating_editor.html',
};
angular
.module('grafana.directives')
.directive('dashEditorLink', function($timeout) {
@ -25,7 +31,7 @@ function (angular, $) {
angular
.module('grafana.directives')
.directive('dashEditorView', function($compile) {
.directive('dashEditorView', function($compile, $location) {
return {
restrict: 'A',
link: function(scope, elem) {
@ -48,10 +54,9 @@ function (angular, $) {
if (editorScope) { editorScope.dismiss(); }
}
scope.onAppEvent("dashboard-loaded", hideEditorPane);
scope.onAppEvent('hide-dash-editor', hideEditorPane);
function showEditorPane(evt, payload, editview) {
if (editview) { payload.src = editViewMap[editview]; }
scope.onAppEvent('show-dash-editor', function(evt, payload) {
if (lastEditor === payload.src) {
hideEditorPane();
return;
@ -70,6 +75,14 @@ function (angular, $) {
lastEditor = null;
editorScope = null;
hideScrollbars(false);
if (editview) {
var urlParams = $location.search();
if (editview === urlParams.editview) {
delete urlParams.editview;
$location.search(urlParams);
}
}
};
// hide page scrollbars while edit pane is visible
@ -79,8 +92,18 @@ function (angular, $) {
var view = $('<div class="dashboard-edit-view" ng-include="' + src + '"></div>');
elem.append(view);
$compile(elem.contents())(editorScope);
}
scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
if (newValue) {
showEditorPane(null, {}, newValue);
} else if (oldValue) {
hideEditorPane();
}
});
scope.onAppEvent('hide-dash-editor', hideEditorPane);
scope.onAppEvent('show-dash-editor', showEditorPane);
}
};
});

View File

@ -18,6 +18,21 @@ function (angular, kbn) {
};
});
angular
.module('grafana.directives')
.directive('watchChange', function() {
return {
scope: { onchange: '&watchChange' },
link: function(scope, element) {
element.on('input', function() {
scope.$apply(function () {
scope.onchange({ inputValue: element.val() });
});
});
}
};
});
angular
.module('grafana.directives')
.directive('editorOptBool', function($compile) {

View File

@ -0,0 +1,29 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AccountCtrl', function($scope, $http, backendSrv) {
$scope.init = function() {
$scope.getAccount();
};
$scope.getAccount = function() {
backendSrv.get('/api/account').then(function(account) {
$scope.account = account;
});
};
$scope.update = function() {
if (!$scope.accountForm.$valid) { return; }
backendSrv.put('/api/account', $scope.account).then($scope.getAccount);
};
$scope.init();
});
});

View File

@ -24,22 +24,12 @@ function (angular) {
};
$scope.removeUser = function(user) {
backendSrv.request({
method: 'DELETE',
url: '/api/account/users/' + user.userId
}).then($scope.get);
backendSrv.delete('/api/account/users/' + user.userId).then($scope.get);
};
$scope.addUser = function() {
if (!$scope.form.$valid) {
return;
}
backendSrv.request({
method: 'PUT',
url: '/api/account/users',
data: $scope.user,
}).then($scope.get);
if (!$scope.form.$valid) { return; }
backendSrv.post('/api/account/users', $scope.user).then($scope.get);
};
$scope.init();

View File

@ -26,12 +26,7 @@ function (angular) {
};
$scope.addToken = function() {
backendSrv.request({
method: 'PUT',
url: '/api/tokens',
data: $scope.token,
desc: 'Add token'
}).then($scope.getTokens);
backendSrv.post('/api/tokens', $scope.token).then($scope.getTokens);
};
$scope.init();

View File

@ -0,0 +1,37 @@
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account'"></div>
<div class="dashboard-edit-view" style="min-height: 500px">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-shield"></i>
Account information
</div>
</div>
<div class="dashboard-editor-body">
<div class="row editor-row">
<div class="section">
<form name="accountForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 120px">
<strong>Account name</strong>
</li>
<li>
<input type="text" required ng-model="account.name" class="input-xlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
</form>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account > API Keys'"></div>
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account'"></div>
<div class="dashboard-edit-view" style="min-height: 500px">

View File

@ -1,4 +1,4 @@
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Admin'"></div>
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account'"></div>
<div class="dashboard-edit-view" style="min-height: 500px">
<div class="editor-row">

View File

@ -1,11 +1,11 @@
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account > Users'"></div>
<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Account'"></div>
<div class="dashboard-edit-view" style="min-height: 500px">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-users"></i>
Account users
Users
</div>
</div>

View File

@ -12,6 +12,7 @@ define([
'./account/datasourcesCtrl',
'./account/apiKeysCtrl',
'./account/importCtrl',
'./account/accountCtrl',
'./admin/adminUsersCtrl',
'./grafanaDatasource/datasource',
], function () {});

View File

@ -0,0 +1,43 @@
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-clock-o"></i>
</li>
<li class="tight-form-item" style="width: 148px">
<strong>Override relative time</strong>
</li>
<li class="tight-form-item" style="width: 50px">
Last
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="1h"
empty-to-null ng-model="panel.timeFrom"
ng-change="get_data()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item tight-form-item-icon">
<i class="fa fa-clock-o"></i>
</li>
<li class="tight-form-item" style="width: 148px">
<strong>Add time shift</strong>
</li>
<li class="tight-form-item" style="width: 50px">
Amount
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="1h"
empty-to-null ng-model="panel.timeShift"
ng-change="get_data()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@ -19,12 +19,23 @@ define([
this.time = dashboard.time;
this._initTimeFromUrl();
this._parseTime();
if(this.dashboard.refresh) {
this.set_interval(this.dashboard.refresh);
}
};
this._parseTime = function() {
// when absolute time is saved in json it is turned to a string
if (_.isString(this.time.from) && this.time.from.indexOf('Z') >= 0) {
this.time.from = new Date(this.time.from);
}
if (_.isString(this.time.to) && this.time.to.indexOf('Z') >= 0) {
this.time.to = new Date(this.time.to);
}
};
this._parseUrlParam = function(value) {
if (value.indexOf('now') !== -1) {
return value;
@ -109,9 +120,7 @@ define([
this.timeRange = function(parse) {
var _t = this.time;
if(_.isUndefined(_t) || _.isUndefined(_t.from)) {
return false;
}
if(parse === false) {
return {
from: _t.from,

View File

@ -53,12 +53,13 @@ function (angular, _, $) {
state.panelId = parseInt(state.panelId) || null;
state.fullscreen = state.fullscreen ? true : null;
state.edit = (state.edit === "true" || state.edit === true) || null;
state.editview = state.editview || null;
return state;
};
DashboardViewState.prototype.serializeToUrl = function() {
var urlState = _.clone(this.state);
urlState.fullscreen = this.state.fullscreen ? true : null,
urlState.fullscreen = this.state.fullscreen ? true : null;
urlState.edit = this.state.edit ? true : null;
return urlState;
};

View File

@ -276,6 +276,7 @@ function (angular, _, $, config, kbn, moment) {
targetValue = targets[this._seriesRefLetters[i]];
targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
targets[this._seriesRefLetters[i]] = targetValue;
clean_options.push("target=" + encodeURIComponent(targetValue));
}

View File

@ -293,7 +293,7 @@ function (angular, _, config, gfunc, Parser) {
function MetricSegment(options) {
if (options === '*' || options.value === '*') {
this.value = '*';
this.html = $sce.trustAsHtml('<i class="icon-asterisk"><i>');
this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
this.expandable = true;
return;
}

View File

@ -374,7 +374,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
var fromIsAbsolute = from[from.length-1] === 's';
if (until === 'now()' && !fromIsAbsolute) {
return 'time > now() - ' + from;
return 'time > ' + from;
}
return 'time > ' + from + ' and time < ' + until;
@ -382,14 +382,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
function getInfluxTime(date) {
if (_.isString(date)) {
if (date === 'now') {
return 'now()';
}
else if (date.indexOf('now') >= 0) {
return date.substring(4);
}
date = kbn.parseDate(date);
return date.replace('now', 'now()');
}
return to_utc_epoch_seconds(date);

View File

@ -7,51 +7,53 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-user"></i>
Your info
Personal information
</div>
</div>
<div class="dashboard-editor-body">
<div class="row">
<form name="userForm">
<div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Name</strong>
</li>
<li>
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Email</strong>
</li>
<li>
<input type="text" required ng-model="user.email" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Username</strong>
</li>
<li>
<input type="text" required ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<form name="userForm">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Name</strong>
</li>
<li>
<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Email</strong>
</li>
<li>
<input type="text" required ng-model="user.email" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" style="margin-top: 10px">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Username</strong>
</li>
<li>
<input type="text" required ng-model="user.login" class="input-xxlarge tight-form-input last" >
</li>
</ul>
<div class="clearfix"></div>
</div>
<br>
<button type="submit" class="btn btn-success" ng-click="update()">Update</button>
</form>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
</form>
</div>
</div>
</div>
@ -59,7 +61,7 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-cubes"></i>
Your accounts
Your accounts
</div>
</div>
<br>
@ -87,7 +89,7 @@
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="fa fa-plus-square"></i>
Add account
Add account
</div>
</div>
<br>
@ -111,5 +113,8 @@
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -33,12 +33,11 @@ function (angular) {
$scope.update = function() {
if (!$scope.userForm.$valid) { return; }
backendSrv.post('/api/user/', $scope.user);
backendSrv.put('/api/user/', $scope.user);
};
$scope.createAccount = function() {
backendSrv.put('/api/account/', $scope.newAccount).then($scope.getUserAccounts);
backendSrv.post('/api/account/', $scope.newAccount).then($scope.getUserAccounts);
};
$scope.init();

View File

@ -81,8 +81,8 @@ function (angular, _, kbn) {
this._updateNonQueryVariable = function(variable) {
// extract options in comma seperated string
variable.options = _.map(variable.query.split(/[\s,]+/), function(text) {
return { text: text, value: text };
variable.options = _.map(variable.query.split(/[,]+/), function(text) {
return { text: text.trim(), value: text.trim() };
});
if (variable.type === 'interval') {

View File

@ -71,7 +71,7 @@ function ($) {
for (i = 0; i < seriesList.length; i++) {
series = seriesList[i];
if (!series.data.length) {
if (!series.data.length || (scope.panel.legend.hideEmpty && series.allIsNull)) {
results.push({ hidden: true });
continue;
}
@ -163,7 +163,7 @@ function ($) {
value = series.formatValue(hoverInfo.value);
seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
seriesHtml += '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
plot.highlight(i, hoverInfo.hoverIndex);
}
@ -174,7 +174,7 @@ function ($) {
else if (item) {
series = seriesList[item.seriesIndex];
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];

View File

@ -3,6 +3,10 @@
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<span class="graph-time-info" ng-if="panelMeta.timeInfo">
<i class="fa fa-clock-o"></i> {{panelMeta.timeInfo}}
</span>
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
No datapoints <tip>No datapoints returned from metric query</tip>

View File

@ -26,6 +26,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
$scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/dashboard/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
@ -88,6 +89,9 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
value_type: 'cumulative',
shared: false,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
@ -114,6 +118,26 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
$scope.updateTimeRange = function () {
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
$scope.panelMeta.timeInfo = "";
// check panel time overrrides
if ($scope.panel.timeFrom) {
if (_.isString($scope.rangeUnparsed.from)) {
$scope.panelMeta.timeInfo = "last " + $scope.panel.timeFrom;
$scope.rangeUnparsed.from = 'now-' + $scope.panel.timeFrom;
$scope.range.from = kbn.parseDate($scope.rangeUnparsed.from);
}
}
if ($scope.panel.timeShift) {
var timeShift = '-' + $scope.panel.timeShift;
$scope.panelMeta.timeInfo += ' timeshift ' + timeShift;
$scope.range.from = kbn.parseDateMath(timeShift, $scope.range.from);
$scope.range.to = kbn.parseDateMath(timeShift, $scope.range.to);
$scope.rangeUnparsed = $scope.range;
}
if ($scope.panel.maxDataPoints) {
$scope.resolution = $scope.panel.maxDataPoints;
}

View File

@ -1,18 +1,44 @@
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Relative time options <small>comma seperated</small></label>
<input type="text" array-join class="input-xlarge" ng-model="panel.time_options">
</div>
<div class="editor-option">
<label class="small">Auto-refresh options <small>comma seperated</small></label>
<input type="text" array-join class="input-xlarge" ng-model="panel.refresh_intervals">
</div>
<div class="section">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 148px">
<strong>Relative time options</strong></small>
</li>
<li>
<input type="text" class="input-xlarge tight-form-input"
ng-model="panel.time_options" array-join>
</li>
<li class="tight-form-item">
Until
</li>
<li class="tight-form-item">
now-
</li>
<li>
<input type="text" class="input-mini tight-form-input last"
ng-model="panel.nowDelay" placeholder="0m" bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'" data-placement="right">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 148px">
<strong>Auto-refresh options</strong>
</li>
<li>
<input type="text" class="input-xlarge tight-form-input"
ng-model="panel.refresh_intervals" array-join>
</li>
</ul>
<div class="clearfix"></div>
</div>
<p>
<br>
<i class="fa fa-info-circle"></i>
For these changes to fully take effect save and reload the dashboard.
</i>
</div>
</div>
<p>
<br>
<i class="fa fa-info-circle"></i>
For these changes to fully take effect save and reload the dashboard.
</i>
</div>
</div>

View File

@ -58,10 +58,14 @@ function (angular, app, _, moment, kbn) {
$scope.init = function() {
var time = timeSrv.timeRange(true);
if(time) {
$scope.panel.now = timeSrv.timeRange(false).to === "now" ? true : false;
$scope.time = getScopeTimeObj(time.from,time.to);
$scope.panel.now = false;
var unparsed = timeSrv.timeRange(false);
if (_.isString(unparsed.to) && unparsed.to.indexOf('now') === 0) {
$scope.panel.now = true;
}
$scope.time = getScopeTimeObj(time.from, time.to);
};
$scope.customTime = function() {
@ -142,6 +146,10 @@ function (angular, app, _, moment, kbn) {
to: "now"
};
if ($scope.panel.nowDelay) {
_filter.to = 'now-' + $scope.panel.nowDelay;
}
timeSrv.setTime(_filter);
$scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date());

View File

@ -104,7 +104,7 @@
<div ng-show='dashboard.editable' class="row-fluid add-row-panel-hint">
<div class="span12" style="text-align:right;">
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-mini">
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-small">
<span><i class="fa fa-plus"></i> ADD ROW</span>
</span>
</div>

View File

@ -1,15 +1,22 @@
<div class="navbar navbar-static-top">
<div class="navbar navbar-static-top" ng-controller='DashboardNavCtrl' ng-init="init()">
<div class="navbar-inner">
<div class="container-fluid">
<span class="brand">
<a ng-click="toggleSideMenu()">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<span class="hamburger">
<a class="pointer" ng-click="toggleSideMenu()">
<i class="fa fa-bars"></i>
</a>
</span>
<span class="brand">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<a ng-click="openSearch()" class="page-title">
{{dashboard.title}}
<span class="small">
<i class="fa fa-angle-down"></i>
</span>
</a>
<span class="page-title">{{dashboard.title}}</span>
</span>
<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
<ul class="nav pull-right">
<li ng-show="dashboardViewState.fullscreen">
<a ng-click="exitFullscreen()">
Back to dashboard
@ -22,53 +29,12 @@
</li>
<li class="dropdown grafana-menu-save">
<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="saveDashboard()">
<i class='fa fa-save'></i>
</a>
<ul class="save-dashboard-dropdown dropdown-menu" ng-if="saveDropdownOpened">
<li>
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
<input class='input-medium' ng-model="dashboard.title" type="text" />
<button class="btn" ng-click="saveDashboard()"><i class="fa fa-save"></i></button>
</form>
</li>
<li>
<a class="link" ng-click="set_default()">Save as Home</a>
</li>
<li>
<a class="link" ng-click="purge_default()">Reset Home</a>
</li>
<li ng-show="!isFavorite">
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
</li>
<li ng-show="isFavorite">
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
</li>
<li>
<a class="link" ng-click="editJson()">Dashboard JSON</a>
</li>
<li>
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
</li>
<li ng-show="db.saveTemp">
<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
Share temp copy
</a>
</li>
</ul>
</li>
<li class="dropdown grafana-menu-load">
<a ng-click="openSearch()" bs-tooltip="'Search'" data-placement="bottom">
<i class='fa fa-folder-open'></i>
</a>
</li>
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='fa fa-home'></i></a></li>
<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" dash-editor-link="app/partials/dasheditor.html"><i class='fa fa-cog pointer'></i></a></li>
<!-- <li class="grafana&#45;menu&#45;home"><a bs&#45;tooltip="'Goto saved default'" data&#45;placement="bottom" href='#/'><i class='fa fa&#45;home'></i></a></li> -->
<li class="grafana-menu-stop-playlist hide">
<a class='small' ng-click='stopPlaylist(2)'>

View File

@ -54,7 +54,7 @@
<td><i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td><i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td>
<a ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="btn btn-danger btn-mini">
<a ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>

View File

@ -1,36 +0,0 @@
<div ng-controller="MetricKeysCtrl" ng-init="init()">
<h5>Load metrics keys into elastic search</h5>
<p>
Work in progress...
</p>
<!-- <div class="row-fluid">
<div class="span12">
<label class="small">Load metrics recursive starting from this metric path</label>
<input type="text" class="input-xlarge" ng-model="metricPath"> </input>
</div>
</div>
<div class="row-fluid" style="margin-top: 15px;">
<div class="span12">
<button class="btn btn-success" ng-click="createIndex()">Clear/Create index</button>
<button class="btn btn-success" ng-click="loadMetricsFromPath()">Load from metric path</button>
<button class="btn btn-danger" ng-click="loadAll()">Load all</button>
<tip>Load all will fetch all metrics in one call, can be intensive for graphite and for the browser if you have a lot of metrics</tip>
</div>
</div>
<div class="row-fluid" style="margin-top: 15px;">
<div class="span12" ng-show="infoText" style="padding-top: 10px;">
{{infoText}}
</div>
<div class="span12 alert alert-error" ng-show="errorText">
{{errorText}}
</div>
</div>
<div class="row-fluid" ng-show="metricCounter">
<div class="span12" style="padding-top: 10px;">
Metrics indexed: {{metricCounter}}
</div>
</div> -->
</div>

View File

@ -7,82 +7,74 @@
</div>
<div class="login-inner-box">
<h1 ng-if="mode === 'login'">Login</h1>
<h1 ng-if="mode === 'signup'">Sign up</h1>
<div class="login-tab-header">
<button class="btn-login-tab" ng-click="loginMode = true;" ng-class="{active: loginMode}">
Log in
</button>
<button class="btn-login-tab" ng-click="loginMode = false;" ng-class="{active: !loginMode}">
Sign up
</button>
</div>
<form name="loginForm" class="login-form">
<div class="tight-form" ng-if="mode === 'login'">
<div class="tight-form" ng-if="loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
User
<strong>User</strong>
</li>
<li>
<input type="text" class="tight-form-input last" ng-model='loginModel.user' placeholder="email or username" style="width: 246px">
<input type="text" class="tight-form-input last" ng-model='formModel.user' placeholder="email or username" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="mode === 'login'">
<div class="tight-form" ng-if="loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
Password
<strong>Password</strong>
</li>
<li>
<input type="password" class="tight-form-input last" required ng-model="loginModel.password" id="inputPassword" style="width: 246px">
<input type="password" class="tight-form-input last" required ng-model="formModel.password" id="inputPassword" style="width: 246px" placeholder="password">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="!loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Email</strong>
</li>
<li>
<input type="email" class="tight-form-input last" required ng-model='formModel.email' placeholder="email" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="mode === 'signup'">
<div class="tight-form" ng-if="!loginMode">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
Email
<strong>Password</strong>
</li>
<li>
<input type="email" class="tight-form-input last" required ng-model='newUser.email' placeholder="email" style="width: 246px">
<input type="password" class="tight-form-input last" watch-change="passwordChanged(inputValue)" ng-minlength="4" required ng-model='formModel.password' placeholder="password" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="mode === 'signup'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
Name
</li>
<li>
<input type="text" class="tight-form-input last" ng-model='newUser.name' placeholder="your full name (optional)" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">
<em>{{strengthText}}</em>
</div>
<div class="tight-form" ng-if="mode === 'signup'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
Password
</li>
<li>
<input type="password" class="tight-form-input last" required ng-model='newUser.password' placeholder="" style="width: 246px">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item login-signup-button" ng-class="{'login-signup-button-disable': mode === 'signup'}">
<a ng-click="login()">Log in</a>
</li>
<li class="tight-form-item login-signup-button last" ng-class="{'login-signup-button-disable': mode === 'login'}">
<a ng-click="signUp()">Sign up</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<button type="submit" ng-click="submit();" class="hidden"></button>
<div class="login-submit-button-row">
<button type="submit" class="btn" ng-click="submit();"
ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
{{submitBtnText}}
</button>
</div>
</form>
<div class="clearfix"></div>

View File

@ -1,10 +1,13 @@
<div class="navbar navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<span class="brand">
<a ng-click="toggleSideMenu()">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<span class="hamburger">
<a class="pointer" ng-click="toggleSideMenu()">
<i class="fa fa-bars"></i>
</a>
</span>
<span class="brand">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<span class="page-title">{{pageTitle}}</span>
</span>
</div>

View File

@ -41,7 +41,7 @@
<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td>
<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-mini">
<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>

View File

@ -1,45 +1,25 @@
<section class="pro-sidemenu-items">
<div class="dropdown">
<a class="pro-sidemenu-link pointer gravatar" data-toggle="dropdown" title="{{grafana.user.email}}">
<span class="gravatar-missing">f</span>
<img ng-src="{{grafana.user.gravatarUrl}}" width="35"> <span class="gravatar-email small">{{grafana.user.login}}</span>
</a>
<ul class="dropdown-menu">
<li><a href="/login?logout">Logout</a></li>
</ul>
</div>
<a class="pro-sidemenu-link" ng-href="{{appSubUrl}}/">
<i class="fa fa-th-large"></i>
Dashboards
</a>
<a class="pro-sidemenu-link" href="account/datasources">
<i class="fa fa-sitemap"></i>
Data
</a>
<a class="pro-sidemenu-link" href="account/users">
<i class="fa fa-users"></i>Users
</a>
<a class="pro-sidemenu-link" href="account/apikeys">
<i class="fa fa-key"></i>API Keys
</a>
<a class="pro-sidemenu-link" href="account/import">
<i class="fa fa-download"></i>
Import
</a>
<a class="pro-sidemenu-link" href="profile">
<i class="fa fa-user"></i>
Profile
</a>
<a class="pro-sidemenu-link" href="admin/users" ng-if="grafana.user.isGrafanaAdmin">
<i class="fa fa-institution"></i>Admin
</a>
<a class="pro-sidemenu-link" href="login?logout">
<i class="fa fa-sign-out"></i>Sign out
</a>
</section>
<div ng-controller="SideMenuCtrl" ng-init="init()">
<ul class="sidemenu">
<li class="dropdown">
<a class="sidemenu-user pointer" data-toggle="dropdown" title="{{grafana.user.email}}">
<span class="gravatar-missing">f</span>
<img ng-src="{{grafana.user.gravatarUrl}}" width="35">
<span class="gravatar-email small">{{grafana.user.login}}</span>
</a>
<ul class="dropdown-menu">
<li><a href="{{appSubUrl}}/login?logout">Logout</a></li>
</ul>
</li>
<li ng-repeat-start="item in menu" ng-class="{'active': item.active}">
<a href="{{item.href}}" class="sidemenu-item"><i class="{{item.icon}}"></i>{{item.text}}</a>
</li>
<li ng-repeat-end ng-if="item.active">
<ul class="sidemenu-links">
<li ng-repeat="link in item.links">
<a href="{{link.href}}" class="sidemenu-link" ng-class="{active: link.active}"><i class="fa fa-angle-right"></i>{{link.text}}</a>
</li>
</ul>
</li>
</ul>
</div>

View File

@ -31,7 +31,7 @@
{{variable.query}}
</td>
<td style="width: 1%">
<a ng-click="edit(variable)" class="btn btn-success btn-mini">
<a ng-click="edit(variable)" class="btn btn-success btn-small">
<i class="fa fa-edit"></i>
Edit
</a>
@ -39,7 +39,7 @@
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%">
<a ng-click="removeVariable(variable)" class="btn btn-danger btn-mini">
<a ng-click="removeVariable(variable)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>

View File

@ -30,6 +30,10 @@ define([
controller : 'DashFromImportCtrl',
reloadOnSearch: false,
})
.when('/account', {
templateUrl: 'app/features/account/partials/account.html',
controller : 'AccountCtrl',
})
.when('/account/datasources', {
templateUrl: 'app/features/account/partials/datasources.html',
controller : 'DataSourcesCtrl',

View File

@ -21,6 +21,7 @@ function (angular, store) {
// do we have a previous dash
if (prevDashPath) {
$location.path(prevDashPath);
return;
}
var savedRoute = store.get('grafanaDashboardDefault');

View File

@ -363,7 +363,7 @@ div.subnav {
background-image: none;
.box-shadow(none);
border: none;
.border-radius(2px);
.border-radius(3px);
text-shadow: none;
&.disabled {

View File

@ -309,7 +309,7 @@ div.subnav {
background-image: none;
.box-shadow(none);
border: none;
.border-radius(0);
.border-radius(3px);
text-shadow: none;
&.disabled {

View File

@ -52,6 +52,16 @@
float: left;
}
.navbar .brand {
margin-left: 0;
}
.hamburger {
float: left;
padding: 15px 0 14px 0;
font-size: 1.4em;
}
.page-title {
padding: 15px 0;
display: block;

View File

@ -141,7 +141,7 @@
vertical-align: top;
position: relative;
left: 4px;
top: -20px;
top: -25px;
}
.graph-legend {
@ -260,7 +260,6 @@
transform-origin: right top;
}
.axisLabel {
color: @textColor;
font-size: @fontSizeSmall;
@ -269,3 +268,13 @@
font-size: 12px;
}
.graph-time-info {
font-weight: bold;
float: right;
margin-right: 15px;
color: @blue;
font-size: 85%;
position: relative;
top: -20px;
}

View File

@ -58,7 +58,6 @@
.bgInverse {
background: @btnInverseBackground;
color: rgba(255,255,255,.90);
}
code, pre {

View File

@ -1,5 +1,8 @@
.pro-sidemenu {
display: none;
a:focus {
text-decoration: none;
}
}
.pro-sidemenu-open {
@ -29,34 +32,40 @@
}
}
.pro-sidemenu-items {
.sidemenu {
list-style: none;
background: @grafanaPanelBackground;
margin: 0;
padding: 0;
}
.pro-sidemenu-link {
font-size: 1.0rem;
padding: 14px 10px 14px 20px;
.sidemenu-links {
margin: 0;
padding: 5px 0;
list-style: none;
background: @grafanaTargetFuncBackground;
li {
display: block;
}
}
.sidemenu-link {
display: block;
background: @grafanaPanelBackground;
color: @grayLight;
padding: 6px 0 6px 30px;
font-size: 15px;
color: @gray;
i {
padding-right: 15px;
}
border-bottom: 1px solid black;
}
.pro-sidemenu-link:first-child {
// border-top: 1px solid black;
}
.pro-side-menu-user {
padding-left: 5px;
img {
width: 49px;
padding-right: 10px;
&.active {
color: white;
font-weight: bold;
}
}
.gravatar {
.sidemenu-user {
padding: 8px 10px 7px 15px;
display: block;
width: 170px;
overflow: hidden;
text-overflow: ellipsis;
@ -67,29 +76,102 @@
.gravatar-email {
padding-left: 4px;
}
img {
width: 35px;
padding-right: 10px;
}
border-bottom: 1px solid black;
}
.sidemenu-item {
font-size: 17px;
padding: 14px 10px 14px 20px;
display: block;
i {
padding-right: 15px;
}
border-bottom: 1px solid black;
}
.login-form {
width: 50%;
float: left;
margin-left: 25%;
margin-right: 25%;
padding-top: 50px;
}
.login-box {
width: 700px;
margin: 100px auto 0 auto;
}
.login-box-logo {
text-align: center;
padding-bottom: 50px;
}
.login-inner-box {
background: @grafanaPanelBackground;
h1 {
font-size: 1.15em;
background: @grafanaTargetBackground;
text-align: center;
padding: 2px;
}
.login-tab-header {
background: @grafanaTargetBackground;
text-align: center;
}
.btn-login-tab {
background: transparent;
border: none;
font-size: 15px;
padding: 10px 10px;
&.active {
background: darken(@grafanaTargetBackground, 5%);
color: @white;
}
&:focus {
outline: none;
}
font-weight: bold;
display: inline-block;
width: 170px;
color: @textColor;
}
.password-strength {
display: block;
width: 50px;
overflow: visible;
white-space: nowrap;
padding-top: 3px;
margin-left: 97px;
color: darken(@textColor, 20%);
border-top: 3px solid @red;
&.password-strength-ok {
width: 170px;
border-top: 3px solid lighten(@yellow, 10%);
}
&.password-strength-good {
width: 254px;
border-top: 3px solid lighten(@green, 10%);
}
}
.login-signup-button {
width: 45%;
.login-submit-button-row {
text-align: center;
a {
margin-top: 40px;
button {
padding: 9px 7px;
font-size: 14px;
font-weight: bold;
}
&.login-signup-button-disable {
a {
color: darken(@linkColor, 35%);
}
width: 150px;
display: inline-block;
border: 1px solid lighten(@btnInverseBackground, 10%);
}
}
@ -107,25 +189,4 @@
}
}
.login-form {
width: 50%;
float: left;
margin-left: 25%;
margin-right: 25%;
padding-top: 30px;
}
.login-box {
width: 700px;
margin: 100px auto 0 auto;
}
.login-box-logo {
text-align: center;
padding-bottom: 50px;
}
.register-box {
margin-top: 100px;
}

View File

@ -49,8 +49,8 @@
.panel-loading {
position:absolute;
top: 0px;
right: 4px;
top: -3px;
right: 0px;
z-index: 800;
}

View File

@ -17,7 +17,7 @@
// Accent colors
// -------------------------
@blue: #33B5E5;
@blueDark: #0099CC;
@blueDark: #0086b3;
@green: #669900;
@red: #CC3900;
@yellow: #ECBB13;
@ -111,12 +111,12 @@
// Buttons
// -------------------------
@btnBackground: @grayLight;
@btnBackground: @grayDark;
@btnBackgroundHighlight: darken(@grayLight, 15%);
@btnBorder: #bbb;
@btnPrimaryBackground: lighten(@blue, 5%);
@btnPrimaryBackgroundHighlight: darken(@blue, 5%);
@btnPrimaryBackground: lighten(@blueDark, 5%);
@btnPrimaryBackgroundHighlight: darken(@blueDark, 5%);
@btnInfoBackground: lighten(@purple, 5%);
@btnInfoBackgroundHighlight: darken(@purple, 5%);
@ -130,8 +130,8 @@
@btnDangerBackground: lighten(@red, 5%);
@btnDangerBackgroundHighlight: darken(@red, 5%);
@btnInverseBackground: lighten(@black, 5%);
@btnInverseBackgroundHighlight: darken(@black, 5%);
@btnInverseBackground: @grayDark;
@btnInverseBackgroundHighlight: lighten(@grayDark, 5%);
// Forms

View File

@ -19,6 +19,7 @@ define([
tooltip: {
shared: true
},
legend: { },
stack: false
};

View File

@ -74,6 +74,13 @@ define([
expect(results[2]).to.be('target=asPercent(series1%2Cseries2)');
});
it('should replace target placeholder when nesting query references', function() {
var results = ctx.ds.buildGraphiteParams({
targets: [{target: 'series1'}, {target: 'sumSeries(#A)'}, {target: 'asPercent(#A,#B)'}]
});
expect(results[2]).to.be('target=' + encodeURIComponent("asPercent(series1,sumSeries(series1))"));
});
it('should fix wrong minute interval parameters', function() {
var results = ctx.ds.buildGraphiteParams({
targets: [{target: "summarize(prod.25m.count, '25m', 'sum')" }]

View File

@ -17,7 +17,7 @@ define([
describe('When querying influxdb with one target using query editor target spec', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+
"+where+time+%3E+now()+-+1h+group+by+time(1s)+order+asc";
"+where+time+%3E+now()-1h+group+by+time(1s)+order+asc";
var query = {
range: { from: 'now-1h', to: 'now' },
targets: [{ series: 'test', column: 'value', function: 'mean' }],
@ -50,7 +50,7 @@ define([
describe('When querying influxdb with one raw query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+value+from+series"+
"+where+time+%3E+now()+-+1h";
"+where+time+%3E+now()-1h";
var query = {
range: { from: 'now-1h', to: 'now' },
targets: [{ query: "select value from series where $timeFilter", rawQuery: true }]
@ -73,7 +73,7 @@ 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";
"+where+time+%3E+now()-1h";
var range = { from: 'now-1h', to: 'now' };
var annotation = { query: 'select title from events.$server where $timeFilter' };

View File

@ -69,4 +69,15 @@ define([
});
describe('relative time to date parsing', function() {
it('should handle negative time', function() {
var date = kbn.parseDateMath('-2d', new Date(2014,1,5));
expect(date.getTime()).to.equal(new Date(2014, 1, 3).getTime());
});
it('should handle multiple math expressions', function() {
var date = kbn.parseDateMath('-2d-6h', new Date(2014, 1, 5));
expect(date.toString()).to.equal(new Date(2014, 1, 2, 18).toString());
});
});
});