Просмотр исходного кода

Merge branch 'master' into v4.2.x

Torkel Ödegaard 8 лет назад
Родитель
Сommit
140a0982e8

+ 15 - 2
CHANGELOG.md

@@ -1,4 +1,17 @@
-# 4.2.0 (unreleased)
+# 4.3.0 (unreleased)
+
+## Minor Enchancements
+* **Threema**: Add emoji to Threema alert notifications [#7676](https://github.com/grafana/grafana/pull/7676) thx [@dbrgn](https://github.com/dbrgn)
+* **Panels**: Support dm3 unit [#7695](https://github.com/grafana/grafana/issues/7695) thx [@mitjaziv](https://github.com/mitjaziv)
+
+# 4.2.0-beta2 (unreleased)
+## Minor Enhancements
+* **Templates**: Prevent use of the prefix `__` for templates in web UI [#7678](https://github.com/grafana/grafana/issues/7678)
+
+## Bugfixes
+* **Webhook**: Use proxy settings from environment variables [#7710](https://github.com/grafana/grafana/issues/7710)
+
+# 4.2.0-beta1 (2017-02-27)
 
 
 ## Enhancements
 ## Enhancements
 * **Telegram**: Added Telegram alert notifier [#7098](https://github.com/grafana/grafana/pull/7098), thx [@leonoff](https://github.com/leonoff)
 * **Telegram**: Added Telegram alert notifier [#7098](https://github.com/grafana/grafana/pull/7098), thx [@leonoff](https://github.com/leonoff)
@@ -13,7 +26,7 @@
 * **Alerting**: Uploading images for alert notifications is now optional [#7419](https://github.com/grafana/grafana/issues/7419)
 * **Alerting**: Uploading images for alert notifications is now optional [#7419](https://github.com/grafana/grafana/issues/7419)
 * **Dashboard**: Adds shortcut for collapsing/expanding all rows [#552](https://github.com/grafana/grafana/issues/552), thx [@mtanda](https://github.com/mtanda)
 * **Dashboard**: Adds shortcut for collapsing/expanding all rows [#552](https://github.com/grafana/grafana/issues/552), thx [@mtanda](https://github.com/mtanda)
 * **Alerting**: Adds de duping of alert notifications [#7632](https://github.com/grafana/grafana/pull/7632)
 * **Alerting**: Adds de duping of alert notifications [#7632](https://github.com/grafana/grafana/pull/7632)
-* **Orgs**: Sharing dashboards using Grafana share feature will not redirect to correct org. [#1613](https://github.com/grafana/grafana/issues/1613)
+* **Orgs**: Sharing dashboards using Grafana share feature will now redirect to correct org. [#1613](https://github.com/grafana/grafana/issues/1613)
 * **Pushover**: Add Pushover alert notifications [#7526](https://github.com/grafana/grafana/pull/7526) thx [@devkid](https://github.com/devkid)
 * **Pushover**: Add Pushover alert notifications [#7526](https://github.com/grafana/grafana/pull/7526) thx [@devkid](https://github.com/devkid)
 * **Threema**: Add Threema Gateway alert notification integration [#7482](https://github.com/grafana/grafana/pull/7482) thx [@dbrgn](https://github.com/dbrgn)
 * **Threema**: Add Threema Gateway alert notification integration [#7482](https://github.com/grafana/grafana/pull/7482) thx [@dbrgn](https://github.com/dbrgn)
 
 

+ 1 - 1
appveyor.yml

@@ -30,7 +30,7 @@ install:
 build_script:
 build_script:
   - go run build.go build
   - go run build.go build
   - grunt release
   - grunt release
-  - go run build.go sha1-dist
+  - go run build.go sha-dist
   - cp dist/* .
   - cp dist/* .
 
 
 artifacts:
 artifacts:

+ 9 - 9
build.go

@@ -5,7 +5,7 @@ package main
 import (
 import (
 	"bytes"
 	"bytes"
 	"crypto/md5"
 	"crypto/md5"
-	"crypto/sha1"
+	"crypto/sha256"
 	"encoding/json"
 	"encoding/json"
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
@@ -105,8 +105,8 @@ func main() {
 			grunt(gruntBuildArg("release")...)
 			grunt(gruntBuildArg("release")...)
 			createDebPackages()
 			createDebPackages()
 
 
-		case "sha1-dist":
-			sha1FilesInDist()
+		case "sha-dist":
+			shaFilesInDist()
 
 
 		case "latest":
 		case "latest":
 			makeLatestDistCopies()
 			makeLatestDistCopies()
@@ -522,14 +522,14 @@ func md5File(file string) error {
 	return out.Close()
 	return out.Close()
 }
 }
 
 
-func sha1FilesInDist() {
+func shaFilesInDist() {
 	filepath.Walk("./dist", func(path string, f os.FileInfo, err error) error {
 	filepath.Walk("./dist", func(path string, f os.FileInfo, err error) error {
 		if path == "./dist" {
 		if path == "./dist" {
 			return nil
 			return nil
 		}
 		}
 
 
-		if strings.Contains(path, ".sha1") == false {
-			err := sha1File(path)
+		if strings.Contains(path, ".sha256") == false {
+			err := shaFile(path)
 			if err != nil {
 			if err != nil {
 				log.Printf("Failed to create sha file. error: %v\n", err)
 				log.Printf("Failed to create sha file. error: %v\n", err)
 			}
 			}
@@ -538,20 +538,20 @@ func sha1FilesInDist() {
 	})
 	})
 }
 }
 
 
-func sha1File(file string) error {
+func shaFile(file string) error {
 	fd, err := os.Open(file)
 	fd, err := os.Open(file)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	defer fd.Close()
 	defer fd.Close()
 
 
-	h := sha1.New()
+	h := sha256.New()
 	_, err = io.Copy(h, fd)
 	_, err = io.Copy(h, fd)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	out, err := os.Create(file + ".sha1")
+	out, err := os.Create(file + ".sha256")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 4 - 4
circle.yml

@@ -33,7 +33,7 @@ dependencies:
 
 
 test:
 test:
   override:
   override:
-     - bash scripts/circle-test.sh
+    - bash scripts/circle-test.sh
 
 
 deployment:
 deployment:
   gh_branch:
   gh_branch:
@@ -41,7 +41,7 @@ deployment:
     commands:
     commands:
       - ./scripts/build/deploy.sh
       - ./scripts/build/deploy.sh
       - ./scripts/build/sign_packages.sh
       - ./scripts/build/sign_packages.sh
-      - go run build.go sha1-dist
+      - go run build.go sha-dist
       - aws s3 sync ./dist s3://$BUCKET_NAME/master
       - aws s3 sync ./dist s3://$BUCKET_NAME/master
       - ./scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master
       - ./scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master
       - ./scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}
       - ./scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}
@@ -50,7 +50,7 @@ deployment:
     commands:
     commands:
       - ./scripts/build/deploy.sh
       - ./scripts/build/deploy.sh
       - ./scripts/build/sign_packages.sh
       - ./scripts/build/sign_packages.sh
-      - go run build.go sha1-dist
+      - go run build.go sha-dist
       - aws s3 sync ./dist s3://$BUCKET_NAME/release
       - aws s3 sync ./dist s3://$BUCKET_NAME/release
       - ./scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} release
       - ./scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} release
-
+      - ./scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} ${CIRCLE_TAG}

+ 5 - 0
docs/sources/alerting/notifications.md

@@ -101,4 +101,9 @@ config file.
 
 
 This is an optional requirement, you can get slack and email notifications without setting this up.
 This is an optional requirement, you can get slack and email notifications without setting this up.
 
 
+# Configure the link back to Grafana from alert notifications
+
+All alert notifications contains a link back to the triggered alert in the Grafana instance. 
+This url is based on the [domain](/installation/configuration/#domain) setting in Grafana. 
+
 
 

+ 1 - 1
docs/sources/guides/whats-new-in-v4-1.md

@@ -7,7 +7,7 @@ type = "docs"
 name = "Version 4.1"
 name = "Version 4.1"
 identifier = "v4.1"
 identifier = "v4.1"
 parent = "whatsnew"
 parent = "whatsnew"
-weight = -1
+weight = 3
 +++
 +++
 
 
 
 

+ 88 - 0
docs/sources/guides/whats-new-in-v4-2.md

@@ -0,0 +1,88 @@
++++
+title = "What's New in Grafana v4.2"
+description = "Feature & improvement highlights for Grafana v4.2"
+keywords = ["grafana", "new", "documentation", "4.2.0"]
+type = "docs"
+[menu.docs]
+name = "Version 4.2"
+identifier = "v4.2"
+parent = "whatsnew"
+weight = -1
++++
+
+## Whats new in Grafana v4.2
+
+Grafana v4.2 Beta is now [available for download](/download/4_2_0/).
+Just like the last release this one contains lots bug fixes and minor improvements.
+We are very happy to say that 27 of 40 issues was closed by pull requests from the community.
+Big thumbs up!
+
+## Release Highlights
+
+- **Hipchat**: Adds support for sending alert notifications to hipchat [#6451](https://github.com/grafana/grafana/issues/6451), thx [@jregovic](https://github.com/jregovic)
+- **Telegram**: Added Telegram alert notifier [#7098](https://github.com/grafana/grafana/pull/7098), thx [@leonoff](https://github.com/leonoff)
+- **LINE**: Add LINE as alerting notification channel [#7301](https://github.com/grafana/grafana/pull/7301), thx [@huydx](https://github.com/huydx)
+- **Templating**: Make $__interval and $__interval_ms global built in variables that can be used in by any datasource (in panel queries), closes [#7190](https://github.com/grafana/grafana/issues/7190), closes [#6582](https://github.com/grafana/grafana/issues/6582)
+- **Alerting**: Adds deduping of alert notifications [#7632](https://github.com/grafana/grafana/pull/7632)
+- **Alerting**: Better information about why an alert triggered [#7035](https://github.com/grafana/grafana/issues/7035)
+- **Orgs**: Sharing dashboards using Grafana share feature will now redirect to correct org. [#6948](https://github.com/grafana/grafana/issues/6948)
+- [Full changelog](https://github.com/grafana/grafana/blob/master/CHANGELOG.md)
+
+### New alert notification channels
+
+This release adds **five** new alert notifications channels, all of them contributed by the community.
+
+* Hipchat
+* Telegram
+* LINE
+* Pushover
+* Threema
+
+### Templating
+
+We added two new global built in variables in grafana. `$__interval` and `$__interval_ms` are now reserved template names in grafana and can be used by any datasource.
+We might add more global built in variables in the future and if we do we will prefix them with `$__`. So please avoid using that in your template variables.
+
+### Dedupe alert notifications when running multiple servers
+
+In this release we will dedupe alert notificiations when you are running multiple servers.
+This makes it possible to run alerting on multiple servers and only get one notification.
+
+We currently solve this with sql transactions which puts some limitations for how many servers you can use to execute the same rules.
+3-5 servers should not be a problem but as always, it depends on how many alerts you have and how frequently they execute.
+
+Next up for a better HA situation is to add support for workload balancing between Grafana servers.
+
+### Alerting more info
+
+You can now see the reason why an alert triggered in the alert history. Its also easier to detect when an alert is set to `alerting` due to the `no_data` option.
+
+### Improved support for multi-org setup
+
+When loading dashboards we now set an query parameter called orgId. So we can detect from which org an user shared a dashboard.
+This makes it possible for users to share dashboards between orgs without changing org first.
+
+We aim to introduce [dashboard groups](https://github.com/grafana/grafana/issues/1611) sometime in the future which will introduce access control and user groups within one org.
+Making it possible to have users in multiple groups and have detailed access control.
+
+## Upgrade & Breaking changes
+
+If your using https in grafana we now force you to use tls 1.2 and the most secure ciphers.
+We think its better to be secure by default rather then making it configurable.
+If you want to run https with lower versions of tls we suggest you put a reserve proxy in front of grafana.
+
+If you have template variables name `$__interval` or `$__interval_ms` they will no longer work since these keywords
+are reserved as global built in variables. We might add more global built in variables in the future and if we do, we will prefix them with `$__`. So please avoid using that in your template variables.
+
+## Changelog
+
+Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list
+of new features, changes, and bug fixes.
+
+## Download
+
+Head to [v4.2-beta download page](/download/4_2_0/) for download links & instructions.
+
+## Thanks
+
+A big thanks to all the Grafana users who contribute by submitting PRs, bug reports & feedback!

+ 17 - 2
docs/sources/installation/configuration.md

@@ -135,6 +135,10 @@ Path to the certificate file (if `protocol` is set to `https`).
 
 
 Path to the certificate key file (if `protocol` is set to `https`).
 Path to the certificate key file (if `protocol` is set to `https`).
 
 
+### router_logging
+
+Set to true for Grafana to log all HTTP requests (not just errors). These are logged as Info level events
+to grafana log.
 <hr />
 <hr />
 
 
 <hr />
 <hr />
@@ -457,7 +461,7 @@ session provider you have configured.
 
 
 - **file:** session file path, e.g. `data/sessions`
 - **file:** session file path, e.g. `data/sessions`
 - **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, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
-- **postgres:** ex:  user=a password=b host=localhost port=5432 dbname=c sslmode=disable
+- **postgres:** ex:  user=a password=b host=localhost port=5432 dbname=c sslmode=require
 - **memcache:** ex:  127.0.0.1:11211
 - **memcache:** ex:  127.0.0.1:11211
 - **redis:** ex: `addr=127.0.0.1:6379,pool_size=100,prefix=grafana`
 - **redis:** ex: `addr=127.0.0.1:6379,pool_size=100,prefix=grafana`
 
 
@@ -473,6 +477,17 @@ Mysql Example:
         PRIMARY KEY (`key`)
         PRIMARY KEY (`key`)
     ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
     ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 
+Postgres Example:
+
+    CREATE TABLE session (
+        key       CHAR(16) NOT NULL,
+        data      BYTEA,
+        expiry    INTEGER NOT NULL,
+        PRIMARY KEY (key)
+    );
+
+Postgres valid `sslmode` are `disable`, `require` (default), `verify-ca`, and `verify-full`.
+
 ### cookie_name
 ### cookie_name
 
 
 The name of the Grafana session cookie.
 The name of the Grafana session cookie.
@@ -602,7 +617,7 @@ You can choose between (s3, webdav). If left empty Grafana will ignore the uploa
 ## [external_image_storage.s3]
 ## [external_image_storage.s3]
 
 
 ### bucket_url
 ### bucket_url
-Bucket URL for S3. AWS region can be specified within URL or defaults to 'us-east-1', e.g. 
+Bucket URL for S3. AWS region can be specified within URL or defaults to 'us-east-1', e.g.
 - http://grafana.s3.amazonaws.com/
 - http://grafana.s3.amazonaws.com/
 - https://grafana.s3-ap-southeast-2.amazonaws.com/
 - https://grafana.s3-ap-southeast-2.amazonaws.com/
 - https://grafana.s3-cn-north-1.amazonaws.com.cn
 - https://grafana.s3-cn-north-1.amazonaws.com.cn

+ 9 - 0
docs/sources/installation/debian.md

@@ -16,6 +16,7 @@ weight = 1
 Description | Download
 Description | Download
 ------------ | -------------
 ------------ | -------------
 Stable for Debian-based Linux | [4.1.2 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_4.1.2-1486989747_amd64.deb)
 Stable for Debian-based Linux | [4.1.2 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_4.1.2-1486989747_amd64.deb)
+Beta for Debian-based Linux | [4.2.0-beta1 (x86-64 deb)](https://grafanarel.s3.amazonaws.com/builds/grafana_4.2.0-beta1_amd64.deb)
 
 
 ## Install Stable
 ## Install Stable
 
 
@@ -25,6 +26,14 @@ $ sudo apt-get install -y adduser libfontconfig
 $ sudo dpkg -i grafana_4.1.2-1486989747_amd64.deb
 $ sudo dpkg -i grafana_4.1.2-1486989747_amd64.deb
 ```
 ```
 
 
+## Install Beta
+
+```
+$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_4.2.0-beta1_amd64.deb
+$ sudo apt-get install -y adduser libfontconfig
+$ sudo dpkg -i grafana_4.2.0-beta1_amd64.deb
+```
+
 ## APT Repository
 ## APT Repository
 
 
 Add the following line to your `/etc/apt/sources.list` file.
 Add the following line to your `/etc/apt/sources.list` file.

+ 1 - 0
docs/sources/installation/rpm.md

@@ -16,6 +16,7 @@ weight = 2
 Description | Download
 Description | Download
 ------------ | -------------
 ------------ | -------------
 Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.1.2 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-4.1.2-1486989747.x86_64.rpm)
 Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.1.2 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-4.1.2-1486989747.x86_64.rpm)
+Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [4.2.0-beta1 (x86-64 rpm)](https://grafanarel.s3.amazonaws.com/builds/grafana-4.2.0-beta1.x86_64.rpm)
 
 
 ## Install Stable
 ## Install Stable
 
 

+ 1 - 0
docs/sources/installation/windows.md

@@ -14,6 +14,7 @@ weight = 3
 Description | Download
 Description | Download
 ------------ | -------------
 ------------ | -------------
 Latest stable package for Windows | [grafana.4.1.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/builds/grafana-4.1.2.windows-x64.zip)
 Latest stable package for Windows | [grafana.4.1.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/builds/grafana-4.1.2.windows-x64.zip)
+Latest beta package for Windows | [grafana-4.2.0-beta1.windows-x64.zip](https://grafanarel.s3.amazonaws.com/builds/grafana-4.2.0-beta1.windows-x64.zip)
 
 
 ## Configure
 ## Configure
 
 

+ 49 - 9
pkg/api/cloudwatch/cloudwatch.go

@@ -114,7 +114,10 @@ func getCredentials(dsInfo *datasourceInfo) (*credentials.Credentials, error) {
 			DurationSeconds: aws.Int64(900),
 			DurationSeconds: aws.Int64(900),
 		}
 		}
 
 
-		stsSess := session.New()
+		stsSess, err := session.NewSession()
+		if err != nil {
+			return nil, err
+		}
 		stsCreds := credentials.NewChainCredentials(
 		stsCreds := credentials.NewChainCredentials(
 			[]credentials.Provider{
 			[]credentials.Provider{
 				&credentials.EnvProvider{},
 				&credentials.EnvProvider{},
@@ -126,7 +129,11 @@ func getCredentials(dsInfo *datasourceInfo) (*credentials.Credentials, error) {
 			Credentials: stsCreds,
 			Credentials: stsCreds,
 		}
 		}
 
 
-		svc := sts.New(session.New(stsConfig), stsConfig)
+		sess, err := session.NewSession(stsConfig)
+		if err != nil {
+			return nil, err
+		}
+		svc := sts.New(sess, stsConfig)
 		resp, err := svc.AssumeRole(params)
 		resp, err := svc.AssumeRole(params)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -139,7 +146,10 @@ func getCredentials(dsInfo *datasourceInfo) (*credentials.Credentials, error) {
 		}
 		}
 	}
 	}
 
 
-	sess := session.New()
+	sess, err := session.NewSession()
+	if err != nil {
+		return nil, err
+	}
 	creds := credentials.NewChainCredentials(
 	creds := credentials.NewChainCredentials(
 		[]credentials.Provider{
 		[]credentials.Provider{
 			&credentials.StaticProvider{Value: credentials.Value{
 			&credentials.StaticProvider{Value: credentials.Value{
@@ -185,7 +195,12 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {
@@ -232,7 +247,12 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {
@@ -273,7 +293,12 @@ func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {
@@ -316,7 +341,12 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {
@@ -360,7 +390,12 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {
@@ -396,7 +431,12 @@ func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		c.JsonApiErr(500, "Unable to call AWS API", err)
 		return
 		return
 	}
 	}
-	svc := ec2.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+	svc := ec2.New(sess, cfg)
 
 
 	reqParam := &struct {
 	reqParam := &struct {
 		Parameters struct {
 		Parameters struct {

+ 5 - 2
pkg/api/cloudwatch/metrics.go

@@ -258,8 +258,11 @@ func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error)
 		Region:      aws.String(cwData.Region),
 		Region:      aws.String(cwData.Region),
 		Credentials: creds,
 		Credentials: creds,
 	}
 	}
-
-	svc := cloudwatch.New(session.New(cfg), cfg)
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		return cloudwatch.ListMetricsOutput{}, err
+	}
+	svc := cloudwatch.New(sess, cfg)
 
 
 	params := &cloudwatch.ListMetricsInput{
 	params := &cloudwatch.ListMetricsInput{
 		Namespace: aws.String(cwData.Namespace),
 		Namespace: aws.String(cwData.Namespace),

+ 9 - 2
pkg/components/imguploader/s3uploader.go

@@ -35,7 +35,10 @@ func NewS3Uploader(region, bucket, acl, accessKey, secretKey string) *S3Uploader
 }
 }
 
 
 func (u *S3Uploader) Upload(imageDiskPath string) (string, error) {
 func (u *S3Uploader) Upload(imageDiskPath string) (string, error) {
-	sess := session.New()
+	sess, err := session.NewSession()
+	if err != nil {
+		return "", err
+	}
 	creds := credentials.NewChainCredentials(
 	creds := credentials.NewChainCredentials(
 		[]credentials.Provider{
 		[]credentials.Provider{
 			&credentials.StaticProvider{Value: credentials.Value{
 			&credentials.StaticProvider{Value: credentials.Value{
@@ -58,7 +61,11 @@ func (u *S3Uploader) Upload(imageDiskPath string) (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	svc := s3.New(session.New(cfg), cfg)
+	sess, err = session.NewSession(cfg)
+	if err != nil {
+		return "", err
+	}
+	svc := s3.New(sess, cfg)
 	params := &s3.PutObjectInput{
 	params := &s3.PutObjectInput{
 		Bucket:      aws.String(u.bucket),
 		Bucket:      aws.String(u.bucket),
 		Key:         aws.String(key),
 		Key:         aws.String(key),

+ 7 - 1
pkg/middleware/session.go

@@ -1,6 +1,7 @@
 package middleware
 package middleware
 
 
 import (
 import (
+	"math/rand"
 	"time"
 	"time"
 
 
 	"github.com/go-macaron/session"
 	"github.com/go-macaron/session"
@@ -8,6 +9,7 @@ import (
 	_ "github.com/go-macaron/session/mysql"
 	_ "github.com/go-macaron/session/mysql"
 	_ "github.com/go-macaron/session/postgres"
 	_ "github.com/go-macaron/session/postgres"
 	_ "github.com/go-macaron/session/redis"
 	_ "github.com/go-macaron/session/redis"
+	"github.com/grafana/grafana/pkg/log"
 	"gopkg.in/macaron.v1"
 	"gopkg.in/macaron.v1"
 )
 )
 
 
@@ -22,10 +24,12 @@ var sessionManager *session.Manager
 var sessionOptions *session.Options
 var sessionOptions *session.Options
 var startSessionGC func()
 var startSessionGC func()
 var getSessionCount func() int
 var getSessionCount func() int
+var sessionLogger = log.New("session")
 
 
 func init() {
 func init() {
 	startSessionGC = func() {
 	startSessionGC = func() {
 		sessionManager.GC()
 		sessionManager.GC()
+		sessionLogger.Debug("Session GC")
 		time.AfterFunc(time.Duration(sessionOptions.Gclifetime)*time.Second, startSessionGC)
 		time.AfterFunc(time.Duration(sessionOptions.Gclifetime)*time.Second, startSessionGC)
 	}
 	}
 	getSessionCount = func() int {
 	getSessionCount = func() int {
@@ -67,7 +71,9 @@ func Sessioner(options *session.Options) macaron.Handler {
 		panic(err)
 		panic(err)
 	}
 	}
 
 
-	go startSessionGC()
+	// start GC threads after some random seconds
+	rndSeconds := 10 + rand.Int63n(180)
+	time.AfterFunc(time.Duration(rndSeconds)*time.Second, startSessionGC)
 
 
 	return func(ctx *Context) {
 	return func(ctx *Context) {
 		ctx.Next()
 		ctx.Next()

+ 1 - 1
pkg/services/alerting/notifiers/email.go

@@ -16,7 +16,7 @@ func init() {
 	alerting.RegisterNotifier(&alerting.NotifierPlugin{
 	alerting.RegisterNotifier(&alerting.NotifierPlugin{
 		Type:        "email",
 		Type:        "email",
 		Name:        "Email",
 		Name:        "Email",
-		Description: "Sends notifications using Grafana server configured STMP settings",
+		Description: "Sends notifications using Grafana server configured SMTP settings",
 		Factory:     NewEmailNotifier,
 		Factory:     NewEmailNotifier,
 		OptionsTemplate: `
 		OptionsTemplate: `
       <h3 class="page-heading">Email addresses</h3>
       <h3 class="page-heading">Email addresses</h3>

+ 14 - 2
pkg/services/alerting/notifiers/threema.go

@@ -126,9 +126,21 @@ func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error
 	data.Set("to", notifier.RecipientID)
 	data.Set("to", notifier.RecipientID)
 	data.Set("secret", notifier.APISecret)
 	data.Set("secret", notifier.APISecret)
 
 
+	// Determine emoji
+	stateEmoji := ""
+	switch evalContext.Rule.State {
+	case m.AlertStateOK:
+		stateEmoji = "\u2705 " // White Heavy Check Mark
+	case m.AlertStateNoData:
+		stateEmoji = "\u2753 " // Black Question Mark Ornament
+	case m.AlertStateAlerting:
+		stateEmoji = "\u26A0 " // Warning sign
+	}
+
 	// Build message
 	// Build message
-	message := fmt.Sprintf("%s\n\n*State:* %s\n*Message:* %s\n",
-		evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
+	message := fmt.Sprintf("%s%s\n\n*State:* %s\n*Message:* %s\n",
+		stateEmoji, evalContext.GetNotificationTitle(),
+		evalContext.Rule.Name, evalContext.Rule.Message)
 	ruleURL, err := evalContext.GetRuleUrl()
 	ruleURL, err := evalContext.GetRuleUrl()
 	if err == nil {
 	if err == nil {
 		message = message + fmt.Sprintf("*URL:* %s\n", ruleURL)
 		message = message + fmt.Sprintf("*URL:* %s\n", ruleURL)

+ 123 - 0
pkg/services/sqlstore/logger.go

@@ -0,0 +1,123 @@
+package sqlstore
+
+import (
+	"fmt"
+
+	glog "github.com/grafana/grafana/pkg/log"
+
+	"github.com/go-xorm/core"
+)
+
+type XormLogger struct {
+	grafanaLog glog.Logger
+	level      glog.Lvl
+	showSQL    bool
+}
+
+func NewXormLogger(level glog.Lvl, grafanaLog glog.Logger) *XormLogger {
+	return &XormLogger{
+		grafanaLog: grafanaLog,
+		level:      level,
+		showSQL:    true,
+	}
+}
+
+// Error implement core.ILogger
+func (s *XormLogger) Err(v ...interface{}) error {
+	if s.level <= glog.LvlError {
+		s.grafanaLog.Error(fmt.Sprint(v...))
+	}
+	return nil
+}
+
+// Errorf implement core.ILogger
+func (s *XormLogger) Errf(format string, v ...interface{}) error {
+	if s.level <= glog.LvlError {
+		s.grafanaLog.Error(fmt.Sprintf(format, v...))
+	}
+	return nil
+}
+
+// Debug implement core.ILogger
+func (s *XormLogger) Debug(v ...interface{}) error {
+	if s.level <= glog.LvlDebug {
+		s.grafanaLog.Debug(fmt.Sprint(v...))
+	}
+	return nil
+}
+
+// Debugf implement core.ILogger
+func (s *XormLogger) Debugf(format string, v ...interface{}) error {
+	if s.level <= glog.LvlDebug {
+		s.grafanaLog.Debug(fmt.Sprintf(format, v...))
+	}
+	return nil
+}
+
+// Info implement core.ILogger
+func (s *XormLogger) Info(v ...interface{}) error {
+	if s.level <= glog.LvlInfo {
+		s.grafanaLog.Info(fmt.Sprint(v...))
+	}
+	return nil
+}
+
+// Infof implement core.ILogger
+func (s *XormLogger) Infof(format string, v ...interface{}) error {
+	if s.level <= glog.LvlInfo {
+		s.grafanaLog.Info(fmt.Sprintf(format, v...))
+	}
+	return nil
+}
+
+// Warn implement core.ILogger
+func (s *XormLogger) Warning(v ...interface{}) error {
+	if s.level <= glog.LvlWarn {
+		s.grafanaLog.Warn(fmt.Sprint(v...))
+	}
+	return nil
+}
+
+// Warnf implement core.ILogger
+func (s *XormLogger) Warningf(format string, v ...interface{}) error {
+	if s.level <= glog.LvlWarn {
+		s.grafanaLog.Warn(fmt.Sprintf(format, v...))
+	}
+	return nil
+}
+
+// Level implement core.ILogger
+func (s *XormLogger) Level() core.LogLevel {
+	switch s.level {
+	case glog.LvlError:
+		return core.LOG_ERR
+	case glog.LvlWarn:
+		return core.LOG_WARNING
+	case glog.LvlInfo:
+		return core.LOG_INFO
+	case glog.LvlDebug:
+		return core.LOG_DEBUG
+	default:
+		return core.LOG_ERR
+	}
+}
+
+// SetLevel implement core.ILogger
+func (s *XormLogger) SetLevel(l core.LogLevel) error {
+	return nil
+}
+
+// ShowSQL implement core.ILogger
+func (s *XormLogger) ShowSQL(show ...bool) {
+	s.grafanaLog.Error("ShowSQL", "show", "show")
+	if len(show) == 0 {
+		s.showSQL = true
+		return
+	}
+	s.showSQL = show[0]
+}
+
+// IsShowSQL implement core.ILogger
+func (s *XormLogger) IsShowSQL() bool {
+	return s.showSQL
+}

+ 3 - 0
pkg/services/sqlstore/sqlstore.go

@@ -160,6 +160,9 @@ func getEngine() (*xorm.Engine, error) {
 		engine.SetMaxConns(DbCfg.MaxConn)
 		engine.SetMaxConns(DbCfg.MaxConn)
 		engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
 		engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
 		engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
 		engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
+		// engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
+		// engine.ShowSQL = true
+		// engine.ShowInfo = true
 	}
 	}
 	return engine, nil
 	return engine, nil
 }
 }

+ 4 - 0
pkg/tsdb/opentsdb/opentsdb.go

@@ -190,6 +190,10 @@ func (e *OpenTsdbExecutor) buildMetric(query *tsdb.Query) map[string]interface{}
 			rateOptions["resetValue"] = resetValue.MustFloat64()
 			rateOptions["resetValue"] = resetValue.MustFloat64()
 		}
 		}
 
 
+		if !counterMaxCheck && (!resetValueCheck || resetValue.MustFloat64() == 0) {
+			rateOptions["dropcounter"] = true
+		}
+
 		metric["rateOptions"] = rateOptions
 		metric["rateOptions"] = rateOptions
 	}
 	}
 
 

+ 6 - 4
public/app/core/utils/kbn.js

@@ -488,6 +488,7 @@ function($, _) {
   kbn.valueFormats.litre  = kbn.formatBuilders.decimalSIPrefix('L');
   kbn.valueFormats.litre  = kbn.formatBuilders.decimalSIPrefix('L');
   kbn.valueFormats.mlitre = kbn.formatBuilders.decimalSIPrefix('L', -1);
   kbn.valueFormats.mlitre = kbn.formatBuilders.decimalSIPrefix('L', -1);
   kbn.valueFormats.m3     = kbn.formatBuilders.decimalSIPrefix('m3');
   kbn.valueFormats.m3     = kbn.formatBuilders.decimalSIPrefix('m3');
+  kbn.valueFormats.dm3    = kbn.formatBuilders.decimalSIPrefix('dm3');
   kbn.valueFormats.gallons  = kbn.formatBuilders.fixedUnit('gal');
   kbn.valueFormats.gallons  = kbn.formatBuilders.fixedUnit('gal');
 
 
   // Flow
   // Flow
@@ -805,10 +806,11 @@ function($, _) {
       {
       {
         text: 'volume',
         text: 'volume',
         submenu: [
         submenu: [
-          {text: 'millilitre',  value: 'mlitre' },
-          {text: 'litre',       value: 'litre'  },
-          {text: 'cubic metre', value: 'm3'     },
-          {text: 'gallons',     value: 'gallons'},
+          {text: 'millilitre',      value: 'mlitre' },
+          {text: 'litre',           value: 'litre'  },
+          {text: 'cubic metre',     value: 'm3'     },
+          {text: 'cubic decimetre', value: 'dm3'    },
+          {text: 'gallons',         value: 'gallons'},
         ]
         ]
       },
       },
       {
       {

+ 1 - 0
public/app/features/dashboard/dashnav/dashnav.ts

@@ -106,6 +106,7 @@ export class DashNavCtrl {
         confirmText: confirmText,
         confirmText: confirmText,
         yesText: 'Delete',
         yesText: 'Delete',
         onConfirm: function() {
         onConfirm: function() {
+          $scope.dashboardMeta.canSave = false;
           $scope.deleteDashboardConfirmed();
           $scope.deleteDashboardConfirmed();
         }
         }
       });
       });

+ 7 - 3
public/app/features/templating/datasource_variable.ts

@@ -2,7 +2,7 @@
 
 
 import _ from 'lodash';
 import _ from 'lodash';
 import kbn from 'app/core/utils/kbn';
 import kbn from 'app/core/utils/kbn';
-import {Variable, assignModelProperties, variableTypes} from './variable';
+import {Variable, containsVariable, assignModelProperties, variableTypes} from './variable';
 import {VariableSrv} from './variable_srv';
 import {VariableSrv} from './variable_srv';
 
 
 export class DatasourceVariable implements Variable {
 export class DatasourceVariable implements Variable {
@@ -25,7 +25,7 @@ export class DatasourceVariable implements Variable {
   };
   };
 
 
   /** @ngInject **/
   /** @ngInject **/
-  constructor(private model, private datasourceSrv, private variableSrv) {
+  constructor(private model, private datasourceSrv, private variableSrv, private templateSrv) {
     assignModelProperties(this, model, this.defaults);
     assignModelProperties(this, model, this.defaults);
     this.refresh = 1;
     this.refresh = 1;
   }
   }
@@ -48,7 +48,8 @@ export class DatasourceVariable implements Variable {
     var regex;
     var regex;
 
 
     if (this.regex) {
     if (this.regex) {
-      regex = kbn.stringToJsRegex(this.regex);
+      regex = this.templateSrv.replace(this.regex, null, 'regex');
+      regex = kbn.stringToJsRegex(regex);
     }
     }
 
 
     for (var i = 0; i < sources.length; i++) {
     for (var i = 0; i < sources.length; i++) {
@@ -74,6 +75,9 @@ export class DatasourceVariable implements Variable {
   }
   }
 
 
   dependsOn(variable) {
   dependsOn(variable) {
+    if (this.regex) {
+      return containsVariable(this.regex, variable.name);
+    }
     return false;
     return false;
   }
   }
 
 

+ 3 - 2
public/app/features/templating/interval_variable.ts

@@ -59,8 +59,9 @@ export class IntervalVariable implements Variable {
   }
   }
 
 
   updateOptions() {
   updateOptions() {
-   // extract options in comma separated string
-    this.options = _.map(this.query.split(/[,]+/), function(text) {
+    // extract options between quotes and/or comma
+    this.options = _.map(this.query.match(/(["'])(.*?)\1|\w+/g), function(text) {
+      text = text.replace(/["']+/g, '');
       return {text: text.trim(), value: text.trim()};
       return {text: text.trim(), value: text.trim()};
     });
     });
 
 

+ 1 - 1
public/app/features/templating/partials/editor.html

@@ -124,7 +124,7 @@
               Step count <tip>How many times should the current time range be divided to calculate the value</tip>
               Step count <tip>How many times should the current time range be divided to calculate the value</tip>
             </span>
             </span>
             <div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
             <div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
-              <select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [2,3,4,5,10,20,30,40,50,100,200,300,400,500]" ng-change="runQuery()"></select>
+              <select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [1,2,3,4,5,10,20,30,40,50,100,200,300,400,500]" ng-change="runQuery()"></select>
             </div>
             </div>
           </div>
           </div>
           <div class="gf-form">
           <div class="gf-form">

+ 3 - 0
public/app/plugins/datasource/elasticsearch/metric_agg.js

@@ -162,6 +162,9 @@ function (angular, _, queryDef) {
     };
     };
 
 
     $scope.getFieldsInternal = function() {
     $scope.getFieldsInternal = function() {
+      if ($scope.agg.type === 'cardinality') {
+        return $scope.getFields();
+      }
       return $scope.getFields({$fieldType: 'number'});
       return $scope.getFields({$fieldType: 'number'});
     };
     };
 
 

+ 1 - 1
public/app/plugins/datasource/influxdb/influx_query.ts

@@ -167,7 +167,7 @@ export default class InfluxQuery {
     var policy = this.target.policy;
     var policy = this.target.policy;
     var measurement = this.target.measurement || 'measurement';
     var measurement = this.target.measurement || 'measurement';
 
 
-    if (!measurement.match('^/.*/')) {
+    if (!measurement.match('^/.*/$')) {
       measurement = '"' + measurement+ '"';
       measurement = '"' + measurement+ '"';
     } else if (interpolate) {
     } else if (interpolate) {
       measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
       measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');

+ 2 - 0
public/app/plugins/panel/graph/legend.js

@@ -65,7 +65,9 @@ function (angular, _, $) {
           var el = $(e.currentTarget);
           var el = $(e.currentTarget);
           var index = getSeriesIndexForElement(el);
           var index = getSeriesIndexForElement(el);
           var seriesInfo = seriesList[index];
           var seriesInfo = seriesList[index];
+          var scrollPosition = $($container.children('tbody')).scrollTop();
           ctrl.toggleSeries(seriesInfo, e);
           ctrl.toggleSeries(seriesInfo, e);
+          $($container.children('tbody')).scrollTop(scrollPosition);
         }
         }
 
 
         function sortLegend(e) {
         function sortLegend(e) {

+ 1 - 18
scripts/build/build.sh

@@ -7,20 +7,7 @@
 GOPATH=/go
 GOPATH=/go
 REPO_PATH=$GOPATH/src/github.com/grafana/grafana
 REPO_PATH=$GOPATH/src/github.com/grafana/grafana
 
 
-mkdir -p /go/src/github.com/grafana
-cd /go/src/github.com/grafana
-
-if [ "$CIRCLE_TAG" != "" ]; then
-  echo "Builing from tag $CIRCLE_TAG"
-  git clone https://github.com/grafana/grafana.git
-  cd $REPO_PATH
-  git checkout $CIRCLE_TAG
-else
-  echo "Building from branch $CIRCLE_BRANCH"
-  git clone --depth 1 https://github.com/grafana/grafana.git -b $CIRCLE_BRANCH
-  cd $REPO_PATH
-fi
-
+cd /go/src/github.com/grafana/grafana
 echo "current dir: $(pwd)"
 echo "current dir: $(pwd)"
 
 
 if [ "$CIRCLE_TAG" != "" ]; then
 if [ "$CIRCLE_TAG" != "" ]; then
@@ -47,7 +34,3 @@ else
   echo "Packaging incremental build for $CIRCLE_BRANCH"
   echo "Packaging incremental build for $CIRCLE_BRANCH"
   go run build.go -buildNumber=${CIRCLE_BUILD_NUM} package latest
   go run build.go -buildNumber=${CIRCLE_BUILD_NUM} package latest
 fi
 fi
-
-cp dist/* /tmp/dist/
-
-

+ 3 - 1
scripts/build/deploy.sh

@@ -5,8 +5,10 @@ mkdir -p dist
 echo "Circle branch: ${CIRCLE_BRANCH}"
 echo "Circle branch: ${CIRCLE_BRANCH}"
 echo "Circle tag: ${CIRCLE_TAG}"
 echo "Circle tag: ${CIRCLE_TAG}"
 docker run -i -t --name gfbuild \
 docker run -i -t --name gfbuild \
-  -v $(pwd)/dist:/tmp/dist \
+  -v $(pwd):/go/src/github.com/grafana/grafana \
   -e "CIRCLE_BRANCH=${CIRCLE_BRANCH}" \
   -e "CIRCLE_BRANCH=${CIRCLE_BRANCH}" \
   -e "CIRCLE_TAG=${CIRCLE_TAG}" \
   -e "CIRCLE_TAG=${CIRCLE_TAG}" \
   -e "CIRCLE_BUILD_NUM=${CIRCLE_BUILD_NUM}" \
   -e "CIRCLE_BUILD_NUM=${CIRCLE_BUILD_NUM}" \
   grafana/buildcontainer
   grafana/buildcontainer
+
+sudo chown -R ${USER:=$(/usr/bin/id -run)}:$USER dist

+ 3 - 2
scripts/circle-test.sh

@@ -24,7 +24,8 @@ exit_if_fail test -z "$(gofmt -s -l ./pkg | tee /dev/stderr)"
 echo "running go vet"
 echo "running go vet"
 exit_if_fail test -z "$(go vet ./pkg/... | tee /dev/stderr)"
 exit_if_fail test -z "$(go vet ./pkg/... | tee /dev/stderr)"
 
 
+echo "building binaries"
 exit_if_fail go run build.go build
 exit_if_fail go run build.go build
-exit_if_fail go test -v ./pkg/...
-
 
 
+echo "running go test"
+exit_if_fail go test -v ./pkg/...