diff --git a/CHANGELOG.md b/CHANGELOG.md
index 850ebfee008..6cd518f11eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
-# 2.6.1
+# 3.0.0 (unrelased master branch)
+
+
+### Breaking changes
+**InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds. Can easily be installed via plugin system, closes #3523
+
+# 2.6.1 (unrelased, 2.6.x branch)
### New Features
* **Elasticsearch**: Support for derivative unit option, closes [#3512](https://github.com/grafana/grafana/issues/3512)
diff --git a/conf/defaults.ini b/conf/defaults.ini
index 92ed0d7d344..06938b9370b 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -15,6 +15,10 @@ data = data
# Directory where grafana can store logs
#
logs = data/log
+#
+# Directory where grafana will automatically scan and look for plugins
+#
+plugins = data/plugins
#################################### Server ####################################
[server]
diff --git a/conf/sample.ini b/conf/sample.ini
index 97480e6e008..b875cbda093 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -15,7 +15,12 @@
# Directory where grafana can store logs
#
;logs = /var/log/grafana
+#
+# Directory where grafana will automatically scan and look for plugins
+#
+;plugins = /var/lib/grafana/plugins
+#
#################################### Server ####################################
[server]
# Protocol (http or https)
diff --git a/packaging/deb/default/grafana-server b/packaging/deb/default/grafana-server
index 14d2545f6ce..dd06906b903 100644
--- a/packaging/deb/default/grafana-server
+++ b/packaging/deb/default/grafana-server
@@ -15,3 +15,5 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=false
+
+PLUGINS_DIR=/var/lib/grafana/plugins
diff --git a/packaging/deb/init.d/grafana-server b/packaging/deb/init.d/grafana-server
index 9de4bf42092..61e82d4c612 100755
--- a/packaging/deb/init.d/grafana-server
+++ b/packaging/deb/init.d/grafana-server
@@ -30,12 +30,14 @@ GRAFANA_HOME=/usr/share/grafana
CONF_DIR=/etc/grafana
WORK_DIR=$GRAFANA_HOME
DATA_DIR=/var/lib/grafana
+PLUGINS_DIR=/var/lib/grafana/plugins
LOG_DIR=/var/log/grafana
CONF_FILE=$CONF_DIR/grafana.ini
MAX_OPEN_FILES=10000
PID_FILE=/var/run/$NAME.pid
DAEMON=/usr/sbin/$NAME
+
umask 0027
if [ `id -u` -ne 0 ]; then
@@ -59,7 +61,7 @@ if [ -f "$DEFAULT" ]; then
. "$DEFAULT"
fi
-DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
+DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
case "$1" in
start)
diff --git a/packaging/deb/systemd/grafana-server.service b/packaging/deb/systemd/grafana-server.service
index dd5d2097149..923a0d76cc2 100644
--- a/packaging/deb/systemd/grafana-server.service
+++ b/packaging/deb/systemd/grafana-server.service
@@ -14,7 +14,8 @@ ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE} \
cfg:default.paths.logs=${LOG_DIR} \
- cfg:default.paths.data=${DATA_DIR}
+ cfg:default.paths.data=${DATA_DIR} \
+ cfg:default.paths.plugins=${PLUGINS_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
UMask=0027
diff --git a/packaging/rpm/init.d/grafana-server b/packaging/rpm/init.d/grafana-server
index bb27882f625..c60f4fb6080 100755
--- a/packaging/rpm/init.d/grafana-server
+++ b/packaging/rpm/init.d/grafana-server
@@ -29,6 +29,7 @@ GRAFANA_HOME=/usr/share/grafana
CONF_DIR=/etc/grafana
WORK_DIR=$GRAFANA_HOME
DATA_DIR=/var/lib/grafana
+PLUGINS_DIR=/var/lib/grafana/plugins
LOG_DIR=/var/log/grafana
CONF_FILE=$CONF_DIR/grafana.ini
MAX_OPEN_FILES=10000
@@ -63,7 +64,7 @@ fi
# overwrite settings from default file
[ -e /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
-DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
+DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
function isRunning() {
status -p $PID_FILE $NAME > /dev/null 2>&1
diff --git a/packaging/rpm/sysconfig/grafana-server b/packaging/rpm/sysconfig/grafana-server
index 14d2545f6ce..dd06906b903 100644
--- a/packaging/rpm/sysconfig/grafana-server
+++ b/packaging/rpm/sysconfig/grafana-server
@@ -15,3 +15,5 @@ CONF_DIR=/etc/grafana
CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=false
+
+PLUGINS_DIR=/var/lib/grafana/plugins
diff --git a/packaging/rpm/systemd/grafana-server.service b/packaging/rpm/systemd/grafana-server.service
index fb2ec24d123..20762ff6935 100644
--- a/packaging/rpm/systemd/grafana-server.service
+++ b/packaging/rpm/systemd/grafana-server.service
@@ -14,7 +14,8 @@ ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE} \
cfg:default.paths.logs=${LOG_DIR} \
- cfg:default.paths.data=${DATA_DIR}
+ cfg:default.paths.data=${DATA_DIR} \
+ cfg:default.paths.plugins=${PLUGINS_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index 10adc6ba09b..315a947a8d4 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -29,7 +29,9 @@ func Init() error {
Panels = make(map[string]PanelPlugin)
scan(path.Join(setting.StaticRootPath, "app/plugins"))
+ scan(path.Join(setting.PluginsPath))
checkExternalPluginPaths()
+
return nil
}
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index 28e0364e766..4e081e4e00b 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -48,9 +48,10 @@ var (
BuildStamp int64
// Paths
- LogsPath string
- HomePath string
- DataPath string
+ LogsPath string
+ HomePath string
+ DataPath string
+ PluginsPath string
// Log settings.
LogModes []string
@@ -393,6 +394,7 @@ func NewConfigContext(args *CommandLineArgs) error {
loadConfiguration(args)
Env = Cfg.Section("").Key("app_mode").MustString("development")
+ PluginsPath = Cfg.Section("paths").Key("plugins").String()
server := Cfg.Section("server")
AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
@@ -626,6 +628,7 @@ func LogConfigurationInfo() {
text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
+ text.WriteString(fmt.Sprintf(" plugins: %s\n", PluginsPath))
log.Info(text.String())
}
diff --git a/public/app/plugins/datasource/influxdb_08/datasource.js b/public/app/plugins/datasource/influxdb_08/datasource.js
deleted file mode 100644
index cba2e27b0cf..00000000000
--- a/public/app/plugins/datasource/influxdb_08/datasource.js
+++ /dev/null
@@ -1,288 +0,0 @@
-define([
- 'angular',
- 'lodash',
- 'app/core/utils/datemath',
- './influx_series',
- './query_builder',
- './directives',
- './query_ctrl',
- './func_editor',
-],
-function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) {
- 'use strict';
-
- var module = angular.module('grafana.services');
-
- module.factory('InfluxDatasource_08', function($q, backendSrv, templateSrv) {
-
- function InfluxDatasource(datasource) {
- this.urls = _.map(datasource.url.split(','), function(url) {
- return url.trim();
- });
-
- this.username = datasource.username;
- this.password = datasource.password;
- this.name = datasource.name;
- this.basicAuth = datasource.basicAuth;
- }
-
- InfluxDatasource.prototype.query = function(options) {
- var timeFilter = getTimeFilter(options);
-
- var promises = _.map(options.targets, function(target) {
- if (target.hide || !((target.series && target.column) || target.query)) {
- return [];
- }
-
- // build query
- var queryBuilder = new InfluxQueryBuilder(target);
- var query = queryBuilder.build();
-
- // replace grafana variables
- query = query.replace('$timeFilter', timeFilter);
- query = query.replace(/\$interval/g, (target.interval || options.interval));
-
- // replace templated variables
- query = templateSrv.replace(query, options.scopedVars);
-
- var alias = target.alias ? templateSrv.replace(target.alias, options.scopedVars) : '';
-
- var handleResponse = _.partial(handleInfluxQueryResponse, alias, queryBuilder.groupByField);
- return this._seriesQuery(query).then(handleResponse);
-
- }, this);
-
- return $q.all(promises).then(function(results) {
- return { data: _.flatten(results) };
- });
- };
-
- InfluxDatasource.prototype.annotationQuery = function(options) {
- var timeFilter = getTimeFilter({rangeRaw: options.rangeRaw});
- var query = options.annotation.query.replace('$timeFilter', timeFilter);
- query = templateSrv.replace(query);
-
- return this._seriesQuery(query).then(function(results) {
- return new InfluxSeries({seriesList: results, annotation: options.annotation}).getAnnotations();
- });
- };
-
- InfluxDatasource.prototype.listColumns = function(seriesName) {
- seriesName = templateSrv.replace(seriesName);
-
- if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
- seriesName = '"' + seriesName+ '"';
- }
-
- return this._seriesQuery('select * from ' + seriesName + ' limit 1').then(function(data) {
- if (!data) {
- return [];
- }
- return data[0].columns.map(function(item) {
- return /^\w+$/.test(item) ? item : ('"' + item + '"');
- });
- });
- };
-
- InfluxDatasource.prototype.listSeries = function(query) {
- // wrap in regex
- if (query && query.length > 0 && query[0] !== '/') {
- query = '/' + query + '/';
- }
-
- return this._seriesQuery('list series ' + query).then(function(data) {
- if (!data || data.length === 0) {
- return [];
- }
- return _.map(data[0].points, function(point) {
- return point[1];
- });
- });
- };
-
- InfluxDatasource.prototype.testDatasource = function() {
- return this.metricFindQuery('list series').then(function () {
- return { status: "success", message: "Data source is working", title: "Success" };
- });
- };
-
- InfluxDatasource.prototype.metricFindQuery = function (query) {
- var interpolated;
- try {
- interpolated = templateSrv.replace(query);
- }
- catch (err) {
- return $q.reject(err);
- }
-
- return this._seriesQuery(interpolated)
- .then(function (results) {
- if (!results || results.length === 0) { return []; }
-
- return _.map(results[0].points, function (metric) {
- return {
- text: metric[1],
- expandable: false
- };
- });
- });
- };
-
- function retry(deferred, callback, delay) {
- return callback().then(undefined, function(reason) {
- if (reason.status !== 0 || reason.status >= 300) {
- reason.message = 'InfluxDB Error:
' + reason.data;
- deferred.reject(reason);
- }
- else {
- setTimeout(function() {
- return retry(deferred, callback, Math.min(delay * 2, 30000));
- }, delay);
- }
- });
- }
-
- InfluxDatasource.prototype._seriesQuery = function(query) {
- return this._influxRequest('GET', '/series', {
- q: query,
- });
- };
-
- InfluxDatasource.prototype._influxRequest = function(method, url, data) {
- var _this = this;
- var deferred = $q.defer();
-
- retry(deferred, function() {
- var currentUrl = _this.urls.shift();
- _this.urls.push(currentUrl);
-
- var params = {
- u: _this.username,
- p: _this.password,
- };
-
- if (method === 'GET') {
- _.extend(params, data);
- data = null;
- }
-
- var options = {
- method: method,
- url: currentUrl + url,
- params: params,
- data: data,
- inspect: { type: 'influxdb' },
- };
-
- options.headers = options.headers || {};
- if (_this.basicAuth) {
- options.headers.Authorization = 'Basic ' + _this.basicAuth;
- }
-
- return backendSrv.datasourceRequest(options).then(function(response) {
- deferred.resolve(response.data);
- });
- }, 10);
-
- return deferred.promise;
- };
-
- InfluxDatasource.prototype._getDashboardInternal = function(id) {
- var queryString = 'select dashboard from "grafana.dashboard_' + btoa(id) + '"';
-
- return this._seriesQuery(queryString).then(function(results) {
- if (!results || !results.length) {
- 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) {
- return this._getDashboardInternal(id).then(function(dashboard) {
- if (dashboard !== null) {
- return dashboard;
- }
- throw "Dashboard not found";
- }, function(err) {
- throw "Could not load dashboard, " + err.data;
- });
- };
-
- InfluxDatasource.prototype.searchDashboards = function() {
- var influxQuery = 'select * from /grafana.dashboard_.*/ ';
- return this._seriesQuery(influxQuery).then(function(results) {
- var hits = { dashboards: [], tags: [], tagsOnly: false };
-
- if (!results || !results.length) {
- return hits;
- }
-
- for (var i = 0; i < results.length; i++) {
- var dashCol = _.indexOf(results[i].columns, 'title');
- var idCol = _.indexOf(results[i].columns, 'id');
-
- var hit = {
- id: results[i].points[0][dashCol],
- title: results[i].points[0][dashCol],
- };
-
- if (idCol !== -1) {
- hit.id = results[i].points[0][idCol];
- }
-
- hits.dashboards.push(hit);
- }
- return hits;
- });
- };
-
- function handleInfluxQueryResponse(alias, groupByField, seriesList) {
- var influxSeries = new InfluxSeries({
- seriesList: seriesList,
- alias: alias,
- groupByField: groupByField
- });
-
- return influxSeries.getTimeSeries();
- }
-
- function getTimeFilter(options) {
- var from = getInfluxTime(options.rangeRaw.from, false);
- var until = getInfluxTime(options.rangeRaw.to, true);
- var fromIsAbsolute = from[from.length-1] === 's';
-
- if (until === 'now()' && !fromIsAbsolute) {
- return 'time > ' + from;
- }
-
- return 'time > ' + from + ' and time < ' + until;
- }
-
- function getInfluxTime(date, roundUp) {
- if (_.isString(date)) {
- if (date === 'now') {
- return 'now()';
- }
-
- var parts = /^now-(\d+)([d|h|m|s])$/.exec(date);
- if (parts) {
- var amount = parseInt(parts[1]);
- var unit = parts[2];
- return 'now()-' + amount + unit;
- }
-
- date = dateMath.parse(date, roundUp);
- }
- return (date.valueOf() / 1000).toFixed(0) + 's';
- }
-
- return InfluxDatasource;
- });
-});
diff --git a/public/app/plugins/datasource/influxdb_08/directives.js b/public/app/plugins/datasource/influxdb_08/directives.js
deleted file mode 100644
index 35a29975442..00000000000
--- a/public/app/plugins/datasource/influxdb_08/directives.js
+++ /dev/null
@@ -1,21 +0,0 @@
-define([
- 'angular',
-],
-function (angular) {
- 'use strict';
-
- var module = angular.module('grafana.directives');
-
- module.directive('metricQueryEditorInfluxdb08', function() {
- return {controller: 'InfluxQueryCtrl_08', templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.editor.html'};
- });
-
- module.directive('metricQueryOptionsInfluxdb08', function() {
- return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.options.html'};
- });
-
- module.directive('annotationsQueryEditorInfluxdb08', function() {
- return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/annotations.editor.html'};
- });
-
-});
diff --git a/public/app/plugins/datasource/influxdb_08/func_editor.js b/public/app/plugins/datasource/influxdb_08/func_editor.js
deleted file mode 100644
index 7d48f40792f..00000000000
--- a/public/app/plugins/datasource/influxdb_08/func_editor.js
+++ /dev/null
@@ -1,136 +0,0 @@
-define([
- 'angular',
- 'lodash',
- 'jquery',
-],
-function (angular, _, $) {
- 'use strict';
-
- angular
- .module('grafana.directives')
- .directive('influxdbFuncEditor08', function($compile) {
-
- var funcSpanTemplate = '{{target.function}}(';
-
- var paramTemplate = '';
-
- return {
- restrict: 'A',
- link: function postLink($scope, elem) {
- var $funcLink = $(funcSpanTemplate);
-
- $scope.functionMenu = _.map($scope.functions, function(func) {
- return {
- text: func,
- click: "changeFunction('" + func + "');"
- };
- });
-
- function clickFuncParam() {
- /*jshint validthis:true */
-
- var $link = $(this);
- var $input = $link.next();
-
- $input.val($scope.target.column);
- $input.css('width', ($link.width() + 16) + 'px');
-
- $link.hide();
- $input.show();
- $input.focus();
- $input.select();
-
- var typeahead = $input.data('typeahead');
- if (typeahead) {
- $input.val('');
- typeahead.lookup();
- }
- }
-
- function inputBlur() {
- /*jshint validthis:true */
-
- var $input = $(this);
- var $link = $input.prev();
-
- if ($input.val() !== '') {
- $link.text($input.val());
-
- $scope.target.column = $input.val();
- $scope.$apply($scope.get_data);
- }
-
- $input.hide();
- $link.show();
- }
-
- function inputKeyPress(e) {
- /*jshint validthis:true */
-
- if(e.which === 13) {
- inputBlur.call(this);
- }
- }
-
- function inputKeyDown() {
- /*jshint validthis:true */
- this.style.width = (3 + this.value.length) * 8 + 'px';
- }
-
- function addTypeahead($input) {
- $input.attr('data-provide', 'typeahead');
-
- $input.typeahead({
- source: function () {
- return $scope.listColumns.apply(null, arguments);
- },
- minLength: 0,
- items: 20,
- updater: function (value) {
- setTimeout(function() {
- inputBlur.call($input[0]);
- }, 0);
- return value;
- }
- });
-
- var typeahead = $input.data('typeahead');
- typeahead.lookup = function () {
- var items;
- this.query = this.$element.val() || '';
- items = this.source(this.query, $.proxy(this.process, this));
- return items ? this.process(items) : items;
- };
- }
-
- function addElementsAndCompile() {
- $funcLink.appendTo(elem);
-
- var $paramLink = $('' + $scope.target.column + '');
- var $input = $(paramTemplate);
-
- $paramLink.appendTo(elem);
- $input.appendTo(elem);
-
- $input.blur(inputBlur);
- $input.keyup(inputKeyDown);
- $input.keypress(inputKeyPress);
- $paramLink.click(clickFuncParam);
-
- addTypeahead($input);
-
- $(')').appendTo(elem);
-
- $compile(elem.contents())($scope);
- }
-
- addElementsAndCompile();
-
- }
- };
-
- });
-
-});
diff --git a/public/app/plugins/datasource/influxdb_08/influx_series.js b/public/app/plugins/datasource/influxdb_08/influx_series.js
deleted file mode 100644
index bca6ca7a865..00000000000
--- a/public/app/plugins/datasource/influxdb_08/influx_series.js
+++ /dev/null
@@ -1,129 +0,0 @@
-define([
- 'lodash',
-],
-function (_) {
- 'use strict';
-
- function InfluxSeries(options) {
- this.seriesList = options.seriesList;
- this.alias = options.alias;
- this.groupByField = options.groupByField;
- this.annotation = options.annotation;
- }
-
- var p = InfluxSeries.prototype;
-
- p.getTimeSeries = function() {
- var output = [];
- var self = this;
- var i;
-
- _.each(self.seriesList, function(series) {
- var seriesName;
- var timeCol = series.columns.indexOf('time');
- var valueCol = 1;
- var groupByCol = -1;
-
- if (self.groupByField) {
- groupByCol = series.columns.indexOf(self.groupByField);
- }
-
- // find value column
- _.each(series.columns, function(column, index) {
- if (column !== 'time' && column !== 'sequence_number' && column !== self.groupByField) {
- valueCol = index;
- }
- });
-
- var groups = {};
-
- if (self.groupByField) {
- groups = _.groupBy(series.points, function (point) {
- return point[groupByCol];
- });
- }
- else {
- groups[series.columns[valueCol]] = series.points;
- }
-
- _.each(groups, function(groupPoints, key) {
- var datapoints = [];
- for (i = 0; i < groupPoints.length; i++) {
- var metricValue = isNaN(groupPoints[i][valueCol]) ? null : groupPoints[i][valueCol];
- datapoints[i] = [metricValue, groupPoints[i][timeCol]];
- }
-
- seriesName = series.name + '.' + key;
-
- if (self.alias) {
- seriesName = self.createNameForSeries(series.name, key);
- }
-
- output.push({ target: seriesName, datapoints: datapoints });
- });
- });
-
- return output;
- };
-
- p.getAnnotations = function () {
- var list = [];
- var self = this;
-
- _.each(this.seriesList, function (series) {
- var titleCol = null;
- var timeCol = null;
- var tagsCol = null;
- var textCol = null;
-
- _.each(series.columns, function(column, index) {
- if (column === 'time') { timeCol = index; return; }
- if (column === 'sequence_number') { return; }
- if (!titleCol) { titleCol = index; }
- if (column === self.annotation.titleColumn) { titleCol = index; return; }
- if (column === self.annotation.tagsColumn) { tagsCol = index; return; }
- if (column === self.annotation.textColumn) { textCol = index; return; }
- });
-
- _.each(series.points, function (point) {
- var data = {
- annotation: self.annotation,
- time: point[timeCol],
- title: point[titleCol],
- tags: point[tagsCol],
- text: point[textCol]
- };
-
- if (tagsCol) {
- data.tags = point[tagsCol];
- }
-
- list.push(data);
- });
- });
-
- return list;
- };
-
- p.createNameForSeries = function(seriesName, groupByColValue) {
- var regex = /\$(\w+)/g;
- var segments = seriesName.split('.');
-
- return this.alias.replace(regex, function(match, group) {
- if (group === 's') {
- return seriesName;
- }
- else if (group === 'g') {
- return groupByColValue;
- }
- var index = parseInt(group);
- if (_.isNumber(index) && index < segments.length) {
- return segments[index];
- }
- return match;
- });
-
- };
-
- return InfluxSeries;
-});
diff --git a/public/app/plugins/datasource/influxdb_08/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb_08/partials/annotations.editor.html
deleted file mode 100644
index cec47e7a7e4..00000000000
--- a/public/app/plugins/datasource/influxdb_08/partials/annotations.editor.html
+++ /dev/null
@@ -1,29 +0,0 @@
-