Compare commits

...

15 Commits

Author SHA1 Message Date
Torkel Ödegaard
dc77d555f9 fix(elasticsearch): fixed templating issue with elasticsearch, fixes #5135 2016-05-23 10:58:31 +02:00
Torkel Ödegaard
1da149d9e1 feat(annotations): annotations can now use a template variable as data source, closes #5054 2016-05-23 09:28:14 +02:00
Torkel Ödegaard
43ba563a1c fix(logging): change log level to trace for plugin proxy logging call, fixes #5126 2016-05-23 07:55:55 +02:00
Torkel Ödegaard
e6f251011f fix(panel span): fixed issue setting panel span while in fullscren and also an issue when changing repeat variable while in fullscreen view, fixes #4957 2016-05-20 15:15:02 +02:00
Torkel Ödegaard
f00cbc0aeb fix(panel height): fixed issue with singlestat height, fixes #4679, fixes #4894, fixes #5113 2016-05-20 12:23:26 +02:00
Torkel Ödegaard
ee86d24797 fix(elasticsearch): minor editor layout fix 2016-05-20 10:31:22 +02:00
Torkel Ödegaard
e0e8fd6637 fix(templating): fixed handling of numeric values in tempalting query results, fixes #5097 2016-05-20 10:24:24 +02:00
Torkel Ödegaard
2416ee04c8 fix(templating): fixed detection of nested template variables, fixes #5103 2016-05-20 09:58:07 +02:00
Torkel Ödegaard
df0ddc0b50 fix(prometheus): fixed bug in prometheus query editor, fixes #5107 2016-05-20 09:03:52 +02:00
Torkel Ödegaard
37821e6d63 fix(share modal): fixed link in share modal when sub app url is /dashboard/, fixes #5109 2016-05-20 08:31:27 +02:00
Torkel Ödegaard
d474eba53a fix(logging): fixed reading config level from config file, fixes #5079 2016-05-18 14:18:08 +02:00
Torkel Ödegaard
cd80884b76 fix(dashboard timepicker): fixed issue with time picker and UTC when reading time from url, fixes #5078 2016-05-18 12:01:11 +02:00
Mitsuhiro Tanda
8d2f350ad1 (prometheus) fix prometheus link 2016-05-18 07:50:30 +02:00
Torkel Ödegaard
99db89067e fix(): reverted RESTART_ON_UPGRADE change 2016-05-17 08:34:34 +02:00
Torkel Ödegaard
7f0fe31881 docs(): updated download links 2016-05-16 13:41:50 +02:00
26 changed files with 148 additions and 74 deletions

View File

@@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
Stable .deb for Debian-based Linux | [grafana_3.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb)
Stable .deb for Debian-based Linux | [grafana_3.0.2-1463383025_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb)
## Install Stable
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_3.0.1_amd64.deb
$ sudo dpkg -i grafana_3.0.2-1463383025_amd64.deb
## APT Repository

View File

@@ -10,24 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm)
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.2-1463383025.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm)
## Install Stable Release from package file
You can install Grafana using Yum directly.
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-3.0.1-1.x86_64.rpm
$ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-3.0.1-1.x86_64.rpm
$ sudo rpm -i --nodeps grafana-3.0.2-1463383025.x86_64.rpm
## Install via YUM Repository

View File

@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
Description | Download
------------ | -------------
Stable Zip package for Windows | [grafana.2.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
Stable Zip package for Windows | [grafana.3.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.2.windows-x64.zip)
## Configure

View File

@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "3.0.2",
"version": "3.0.3",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"

View File

@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
RESTART_ON_UPGRADE=false
PLUGINS_DIR=/var/lib/grafana/plugins

View File

@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
RESTART_ON_UPGRADE=false
PLUGINS_DIR=/var/lib/grafana/plugins

View File

@@ -88,7 +88,7 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins
}
for key, value := range headers {
log.Info("setting key %v value %v", key, value[0])
log.Trace("setting key %v value %v", key, value[0])
req.Header.Set(key, value[0])
}
}

View File

@@ -526,6 +526,17 @@ var logLevels = map[string]int{
"Critical": 5,
}
func getLogLevel(key string, defaultName string) (string, int) {
levelName := Cfg.Section(key).Key("level").In(defaultName, []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
return levelName, level
}
func initLogging(args *CommandLineArgs) {
//close any existing log handlers.
log.Close()
@@ -533,8 +544,12 @@ func initLogging(args *CommandLineArgs) {
LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
defaultLevelName, _ := getLogLevel("log", "Info")
LogConfigs = make([]util.DynMap, len(LogModes))
for i, mode := range LogModes {
mode = strings.TrimSpace(mode)
sec, err := Cfg.GetSection("log." + mode)
if err != nil {
@@ -542,12 +557,7 @@ func initLogging(args *CommandLineArgs) {
}
// Log level.
levelName := Cfg.Section("log."+mode).Key("level").In("Trace",
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
_, level := getLogLevel("log."+mode, defaultLevelName)
// Generate log configuration.
switch mode {

View File

@@ -66,14 +66,17 @@ function (angular, _, coreModule, config) {
};
this.getAnnotationSources = function() {
return _.reduce(config.datasources, function(memo, value) {
var sources = [];
this.addDataSourceVariables(sources);
_.each(config.datasources, function(value) {
if (value.meta && value.meta.annotations) {
memo.push(value);
sources.push(value);
}
});
return memo;
}, []);
return sources;
};
this.getMetricSources = function(options) {
@@ -90,24 +93,7 @@ function (angular, _, coreModule, config) {
});
if (!options || !options.skipVariables) {
// look for data source variables
for (var i = 0; i < templateSrv.variables.length; i++) {
var variable = templateSrv.variables[i];
if (variable.type !== 'datasource') {
continue;
}
var first = variable.current.value;
var ds = config.datasources[first];
if (ds) {
metricSources.push({
name: '$' + variable.name,
value: '$' + variable.name,
meta: ds.meta,
});
}
}
this.addDataSourceVariables(metricSources);
}
metricSources.sort(function(a, b) {
@@ -123,6 +109,27 @@ function (angular, _, coreModule, config) {
return metricSources;
};
this.addDataSourceVariables = function(list) {
// look for data source variables
for (var i = 0; i < templateSrv.variables.length; i++) {
var variable = templateSrv.variables[i];
if (variable.type !== 'datasource') {
continue;
}
var first = variable.current.value;
var ds = config.datasources[first];
if (ds) {
list.push({
name: '$' + variable.name,
value: '$' + variable.name,
meta: ds.meta,
});
}
}
};
this.init();
});
});

View File

@@ -30,7 +30,7 @@ function (angular, _, $) {
$scope.datasourceChanged = function() {
return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
$scope.currentDatasource = ds;
$scope.currentAnnotation.datasource = ds.name;
$scope.currentAnnotation.datasource = $scope.currentAnnotation.datasource;
});
};

View File

@@ -142,12 +142,18 @@ function (angular, _, config) {
});
module.directive('panelWidth', function() {
var fullscreen = false;
return function(scope, element) {
function updateWidth() {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
if (!fullscreen) {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
}
}
scope.onAppEvent('panel-fullscreen-enter', function(evt, info) {
fullscreen = true;
if (scope.panel.id !== info.panelId) {
element.hide();
} else {
@@ -156,14 +162,20 @@ function (angular, _, config) {
});
scope.onAppEvent('panel-fullscreen-exit', function(evt, info) {
fullscreen = false;
if (scope.panel.id !== info.panelId) {
element.show();
} else {
updateWidth();
}
updateWidth();
});
scope.$watch('panel.span', updateWidth);
if (fullscreen) {
element.hide();
}
};
});

View File

@@ -70,7 +70,7 @@ function (angular, _, require, config) {
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
var soloUrl = $scope.shareUrl;
soloUrl = soloUrl.replace('/dashboard/', '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace("&fullscreen", "");
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';

View File

@@ -50,7 +50,7 @@ define([
if (!isNaN(value)) {
var epoch = parseInt(value);
return moment(epoch);
return moment.utc(epoch);
}
return null;

View File

@@ -8,6 +8,7 @@ import $ from 'jquery';
const TITLE_HEIGHT = 25;
const EMPTY_TITLE_HEIGHT = 9;
const PANEL_PADDING = 5;
const PANEL_BORDER = 2;
import {Emitter} from 'app/core/core';
@@ -141,7 +142,7 @@ export class PanelCtrl {
}
}
this.height = this.containerHeight - (PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
}
render(payload?) {

View File

@@ -42,6 +42,16 @@ function (angular, _) {
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
}
this.luceneFormat = function(value) {
if (typeof value === 'string') {
return luceneEscape(value);
}
var quotedValues = _.map(value, function(val) {
return '\"' + luceneEscape(val) + '\"';
});
return '(' + quotedValues.join(' OR ') + ')';
};
this.formatValue = function(value, format, variable) {
// for some scopedVars there is no variable
variable = variable || {};
@@ -60,13 +70,7 @@ function (angular, _) {
return '(' + escapedValues.join('|') + ')';
}
case "lucene": {
if (typeof value === 'string') {
return luceneEscape(value);
}
var quotedValues = _.map(value, function(val) {
return '\"' + luceneEscape(val) + '\"';
});
return '(' + quotedValues.join(' OR ') + ')';
return this.luceneFormat(value, format, variable);
}
case "pipe": {
if (typeof value === 'string') {
@@ -97,8 +101,11 @@ function (angular, _) {
if (!str) {
return false;
}
var match = this._regex.exec(str);
return match && (match[1] === variableName || match[2] === variableName);
variableName = regexEscape(variableName);
var findVarRegex = new RegExp('\\$(' + variableName + ')[\\W|$]|\\[\\[(' + variableName + ')\\]\\]', 'g');
var match = findVarRegex.exec(str);
return match !== null;
};
this.highlightVariablesAsHtml = function(str) {

View File

@@ -313,6 +313,14 @@ function (angular, _, kbn) {
var value = item.value || item.text;
var text = item.text || item.value;
if (_.isNumber(value)) {
value = value.toString();
}
if (_.isNumber(text)) {
text = text.toString();
}
if (regex) {
matches = regex.exec(value);
if (!matches) { continue; }

View File

@@ -78,7 +78,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
range[timeField]["format"] = "epoch_millis";
}
var queryInterpolated = templateSrv.replace(queryString);
var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene');
var filter = { "bool": { "must": [{ "range": range }] } };
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
var data = {
@@ -204,6 +204,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
});
};
function escapeForJson(value) {
return value.replace(/\"/g, '\\"');
}
function luceneThenJsonFormat(value) {
return escapeForJson(templateSrv.luceneFormat(value));
}
this.getFields = function(query) {
return this._get('/_mapping').then(function(res) {
var fields = {};
@@ -246,7 +254,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
var header = this.getQueryHeader('count', range.from, range.to);
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
esQuery = esQuery.replace("$lucene_query", queryDef.query || '*');
esQuery = esQuery.replace("$lucene_query", escapeForJson(queryDef.query || '*'));
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
esQuery = header + '\n' + esQuery + '\n';
@@ -260,7 +268,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
};
this.metricFindQuery = function(query) {
query = templateSrv.replace(query);
query = templateSrv.replace(query, {}, luceneThenJsonFormat);
query = angular.fromJson(query);
if (!query) {
return $q.when([]);

View File

@@ -70,9 +70,9 @@
</div>
<div ng-if="agg.type === 'filters'">
<div class="gf-form-inline" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
<div class="gf-form-inline offset-width-7" ng-repeat="filter in agg.settings.filters">
<div class="gf-form">
<label class="gf-form-item width-10">Query {{$index + 1}}</label>
<label class="gf-form-label width-10">Query {{$index + 1}}</label>
<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
</div>
<div class="gf-form">
@@ -88,7 +88,7 @@
<div ng-if="agg.type === 'geohash_grid'">
<div class="gf-form offset-width-7">
<label class="gf-form-label">Precision</label>
<label class="gf-form-label width-10">Precision</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
</div>
</div>

View File

@@ -43,7 +43,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
}
function interpolateQueryExpr(value, variable, defaultFormatFn) {
this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
// if no multi or include all do not regexEscape
if (!variable.multi && !variable.includeAll) {
return value;
@@ -59,6 +59,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
// Called once per panel (graph)
this.query = function(options) {
var self = this;
var start = getPrometheusTime(options.range.from, false);
var end = getPrometheusTime(options.range.to, true);
@@ -73,7 +74,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
activeTargets.push(target);
var query: any = {};
query.expr = templateSrv.replace(target.expr, options.scopedVars, interpolateQueryExpr);
query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
var interval = target.interval || options.interval;
var intervalFactor = target.intervalFactor || 1;
@@ -99,7 +100,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return this.performTimeSeriesQuery(query, start, end);
}, this));
var self = this;
return $q.all(allQueryPromise)
.then(function(allResponse) {
var result = [];
@@ -160,7 +160,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
var interpolated;
try {
interpolated = templateSrv.replace(expr, {}, interpolateQueryExpr);
interpolated = templateSrv.replace(expr, {}, this.interpolateQueryExpr);
} catch (err) {
return $q.reject(err);
}

View File

@@ -58,10 +58,14 @@ class PrometheusQueryCtrl extends QueryCtrl {
updateLink() {
var range = this.panelCtrl.range;
if (!range) {
return;
}
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
var expr = {
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars),
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
range_input: rangeDiff + 's',
end_input: endTime,
step_input: '',

View File

@@ -66,7 +66,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function getLegendHeight(panelHeight) {
if (!panel.legend.show || panel.legend.rightSide) {
return 2;
return 0;
}
if (panel.legend.alignAsTable) {

View File

@@ -1,4 +1,3 @@
<div class="singlestat-panel">
</div>
<div class="clearfix"></div>

View File

@@ -234,10 +234,13 @@ class SingleStatCtrl extends MetricsPanelCtrl {
var panel = ctrl.panel;
var templateSrv = this.templateSrv;
var data, linkInfo;
var width, height;
var $panelContainer = elem.find('.panel-container');
elem = elem.find('.singlestat-panel');
function setElementHeight() {
width = elem.width();
height = elem.height();
elem.css('height', ctrl.height + 'px');
}
@@ -291,8 +294,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
}
var plotCanvas = $('<div></div>');
var width = elem.width();
var height = elem.height();
var plotCss = {
top: '10px',
margin: 'auto',

View File

@@ -5,7 +5,6 @@
}
.singlestat-panel-value-container {
padding: 20px;
display: table-cell;
vertical-align: middle;
text-align: center;

View File

@@ -141,8 +141,8 @@ define([
});
it('slash should be properly escaped in regex format', function() {
var result = _templateSrv.formatValue('Gi3/14', 'regex');
expect(result).to.be('Gi3\\/14');
var result = _templateSrv.formatValue('Gi3/14', 'regex');
expect(result).to.be('Gi3\\/14');
});
});
@@ -200,6 +200,11 @@ define([
expect(contains).to.be(true);
});
it('should find it when part of segment', function() {
var contains = _templateSrv.containsVariable('metrics.$env.$group-*', 'group');
expect(contains).to.be(true);
});
});
describe('updateTemplateData with simple value', function() {

View File

@@ -126,6 +126,19 @@ define([
});
});
describeUpdateVariable('query variable with numeric results', function(scenario) {
scenario.setup(function() {
scenario.variable = { type: 'query', query: '', name: 'test', current: {} };
scenario.queryResult = [{text: 12, value: 12}];
});
it('should set current value to first option', function() {
expect(scenario.variable.current.value).to.be('12');
expect(scenario.variable.options[0].value).to.be('12');
expect(scenario.variable.options[0].text).to.be('12');
});
});
describeUpdateVariable('interval variable without auto', function(scenario) {
scenario.setup(function() {
scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };