Browse Source

Merge branch 'master' into apps

Conflicts:
	pkg/services/sqlstore/migrations/migrations.go
Torkel Ödegaard 10 years ago
parent
commit
69d0e82453

+ 4 - 0
CHANGELOG.md

@@ -7,12 +7,16 @@
 
 ### Enhancements ###
 * **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/pull/3458)
+* **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/pull/3584)
 
 # 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)
 
+### Bug fixes
+* **Graph Panel**: Fixed typehead when adding series style override, closes [#3554](https://github.com/grafana/grafana/issues/3554)
+
 # 2.6.0 (2015-12-14)
 
 ### New Features

+ 1 - 1
LICENSE.md

@@ -1,4 +1,4 @@
-Copyright 2014-2015 Torkel Ödegaard, Raintank Inc.
+Copyright 2014-2016 Torkel Ödegaard, Raintank Inc.
 
 Licensed under the Apache License, Version 2.0 (the "License"); you
 may not use this file except in compliance with the License. You may

+ 10 - 2
conf/defaults.ini

@@ -63,9 +63,15 @@ name = grafana
 user = root
 password =
 
-# For "postgres" only, either "disable", "require" or "verify-full"
+# For "postgres", use either "disable", "require" or "verify-full"
+# For "mysql", use either "true", "false", or "skip-verify".
 ssl_mode = disable
 
+ca_cert_path =
+client_key_path =
+client_cert_path =
+server_cert_name =
+
 # For "sqlite3" only, path relative to data_path setting
 path = grafana.db
 
@@ -79,7 +85,9 @@ provider = file
 # file: session dir path, is relative to grafana data_path
 # redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
 # postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
-# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
+# mysql: go-sql-driver/mysql dsn config string, examples:
+#         `user:password@tcp(127.0.0.1:3306)/database_name`
+#         `user:password@unix(/var/run/mysqld/mysqld.sock)/database_name`
 # memcache: 127.0.0.1:11211
 
 

+ 5 - 0
docs/sources/datasources/elasticsearch.md

@@ -77,6 +77,11 @@ The Elasticsearch datasource supports two types of queries you can use to fill t
 {"find": "fields", "type": "string"}
 ```
 
+### Fields filtered by type, with filter
+```json
+{"find": "fields", "type": "string", "query": <lucene query>}
+```
+
 ### Multi format / All format
 Use lucene format.
 

+ 18 - 1
docs/sources/installation/configuration.md

@@ -156,7 +156,24 @@ The database user's password (not applicable for `sqlite3`).
 
 ### ssl_mode
 
-For `postgres` only, either `disable`, `require` or `verify-full`.
+For Postgres, use either `disable`, `require` or `verify-full`.
+For MySQL, use either `true`, `false`, or `skip-verify`.
+
+### ca_cert_path 
+
+(MySQL only) The path to the CA certificate to use. On many linux systems, certs can be found in `/etc/ssl/certs`.
+
+### client_key_path 
+
+(MySQL only) The path to the client key. Only if server requires client authentication.
+
+### client_cert_path 
+
+(MySQL only) The path to the client cert. Only if server requires client authentication.
+
+### server_cert_name 
+
+(MySQL only) The common name field of the certificate used by the `mysql` server. Not necessary if `ssl_mode` is set to `skip-verify`.
 
 <hr />
 

+ 3 - 0
pkg/plugins/plugins.go

@@ -79,6 +79,9 @@ func scan(pluginDir string) error {
 	}
 
 	if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
+		if pluginDir != "data/plugins" {
+			log.Warn("Could not scan dir \"%v\" error: %s", pluginDir, err)
+		}
 		return err
 	}
 

+ 1 - 0
pkg/services/sqlstore/migrations/migrations.go

@@ -19,6 +19,7 @@ func AddMigrations(mg *Migrator) {
 	addDashboardSnapshotMigrations(mg)
 	addQuotaMigration(mg)
 	addAppPluginMigration(mg)
+	addSessionMigration(mg)
 }
 
 func addMigrationLogMigrations(mg *Migrator) {

+ 16 - 0
pkg/services/sqlstore/migrations/session_mig.go

@@ -0,0 +1,16 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addSessionMigration(mg *Migrator) {
+	var sessionV1 = Table{
+		Name: "session",
+		Columns: []*Column{
+			{Name: "key", Type: DB_Char, IsPrimaryKey: true, Length: 16},
+			{Name: "data", Type: DB_Blob},
+			{Name: "expiry", Type: DB_Integer, Length: 255, Nullable: false},
+		},
+	}
+
+	mg.AddMigration("create session table", NewAddTableMigration(sessionV1))
+}

+ 35 - 2
pkg/services/sqlstore/sqlstore.go

@@ -14,12 +14,21 @@ import (
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 	"github.com/grafana/grafana/pkg/setting"
 
+	"github.com/go-sql-driver/mysql"
 	_ "github.com/go-sql-driver/mysql"
 	"github.com/go-xorm/xorm"
 	_ "github.com/lib/pq"
 	_ "github.com/mattn/go-sqlite3"
 )
 
+type MySQLConfig struct {
+	SslMode        string
+	CaCertPath     string
+	ClientKeyPath  string
+	ClientCertPath string
+	ServerCertName string
+}
+
 var (
 	x       *xorm.Engine
 	dialect migrator.Dialect
@@ -30,6 +39,8 @@ var (
 		Type, Host, Name, User, Pwd, Path, SslMode string
 	}
 
+	mysqlConfig MySQLConfig
+
 	UseSQLite3 bool
 )
 
@@ -113,8 +124,22 @@ func getEngine() (*xorm.Engine, error) {
 	cnnstr := ""
 	switch DbCfg.Type {
 	case "mysql":
-		cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
-			DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)
+		protocol := "tcp"
+		if strings.HasPrefix(DbCfg.Host, "/") {
+			protocol = "unix"
+		}
+
+		cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8",
+			DbCfg.User, DbCfg.Pwd, protocol, DbCfg.Host, DbCfg.Name)
+
+		if mysqlConfig.SslMode == "true" || mysqlConfig.SslMode == "skip-verify" {
+			tlsCert, err := makeCert("custom", mysqlConfig)
+			if err != nil {
+				return nil, err
+			}
+			mysql.RegisterTLSConfig("custom", tlsCert)
+			cnnstr += "&tls=custom"
+		}
 	case "postgres":
 		var host, port = "127.0.0.1", "5432"
 		fields := strings.Split(DbCfg.Host, ":")
@@ -156,4 +181,12 @@ func LoadConfig() {
 	}
 	DbCfg.SslMode = sec.Key("ssl_mode").String()
 	DbCfg.Path = sec.Key("path").MustString("data/grafana.db")
+
+	if DbCfg.Type == "mysql" {
+		mysqlConfig.SslMode = DbCfg.SslMode
+		mysqlConfig.CaCertPath = sec.Key("ca_cert_path").String()
+		mysqlConfig.ClientKeyPath = sec.Key("client_key_path").String()
+		mysqlConfig.ClientCertPath = sec.Key("client_cert_path").String()
+		mysqlConfig.ServerCertName = sec.Key("server_cert_name").String()
+	}
 }

+ 41 - 0
pkg/services/sqlstore/tls_mysql.go

@@ -0,0 +1,41 @@
+package sqlstore
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"io/ioutil"
+)
+
+func makeCert(tlsPoolName string, config MySQLConfig) (*tls.Config, error) {
+	rootCertPool := x509.NewCertPool()
+	pem, err := ioutil.ReadFile(config.CaCertPath)
+	if err != nil {
+		return nil, fmt.Errorf("Could not read DB CA Cert path: %v", config.CaCertPath)
+	}
+	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+		return nil, err
+	}
+	clientCert := make([]tls.Certificate, 0, 1)
+	if config.ClientCertPath != "" && config.ClientKeyPath != "" {
+
+		certs, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientKeyPath)
+		if err != nil {
+			return nil, err
+		}
+		clientCert = append(clientCert, certs)
+	}
+	tlsConfig := &tls.Config{
+		RootCAs:      rootCertPool,
+		Certificates: clientCert,
+	}
+	tlsConfig.ServerName = config.ServerCertName
+	if config.SslMode == "skip-verify" {
+		tlsConfig.InsecureSkipVerify = true
+	}
+	// Return more meaningful error before it is too late
+	if config.ServerCertName == "" && !tlsConfig.InsecureSkipVerify {
+		return nil, fmt.Errorf("server_cert_name is missing. Consider using ssl_mode = skip-verify.")
+	}
+	return tlsConfig, nil
+}

+ 1 - 2
public/app/core/directives/dropdown_typeahead.js

@@ -74,11 +74,10 @@ function (_, $, coreModule) {
           updater: function (value) {
             var result = {};
             _.each($scope.menuItems, function(menuItem) {
-              result.$item = menuItem;
-
               _.each(menuItem.submenu, function(submenuItem) {
                 if (value === (menuItem.text + ' ' + submenuItem.text)) {
                   result.$subItem = submenuItem;
+                  result.$item = menuItem;
                 }
               });
             });

+ 0 - 44
public/app/features/dashboard/timepicker/input_date.js

@@ -1,44 +0,0 @@
-define([
-  "angular",
-  "lodash",
-  "moment",
-],function (angular, _, moment) {
-  'use strict';
-
-  angular.
-    module("grafana.directives").
-    directive('inputDatetime', function () {
-    return {
-      restrict: 'A',
-      require: 'ngModel',
-      link: function ($scope, $elem, attrs, ngModel) {
-        var format = 'YYYY-MM-DD HH:mm:ss';
-
-        var fromUser = function (text) {
-          if (text.indexOf('now') !== -1) {
-            return text;
-          }
-          var parsed;
-          if ($scope.ctrl.isUtc) {
-            parsed = moment.utc(text, format);
-          } else {
-            parsed = moment(text, format);
-          }
-
-          return parsed.isValid() ? parsed : undefined;
-        };
-
-        var toUser = function (currentValue) {
-          if (moment.isMoment(currentValue)) {
-            return currentValue.format(format);
-          } else {
-            return currentValue;
-          }
-        };
-
-        ngModel.$parsers.push(fromUser);
-        ngModel.$formatters.push(toUser);
-      }
-    };
-  });
-});

+ 41 - 0
public/app/features/dashboard/timepicker/input_date.ts

@@ -0,0 +1,41 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import _ from 'lodash';
+import angular from 'angular';
+import moment from 'moment';
+
+export function inputDateDirective() {
+  return {
+    restrict: 'A',
+    require: 'ngModel',
+    link: function ($scope, $elem, attrs, ngModel) {
+      var format = 'YYYY-MM-DD HH:mm:ss';
+
+      var fromUser = function (text) {
+        if (text.indexOf('now') !== -1) {
+          return text;
+        }
+        var parsed;
+        if ($scope.ctrl.isUtc) {
+          parsed = moment.utc(text, format);
+        } else {
+          parsed = moment(text, format);
+        }
+
+        return parsed.isValid() ? parsed : undefined;
+      };
+
+      var toUser = function (currentValue) {
+        if (moment.isMoment(currentValue)) {
+          return currentValue.format(format);
+        } else {
+          return currentValue;
+        }
+      };
+
+      ngModel.$parsers.push(fromUser);
+      ngModel.$formatters.push(toUser);
+    }
+  };
+}
+

+ 3 - 3
public/app/features/dashboard/timepicker/timepicker.ts

@@ -1,5 +1,4 @@
 ///<reference path="../../../headers/common.d.ts" />
-///<amd-dependency path="./input_date" name="inputDate" />
 
 import _ from 'lodash';
 import kbn  from 'app/core/utils/kbn';
@@ -9,8 +8,6 @@ import moment from 'moment';
 import * as dateMath from 'app/core/utils/datemath';
 import * as rangeUtil from 'app/core/utils/rangeutil';
 
-declare var inputDate: any;
-
 export class TimePickerCtrl {
 
   static tooltipFormat = 'MMM D, YYYY HH:mm:ss';
@@ -179,3 +176,6 @@ export function timePickerDirective() {
 
 angular.module('grafana.directives').directive('gfTimePickerSettings', settingsDirective);
 angular.module('grafana.directives').directive('gfTimePicker', timePickerDirective);
+
+import {inputDateDirective} from './input_date';
+angular.module("grafana.directives").directive('inputDatetime', inputDateDirective);

+ 1 - 1
public/app/grafana.ts

@@ -56,7 +56,7 @@ export class GrafanaApp {
       'ang-drag-drop',
       'grafana',
       'pasvaz.bindonce',
-      'ui.bootstrap.tabs',
+      'ui.bootstrap',
       'ui.bootstrap.tpls',
     ];
 

+ 3 - 3
public/app/plugins/datasource/prometheus/query_ctrl.js

@@ -7,7 +7,7 @@ function (angular, _) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('PrometheusQueryCtrl', function($scope) {
+  module.controller('PrometheusQueryCtrl', function($scope, templateSrv) {
 
     $scope.init = function() {
       var target = $scope.target;
@@ -46,9 +46,9 @@ function (angular, _) {
 
     $scope.linkToPrometheus = function() {
       var range = Math.ceil(($scope.range.to.valueOf() - $scope.range.from.valueOf()) / 1000);
-      var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:MM');
+      var endTime = $scope.range.to.utc().format('YYYY-MM-DD HH:mm');
       var expr = {
-        expr: $scope.target.expr,
+        expr: templateSrv.replace($scope.target.expr, $scope.panel.scopedVars),
         range_input: range + 's',
         end_input: endTime,
         step_input: '',

+ 7 - 0
public/app/plugins/panels/graph/axisEditor.html

@@ -181,6 +181,13 @@
 				<li class="tight-form-item">
 					<editor-checkbox text="Right side" model="panel.legend.rightSide" change="render()"></editor-checkbox>
 				</li>
+				<li ng-if="panel.legend.rightSide" class="tight-form-item">
+					Side width
+				</li>
+				<li ng-if="panel.legend.rightSide" style="width: 105px">
+					<input type="number" class="input-small tight-form-input" placeholder="250" bs-tooltip="'Set a min-width for the legend side table/block'" data-placement="right"
+					ng-model="panel.legend.sideWidth" ng-change="render()" ng-model-onblur>
+				</li>
 			</ul>
 			<div class="clearfix"></div>
 		</div>

+ 9 - 3
public/app/plugins/panels/graph/graph.tooltip.js

@@ -79,9 +79,9 @@ function ($) {
           // Stacked series can increase its length on each new stacked serie if null points found,
           // to speed the index search we begin always on the last found hoverIndex.
           var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
-          results.push({ value: value, hoverIndex: newhoverIndex});
+          results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label });
         } else {
-          results.push({ value: value, hoverIndex: hoverIndex});
+          results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label });
         }
       }
 
@@ -126,6 +126,8 @@ function ($) {
         relativeTime = dashboard.getRelativeTime(seriesHoverInfo.time);
         absoluteTime = dashboard.formatDate(seriesHoverInfo.time);
 
+        seriesHoverInfo.sort(byToolTipValue);
+
         for (i = 0; i < seriesHoverInfo.length; i++) {
           hoverInfo = seriesHoverInfo[i];
 
@@ -138,7 +140,7 @@ function ($) {
           value = series.formatValue(hoverInfo.value);
 
           seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
-          seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
+          seriesHtml += '<i class="fa fa-minus" style="color:' + hoverInfo.color +';"></i> ' + hoverInfo.label + ':</div>';
           seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
           plot.highlight(i, hoverInfo.hoverIndex);
         }
@@ -174,5 +176,9 @@ function ($) {
     });
   }
 
+  function byToolTipValue(a, b) {
+    return parseFloat(b.value) - parseFloat(a.value);
+  }
+
   return GraphTooltip;
 });

+ 4 - 0
public/app/plugins/panels/graph/legend.js

@@ -101,6 +101,10 @@ function (angular, _, $) {
 
           $container.empty();
 
+          // Set min-width if side style and there is a value, otherwise remove the CSS propery
+          var width = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + "px" : "";
+          $container.css("min-width", width);
+
           $container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
 
           if (panel.legend.alignAsTable) {

+ 2 - 1
public/app/plugins/panels/singlestat/singleStatPanel.js

@@ -97,7 +97,8 @@ function (angular, app, _, $) {
             plotCss.bottom = '5px';
             plotCss.left = '-5px';
             plotCss.width = (width - 10) + 'px';
-            plotCss.height = (height - 45) + 'px';
+            var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5;
+            plotCss.height = (height - dynamicHeightMargin) + 'px';
           }
           else {
             plotCss.bottom = "0px";