diff --git a/.circleci/config.yml b/.circleci/config.yml
index ec1fcfb411f..209cf5c98cc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -19,7 +19,7 @@ version: 2
jobs:
mysql-integration-test:
docker:
- - image: circleci/golang:1.11.4
+ - image: circleci/golang:1.11.5
- image: circleci/mysql:5.6-ram
environment:
MYSQL_ROOT_PASSWORD: rootpass
@@ -39,7 +39,7 @@ jobs:
postgres-integration-test:
docker:
- - image: circleci/golang:1.11.4
+ - image: circleci/golang:1.11.5
- image: circleci/postgres:9.3-ram
environment:
POSTGRES_USER: grafanatest
@@ -74,27 +74,16 @@ jobs:
gometalinter:
docker:
- - image: circleci/golang:1.11.4
+ - image: circleci/golang:1.11.5
environment:
# we need CGO because of go-sqlite3
CGO_ENABLED: 1
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
- - run: 'go get -u github.com/alecthomas/gometalinter'
- - run: 'go get -u github.com/tsenart/deadcode'
- - run: 'go get -u github.com/jgautheron/goconst/cmd/goconst'
- - run: 'go get -u github.com/gordonklaus/ineffassign'
- - run: 'go get -u honnef.co/go/tools/cmd/megacheck'
- - run: 'go get -u github.com/opennota/check/cmd/structcheck'
- - run: 'go get -u github.com/mdempsky/unconvert'
- - run: 'go get -u github.com/opennota/check/cmd/varcheck'
- run:
- name: run linters
- command: 'gometalinter --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=goconst --enable=gofmt --enable=ineffassign --enable=megacheck --enable=structcheck --enable=unconvert --enable=varcheck ./...'
- - run:
- name: run go vet
- command: 'go vet ./pkg/...'
+ name: Gometalinter tests
+ command: './scripts/gometalinter.sh'
test-frontend:
docker:
@@ -117,7 +106,7 @@ jobs:
test-backend:
docker:
- - image: circleci/golang:1.11.4
+ - image: circleci/golang:1.11.5
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -127,7 +116,7 @@ jobs:
build-all:
docker:
- - image: grafana/build-container:1.2.2
+ - image: grafana/build-container:1.2.3
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -158,9 +147,6 @@ jobs:
- run:
name: sha-sum packages
command: 'go run build.go sha-dist'
- - run:
- name: Build Grafana.com master publisher
- command: 'go build -o scripts/publish scripts/build/publish.go'
- run:
name: Test and build Grafana.com release publisher
command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
@@ -169,13 +155,12 @@ jobs:
paths:
- dist/grafana*
- scripts/*.sh
- - scripts/publish
- scripts/build/release_publisher/release_publisher
- scripts/build/publish.sh
build:
docker:
- - image: grafana/build-container:1.2.2
+ - image: grafana/build-container:1.2.3
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -244,7 +229,7 @@ jobs:
build-enterprise:
docker:
- - image: grafana/build-container:1.2.2
+ - image: grafana/build-container:1.2.3
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -276,7 +261,7 @@ jobs:
build-all-enterprise:
docker:
- - image: grafana/build-container:1.2.2
+ - image: grafana/build-container:1.2.3
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -323,7 +308,7 @@ jobs:
deploy-enterprise-master:
docker:
- - image: grafana/grafana-ci-deploy:1.1.0
+ - image: grafana/grafana-ci-deploy:1.2.0
steps:
- attach_workspace:
at: .
@@ -346,7 +331,7 @@ jobs:
deploy-enterprise-release:
docker:
- - image: grafana/grafana-ci-deploy:1.1.0
+ - image: grafana/grafana-ci-deploy:1.2.0
steps:
- attach_workspace:
at: .
@@ -370,15 +355,15 @@ jobs:
command: './scripts/build/load-signing-key.sh'
- run:
name: Update Debian repository
- command: './scripts/build/update_repo/update-deb.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+ command: './scripts/build/update_repo/update-deb.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "enterprise-dist"'
- run:
name: Update RPM repository
- command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+ command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "enterprise-dist"'
deploy-master:
docker:
- - image: grafana/grafana-ci-deploy:1.1.0
+ - image: grafana/grafana-ci-deploy:1.2.0
steps:
- attach_workspace:
at: .
@@ -404,11 +389,12 @@ jobs:
name: Publish to Grafana.com
command: |
rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
- ./scripts/publish -apiKey ${GRAFANA_COM_API_KEY}
+ rm dist/*latest*
+ cd dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -from-local
deploy-release:
docker:
- - image: grafana/grafana-ci-deploy:1.1.0
+ - image: grafana/grafana-ci-deploy:1.2.0
steps:
- checkout
- attach_workspace:
@@ -433,10 +419,10 @@ jobs:
command: './scripts/build/load-signing-key.sh'
- run:
name: Update Debian repository
- command: './scripts/build/update_repo/update-deb.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+ command: './scripts/build/update_repo/update-deb.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "dist"'
- run:
name: Update RPM repository
- command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+ command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "dist"'
workflows:
version: 2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9225d6545e4..8c2f94609ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,29 +1,46 @@
-# 5.5.0 (unreleased)
+# 6.0.0-beta1 (unreleased)
### New Features
* **Alerting**: Adds support for Google Hangouts Chat notifications [#11221](https://github.com/grafana/grafana/issues/11221), thx [@PatrickSchuster](https://github.com/PatrickSchuster)
* **Elasticsearch**: Support bucket script pipeline aggregations [#5968](https://github.com/grafana/grafana/issues/5968)
+* **Influxdb**: Add support for time zone (`tz`) clause [#10322](https://github.com/grafana/grafana/issues/10322), thx [@cykl](https://github.com/cykl)
* **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109)
### Minor
+* **Alerting**: Use seperate timeouts for alert evals and notifications [#14701](https://github.com/grafana/grafana/issues/14701), thx [@sharkpc0813](https://github.com/sharkpc0813)
* **Elasticsearch**: Add support for offset in date histogram aggregation [#12653](https://github.com/grafana/grafana/issues/12653), thx [@mattiarossi](https://github.com/mattiarossi)
* **Elasticsearch**: Add support for moving average and derivative using doc count (metric count) [#8843](https://github.com/grafana/grafana/issues/8843) [#11175](https://github.com/grafana/grafana/issues/11175)
+* **Elasticsearch**: Add support for template variable interpolation in alias field [#4075](https://github.com/grafana/grafana/issues/4075), thx [@SamuelToh](https://github.com/SamuelToh)
+* **Influxdb**: Fix autocomplete of measurements does not escape search string properly [#11503](https://github.com/grafana/grafana/issues/11503), thx [@SamuelToh](https://github.com/SamuelToh)
+* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
+* **Cloudwatch**: Fix Assume Role Arn [#14722](https://github.com/grafana/grafana/issues/14722), thx [@jaken551](https://github.com/jaken551)
+* **Provisioning**: Fixes bug causing infinite growth in dashboard_version table. [#12864](https://github.com/grafana/grafana/issues/12864)
* **Auth**: Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled [#14246](https://github.com/grafana/grafana/issues/14246), thx [@SilverFire](https://github.com/SilverFire)
-* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
* **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh)
-* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK]req(https://github.com/IntegersOfK)
* **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483)
* **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548)
-* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
+* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
+* **Dashboard**: `Min width` changed to `Max per row` for repeating panels. This lets you specify the maximum number of panels to show per row and by that repeated panels will always take up full width of row [#12991](https://github.com/grafana/grafana/pull/12991), thx [@pgiraud](https://github.com/pgiraud)
+* **Dashboard**: Retain decimal precision when exporting CSV [#13929](https://github.com/grafana/grafana/issues/13929), thx [@cinaglia](https://github.com/cinaglia)
+* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK)
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
-* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
-* **Provisioning**: Fixes bug causing infinite growth in dashboard_version table. [#12864](https://github.com/grafana/grafana/issues/12864)
+* **Units**: Add Floating Point Operations per Second units [#14558](https://github.com/grafana/grafana/pull/14558), thx [@hahnjo](https://github.com/hahnjo)
+* **Table**: Renders epoch string as date if date column style [#14484](https://github.com/grafana/grafana/issues/14484)
+* **Piechart/Flot**: Fixes multiple piechart instances with donut bug [#15062](https://github.com/grafana/grafana/pull/15062)
+* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
+* **Dataproxy**: Add global datasource proxy timeout setting [#5699](https://github.com/grafana/grafana/issues/5699), thx [@RangerRick](https://github.com/RangerRick)
+* **Database**: Support specifying database host using IPV6 for backend database and sql datasources [#13711](https://github.com/grafana/grafana/issues/13711), thx [@ellisvlad](https://github.com/ellisvlad)
### Bug fixes
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
* **Prometheus**: Query for annotation always uses 60s step regardless of dashboard range, fixes [#14795](https://github.com/grafana/grafana/issues/14795)
+* **Annotations**: Fix creating annotation when graph panel has no data points position the popup outside viewport [#13765](https://github.com/grafana/grafana/issues/13765), thx [@banjeremy](https://github.com/banjeremy)
+
+### Breaking changes
+* **Text Panel**: The text panel does no longer by default allow unsantizied HTML. [#4117](https://github.com/grafana/grafana/issues/4117). This means that if you have text panels with scripts tags they will no longer work as before. To enable unsafe javascript execution in text panels enable the settings `disable_sanitize_html` under the section `[panels]` in your Grafana ini file, or set env variable `GF_PANELS_DISABLE_SANITIZE_HTML=true`.
+* **Dashboard**: Panel property `minSpan` replaced by `maxPerRow`. Dashboard migration will automatically migrate all dashboard panels using the `minSpan` property to the new `maxPerRow` property [#12991](https://github.com/grafana/grafana/pull/12991)
# 5.4.3 (2019-01-14)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 94daa2dc4d5..62f610cab59 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,7 +2,7 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
diff --git a/Dockerfile b/Dockerfile
index c3af89b6092..c3e59c8048e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Golang build container
-FROM golang:1.11.4
+FROM golang:1.11.5
WORKDIR $GOPATH/src/github.com/grafana/grafana
@@ -19,11 +19,13 @@ COPY package.json package.json
RUN go run build.go build
# Node build container
-FROM node:8
+FROM node:10.14.2
WORKDIR /usr/src/app/
COPY package.json yarn.lock ./
+COPY packages packages
+
RUN yarn install --pure-lockfile --no-progress
COPY Gruntfile.js tsconfig.json tslint.json ./
diff --git a/PLUGIN_DEV.md b/PLUGIN_DEV.md
index 168b21dbd88..47743e5fbf9 100644
--- a/PLUGIN_DEV.md
+++ b/PLUGIN_DEV.md
@@ -1,7 +1,7 @@
# Plugin Development
-This document is not meant as complete guide for developing plugins but more as a changelog for changes in
-Grafana that can impact plugin development. When ever you as plugin author encounter an issue with your plugin after
+This document is not meant as a complete guide for developing plugins but more as a changelog for changes in
+Grafana that can impact plugin development. Whenever you as a plugin author encounter an issue with your plugin after
upgrading Grafana please check here before creating an issue.
## Links
diff --git a/README.md b/README.md
index ff5da04f209..3df6a383e05 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ If you have any problems please read the [troubleshooting guide](http://docs.gra
Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides.
## Run from master
-If you want to build a package yourself, or contribute - Here is a guide for how to do that. You can always find
+If you want to build a package yourself, or contribute - here is a guide for how to do that. You can always find
the latest master builds [here](https://grafana.com/grafana/download)
### Dependencies
@@ -71,7 +71,7 @@ Open grafana in your browser (default: `http://localhost:3000`) and login with a
### Building a Docker image
-There are two different ways to build a Grafana docker image. If you're machine is setup for Grafana development and you run linux/amd64 you can build just the image. Otherwise, there is the option to build Grafana completely within Docker.
+There are two different ways to build a Grafana docker image. If your machine is setup for Grafana development and you run linux/amd64 you can build just the image. Otherwise, there is the option to build Grafana completely within Docker.
Run the image you have built using: `docker run --rm -p 3000:3000 grafana/grafana:dev`
@@ -90,7 +90,7 @@ Choose this option to build on platforms other than linux/amd64 and/or not have
The resulting image will be tagged as `grafana/grafana:dev`
-Notice: If you are using Docker for MacOS, be sure to let limit of Memory bigger than 2 GiB (at docker -> Preferences -> Advanced), otherwize you may faild at `grunt build`
+Notice: If you are using Docker for MacOS, be sure to set the memory limit to be larger than 2 GiB (at docker -> Preferences -> Advanced), otherwise `grunt build` may fail.
### Dev config
@@ -129,8 +129,8 @@ GRAFANA_TEST_DB=postgres go test ./pkg/...
## Contribute
-If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
-And if you have time clone this repo and submit a pull request and help me make Grafana
+If you have any ideas for improvement or have found a bug, do not hesitate to open an issue.
+And if you have time, clone this repo and submit a pull request to help me make Grafana
the kickass metrics & devops dashboard we all dream about!
Read the [contributing](https://github.com/grafana/grafana/blob/master/CONTRIBUTING.md) guide then check the [`beginner friendly`](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22) label to find issues that are easy and that we would like help with.
diff --git a/ROADMAP.md b/ROADMAP.md
index 891bc9f790b..b5e62578475 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -5,18 +5,22 @@ But it will give you an idea of our current vision and plan.
### Short term (1-2 months)
- PRs & Bugs
- - Multi-Stat panel
+ - React Panel Support
+ - React Query Editor Support
- Metrics & Log Explore UI
-
+ - Grafana UI library shared between grafana & plugins
+ - Seperate visualization from panels
+ - More reuse between Explore & dashboard
+ - Explore logging support for more data sources
+
### Mid term (2-4 months)
- - React Panels
- - Change visualization (panel type) on the fly.
- - Templating Query Editor UI Plugin hook
- - Backend plugins
+ - Drilldown links
+ - Dashboards as code workflows
+ - React migration
+ - New panels
### Long term (4 - 8 months)
- Alerting improvements (silence, per series tracking, etc)
- - Progress on React migration
### In a distant future far far away
- Meta queries
diff --git a/appveyor.yml b/appveyor.yml
index 5f97784dd38..ccf9b5a06e1 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,7 +7,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana
environment:
nodejs_version: "8"
GOPATH: C:\gopath
- GOVERSION: 1.11.4
+ GOVERSION: 1.11.5
install:
- rmdir c:\go /s /q
diff --git a/build.go b/build.go
index 4486cd3deb9..ebe240d97ef 100644
--- a/build.go
+++ b/build.go
@@ -46,6 +46,8 @@ var (
binaries []string = []string{"grafana-server", "grafana-cli"}
isDev bool = false
enterprise bool = false
+ skipRpmGen bool = false
+ skipDebGen bool = false
)
func main() {
@@ -67,6 +69,8 @@ func main() {
flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana")
flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system")
flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps")
+ flag.BoolVar(&skipRpmGen, "skipRpm", skipRpmGen, "skip rpm package generation (default: false)")
+ flag.BoolVar(&skipDebGen, "skipDeb", skipDebGen, "skip deb package generation (default: false)")
flag.Parse()
buildId = shortenBuildId(buildIdRaw)
@@ -165,6 +169,7 @@ func makeLatestDistCopies() {
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
+ ".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
}
@@ -239,6 +244,8 @@ func createDebPackages() {
previousPkgArch := pkgArch
if pkgArch == "armv7" {
pkgArch = "armhf"
+ } else if pkgArch == "armv6" {
+ pkgArch = "armel"
}
createPackage(linuxPackageOptions{
packageType: "deb",
@@ -289,8 +296,13 @@ func createRpmPackages() {
}
func createLinuxPackages() {
- createDebPackages()
- createRpmPackages()
+ if !skipDebGen {
+ createDebPackages()
+ }
+
+ if !skipRpmGen {
+ createRpmPackages()
+ }
}
func createPackage(options linuxPackageOptions) {
diff --git a/conf/defaults.ini b/conf/defaults.ini
index 7f61ac96870..788112ae67e 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -106,6 +106,22 @@ path = grafana.db
# For "sqlite3" only. cache mode setting used for connecting to the database
cache_mode = private
+#################################### Login ###############################
+
+[login]
+
+# Login cookie name
+cookie_name = grafana_session
+
+# How many days an session can be unused before we inactivate it
+login_remember_days = 7
+
+# How often should the login token be rotated. default to '10m'
+rotate_token_minutes = 10
+
+# How long should Grafana keep expired tokens before deleting them
+delete_expired_token_after_days = 30
+
#################################### Session #############################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
@@ -143,6 +159,9 @@ conn_max_lifetime = 14400
# This enables data proxy logging, default is false
logging = false
+# How long the data proxy should wait before timing out default is 30 (seconds)
+timeout = 30
+
#################################### Analytics ###########################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
@@ -175,11 +194,6 @@ admin_password = admin
# used for signing
secret_key = SW2YcwTIb9zpOOhoPsMm
-# Auto-login remember days
-login_remember_days = 7
-cookie_username = grafana_user
-cookie_remember_name = grafana_remember
-
# disable gravatar profile images
disable_gravatar = false
@@ -189,6 +203,9 @@ data_source_proxy_whitelist =
# disable protection against brute force login attempts
disable_brute_force_login_protection = false
+# set cookies as https only. default is false
+https_flag_cookies = false
+
#################################### Snapshots ###########################
[snapshots]
# snapshot sharing options
@@ -490,7 +507,7 @@ concurrent_render_limit = 5
#################################### Explore #############################
[explore]
# Enable the Explore section
-enabled = false
+enabled = true
#################################### Internal Grafana Metrics ############
# Metrics available at HTTP API Url /metrics
@@ -570,6 +587,7 @@ callback_url =
[panels]
enable_alpha = false
+disable_sanitize_html = false
[enterprise]
license_path =
diff --git a/conf/sample.ini b/conf/sample.ini
index 014016d45bc..89880106345 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -102,6 +102,22 @@ log_queries =
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
+#################################### Login ###############################
+
+[login]
+
+# Login cookie name
+;cookie_name = grafana_session
+
+# How many days an session can be unused before we inactivate it
+;login_remember_days = 7
+
+# How often should the login token be rotated. default to '10'
+;rotate_token_minutes = 10
+
+# How long should Grafana keep expired tokens before deleting them
+;delete_expired_token_after_days = 30
+
#################################### Session ####################################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
@@ -130,6 +146,9 @@ log_queries =
# This enables data proxy logging, default is false
;logging = false
+# How long the data proxy should wait before timing out default is 30 (seconds)
+;timeout = 30
+
#################################### Analytics ####################################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
@@ -162,11 +181,6 @@ log_queries =
# used for signing
;secret_key = SW2YcwTIb9zpOOhoPsMm
-# Auto-login remember days
-;login_remember_days = 7
-;cookie_username = grafana_user
-;cookie_remember_name = grafana_remember
-
# disable gravatar profile images
;disable_gravatar = false
@@ -176,6 +190,9 @@ log_queries =
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
+# set cookies as https only. default is false
+;https_flag_cookies = false
+
#################################### Snapshots ###########################
[snapshots]
# snapshot sharing options
@@ -415,7 +432,7 @@ log_queries =
#################################### Explore #############################
[explore]
# Enable the Explore section
-;enabled = false
+;enabled = true
#################################### Internal Grafana Metrics ##########################
# Metrics available at HTTP API Url /metrics
@@ -495,3 +512,8 @@ log_queries =
# Path to a valid Grafana Enterprise license.jwt file
;license_path =
+[panels]
+;enable_alpha = false
+# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
+;disable_sanitize_html = false
+
diff --git a/devenv/dev-dashboards/panel_tests_gauge.json b/devenv/dev-dashboards/panel_tests_gauge.json
new file mode 100644
index 00000000000..c6e81ececc8
--- /dev/null
+++ b/devenv/dev-dashboards/panel_tests_gauge.json
@@ -0,0 +1,1250 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "iteration": 1547810606599,
+ "links": [],
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 11,
+ "panels": [],
+ "title": "Value options tests",
+ "type": "row"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 5,
+ "x": 0,
+ "y": 1
+ },
+ "id": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "2",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Average, 2 decimals, ms unit",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 5,
+ "y": 1
+ },
+ "id": 5,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "max",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Max (90 ms), no decimals",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 5,
+ "x": 11,
+ "y": 1
+ },
+ "id": 6,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "p",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "s",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Current (10 ms), no unit, prefix (p), suffix (s)",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 16,
+ "y": 1
+ },
+ "id": 16,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 4,
+ "w": 5,
+ "x": 19,
+ "y": 1
+ },
+ "id": 18,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10,91"
+ }
+ ],
+ "timeFrom": "1h",
+ "timeShift": null,
+ "title": "",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 16,
+ "y": 5
+ },
+ "id": 17,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 4,
+ "w": 5,
+ "x": 19,
+ "y": 5
+ },
+ "id": 19,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10,81"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "",
+ "type": "gauge"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 9
+ },
+ "id": 15,
+ "panels": [],
+ "title": "Value Mappings",
+ "type": "row"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 4,
+ "x": 0,
+ "y": 10
+ },
+ "id": 12,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": [
+ {
+ "from": "",
+ "id": 1,
+ "operator": "",
+ "text": "TEN",
+ "to": "",
+ "type": 1,
+ "value": "10"
+ }
+ ]
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "value mapping 10 -> TEN",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "description": "should read N/A",
+ "gridPos": {
+ "h": 8,
+ "w": 4,
+ "x": 4,
+ "y": 10
+ },
+ "id": 13,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": [
+ {
+ "from": "",
+ "id": 1,
+ "operator": "",
+ "text": "N/A",
+ "to": "",
+ "type": 1,
+ "value": "null"
+ }
+ ]
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10,null,null,null,null"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "value mapping null -> N/A",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "description": "should read N/A",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 8,
+ "y": 10
+ },
+ "id": 20,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": [
+ {
+ "from": "0",
+ "id": 1,
+ "operator": "",
+ "text": "OK",
+ "to": "10",
+ "type": 2,
+ "value": "null"
+ }
+ ]
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10,null,null,null,null,10"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "value mapping range, 0-10 -> OK, value 10",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "description": "should read N/A",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 14,
+ "y": 10
+ },
+ "id": 21,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "current",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "none",
+ "valueMappings": [
+ {
+ "from": "0",
+ "id": 1,
+ "operator": "",
+ "text": "OK",
+ "to": "90",
+ "type": 2,
+ "value": "null"
+ },
+ {
+ "from": "90",
+ "id": 2,
+ "operator": "",
+ "text": "BAD",
+ "to": "100",
+ "type": 2,
+ "value": ""
+ }
+ ]
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,10,null,null,null,null,10,95"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "value mapping range, 90-100 -> BAD, value 90",
+ "type": "gauge"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 18
+ },
+ "id": 9,
+ "panels": [],
+ "title": "Templating & Repeat",
+ "type": "row"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 0,
+ "y": 19
+ },
+ "id": 7,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "2",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "$Servers",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "repeat": "Servers",
+ "repeatDirection": "h",
+ "scopedVars": {
+ "Servers": {
+ "selected": false,
+ "text": "server1",
+ "value": "server1"
+ }
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "repeat $Servers",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 6,
+ "y": 19
+ },
+ "id": 22,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "2",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "$Servers",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "repeat": null,
+ "repeatDirection": "h",
+ "repeatIteration": 1547810606599,
+ "repeatPanelId": 7,
+ "scopedVars": {
+ "Servers": {
+ "selected": false,
+ "text": "server2",
+ "value": "server2"
+ }
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "repeat $Servers",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 12,
+ "y": 19
+ },
+ "id": 23,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "2",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "$Servers",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "repeat": null,
+ "repeatDirection": "h",
+ "repeatIteration": 1547810606599,
+ "repeatPanelId": 7,
+ "scopedVars": {
+ "Servers": {
+ "selected": false,
+ "text": "server3",
+ "value": "server3"
+ }
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "repeat $Servers",
+ "type": "gauge"
+ },
+ {
+ "datasource": "gdev-testdata",
+ "gridPos": {
+ "h": 8,
+ "w": 6,
+ "x": 18,
+ "y": 19
+ },
+ "id": 24,
+ "links": [],
+ "nullPointMode": "null",
+ "options-gauge": {
+ "baseColor": "#299c46",
+ "decimals": "2",
+ "maxValue": 100,
+ "minValue": 0,
+ "options": {
+ "baseColor": "#299c46",
+ "decimals": 0,
+ "maxValue": 100,
+ "minValue": 0,
+ "prefix": "",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [],
+ "unit": "none",
+ "valueMappings": []
+ },
+ "prefix": "$Servers",
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true,
+ "stat": "avg",
+ "suffix": "",
+ "thresholds": [
+ {
+ "color": "#e24d42",
+ "index": 2,
+ "value": 90
+ },
+ {
+ "color": "#ef843c",
+ "index": 1,
+ "value": 75
+ },
+ {
+ "color": "#7EB26D",
+ "index": 0,
+ "value": null
+ }
+ ],
+ "unit": "ms",
+ "valueMappings": []
+ },
+ "repeat": null,
+ "repeatDirection": "h",
+ "repeatIteration": 1547810606599,
+ "repeatPanelId": 7,
+ "scopedVars": {
+ "Servers": {
+ "selected": false,
+ "text": "server4",
+ "value": "server4"
+ }
+ },
+ "targets": [
+ {
+ "refId": "A",
+ "scenarioId": "csv_metric_values",
+ "stringInput": "1,20,90,30,5,0"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "repeat $Servers",
+ "type": "gauge"
+ }
+ ],
+ "refresh": false,
+ "schemaVersion": 17,
+ "style": "dark",
+ "tags": [
+ "gdev",
+ "panel-tests"
+ ],
+ "templating": {
+ "list": [
+ {
+ "allValue": null,
+ "current": {
+ "selected": true,
+ "tags": [],
+ "text": "All",
+ "value": [
+ "$__all"
+ ]
+ },
+ "hide": 0,
+ "includeAll": true,
+ "label": null,
+ "multi": true,
+ "name": "Servers",
+ "options": [
+ {
+ "selected": true,
+ "text": "All",
+ "value": "$__all"
+ },
+ {
+ "selected": false,
+ "text": "server1",
+ "value": "server1"
+ },
+ {
+ "selected": false,
+ "text": "server2",
+ "value": "server2"
+ },
+ {
+ "selected": false,
+ "text": "server3",
+ "value": "server3"
+ },
+ {
+ "selected": false,
+ "text": "server4",
+ "value": "server4"
+ }
+ ],
+ "query": "server1,server2,server3,server4",
+ "skipUrlSync": false,
+ "type": "custom"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-1h",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "",
+ "title": "Panel Tests - Gauge",
+ "uid": "_5rDmaQiz",
+ "version": 5
+}
diff --git a/devenv/docker/ha_test/docker-compose.yaml b/devenv/docker/ha_test/docker-compose.yaml
index 1195e2a977c..504ee86404d 100644
--- a/devenv/docker/ha_test/docker-compose.yaml
+++ b/devenv/docker/ha_test/docker-compose.yaml
@@ -54,7 +54,8 @@ services:
# - GF_DATABASE_SSL_MODE=disable
# - GF_SESSION_PROVIDER=postgres
# - GF_SESSION_PROVIDER_CONFIG=user=grafana password=password host=db port=5432 dbname=grafana sslmode=disable
- - GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug
+ - GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug,auth:debug
+ - GF_LOGIN_ROTATE_TOKEN_MINUTES=2
ports:
- 3000
depends_on:
diff --git a/devenv/docker/loadtest/README.md b/devenv/docker/loadtest/README.md
new file mode 100644
index 00000000000..8e724637acb
--- /dev/null
+++ b/devenv/docker/loadtest/README.md
@@ -0,0 +1,69 @@
+# Grafana load test
+
+Runs load tests and checks using [k6](https://k6.io/).
+
+## Prerequisites
+
+Docker
+
+## Run
+
+Run load test for 15 minutes:
+
+```bash
+$ ./run.sh
+```
+
+Run load test for custom duration:
+
+```bash
+$ ./run.sh -d 10s
+```
+
+Example output:
+
+```bash
+
+ /\ |‾‾| /‾‾/ /‾/
+ /\ / \ | |_/ / / /
+ / \/ \ | | / ‾‾\
+ / \ | |‾\ \ | (_) |
+ / __________ \ |__| \__\ \___/ .io
+
+ execution: local
+ output: -
+ script: src/auth_token_test.js
+
+ duration: 15m0s, iterations: -
+ vus: 2, max: 2
+
+ done [==========================================================] 15m0s / 15m0s
+
+ █ user auth token test
+
+ █ user authenticates thru ui with username and password
+
+ ✓ response status is 200
+ ✓ response has cookie 'grafana_session' with 32 characters
+
+ █ batch tsdb requests
+
+ ✓ response status is 200
+
+ checks.....................: 100.00% ✓ 32844 ✗ 0
+ data_received..............: 411 MB 457 kB/s
+ data_sent..................: 12 MB 14 kB/s
+ group_duration.............: avg=95.64ms min=16.42ms med=94.35ms max=307.52ms p(90)=137.78ms p(95)=146.75ms
+ http_req_blocked...........: avg=1.27ms min=942ns med=610.08µs max=48.32ms p(90)=2.92ms p(95)=4.25ms
+ http_req_connecting........: avg=1.06ms min=0s med=456.79µs max=47.19ms p(90)=2.55ms p(95)=3.78ms
+ http_req_duration..........: avg=58.16ms min=1ms med=52.59ms max=293.35ms p(90)=109.53ms p(95)=120.19ms
+ http_req_receiving.........: avg=38.98µs min=6.43µs med=32.55µs max=16.2ms p(90)=64.63µs p(95)=78.8µs
+ http_req_sending...........: avg=328.66µs min=8.09µs med=110.77µs max=44.13ms p(90)=552.65µs p(95)=1.09ms
+ http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
+ http_req_waiting...........: avg=57.79ms min=935.02µs med=52.15ms max=293.06ms p(90)=109.04ms p(95)=119.71ms
+ http_reqs..................: 34486 38.317775/s
+ iteration_duration.........: avg=1.09s min=1.81µs med=1.09s max=1.3s p(90)=1.13s p(95)=1.14s
+ iterations.................: 1642 1.824444/s
+ vus........................: 2 min=2 max=2
+ vus_max....................: 2 min=2 max=2
+```
diff --git a/devenv/docker/loadtest/auth_token_test.js b/devenv/docker/loadtest/auth_token_test.js
new file mode 100644
index 00000000000..e1356fb6f9a
--- /dev/null
+++ b/devenv/docker/loadtest/auth_token_test.js
@@ -0,0 +1,71 @@
+import { sleep, check, group } from 'k6';
+import { createClient, createBasicAuthClient } from './modules/client.js';
+import { createTestOrgIfNotExists, createTestdataDatasourceIfNotExists } from './modules/util.js';
+
+export let options = {
+ noCookiesReset: true
+};
+
+let endpoint = __ENV.URL || 'http://localhost:3000';
+const client = createClient(endpoint);
+
+export const setup = () => {
+ const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin');
+ const orgId = createTestOrgIfNotExists(basicAuthClient);
+ const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient);
+ client.withOrgId(orgId);
+ return {
+ orgId: orgId,
+ datasourceId: datasourceId,
+ };
+}
+
+export default (data) => {
+ group("user auth token test", () => {
+ if (__ITER === 0) {
+ group("user authenticates thru ui with username and password", () => {
+ let res = client.ui.login('admin', 'admin');
+
+ check(res, {
+ 'response status is 200': (r) => r.status === 200,
+ 'response has cookie \'grafana_session\' with 32 characters': (r) => r.cookies.grafana_session[0].value.length === 32,
+ });
+ });
+ }
+
+ if (__ITER !== 0) {
+ group("batch tsdb requests", () => {
+ const batchCount = 20;
+ const requests = [];
+ const payload = {
+ from: '1547765247624',
+ to: '1547768847624',
+ queries: [{
+ refId: 'A',
+ scenarioId: 'random_walk',
+ intervalMs: 10000,
+ maxDataPoints: 433,
+ datasourceId: data.datasourceId,
+ }]
+ };
+
+ requests.push({ method: 'GET', url: '/api/annotations?dashboardId=2074&from=1548078832772&to=1548082432772' });
+
+ for (let n = 0; n < batchCount; n++) {
+ requests.push({ method: 'POST', url: '/api/tsdb/query', body: payload });
+ }
+
+ let responses = client.batch(requests);
+ for (let n = 0; n < batchCount; n++) {
+ check(responses[n], {
+ 'response status is 200': (r) => r.status === 200,
+ });
+ }
+ });
+ }
+ });
+
+ sleep(1)
+}
+
+export const teardown = (data) => {}
diff --git a/devenv/docker/loadtest/modules/client.js b/devenv/docker/loadtest/modules/client.js
new file mode 100644
index 00000000000..bda0da64564
--- /dev/null
+++ b/devenv/docker/loadtest/modules/client.js
@@ -0,0 +1,187 @@
+import http from "k6/http";
+import encoding from 'k6/encoding';
+
+export const UIEndpoint = class UIEndpoint {
+ constructor(httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ login(username, pwd) {
+ const payload = { user: username, password: pwd };
+ return this.httpClient.formPost('/login', payload);
+ }
+}
+
+export const DatasourcesEndpoint = class DatasourcesEndpoint {
+ constructor(httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ getById(id) {
+ return this.httpClient.get(`/datasources/${id}`);
+ }
+
+ getByName(name) {
+ return this.httpClient.get(`/datasources/name/${name}`);
+ }
+
+ create(payload) {
+ return this.httpClient.post(`/datasources`, JSON.stringify(payload));
+ }
+
+ delete(id) {
+ return this.httpClient.delete(`/datasources/${id}`);
+ }
+}
+
+export const OrganizationsEndpoint = class OrganizationsEndpoint {
+ constructor(httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ getById(id) {
+ return this.httpClient.get(`/orgs/${id}`);
+ }
+
+ getByName(name) {
+ return this.httpClient.get(`/orgs/name/${name}`);
+ }
+
+ create(name) {
+ let payload = {
+ name: name,
+ };
+ return this.httpClient.post(`/orgs`, JSON.stringify(payload));
+ }
+
+ delete(id) {
+ return this.httpClient.delete(`/orgs/${id}`);
+ }
+}
+
+export const GrafanaClient = class GrafanaClient {
+ constructor(httpClient) {
+ httpClient.onBeforeRequest = this.onBeforeRequest;
+ this.raw = httpClient;
+ this.ui = new UIEndpoint(httpClient);
+ this.orgs = new OrganizationsEndpoint(httpClient.withUrl('/api'));
+ this.datasources = new DatasourcesEndpoint(httpClient.withUrl('/api'));
+ }
+
+ batch(requests) {
+ return this.raw.batch(requests);
+ }
+
+ withOrgId(orgId) {
+ this.orgId = orgId;
+ }
+
+ onBeforeRequest(params) {
+ if (this.orgId && this.orgId > 0) {
+ params = params.headers || {};
+ params.headers["X-Grafana-Org-Id"] = this.orgId;
+ }
+ }
+}
+
+export const BaseClient = class BaseClient {
+ constructor(url, subUrl) {
+ if (url.endsWith('/')) {
+ url = url.substring(0, url.length - 1);
+ }
+
+ if (subUrl.endsWith('/')) {
+ subUrl = subUrl.substring(0, subUrl.length - 1);
+ }
+
+ this.url = url + subUrl;
+ this.onBeforeRequest = () => {};
+ }
+
+ withUrl(subUrl) {
+ let c = new BaseClient(this.url, subUrl);
+ c.onBeforeRequest = this.onBeforeRequest;
+ return c;
+ }
+
+ beforeRequest(params) {
+
+ }
+
+ get(url, params) {
+ params = params || {};
+ this.beforeRequest(params);
+ this.onBeforeRequest(params);
+ return http.get(this.url + url, params);
+ }
+
+ formPost(url, body, params) {
+ params = params || {};
+ this.beforeRequest(params);
+ this.onBeforeRequest(params);
+ return http.post(this.url + url, body, params);
+ }
+
+ post(url, body, params) {
+ params = params || {};
+ params.headers = params.headers || {};
+ params.headers['Content-Type'] = 'application/json';
+
+ this.beforeRequest(params);
+ this.onBeforeRequest(params);
+ return http.post(this.url + url, body, params);
+ }
+
+ delete(url, params) {
+ params = params || {};
+ this.beforeRequest(params);
+ this.onBeforeRequest(params);
+ return http.del(this.url + url, null, params);
+ }
+
+ batch(requests) {
+ for (let n = 0; n < requests.length; n++) {
+ let params = requests[n].params || {};
+ params.headers = params.headers || {};
+ params.headers['Content-Type'] = 'application/json';
+ this.beforeRequest(params);
+ this.onBeforeRequest(params);
+ requests[n].params = params;
+ requests[n].url = this.url + requests[n].url;
+ if (requests[n].body) {
+ requests[n].body = JSON.stringify(requests[n].body);
+ }
+ }
+
+ return http.batch(requests);
+ }
+}
+
+export class BasicAuthClient extends BaseClient {
+ constructor(url, subUrl, username, password) {
+ super(url, subUrl);
+ this.username = username;
+ this.password = password;
+ }
+
+ withUrl(subUrl) {
+ let c = new BasicAuthClient(this.url, subUrl, this.username, this.password);
+ c.onBeforeRequest = this.onBeforeRequest;
+ return c;
+ }
+
+ beforeRequest(params) {
+ params = params || {};
+ params.headers = params.headers || {};
+ let token = `${this.username}:${this.password}`;
+ params.headers['Authorization'] = `Basic ${encoding.b64encode(token)}`;
+ }
+}
+
+export const createClient = (url) => {
+ return new GrafanaClient(new BaseClient(url, ''));
+}
+
+export const createBasicAuthClient = (url, username, password) => {
+ return new GrafanaClient(new BasicAuthClient(url, '', username, password));
+}
diff --git a/devenv/docker/loadtest/modules/util.js b/devenv/docker/loadtest/modules/util.js
new file mode 100644
index 00000000000..af6d4cdac09
--- /dev/null
+++ b/devenv/docker/loadtest/modules/util.js
@@ -0,0 +1,35 @@
+export const createTestOrgIfNotExists = (client) => {
+ let orgId = 0;
+ let res = client.orgs.getByName('k6');
+ if (res.status === 404) {
+ res = client.orgs.create('k6');
+ if (res.status !== 200) {
+ throw new Error('Expected 200 response status when creating org');
+ }
+ orgId = res.json().orgId;
+ } else {
+ orgId = res.json().id;
+ }
+
+ client.withOrgId(orgId);
+ return orgId;
+}
+
+export const createTestdataDatasourceIfNotExists = (client) => {
+ const payload = {
+ access: 'proxy',
+ isDefault: false,
+ name: 'k6-testdata',
+ type: 'testdata',
+ };
+
+ let res = client.datasources.getByName(payload.name);
+ if (res.status === 404) {
+ res = client.datasources.create(payload);
+ if (res.status !== 200) {
+ throw new Error('Expected 200 response status when creating datasource');
+ }
+ }
+
+ return res.json().id;
+}
diff --git a/devenv/docker/loadtest/run.sh b/devenv/docker/loadtest/run.sh
new file mode 100755
index 00000000000..474d75383b6
--- /dev/null
+++ b/devenv/docker/loadtest/run.sh
@@ -0,0 +1,24 @@
+#/bin/bash
+
+PWD=$(pwd)
+
+run() {
+ duration='15m'
+ url='http://localhost:3000'
+
+ while getopts ":d:u:" o; do
+ case "${o}" in
+ d)
+ duration=${OPTARG}
+ ;;
+ u)
+ url=${OPTARG}
+ ;;
+ esac
+ done
+ shift $((OPTIND-1))
+
+ docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus 2 --duration $duration src/auth_token_test.js
+}
+
+run "$@"
diff --git a/docs/sources/auth/gitlab.md b/docs/sources/auth/gitlab.md
index 541aed3fd1f..b6028b0a2a7 100644
--- a/docs/sources/auth/gitlab.md
+++ b/docs/sources/auth/gitlab.md
@@ -47,7 +47,7 @@ authentication:
```bash
[auth.gitlab]
-enabled = false
+enabled = true
allow_sign_up = false
client_id = GITLAB_APPLICATION_ID
client_secret = GITLAB_SECRET
diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md
index e2bcb50bb1d..22f9f38c854 100644
--- a/docs/sources/features/datasources/cloudwatch.md
+++ b/docs/sources/features/datasources/cloudwatch.md
@@ -38,7 +38,7 @@ Name | Description
### IAM Roles
-Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If you grafana
+Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If your Grafana
server is running on AWS you can use IAM Roles and authentication will be handled automatically.
Checkout AWS docs on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md
index 9aaf29ec5f4..364b55b0cfc 100644
--- a/docs/sources/http_api/data_source.md
+++ b/docs/sources/http_api/data_source.md
@@ -188,8 +188,8 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
"defaultRegion": "us-west-1"
},
"secureJsonData": {
- "accessKey": "Ol4pIDpeKSA6XikgOl4p", //should not be encoded
- "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs" //should be Base-64 encoded
+ "accessKey": "Ol4pIDpeKSA6XikgOl4p",
+ "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs"
}
}
```
diff --git a/docs/sources/http_api/other.md b/docs/sources/http_api/other.md
index 5bf0cde05fe..ea905bf88f0 100644
--- a/docs/sources/http_api/other.md
+++ b/docs/sources/http_api/other.md
@@ -82,4 +82,29 @@ HTTP/1.1 200
Content-Type: application/json
{"message": "Logged in"}
-```
\ No newline at end of file
+```
+
+# Health API
+
+## Returns health information about Grafana
+
+`GET /api/health`
+
+**Example Request**
+
+```http
+GET /api/health
+Accept: application/json
+```
+
+**Example Response**:
+
+```http
+HTTP/1.1 200 OK
+
+{
+ "commit": "087143285",
+ "database": "ok",
+ "version": "5.1.3"
+}
+```
diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md
index 0e5a55b3c0e..46bab83654e 100644
--- a/docs/sources/installation/configuration.md
+++ b/docs/sources/installation/configuration.md
@@ -391,6 +391,12 @@ value is `true`.
If you want to track Grafana usage via Google analytics specify *your* Universal
Analytics ID here. By default this feature is disabled.
+### check_for_updates
+
+Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used
+in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor
+send any sensitive information.
+
## [dashboards]
@@ -589,3 +595,14 @@ Default setting for how Grafana handles nodata or null values in alerting. (aler
Alert notifications can include images, but rendering many images at the same time can overload the server.
This limit will protect the server from render overloading and make sure notifications are sent out quickly. Default
value is `5`.
+
+## [panels]
+
+### enable_alpha
+Set to true if you want to test panels that are not yet ready for general usage.
+
+### disable_sanitize_html
+If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. Default
+is false. This settings was introduced in Grafana v6.0.
+
+
diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md
index 71ce6bdd2ae..3ef32b1b10f 100644
--- a/docs/sources/reference/templating.md
+++ b/docs/sources/reference/templating.md
@@ -52,6 +52,7 @@ Filter Option | Example | Raw | Interpolated | Description
`csv`| ${servers:csv} | `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string
`distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB.
`lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression.
+`percentencode` | ${servers:percentencode} | `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded.
Test the formatting options on the [Grafana Play site](http://play.grafana.org/d/cJtIfcWiz/template-variable-formatting-options?orgId=1).
diff --git a/package.json b/package.json
index d0dab98da3a..8bfc189d55d 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
- "version": "5.5.0-pre1",
+ "version": "6.0.0-pre1",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -192,7 +192,8 @@
"slate-react": "^0.12.4",
"tether": "^1.4.0",
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
- "tinycolor2": "^1.4.1"
+ "tinycolor2": "^1.4.1",
+ "xss": "^1.0.3"
},
"resolutions": {
"caniuse-db": "1.0.30000772",
diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
index 519e755b474..12b5ff8062e 100644
--- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
+++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
@@ -1,4 +1,5 @@
import React, { PureComponent } from 'react';
+import _ from 'lodash';
import Scrollbars from 'react-custom-scrollbars';
interface Props {
@@ -6,8 +7,11 @@ interface Props {
autoHide?: boolean;
autoHideTimeout?: number;
autoHideDuration?: number;
- autoMaxHeight?: string;
+ autoHeightMax?: string;
hideTracksWhenNotNeeded?: boolean;
+ scrollTop?: number;
+ setScrollTop: (event: any) => void;
+ autoHeightMin?: number | string;
}
/**
@@ -16,24 +20,67 @@ interface Props {
export class CustomScrollbar extends PureComponent {
static defaultProps: Partial = {
customClassName: 'custom-scrollbars',
- autoHide: true,
+ autoHide: false,
autoHideTimeout: 200,
autoHideDuration: 200,
- autoMaxHeight: '100%',
+ setScrollTop: () => {},
hideTracksWhenNotNeeded: false,
+ autoHeightMin: '0',
+ autoHeightMax: '100%',
};
+ private ref: React.RefObject;
+
+ constructor(props: Props) {
+ super(props);
+ this.ref = React.createRef();
+ }
+
+ updateScroll() {
+ const ref = this.ref.current;
+
+ if (ref && !_.isNil(this.props.scrollTop)) {
+ if (this.props.scrollTop > 10000) {
+ ref.scrollToBottom();
+ } else {
+ ref.scrollTop(this.props.scrollTop);
+ }
+ }
+ }
+
+ componentDidMount() {
+ this.updateScroll();
+ }
+
+ componentDidUpdate() {
+ this.updateScroll();
+ }
+
render() {
- const { customClassName, children, autoMaxHeight } = this.props;
+ const {
+ customClassName,
+ children,
+ autoHeightMax,
+ autoHeightMin,
+ setScrollTop,
+ autoHide,
+ autoHideTimeout,
+ hideTracksWhenNotNeeded,
+ } = this.props;
return (
}
renderTrackVertical={props => }
renderThumbHorizontal={props => }
diff --git a/packages/grafana-ui/src/components/FormField/FormField.test.tsx b/packages/grafana-ui/src/components/FormField/FormField.test.tsx
new file mode 100644
index 00000000000..3c89a347e86
--- /dev/null
+++ b/packages/grafana-ui/src/components/FormField/FormField.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { FormField, Props } from './FormField';
+
+const setup = (propOverrides?: object) => {
+ const props: Props = {
+ label: 'Test',
+ labelWidth: 11,
+ value: 10,
+ onChange: jest.fn(),
+ };
+
+ Object.assign(props, propOverrides);
+
+ return shallow();
+};
+
+describe('Render', () => {
+ it('should render component', () => {
+ const wrapper = setup();
+
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx
new file mode 100644
index 00000000000..593678c7383
--- /dev/null
+++ b/packages/grafana-ui/src/components/FormField/FormField.tsx
@@ -0,0 +1,25 @@
+import React, { InputHTMLAttributes, FunctionComponent } from 'react';
+import { FormLabel } from '..';
+
+export interface Props extends InputHTMLAttributes {
+ label: string;
+ labelWidth?: number;
+ inputWidth?: number;
+}
+
+const defaultProps = {
+ labelWidth: 6,
+ inputWidth: 12,
+};
+
+const FormField: FunctionComponent = ({ label, labelWidth, inputWidth, ...inputProps }) => {
+ return (
+
+ {label}
+
+
+ );
+};
+
+FormField.defaultProps = defaultProps;
+export { FormField };
diff --git a/packages/grafana-ui/src/components/FormField/_FormField.scss b/packages/grafana-ui/src/components/FormField/_FormField.scss
new file mode 100644
index 00000000000..36955e2fca6
--- /dev/null
+++ b/packages/grafana-ui/src/components/FormField/_FormField.scss
@@ -0,0 +1,12 @@
+.form-field {
+ margin-bottom: $gf-form-margin;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ text-align: left;
+ position: relative;
+
+ &--grow {
+ flex-grow: 1;
+ }
+}
diff --git a/packages/grafana-ui/src/components/FormField/__snapshots__/FormField.test.tsx.snap b/packages/grafana-ui/src/components/FormField/__snapshots__/FormField.test.tsx.snap
new file mode 100644
index 00000000000..99eb0803149
--- /dev/null
+++ b/packages/grafana-ui/src/components/FormField/__snapshots__/FormField.test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should render component 1`] = `
+
+
+ Test
+
+
+
+`;
diff --git a/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
new file mode 100644
index 00000000000..2bd4fbc153b
--- /dev/null
+++ b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
@@ -0,0 +1,42 @@
+import React, { FunctionComponent, ReactNode } from 'react';
+import classNames from 'classnames';
+import { Tooltip } from '..';
+
+interface Props {
+ children: ReactNode;
+ className?: string;
+ htmlFor?: string;
+ isFocused?: boolean;
+ isInvalid?: boolean;
+ tooltip?: string;
+ width?: number;
+}
+
+export const FormLabel: FunctionComponent = ({
+ children,
+ isFocused,
+ isInvalid,
+ className,
+ htmlFor,
+ tooltip,
+ width,
+ ...rest
+}) => {
+ const classes = classNames(`gf-form-label width-${width ? width : '10'}`, className, {
+ 'gf-form-label--is-focused': isFocused,
+ 'gf-form-label--is-invalid': isInvalid,
+ });
+
+ return (
+
+ );
+};
diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx
new file mode 100644
index 00000000000..396b7a03162
--- /dev/null
+++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { Gauge, Props } from './Gauge';
+import { TimeSeriesVMs } from '../../types/series';
+import { ValueMapping, MappingType } from '../../types';
+
+jest.mock('jquery', () => ({
+ plot: jest.fn(),
+}));
+
+const setup = (propOverrides?: object) => {
+ const props: Props = {
+ maxValue: 100,
+ valueMappings: [],
+ minValue: 0,
+ prefix: '',
+ showThresholdMarkers: true,
+ showThresholdLabels: false,
+ suffix: '',
+ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }],
+ unit: 'none',
+ stat: 'avg',
+ height: 300,
+ width: 300,
+ timeSeries: {} as TimeSeriesVMs,
+ decimals: 0,
+ };
+
+ Object.assign(props, propOverrides);
+
+ const wrapper = shallow();
+ const instance = wrapper.instance() as Gauge;
+
+ return {
+ instance,
+ wrapper,
+ };
+};
+
+describe('Get font color', () => {
+ it('should get first threshold color when only one threshold', () => {
+ const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+ expect(instance.getFontColor(49)).toEqual('#7EB26D');
+ });
+
+ it('should get the threshold color if value is same as a threshold', () => {
+ const { instance } = setup({
+ thresholds: [
+ { index: 2, value: 75, color: '#6ED0E0' },
+ { index: 1, value: 50, color: '#EAB839' },
+ { index: 0, value: -Infinity, color: '#7EB26D' },
+ ],
+ });
+
+ expect(instance.getFontColor(50)).toEqual('#EAB839');
+ });
+
+ it('should get the nearest threshold color between thresholds', () => {
+ const { instance } = setup({
+ thresholds: [
+ { index: 2, value: 75, color: '#6ED0E0' },
+ { index: 1, value: 50, color: '#EAB839' },
+ { index: 0, value: -Infinity, color: '#7EB26D' },
+ ],
+ });
+
+ expect(instance.getFontColor(55)).toEqual('#EAB839');
+ });
+});
+
+describe('Get thresholds formatted', () => {
+ it('should return first thresholds color for min and max', () => {
+ const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+ expect(instance.getFormattedThresholds()).toEqual([
+ { value: 0, color: '#7EB26D' },
+ { value: 100, color: '#7EB26D' },
+ ]);
+ });
+
+ it('should get the correct formatted values when thresholds are added', () => {
+ const { instance } = setup({
+ thresholds: [
+ { index: 2, value: 75, color: '#6ED0E0' },
+ { index: 1, value: 50, color: '#EAB839' },
+ { index: 0, value: -Infinity, color: '#7EB26D' },
+ ],
+ });
+
+ expect(instance.getFormattedThresholds()).toEqual([
+ { value: 0, color: '#7EB26D' },
+ { value: 50, color: '#7EB26D' },
+ { value: 75, color: '#EAB839' },
+ { value: 100, color: '#6ED0E0' },
+ ]);
+ });
+});
+
+describe('Format value', () => {
+ it('should return if value isNaN', () => {
+ const valueMappings: ValueMapping[] = [];
+ const value = 'N/A';
+ const { instance } = setup({ valueMappings });
+
+ const result = instance.formatValue(value);
+
+ expect(result).toEqual('N/A');
+ });
+
+ it('should return formatted value if there are no value mappings', () => {
+ const valueMappings: ValueMapping[] = [];
+ const value = '6';
+ const { instance } = setup({ valueMappings, decimals: 1 });
+
+ const result = instance.formatValue(value);
+
+ expect(result).toEqual(' 6.0 ');
+ });
+
+ it('should return formatted value if there are no matching value mappings', () => {
+ const valueMappings: ValueMapping[] = [
+ { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+ { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
+ ];
+ const value = '10';
+ const { instance } = setup({ valueMappings, decimals: 1 });
+
+ const result = instance.formatValue(value);
+
+ expect(result).toEqual(' 10.0 ');
+ });
+
+ it('should return mapped value if there are matching value mappings', () => {
+ const valueMappings: ValueMapping[] = [
+ { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+ { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+ ];
+ const value = '11';
+ const { instance } = setup({ valueMappings, decimals: 1 });
+
+ const result = instance.formatValue(value);
+
+ expect(result).toEqual(' 1-20 ');
+ });
+});
diff --git a/public/app/viz/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx
similarity index 59%
rename from public/app/viz/Gauge.tsx
rename to packages/grafana-ui/src/components/Gauge/Gauge.tsx
index 094e630a1c0..2dce20543fd 100644
--- a/public/app/viz/Gauge.tsx
+++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx
@@ -1,12 +1,12 @@
import React, { PureComponent } from 'react';
import $ from 'jquery';
-import { BasicGaugeColor, Threshold, TimeSeriesVMs, MappingType, ValueMapping } from '@grafana/ui';
-import config from '../core/config';
-import kbn from '../core/utils/kbn';
+import { ValueMapping, Threshold, ThemeName, BasicGaugeColor, ThemeNames } from '../../types/panel';
+import { TimeSeriesVMs } from '../../types/series';
+import { getValueFormat } from '../../utils/valueFormats/valueFormats';
+import { TimeSeriesValue, getMappedValue } from '../../utils/valueMappings';
export interface Props {
- baseColor: string;
decimals: number;
height: number;
valueMappings: ValueMapping[];
@@ -21,13 +21,13 @@ export interface Props {
suffix: string;
unit: string;
width: number;
+ theme?: ThemeName;
}
export class Gauge extends PureComponent {
canvasElement: any;
static defaultProps = {
- baseColor: BasicGaugeColor.Green,
maxValue: 100,
valueMappings: [],
minValue: 0,
@@ -38,6 +38,7 @@ export class Gauge extends PureComponent {
thresholds: [],
unit: 'none',
stat: 'avg',
+ theme: ThemeNames.Dark,
};
componentDidMount() {
@@ -48,88 +49,93 @@ export class Gauge extends PureComponent {
this.draw();
}
- formatWithMappings(mappings, value) {
- const valueMaps = mappings.filter(m => m.type === MappingType.ValueToText);
- const rangeMaps = mappings.filter(m => m.type === MappingType.RangeToText);
-
- const valueMap = valueMaps.map(mapping => {
- if (mapping.value && value === mapping.value) {
- return mapping.text;
- }
- })[0];
-
- const rangeMap = rangeMaps.map(mapping => {
- if (mapping.from && mapping.to && value > mapping.from && value < mapping.to) {
- return mapping.text;
- }
- })[0];
-
- return { rangeMap, valueMap };
- }
-
- formatValue(value) {
+ formatValue(value: TimeSeriesValue) {
const { decimals, valueMappings, prefix, suffix, unit } = this.props;
- const formatFunc = kbn.valueFormats[unit];
- const formattedValue = formatFunc(value, decimals);
+ if (isNaN(value as number)) {
+ return value;
+ }
if (valueMappings.length > 0) {
- const { rangeMap, valueMap } = this.formatWithMappings(valueMappings, formattedValue);
-
- if (valueMap) {
- return `${prefix} ${valueMap} ${suffix}`;
- } else if (rangeMap) {
- return `${prefix} ${rangeMap} ${suffix}`;
+ const valueMappedValue = getMappedValue(valueMappings, value);
+ if (valueMappedValue) {
+ return `${prefix} ${valueMappedValue.text} ${suffix}`;
}
}
- if (isNaN(value)) {
- return '-';
- }
+ const formatFunc = getValueFormat(unit);
+ const formattedValue = formatFunc(value as number, decimals);
+ const handleNoValueValue = formattedValue || 'no value';
- return `${prefix} ${formattedValue} ${suffix}`;
+ return `${prefix} ${handleNoValueValue} ${suffix}`;
}
- getFontColor(value) {
- const { baseColor, maxValue, thresholds } = this.props;
+ getFontColor(value: TimeSeriesValue) {
+ const { thresholds } = this.props;
- if (thresholds.length > 0) {
- const atThreshold = thresholds.filter(threshold => value <= threshold.value);
-
- if (atThreshold.length > 0) {
- return atThreshold[0].color;
- } else if (value <= maxValue) {
- return BasicGaugeColor.Red;
- }
+ if (thresholds.length === 1) {
+ return thresholds[0].color;
}
- return baseColor;
+ const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
+ if (atThreshold) {
+ return atThreshold.color;
+ }
+
+ const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
+
+ if (belowThreshold.length > 0) {
+ const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
+ return nearestThreshold.color;
+ }
+
+ return BasicGaugeColor.Red;
+ }
+
+ getFormattedThresholds() {
+ const { maxValue, minValue, thresholds } = this.props;
+
+ const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index);
+ const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1];
+
+ const formattedThresholds = [
+ ...thresholdsSortedByIndex.map(threshold => {
+ if (threshold.index === 0) {
+ return { value: minValue, color: threshold.color };
+ }
+
+ const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
+ return { value: threshold.value, color: previousThreshold.color };
+ }),
+ { value: maxValue, color: lastThreshold.color },
+ ];
+
+ return formattedThresholds;
}
draw() {
const {
- baseColor,
maxValue,
minValue,
timeSeries,
showThresholdLabels,
showThresholdMarkers,
- thresholds,
width,
height,
stat,
+ theme,
} = this.props;
- let value: string | number = '';
+ let value: TimeSeriesValue = '';
if (timeSeries[0]) {
value = timeSeries[0].stats[stat];
} else {
- value = 'N/A';
+ value = null;
}
const dimension = Math.min(width, height * 1.3);
- const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
+ const backgroundColor = theme === ThemeNames.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
const fontScale = parseInt('80', 10) / 100;
const fontSize = Math.min(dimension / 5, 100) * fontScale;
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
@@ -137,17 +143,6 @@ export class Gauge extends PureComponent {
const thresholdMarkersWidth = gaugeWidth / 5;
const thresholdLabelFontSize = fontSize / 2.5;
- const formattedThresholds = [
- { value: minValue, color: BasicGaugeColor.Green },
- ...thresholds.map((threshold, index) => {
- return {
- value: threshold.value,
- color: index === 0 ? threshold.color : thresholds[index].color,
- };
- }),
- { value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor },
- ];
-
const options = {
series: {
gauges: {
@@ -164,7 +159,7 @@ export class Gauge extends PureComponent {
layout: { margin: 0, thresholdWidth: 0 },
cell: { border: { width: 0 } },
threshold: {
- values: formattedThresholds,
+ values: this.getFormattedThresholds(),
label: {
show: showThresholdLabels,
margin: thresholdMarkersWidth + 1,
diff --git a/packages/grafana-ui/src/components/GfFormLabel/GfFormLabel.tsx b/packages/grafana-ui/src/components/GfFormLabel/GfFormLabel.tsx
deleted file mode 100644
index 8b80de64696..00000000000
--- a/packages/grafana-ui/src/components/GfFormLabel/GfFormLabel.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { SFC, ReactNode } from 'react';
-import classNames from 'classnames';
-
-interface Props {
- children: ReactNode;
- htmlFor?: string;
- className?: string;
- isFocused?: boolean;
- isInvalid?: boolean;
-}
-
-export const GfFormLabel: SFC = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
- const classes = classNames('gf-form-label', className, {
- 'gf-form-label--is-focused': isFocused,
- 'gf-form-label--is-invalid': isInvalid,
- });
-
- return (
-
- );
-};
diff --git a/packages/grafana-ui/src/components/Label/Label.tsx b/packages/grafana-ui/src/components/Label/Label.tsx
deleted file mode 100644
index 270b0161226..00000000000
--- a/packages/grafana-ui/src/components/Label/Label.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { SFC, ReactNode } from 'react';
-import { Tooltip } from '../Tooltip/Tooltip';
-
-interface Props {
- tooltip?: string;
- for?: string;
- children: ReactNode;
- width?: number;
- className?: string;
-}
-
-export const Label: SFC = props => {
- return (
-
- {props.children}
- {props.tooltip && (
-
-
-
-
-
- )}
-
- );
-};
diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx
index 348133c6c28..6d83968d546 100644
--- a/packages/grafana-ui/src/components/Select/Select.tsx
+++ b/packages/grafana-ui/src/components/Select/Select.tsx
@@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup';
import IndicatorsContainer from './IndicatorsContainer';
import NoOptionsMessage from './NoOptionsMessage';
import resetSelectStyles from './resetSelectStyles';
-import { CustomScrollbar } from '@grafana/ui';
+import { CustomScrollbar } from '..';
export interface SelectOptionItem {
label?: string;
@@ -61,7 +61,7 @@ interface AsyncProps {
export const MenuList = (props: any) => {
return (
- {props.children}
+ {props.children}
);
};
@@ -202,7 +202,7 @@ export class AsyncSelect extends PureComponent {
classNamePrefix="gf-form-select-box"
className={selectClassNames}
components={{
- Option,
+ Option: SelectOption,
SingleValue,
IndicatorsContainer,
NoOptionsMessage,
diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
index 6e8d88051f3..590aca5c7a1 100644
--- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
+++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
@@ -19,9 +19,15 @@ export class ThresholdsEditor extends PureComponent {
constructor(props: Props) {
super(props);
- const thresholds: Threshold[] =
- props.thresholds.length > 0 ? props.thresholds : [{ index: 0, value: -Infinity, color: colors[0] }];
+ const addDefaultThreshold = this.props.thresholds.length === 0;
+ const thresholds: Threshold[] = addDefaultThreshold
+ ? [{ index: 0, value: -Infinity, color: colors[0] }]
+ : props.thresholds;
this.state = { thresholds };
+
+ if (addDefaultThreshold) {
+ this.onChange();
+ }
}
onAddThreshold = (index: number) => {
@@ -62,7 +68,7 @@ export class ThresholdsEditor extends PureComponent {
},
]),
},
- () => this.updateGauge()
+ () => this.onChange()
);
};
@@ -85,7 +91,7 @@ export class ThresholdsEditor extends PureComponent {
thresholds: newThresholds.filter(t => t !== threshold),
};
},
- () => this.updateGauge()
+ () => this.onChange()
);
};
@@ -99,7 +105,7 @@ export class ThresholdsEditor extends PureComponent {
const value = isNaN(parsedValue) ? null : parsedValue;
const newThresholds = thresholds.map(t => {
- if (t === threshold) {
+ if (t === threshold && t.index !== 0) {
t = { ...t, value: value as number };
}
@@ -124,11 +130,10 @@ export class ThresholdsEditor extends PureComponent {
{
thresholds: newThresholds,
},
- () => this.updateGauge()
+ () => this.onChange()
);
};
- onChangeBaseColor = (color: string) => this.props.onChange(this.state.thresholds);
onBlur = () => {
this.setState(prevState => {
const sortThresholds = this.sortThresholds([...prevState.thresholds]);
@@ -139,10 +144,10 @@ export class ThresholdsEditor extends PureComponent {
return { thresholds: sortThresholds };
});
- this.updateGauge();
+ this.onChange();
};
- updateGauge = () => {
+ onChange = () => {
this.props.onChange(this.state.thresholds);
};
diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
index 9705304d354..c5704e8bc88 100644
--- a/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
+++ b/packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
@@ -1,8 +1,7 @@
-import React, { PureComponent } from 'react';
+import React, { ChangeEvent, PureComponent } from 'react';
-import { MappingType, ValueMapping } from '../../types/panel';
-import { Label } from '../Label/Label';
-import { Select } from '../Select/Select';
+import { MappingType, ValueMapping } from '../../types';
+import { FormField, FormLabel, Select } from '..';
export interface Props {
valueMapping: ValueMapping;
@@ -32,19 +31,19 @@ export default class MappingRow extends PureComponent {
this.state = { ...props.valueMapping };
}
- onMappingValueChange = (event: React.ChangeEvent) => {
+ onMappingValueChange = (event: ChangeEvent) => {
this.setState({ value: event.target.value });
};
- onMappingFromChange = (event: React.ChangeEvent) => {
+ onMappingFromChange = (event: ChangeEvent) => {
this.setState({ from: event.target.value });
};
- onMappingToChange = (event: React.ChangeEvent) => {
+ onMappingToChange = (event: ChangeEvent) => {
this.setState({ to: event.target.value });
};
- onMappingTextChange = (event: React.ChangeEvent) => {
+ onMappingTextChange = (event: ChangeEvent) => {
this.setState({ text: event.target.value });
};
@@ -62,30 +61,28 @@ export default class MappingRow extends PureComponent {
if (type === MappingType.RangeToText) {
return (
<>
-
-
+
+
+
+ Text
-
-
-
-
-
-
-
-
@@ -95,17 +92,16 @@ export default class MappingRow extends PureComponent
{
return (
<>
-
-
-
-
+
-
+
Text
{
return (
-
+
Type