Merge branch 'master' into redux-poc2

This commit is contained in:
Torkel Ödegaard
2018-09-07 14:49:05 +02:00
86 changed files with 1276 additions and 681 deletions

View File

@@ -19,7 +19,7 @@ version: 2
jobs:
mysql-integration-test:
docker:
- image: circleci/golang:1.10
- image: circleci/golang:1.11
- image: circleci/mysql:5.6-ram
environment:
MYSQL_ROOT_PASSWORD: rootpass
@@ -39,7 +39,7 @@ jobs:
postgres-integration-test:
docker:
- image: circleci/golang:1.10
- image: circleci/golang:1.11
- image: circleci/postgres:9.3-ram
environment:
POSTGRES_USER: grafanatest
@@ -74,7 +74,7 @@ jobs:
gometalinter:
docker:
- image: circleci/golang:1.10
- image: circleci/golang:1.11
environment:
# we need CGO because of go-sqlite3
CGO_ENABLED: 1
@@ -115,7 +115,7 @@ jobs:
test-backend:
docker:
- image: circleci/golang:1.10
- image: circleci/golang:1.11
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -125,7 +125,7 @@ jobs:
build-all:
docker:
- image: grafana/build-container:1.0.0
- image: grafana/build-container:1.1.0
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -168,7 +168,7 @@ jobs:
build:
docker:
- image: grafana/build-container:1.0.0
- image: grafana/build-container:1.1.0
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout

View File

@@ -1,5 +1,11 @@
# 5.3.0 (unreleased)
### Minor
* **Alerting**: Link to view full size image in Microsoft Teams alert notifier [#13121](https://github.com/grafana/grafana/issues/13121), thx [@holiiveira](https://github.com/holiiveira)
# 5.3.0-beta1 (2018-09-06)
### New Major Features
* **Alerting**: Notification reminders [#7330](https://github.com/grafana/grafana/issues/7330), thx [@jbaublitz](https://github.com/jbaublitz)
@@ -20,54 +26,56 @@
### Minor
* **Units**: Adds bitcoin axes unit. [#13125](https://github.com/grafana/grafana/pull/13125)
* **Alerting**: Its now possible to configure the default value for how to handle errors and no data in alerting. [#10424](https://github.com/grafana/grafana/issues/10424)
* **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane)
* **Alerting**: Fix rendering timeout which could cause notifications to not be sent due to rendering timing out [#12151](https://github.com/grafana/grafana/issues/12151)
* **Docker**: Make it possible to set a specific plugin url [#12861](https://github.com/grafana/grafana/pull/12861), thx [ClementGautier](https://github.com/ClementGautier)
* **GrafanaCli**: Fixed issue with grafana-cli install plugin resulting in corrupt http response from source error. Fixes [#13079](https://github.com/grafana/grafana/issues/13079)
* **Logging**: Reopen log files after receiving a SIGHUP signal [#13112](https://github.com/grafana/grafana/pull/13112), thx [@filewalkwithme](https://github.com/filewalkwithme)
* **Api**: Delete nonexistent datasource should return 404 [#12313](https://github.com/grafana/grafana/issues/12313), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
* **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248)
* **Dashboard**: Use uid when linking to dashboards internally in a dashboard [#10705](https://github.com/grafana/grafana/issues/10705)
* **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps)
* **Provisioning**: Should allow one default datasource per organisation [#12229](https://github.com/grafana/grafana/issues/12229)
* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
* **OAuth**: Fix overriding tls_skip_verify_insecure using environment variable [#12747](https://github.com/grafana/grafana/issues/12747), thx [@jangaraj](https://github.com/jangaraj)
* **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379)
* **Prometheus**: Heatmap - fix unhandled error when some points are missing [#12484](https://github.com/grafana/grafana/issues/12484)
* **Prometheus**: Add $__interval, $__interval_ms, $__range, $__range_s & $__range_ms support for dashboard and template queries [#12597](https://github.com/grafana/grafana/issues/12597) [#12882](https://github.com/grafana/grafana/issues/12882), thx [@roidelapluie](https://github.com/roidelapluie)
* **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda)
* **Variables**: Limit amount of queries executed when updating variable that other variable(s) are dependent on [#11890](https://github.com/grafana/grafana/issues/11890)
* **Variables**: Support query variable refresh when another variable referenced in `Regex` field change its value [#12952](https://github.com/grafana/grafana/issues/12952), thx [@franciscocpg](https://github.com/franciscocpg)
* **Variables**: Support variables in query variable `Custom all value` field [#12965](https://github.com/grafana/grafana/issues/12965), thx [@franciscocpg](https://github.com/franciscocpg)
* **Elasticsearch**: For alerting/backend, support having index name to the right of pattern in index pattern [#12731](https://github.com/grafana/grafana/issues/12731)
* **Graphite**: Fix for quoting of int function parameters (when using variables) [#11927](https://github.com/grafana/grafana/pull/11927)
* **InfluxDB**: Support timeFilter in query templating for InfluxDB [#12598](https://github.com/grafana/grafana/pull/12598), thx [kichristensen](https://github.com/kichristensen)
* **Postgres/MySQL/MSSQL**: New $__unixEpochGroup and $__unixEpochGroupAlias macros [#12892](https://github.com/grafana/grafana/issues/12892), thx [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: Add previous fill mode to $__timeGroup macro which will fill in previously seen value when point is missing [#12756](https://github.com/grafana/grafana/issues/12756), thx [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: Use metric column as prefix when returning multiple value columns [#12727](https://github.com/grafana/grafana/issues/12727), thx [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: New $__timeGroupAlias macro. Postgres $__timeGroup no longer automatically adds time column alias [#12749](https://github.com/grafana/grafana/issues/12749), thx [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: Escape single quotes in variables [#12785](https://github.com/grafana/grafana/issues/12785), thx [@eMerzh](https://github.com/eMerzh)
* **Postgres/MySQL/MSSQL**: Min time interval support [#13157](https://github.com/grafana/grafana/issues/13157), thx [@svenklemm](https://github.com/svenklemm)
* **MySQL/MSSQL**: Use datetime format instead of epoch for $__timeFilter, $__timeFrom and $__timeTo macros [#11618](https://github.com/grafana/grafana/issues/11618) [#11619](https://github.com/grafana/grafana/issues/11619), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
* **Postgres**: Escape ssl mode parameter in connectionstring [#12644](https://github.com/grafana/grafana/issues/12644), thx [@yogyrahmawan](https://github.com/yogyrahmawan)
* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
* **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane)
* **Alerting**: Fix rendering timeout which could cause notifications to not be sent due to rendering timing out [#12151](https://github.com/grafana/grafana/issues/12151)
* **Cloudwatch**: Improved error handling [#12489](https://github.com/grafana/grafana/issues/12489), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: AppSync metrics and dimensions [#12300](https://github.com/grafana/grafana/issues/12300), thx [@franciscocpg](https://github.com/franciscocpg)
* **Cloudwatch**: Direct Connect metrics and dimensions [#12762](https://github.com/grafana/grafana/pulls/12762), thx [@mindriot88](https://github.com/mindriot88)
* **Cloudwatch**: Added BurstBalance metric to list of AWS RDS metrics [#12561](https://github.com/grafana/grafana/pulls/12561), thx [@activeshadow](https://github.com/activeshadow)
* **Cloudwatch**: Add new Redshift metrics and dimensions [#12063](https://github.com/grafana/grafana/pulls/12063), thx [@A21z](https://github.com/A21z)
* **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248)
* **Dashboard**: Use uid when linking to dashboards internally in a dashboard [#10705](https://github.com/grafana/grafana/issues/10705)
* **Graph**: Option to hide series from tooltip [#3341](https://github.com/grafana/grafana/issues/3341), thx [@mtanda](https://github.com/mtanda)
* **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps)
* **Table**: Adjust header contrast for the light theme [#12668](https://github.com/grafana/grafana/issues/12668)
* **Table**: Fix link color when using light theme and thresholds in use [#12766](https://github.com/grafana/grafana/issues/12766)
* **Table**: Fix for useless horizontal scrollbar for table panel [#9964](https://github.com/grafana/grafana/issues/9964)
* **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2)
* **Elasticsearch**: For alerting/backend, support having index name to the right of pattern in index pattern [#12731](https://github.com/grafana/grafana/issues/12731)
* **OAuth**: Fix overriding tls_skip_verify_insecure using environment variable [#12747](https://github.com/grafana/grafana/issues/12747), thx [@jangaraj](https://github.com/jangaraj)
* **Heatmap**: Fix broken tooltip and crosshair on Firefox [#12486](https://github.com/grafana/grafana/issues/12486)
* **Datasource**: Fix UI issue with secret fields after updating datasource [#11270](https://github.com/grafana/grafana/issues/11270)
* **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda)
* **Variables**: Limit amount of queries executed when updating variable that other variable(s) are dependent on [#11890](https://github.com/grafana/grafana/issues/11890)
* **Variables**: Support query variable refresh when another variable referenced in `Regex` field change its value [#12952](https://github.com/grafana/grafana/issues/12952), thx [@franciscocpg](https://github.com/franciscocpg)
* **Variables**: Support variables in query variable `Custom all value` field [#12965](https://github.com/grafana/grafana/issues/12965), thx [@franciscocpg](https://github.com/franciscocpg)
* **Units**: Change units to include characters for power of 2 and 3 [#12744](https://github.com/grafana/grafana/pull/12744), thx [@Worty](https://github.com/Worty)
* **Units**: Polish złoty currency [#12691](https://github.com/grafana/grafana/pull/12691), thx [@mwegrzynek](https://github.com/mwegrzynek)
* **Graph**: Option to hide series from tooltip [#3341](https://github.com/grafana/grafana/issues/3341), thx [@mtanda](https://github.com/mtanda)
* **UI**: Fix iOS home screen "app" icon and Windows 10 app experience [#12752](https://github.com/grafana/grafana/issues/12752), thx [@andig](https://github.com/andig)
* **Datasource**: Fix UI issue with secret fields after updating datasource [#11270](https://github.com/grafana/grafana/issues/11270)
* **Plugins**: Convert URL-like text to links in plugins readme [#12843](https://github.com/grafana/grafana/pull/12843), thx [pgiraud](https://github.com/pgiraud)
* **Docker**: Make it possible to set a specific plugin url [#12861](https://github.com/grafana/grafana/pull/12861), thx [ClementGautier](https://github.com/ClementGautier)
* **Graphite**: Fix for quoting of int function parameters (when using variables) [#11927](https://github.com/grafana/grafana/pull/11927)
* **InfluxDB**: Support timeFilter in query templating for InfluxDB [#12598](https://github.com/grafana/grafana/pull/12598), thx [kichristensen](https://github.com/kichristensen)
* **Provisioning**: Should allow one default datasource per organisation [#12229](https://github.com/grafana/grafana/issues/12229)
* **Heatmap**: Fix broken tooltip and crosshair on Firefox [#12486](https://github.com/grafana/grafana/issues/12486)
* **Units**: Adds bitcoin axes unit. [#13125](https://github.com/grafana/grafana/pull/13125)
* **Api**: Delete nonexistent datasource should return 404 [#12313](https://github.com/grafana/grafana/issues/12313), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
* **Logging**: Reopen log files after receiving a SIGHUP signal [#13112](https://github.com/grafana/grafana/pull/13112), thx [@filewalkwithme](https://github.com/filewalkwithme)
* **Login**: Show loading animation while waiting for authentication response on login [#12865](https://github.com/grafana/grafana/issues/12865)
* **UI**: Fix iOS home screen "app" icon and Windows 10 app experience [#12752](https://github.com/grafana/grafana/issues/12752), thx [@andig](https://github.com/andig)
* **Plugins**: Convert URL-like text to links in plugins readme [#12843](https://github.com/grafana/grafana/pull/12843), thx [pgiraud](https://github.com/pgiraud)
### Breaking changes
@@ -77,13 +85,14 @@
### New experimental features
These are new features that's still being worked on and are in an experimental phase. We incourage users to try these out and provide any feedback in related issue.
These are new features that's still being worked on and are in an experimental phase. We encourage users to try these out and provide any feedback in related issue.
* **Dashboard**: Auto fit dashboard panels to optimize space used for current TV / Monitor [#12768](https://github.com/grafana/grafana/issues/12768)
### Tech
* **Frontend**: Convert all Frontend Karma tests to Jest tests [#12224](https://github.com/grafana/grafana/issues/12224)
* **Backend**: Upgrade to golang 1.11 [#13030](https://github.com/grafana/grafana/issues/13030)
# 5.2.3 (2018-08-29)

View File

@@ -1,5 +1,5 @@
# Golang build container
FROM golang:1.10
FROM golang:1.11
WORKDIR $GOPATH/src/github.com/grafana/grafana

View File

@@ -24,7 +24,7 @@ the latest master builds [here](https://grafana.com/grafana/download)
### Dependencies
- Go 1.10
- Go 1.11
- NodeJS LTS
### Building the backend

View File

@@ -7,7 +7,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana
environment:
nodejs_version: "6"
GOPATH: C:\gopath
GOVERSION: 1.10
GOVERSION: 1.11
install:
- rmdir c:\go /s /q

View File

@@ -467,6 +467,12 @@ enabled = true
# Makes it possible to turn off alert rule execution but alerting UI is visible
execute_alerts = true
# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
error_or_timeout = alerting
# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)
nodata_or_nullvalues = no_data
#################################### Explore #############################
[explore]
# Enable the Explore section

View File

@@ -387,6 +387,12 @@ log_queries =
# Makes it possible to turn off alert rule execution but alerting UI is visible
;execute_alerts = true
# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
;error_or_timeout = alerting
# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)
;nodata_or_nullvalues = no_data
#################################### Explore #############################
[explore]
# Enable the Explore section

View File

@@ -52,8 +52,6 @@ This admin flag makes a user a `Super Admin`. This means they can access the `Se
### Dashboard & Folder Permissions
> Introduced in Grafana v5.0
{{< docs-imagebox img="/img/docs/v50/folder_permissions.png" max-width="500px" class="docs-image--right" >}}
For dashboards and dashboard folders there is a **Permissions** page that make it possible to

View File

@@ -154,7 +154,7 @@ Since not all datasources have the same configuration settings we only have the
| tlsAuthWithCACert | boolean | *All* | Enable TLS authentication using CA cert |
| tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. |
| graphiteVersion | string | Graphite | Graphite version |
| timeInterval | string | Elastic, InfluxDB & Prometheus | Lowest interval/step value that should be used for this data source |
| timeInterval | string | Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL & MSSQL | Lowest interval/step value that should be used for this data source |
| esVersion | number | Elastic | Elasticsearch version as a number (2/5/56) |
| timeField | string | Elastic | Which field that should be used as timestamp |
| interval | string | Elastic | Index date time format |

View File

@@ -1,42 +1,43 @@
+++
title = "Grafana Authproxy"
title = "Auth Proxy"
description = "Grafana Auth Proxy Guide "
keywords = ["grafana", "configuration", "documentation", "proxy"]
type = "docs"
keywords = ["grafana", "tutorials", "authproxy"]
aliases = ["/tutorials/authproxy/"]
[menu.docs]
parent = "tutorials"
weight = 10
name = "Auth Proxy"
identifier = "auth-proxy"
parent = "authentication"
weight = 2
+++
# Grafana Authproxy
# Auth Proxy Authentication
AuthProxy allows you to offload the authentication of users to a web server (there are many reasons why youd want to run a web server in front of a production version of Grafana, especially if its exposed to the Internet).
You can configure Grafana to let a http reverse proxy handling authentication. Popular web servers have a very
extensive list of pluggable authentication modules, and any of them can be used with the AuthProxy feature.
Below we detail the configuration options for auth proxy.
Popular web servers have a very extensive list of pluggable authentication modules, and any of them can be used with the AuthProxy feature.
The Grafana AuthProxy feature is very simple in design, but it is this simplicity that makes it so powerful.
## Interacting with Grafanas AuthProxy via curl
The AuthProxy feature can be configured through the Grafana configuration file with the following options:
```js
```bash
[auth.proxy]
# Defaults to false, but set to true to enable this feature
enabled = true
# HTTP Header name that will contain the username or email
header_name = X-WEBAUTH-USER
# HTTP Header property, defaults to `username` but can also be `email`
header_property = username
# Set to `true` to enable auto sign up of users who do not exist in Grafana DB. Defaults to `true`.
auto_sign_up = true
# If combined with Grafana LDAP integration define sync interval
ldap_sync_ttl = 60
# Limit where auth proxy requests come from by configuring a list of IP addresses.
# This can be used to prevent users spoofing the X-WEBAUTH-USER header.
whitelist =
# Optionally define more headers to sync other user attributes
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL``
headers =
```
* **enabled**: this is to toggle the feature on or off
* **header_name**: this is the HTTP header name that passes the username or email address of the authenticated user to Grafana. Grafana will trust what ever username is contained in this header and automatically log the user in.
* **header_property**: this tells Grafana whether the value in the header_name is a username or an email address. (In Grafana you can log in using your account username or account email)
* **auto_sign_up**: If set to true, Grafana will automatically create user accounts in the Grafana DB if one does not exist. If set to false, users who do not exist in the GrafanaDB wont be able to log in, even though their username and password are valid.
* **ldap_sync_ttl**: When both auth.proxy and auth.ldap are enabled, user's organisation and role are synchronised from ldap after the http proxy authentication. You can force ldap re-synchronisation after `ldap_sync_ttl` minutes.
* **whitelist**: Comma separated list of trusted authentication proxies IP.
With a fresh install of Grafana, using the above configuration for the authProxy feature, we can send a simple API call to list all users. The only user that will be present is the default “Admin” user that is added the first time Grafana starts up. As you can see all we need to do to authenticate the request is to provide the “X-WEBAUTH-USER” header.
## Interacting with Grafanas AuthProxy via curl
```bash
curl -H "X-WEBAUTH-USER: admin" http://localhost:3000/api/users
@@ -71,7 +72,8 @@ Ill demonstrate how to use Apache for authenticating users. In this example w
### Apache BasicAuth
In this example we use Apache as a reverseProxy in front of Grafana. Apache handles the Authentication of users before forwarding requests to the Grafana backend service.
In this example we use Apache as a reverse proxy in front of Grafana. Apache handles the Authentication of users before forwarding requests to the Grafana backend service.
#### Apache configuration
@@ -116,38 +118,7 @@ In this example we use Apache as a reverseProxy in front of Grafana. Apache hand
* The last 3 lines are then just standard reverse proxy configuration to direct all authenticated requests to our Grafana server running on port 3000.
#### Grafana configuration
```bash
############# Users ################
[users]
# disable user signup / registration
allow_sign_up = false
# Set to true to automatically assign new users to the default organization (id 1)
auto_assign_org = true
# Default role new users will be automatically assigned (if auto_assign_org above is set to true)
auto_assign_org_role = Editor
############ Auth Proxy ########
[auth.proxy]
enabled = true
# the Header name that contains the authenticated user.
header_name = X-WEBAUTH-USER
# does the user authenticate against the proxy using a 'username' or an 'email'
header_property = username
# automatically add the user to the system if they don't already exist.
auto_sign_up = true
```
#### Full walk through using Docker.
##### Grafana Container
## Full walk through using Docker.
For this example, we use the official Grafana docker image available at [Docker Hub](https://hub.docker.com/r/grafana/grafana/)
@@ -166,7 +137,8 @@ header_property = username
auto_sign_up = true
```
* Launch the Grafana container, using our custom grafana.ini to replace `/etc/grafana/grafana.ini`. We don't expose any ports for this container as it will only be connected to by our Apache container.
Launch the Grafana container, using our custom grafana.ini to replace `/etc/grafana/grafana.ini`. We don't expose
any ports for this container as it will only be connected to by our Apache container.
```bash
docker run -i -v $(pwd)/grafana.ini:/etc/grafana/grafana.ini --name grafana grafana/grafana

View File

@@ -0,0 +1,172 @@
+++
title = "OAuth authentication"
description = "Grafana OAuthentication Guide "
keywords = ["grafana", "configuration", "documentation", "oauth"]
type = "docs"
[menu.docs]
name = "Generic OAuth"
identifier = "generic_oauth"
parent = "authentication"
weight = 3
+++
# Generic OAuth Authentication
You can configure many different oauth2 authentication services with Grafana using the generic oauth2 feature. Below you
can find examples using Okta, BitBucket, OneLogin and Azure.
This callback URL must match the full HTTP address that you use in your browser to access Grafana, but with the prefix path of `/login/generic_oauth`.
Example config:
```bash
[auth.generic_oauth]
enabled = true
client_id = YOUR_APP_CLIENT_ID
client_secret = YOUR_APP_CLIENT_SECRET
scopes =
auth_url =
token_url =
api_url =
allowed_domains = mycompany.com mycompany.org
allow_sign_up = true
```
Set api_url to the resource that returns [OpenID UserInfo](https://connect2id.com/products/server/docs/api/userinfo) compatible information.
## Set up OAuth2 with Okta
First set up Grafana as an OpenId client "webapplication" in Okta. Then set the Base URIs to `https://<grafana domain>/` and set the Login redirect URIs to `https://<grafana domain>/login/generic_oauth`.
Finally set up the generic oauth module like this:
```bash
[auth.generic_oauth]
name = Okta
enabled = true
scopes = openid profile email
client_id = <okta application Client ID>
client_secret = <okta application Client Secret>
auth_url = https://<okta domain>/oauth2/v1/authorize
token_url = https://<okta domain>/oauth2/v1/token
api_url = https://<okta domain>/oauth2/v1/userinfo
```
## Set up OAuth2 with Bitbucket
```bash
[auth.generic_oauth]
name = BitBucket
enabled = true
allow_sign_up = true
client_id = <client id>
client_secret = <client secret>
scopes = account email
auth_url = https://bitbucket.org/site/oauth2/authorize
token_url = https://bitbucket.org/site/oauth2/access_token
api_url = https://api.bitbucket.org/2.0/user
team_ids =
allowed_organizations =
```
## Set up OAuth2 with OneLogin
1. Create a new Custom Connector with the following settings:
- Name: Grafana
- Sign On Method: OpenID Connect
- Redirect URI: `https://<grafana domain>/login/generic_oauth`
- Signing Algorithm: RS256
- Login URL: `https://<grafana domain>/login/generic_oauth`
then:
2. Add an App to the Grafana Connector:
- Display Name: Grafana
then:
3. Under the SSO tab on the Grafana App details page you'll find the Client ID and Client Secret.
Your OneLogin Domain will match the url you use to access OneLogin.
Configure Grafana as follows:
```bash
[auth.generic_oauth]
name = OneLogin
enabled = true
allow_sign_up = true
client_id = <client id>
client_secret = <client secret>
scopes = openid email name
auth_url = https://<onelogin domain>.onelogin.com/oidc/auth
token_url = https://<onelogin domain>.onelogin.com/oidc/token
api_url = https://<onelogin domain>.onelogin.com/oidc/me
team_ids =
allowed_organizations =
```
### Set up OAuth2 with Auth0
1. Create a new Client in Auth0
- Name: Grafana
- Type: Regular Web Application
2. Go to the Settings tab and set:
- Allowed Callback URLs: `https://<grafana domain>/login/generic_oauth`
3. Click Save Changes, then use the values at the top of the page to configure Grafana:
```bash
[auth.generic_oauth]
enabled = true
allow_sign_up = true
team_ids =
allowed_organizations =
name = Auth0
client_id = <client id>
client_secret = <client secret>
scopes = openid profile email
auth_url = https://<domain>/authorize
token_url = https://<domain>/oauth/token
api_url = https://<domain>/userinfo
```
### Set up OAuth2 with Azure Active Directory
1. Log in to portal.azure.com and click "Azure Active Directory" in the side menu, then click the "Properties" sub-menu item.
2. Copy the "Directory ID", this is needed for setting URLs later
3. Click "App Registrations" and add a new application registration:
- Name: Grafana
- Application type: Web app / API
- Sign-on URL: `https://<grafana domain>/login/generic_oauth`
4. Click the name of the new application to open the application details page.
5. Note down the "Application ID", this will be the OAuth client id.
6. Click "Settings", then click "Keys" and add a new entry under Passwords
- Key Description: Grafana OAuth
- Duration: Never Expires
7. Click Save then copy the key value, this will be the OAuth client secret.
8. Configure Grafana as follows:
```bash
[auth.generic_oauth]
name = Azure AD
enabled = true
allow_sign_up = true
client_id = <application id>
client_secret = <key value>
scopes = openid email name
auth_url = https://login.microsoftonline.com/<directory id>/oauth2/authorize
token_url = https://login.microsoftonline.com/<directory id>/oauth2/token
api_url =
team_ids =
allowed_organizations =
```
<hr>

View File

@@ -0,0 +1,98 @@
+++
title = "Google OAuth2 Authentication"
description = "Grafana OAuthentication Guide "
keywords = ["grafana", "configuration", "documentation", "oauth"]
type = "docs"
[menu.docs]
name = "GitHub"
identifier = "github_oauth2"
parent = "authentication"
weight = 4
+++
# GitHub OAuth2 Authentication
To enable the GitHub OAuth2 you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use.
## Configure GitHub OAuth application
You need to create a GitHub OAuth application (you find this under the GitHub
settings page). When you create the application you will need to specify
a callback URL. Specify this as callback:
```bash
http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/github
```
This callback URL must match the full HTTP address that you use in your
browser to access Grafana, but with the prefix path of `/login/github`.
When the GitHub OAuth application is created you will get a Client ID and a
Client Secret. Specify these in the Grafana configuration file. For
example:
## Enable GitHub in Grafana
```bash
[auth.github]
enabled = true
allow_sign_up = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
team_ids =
allowed_organizations =
```
Restart the Grafana back-end. You should now see a GitHub login button
on the login page. You can now login or sign up with your GitHub
accounts.
You may allow users to sign-up via GitHub authentication by setting the
`allow_sign_up` option to `true`. When this option is set to `true`, any
user successfully authenticating via GitHub authentication will be
automatically signed up.
### team_ids
Require an active team membership for at least one of the given teams on
GitHub. If the authenticated user isn't a member of at least one of the
teams they will not be able to register or authenticate with your
Grafana instance. For example:
```bash
[auth.github]
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
team_ids = 150,300
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
```
### allowed_organizations
Require an active organization membership for at least one of the given
organizations on GitHub. If the authenticated user isn't a member of at least
one of the organizations they will not be able to register or authenticate with
your Grafana instance. For example
```bash
[auth.github]
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
# space-delimited organization names
allowed_organizations = github google
```

115
docs/sources/auth/gitlab.md Normal file
View File

@@ -0,0 +1,115 @@
+++
title = "Google OAuth2 Authentication"
description = "Grafana OAuthentication Guide "
keywords = ["grafana", "configuration", "documentation", "oauth"]
type = "docs"
[menu.docs]
name = "GitLab"
identifier = "gitlab_oauth"
parent = "authentication"
weight = 5
+++
# GitLab OAuth2 Authentication
To enable the GitLab OAuth2 you must register an application in GitLab. GitLab will generate a client ID and secret key for you to use.
## Create GitLab OAuth keys
You need to [create a GitLab OAuth application](https://docs.gitlab.com/ce/integration/oauth_provider.html).
Choose a descriptive *Name*, and use the following *Redirect URI*:
```
https://grafana.example.com/login/gitlab
```
where `https://grafana.example.com` is the URL you use to connect to Grafana.
Adjust it as needed if you don't use HTTPS or if you use a different port; for
instance, if you access Grafana at `http://203.0.113.31:3000`, you should use
```
http://203.0.113.31:3000/login/gitlab
```
Finally, select *api* as the *Scope* and submit the form. Note that if you're
not going to use GitLab groups for authorization (i.e. not setting
`allowed_groups`, see below), you can select *read_user* instead of *api* as
the *Scope*, thus giving a more restricted access to your GitLab API.
You'll get an *Application Id* and a *Secret* in return; we'll call them
`GITLAB_APPLICATION_ID` and `GITLAB_SECRET` respectively for the rest of this
section.
## Enable GitLab in Grafana
Add the following to your Grafana configuration file to enable GitLab
authentication:
```bash
[auth.gitlab]
enabled = false
allow_sign_up = false
client_id = GITLAB_APPLICATION_ID
client_secret = GITLAB_SECRET
scopes = api
auth_url = https://gitlab.com/oauth/authorize
token_url = https://gitlab.com/oauth/token
api_url = https://gitlab.com/api/v4
allowed_groups =
```
Restart the Grafana backend for your changes to take effect.
If you use your own instance of GitLab instead of `gitlab.com`, adjust
`auth_url`, `token_url` and `api_url` accordingly by replacing the `gitlab.com`
hostname with your own.
With `allow_sign_up` set to `false`, only existing users will be able to login
using their GitLab account, but with `allow_sign_up` set to `true`, *any* user
who can authenticate on GitLab will be able to login on your Grafana instance;
if you use the public `gitlab.com`, it means anyone in the world would be able
to login on your Grafana instance.
You can can however limit access to only members of a given group or list of
groups by setting the `allowed_groups` option.
### allowed_groups
To limit access to authenticated users that are members of one or more [GitLab
groups](https://docs.gitlab.com/ce/user/group/index.html), set `allowed_groups`
to a comma- or space-separated list of groups. For instance, if you want to
only give access to members of the `example` group, set
```ini
allowed_groups = example
```
If you want to also give access to members of the subgroup `bar`, which is in
the group `foo`, set
```ini
allowed_groups = example, foo/bar
```
Note that in GitLab, the group or subgroup name doesn't always match its
display name, especially if the display name contains spaces or special
characters. Make sure you always use the group or subgroup name as it appears
in the URL of the group or subgroup.
Here's a complete example with `alloed_sign_up` enabled, and access limited to
the `example` and `foo/bar` groups:
```ini
[auth.gitlab]
enabled = false
allow_sign_up = true
client_id = GITLAB_APPLICATION_ID
client_secret = GITLAB_SECRET
scopes = api
auth_url = https://gitlab.com/oauth/authorize
token_url = https://gitlab.com/oauth/token
api_url = https://gitlab.com/api/v4
allowed_groups = example, foo/bar
```

View File

@@ -0,0 +1,55 @@
+++
title = "Google OAuth2 Authentication"
description = "Grafana OAuthentication Guide "
keywords = ["grafana", "configuration", "documentation", "oauth"]
type = "docs"
[menu.docs]
name = "Google"
identifier = "ggogle_oauth2"
parent = "authentication"
weight = 3
+++
# Google OAuth2 Authentication
To enable the Google OAuth2 you must register your application with Google. Google will generate a client ID and secret key for you to use.
## Create Google OAuth keys
First, you need to create a Google OAuth Client:
1. Go to https://console.developers.google.com/apis/credentials
2. Click the 'Create Credentials' button, then click 'OAuth Client ID' in the menu that drops down
3. Enter the following:
- Application Type: Web Application
- Name: Grafana
- Authorized Javascript Origins: https://grafana.mycompany.com
- Authorized Redirect URLs: https://grafana.mycompany.com/login/google
- Replace https://grafana.mycompany.com with the URL of your Grafana instance.
4. Click Create
5. Copy the Client ID and Client Secret from the 'OAuth Client' modal
## Enable Google OAuth in Grafana
Specify the Client ID and Secret in the [Grafana configuration file]({{< relref "installation/configuration.md#config-file-locations" >}}). For example:
```bash
[auth.google]
enabled = true
client_id = CLIENT_ID
client_secret = CLIENT_SECRET
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
auth_url = https://accounts.google.com/o/oauth2/auth
token_url = https://accounts.google.com/o/oauth2/token
allowed_domains = mycompany.com mycompany.org
allow_sign_up = true
```
Restart the Grafana back-end. You should now see a Google login button
on the login page. You can now login or sign up with your Google
accounts. The `allowed_domains` option is optional, and domains were separated by space.
You may allow users to sign-up via Google authentication by setting the
`allow_sign_up` option to `true`. When this option is set to `true`, any
user successfully authenticating via Google authentication will be
automatically signed up.

View File

@@ -0,0 +1,12 @@
+++
title = "Authentication"
description = "Authentication"
type = "docs"
[menu.docs]
name = "Authentication"
identifier = "authentication"
parent = "admin"
weight = 3
+++

View File

@@ -4,25 +4,37 @@ description = "Grafana LDAP Authentication Guide "
keywords = ["grafana", "configuration", "documentation", "ldap"]
type = "docs"
[menu.docs]
name = "LDAP Authentication"
name = "LDAP"
identifier = "ldap"
parent = "admin"
parent = "authentication"
weight = 2
+++
# LDAP Authentication
# LDAP
Grafana (2.1 and newer) ships with a strong LDAP integration feature. The LDAP integration in Grafana allows your
Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP
group memberships and Grafana Organization user roles.
The LDAP integration in Grafana allows your Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP
group memberships and Grafana Organization user roles. Below we detail grafana.ini config file
settings and ldap.toml config file options.
## Configuration
You turn on LDAP in the [main config file]({{< relref "configuration.md#auth-ldap" >}}) as well as specify the path to the LDAP
## Enable LDAP
You turn on LDAP in the [main config file]({{< relref "installation/configuration.md" >}}) as well as specify the path to the LDAP
specific configuration file (default: `/etc/grafana/ldap.toml`).
### Example config
```bash
[auth.ldap]
# Set to `true` to enable LDAP integration (default: `false`)
enabled = true
# Path to the LDAP specific configuration file (default: `/etc/grafana/ldap.toml`)
config_file = /etc/grafana/ldap.toml`
# Allow sign up should almost always be true (default) to allow new Grafana users to be created (if ldap authentication is ok). If set to
# false only pre-existing Grafana users will be able to login (if ldap authentication is ok).
allow_sign_up = true
```
```toml
## LDAP Configuration
```bash
# To troubleshoot and get more log info enable ldap debug logging in grafana.ini
# [log]
# filters = ldap:debug
@@ -119,7 +131,7 @@ The search filter and search bases settings are still needed to perform the LDAP
## POSIX schema (no memberOf attribute)
If your ldap server does not support the memberOf attribute add these options:
```toml
```bash
## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available)
group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
## An array of the base DNs to search through for groups. Typically uses ou=groups

View File

@@ -0,0 +1,87 @@
+++
title = "Overview"
description = "Overview for auth"
type = "docs"
[menu.docs]
name = "Overview"
identifier = "overview-auth"
parent = "authentication"
weight = 1
+++
# User Authentication Overview
Grafana provides many ways to authenticate users. Some authentication integrations also enable syncing user
permissions and org memberships.
## OAuth Integrations
- [Google OAuth]({{< relref "auth/google.md" >}})
- [GitHub OAuth]({{< relref "auth/github.md" >}})
- [Gitlab OAuth]({{< relref "auth/gitlab.md" >}})
- [Generic OAuth]({{< relref "auth/generic-oauth.md" >}}) (Okta2, BitBucket, Azure, OneLogin, Auth0)
## LDAP integrations
- [LDAP Authentication]({{< relref "auth/ldap.md" >}}) (OpenLDAP, ActiveDirectory, etc)
## Auth proxy
- [Auth Proxy]({{< relref "auth/auth-proxy.md" >}}) If you want to handle authentication outside Grafana using a reverse
proxy.
## Grafana Auth
Grafana of course has a built in user authentication system with password authenticaten enabled by default. You can
disable authentication by enabling anonymous access. You can also hide login form and only allow login through an auth
provider (listed above). There is also options for allowing self sign up.
### Anonymous authenticaten
You can make Grafana accessible without any login required by enabling anonymous access in the configuration file.
Example:
```bash
[auth.anonymous]
enabled = true
# Organization name that should be used for unauthenticated users
org_name = Main Org.
# Role for unauthenticated users, other valid values are `Editor` and `Admin`
org_role = Viewer
```
If you change your organization name in the Grafana UI this setting needs to be updated to match the new name.
### Basic authentication
Basic auth is enabled by default and works with the built in Grafana user password authentication system and LDAP
authenticaten integration.
To disable basic auth:
```bash
[auth.basic]
enabled = false
```
### Disable login form
You can hide the Grafana login form using the below configuration settings.
```bash
[auth]
disable_login_form ⁼ true
```
### Hide sign-out menu
Set to the option detailed below to true to hide sign-out menu link. Useful if you use an auth proxy.
```bash
[auth]
disable_signout_menu = true
```

View File

@@ -6,7 +6,7 @@ type = "docs"
[menu.docs]
name = "Microsoft SQL Server"
parent = "datasources"
weight = 7
weight = 8
+++
# Using Microsoft SQL Server in Grafana
@@ -33,6 +33,24 @@ Name | Description
*User* | Database user's login/username
*Password* | Database user's password
### Min time interval
A lower limit for the [$__interval](/reference/templating/#the-interval-variable) and [$__interval_ms](/reference/templating/#the-interval-ms-variable) variables.
Recommended to be set to write frequency, for example `1m` if your data is written every minute.
This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formatted as a
number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported:
Identifier | Description
------------ | -------------
`y` | year
`M` | month
`w` | week
`d` | day
`h` | hour
`m` | minute
`s` | second
`ms` | millisecond
### Database User Permissions (Important!)
The database user you specify when you add the data source should only be granted SELECT permissions on

View File

@@ -36,6 +36,24 @@ Name | Description
*User* | Database user's login/username
*Password* | Database user's password
### Min time interval
A lower limit for the [$__interval](/reference/templating/#the-interval-variable) and [$__interval_ms](/reference/templating/#the-interval-ms-variable) variables.
Recommended to be set to write frequency, for example `1m` if your data is written every minute.
This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formatted as a
number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported:
Identifier | Description
------------ | -------------
`y` | year
`M` | month
`w` | week
`d` | day
`h` | hour
`m` | minute
`s` | second
`ms` | millisecond
### Database User Permissions (Important!)
The database user you specify when you add the data source should only be granted SELECT permissions on

View File

@@ -34,6 +34,23 @@ Name | Description
*Version* | This option determines which functions are available in the query builder (only available in Grafana 5.3+).
*TimescaleDB* | TimescaleDB is a time-series database built as a PostgreSQL extension. If enabled, Grafana will use `time_bucket` in the `$__timeGroup` macro and display TimescaleDB specific aggregate functions in the query builder (only available in Grafana 5.3+).
### Min time interval
A lower limit for the [$__interval](/reference/templating/#the-interval-variable) and [$__interval_ms](/reference/templating/#the-interval-ms-variable) variables.
Recommended to be set to write frequency, for example `1m` if your data is written every minute.
This option can also be overridden/configured in a dashboard panel under data source options. It's important to note that this value **needs** to be formatted as a
number followed by a valid time identifier, e.g. `1m` (1 minute) or `30s` (30 seconds). The following time identifiers are supported:
Identifier | Description
------------ | -------------
`y` | year
`M` | month
`w` | week
`d` | day
`h` | hour
`m` | minute
`s` | second
`ms` | millisecond
### Database User Permissions (Important!)

View File

@@ -0,0 +1,18 @@
+++
title = "What's New in Grafana v5.3"
description = "Feature & improvement highlights for Grafana v5.3"
keywords = ["grafana", "new", "documentation", "5.3"]
type = "docs"
[menu.docs]
name = "Version 5.3"
identifier = "v5.3"
parent = "whatsnew"
weight = -9
+++
# What's New in Grafana v5.3
## 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.

View File

@@ -5,7 +5,7 @@ keywords = ["grafana", "http", "documentation", "api", "authentication"]
aliases = ["/http_api/authentication/"]
type = "docs"
[menu.docs]
name = "Authentication"
name = "Authentication HTTP API"
parent = "http_api"
+++

View File

@@ -322,470 +322,17 @@ Defaults to `false`.
## [auth]
### disable_login_form
Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false.
### disable_signout_menu
Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false.
<hr>
## [auth.anonymous]
### enabled
Set to `true` to enable anonymous access. Defaults to `false`
### org_name
Set the organization name that should be used for anonymous users. If
you change your organization name in the Grafana UI this setting needs
to be updated to match the new name.
### org_role
Specify role for anonymous users. Defaults to `Viewer`, other valid
options are `Editor` and `Admin`.
## [auth.github]
You need to create a GitHub OAuth application (you find this under the GitHub
settings page). When you create the application you will need to specify
a callback URL. Specify this as callback:
```bash
http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/github
```
This callback URL must match the full HTTP address that you use in your
browser to access Grafana, but with the prefix path of `/login/github`.
When the GitHub OAuth application is created you will get a Client ID and a
Client Secret. Specify these in the Grafana configuration file. For
example:
```bash
[auth.github]
enabled = true
allow_sign_up = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
team_ids =
allowed_organizations =
```
Restart the Grafana back-end. You should now see a GitHub login button
on the login page. You can now login or sign up with your GitHub
accounts.
You may allow users to sign-up via GitHub authentication by setting the
`allow_sign_up` option to `true`. When this option is set to `true`, any
user successfully authenticating via GitHub authentication will be
automatically signed up.
### team_ids
Require an active team membership for at least one of the given teams on
GitHub. If the authenticated user isn't a member of at least one of the
teams they will not be able to register or authenticate with your
Grafana instance. For example:
```bash
[auth.github]
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
team_ids = 150,300
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
```
### allowed_organizations
Require an active organization membership for at least one of the given
organizations on GitHub. If the authenticated user isn't a member of at least
one of the organizations they will not be able to register or authenticate with
your Grafana instance. For example
```bash
[auth.github]
enabled = true
client_id = YOUR_GITHUB_APP_CLIENT_ID
client_secret = YOUR_GITHUB_APP_CLIENT_SECRET
scopes = user:email,read:org
auth_url = https://github.com/login/oauth/authorize
token_url = https://github.com/login/oauth/access_token
api_url = https://api.github.com/user
allow_sign_up = true
# space-delimited organization names
allowed_organizations = github google
```
<hr>
## [auth.gitlab]
> Only available in Grafana v5.3+.
You need to [create a GitLab OAuth
application](https://docs.gitlab.com/ce/integration/oauth_provider.html).
Choose a descriptive *Name*, and use the following *Redirect URI*:
```
https://grafana.example.com/login/gitlab
```
where `https://grafana.example.com` is the URL you use to connect to Grafana.
Adjust it as needed if you don't use HTTPS or if you use a different port; for
instance, if you access Grafana at `http://203.0.113.31:3000`, you should use
```
http://203.0.113.31:3000/login/gitlab
```
Finally, select *api* as the *Scope* and submit the form. Note that if you're
not going to use GitLab groups for authorization (i.e. not setting
`allowed_groups`, see below), you can select *read_user* instead of *api* as
the *Scope*, thus giving a more restricted access to your GitLab API.
You'll get an *Application Id* and a *Secret* in return; we'll call them
`GITLAB_APPLICATION_ID` and `GITLAB_SECRET` respectively for the rest of this
section.
Add the following to your Grafana configuration file to enable GitLab
authentication:
```ini
[auth.gitlab]
enabled = false
allow_sign_up = false
client_id = GITLAB_APPLICATION_ID
client_secret = GITLAB_SECRET
scopes = api
auth_url = https://gitlab.com/oauth/authorize
token_url = https://gitlab.com/oauth/token
api_url = https://gitlab.com/api/v4
allowed_groups =
```
Restart the Grafana backend for your changes to take effect.
If you use your own instance of GitLab instead of `gitlab.com`, adjust
`auth_url`, `token_url` and `api_url` accordingly by replacing the `gitlab.com`
hostname with your own.
With `allow_sign_up` set to `false`, only existing users will be able to login
using their GitLab account, but with `allow_sign_up` set to `true`, *any* user
who can authenticate on GitLab will be able to login on your Grafana instance;
if you use the public `gitlab.com`, it means anyone in the world would be able
to login on your Grafana instance.
You can can however limit access to only members of a given group or list of
groups by setting the `allowed_groups` option.
### allowed_groups
To limit access to authenticated users that are members of one or more [GitLab
groups](https://docs.gitlab.com/ce/user/group/index.html), set `allowed_groups`
to a comma- or space-separated list of groups. For instance, if you want to
only give access to members of the `example` group, set
```ini
allowed_groups = example
```
If you want to also give access to members of the subgroup `bar`, which is in
the group `foo`, set
```ini
allowed_groups = example, foo/bar
```
Note that in GitLab, the group or subgroup name doesn't always match its
display name, especially if the display name contains spaces or special
characters. Make sure you always use the group or subgroup name as it appears
in the URL of the group or subgroup.
Here's a complete example with `alloed_sign_up` enabled, and access limited to
the `example` and `foo/bar` groups:
```ini
[auth.gitlab]
enabled = false
allow_sign_up = true
client_id = GITLAB_APPLICATION_ID
client_secret = GITLAB_SECRET
scopes = api
auth_url = https://gitlab.com/oauth/authorize
token_url = https://gitlab.com/oauth/token
api_url = https://gitlab.com/api/v4
allowed_groups = example, foo/bar
```
<hr>
## [auth.google]
First, you need to create a Google OAuth Client:
1. Go to https://console.developers.google.com/apis/credentials
2. Click the 'Create Credentials' button, then click 'OAuth Client ID' in the
menu that drops down
3. Enter the following:
- Application Type: Web Application
- Name: Grafana
- Authorized Javascript Origins: https://grafana.mycompany.com
- Authorized Redirect URLs: https://grafana.mycompany.com/login/google
Replace https://grafana.mycompany.com with the URL of your Grafana instance.
4. Click Create
5. Copy the Client ID and Client Secret from the 'OAuth Client' modal
Specify the Client ID and Secret in the Grafana configuration file. For example:
```bash
[auth.google]
enabled = true
client_id = CLIENT_ID
client_secret = CLIENT_SECRET
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
auth_url = https://accounts.google.com/o/oauth2/auth
token_url = https://accounts.google.com/o/oauth2/token
allowed_domains = mycompany.com mycompany.org
allow_sign_up = true
```
Restart the Grafana back-end. You should now see a Google login button
on the login page. You can now login or sign up with your Google
accounts. The `allowed_domains` option is optional, and domains were separated by space.
You may allow users to sign-up via Google authentication by setting the
`allow_sign_up` option to `true`. When this option is set to `true`, any
user successfully authenticating via Google authentication will be
automatically signed up.
## [auth.generic_oauth]
This option could be used if have your own oauth service.
This callback URL must match the full HTTP address that you use in your
browser to access Grafana, but with the prefix path of `/login/generic_oauth`.
```bash
[auth.generic_oauth]
enabled = true
client_id = YOUR_APP_CLIENT_ID
client_secret = YOUR_APP_CLIENT_SECRET
scopes =
auth_url =
token_url =
api_url =
allowed_domains = mycompany.com mycompany.org
allow_sign_up = true
```
Set api_url to the resource that returns [OpenID UserInfo](https://connect2id.com/products/server/docs/api/userinfo) compatible information.
### Set up oauth2 with Okta
First set up Grafana as an OpenId client "webapplication" in Okta. Then set the Base URIs to `https://<grafana domain>/` and set the Login redirect URIs to `https://<grafana domain>/login/generic_oauth`.
Finally set up the generic oauth module like this:
```bash
[auth.generic_oauth]
name = Okta
enabled = true
scopes = openid profile email
client_id = <okta application Client ID>
client_secret = <okta application Client Secret>
auth_url = https://<okta domain>/oauth2/v1/authorize
token_url = https://<okta domain>/oauth2/v1/token
api_url = https://<okta domain>/oauth2/v1/userinfo
```
### Set up oauth2 with Bitbucket
```bash
[auth.generic_oauth]
name = BitBucket
enabled = true
allow_sign_up = true
client_id = <client id>
client_secret = <client secret>
scopes = account email
auth_url = https://bitbucket.org/site/oauth2/authorize
token_url = https://bitbucket.org/site/oauth2/access_token
api_url = https://api.bitbucket.org/2.0/user
team_ids =
allowed_organizations =
```
### Set up oauth2 with OneLogin
1. Create a new Custom Connector with the following settings:
- Name: Grafana
- Sign On Method: OpenID Connect
- Redirect URI: `https://<grafana domain>/login/generic_oauth`
- Signing Algorithm: RS256
- Login URL: `https://<grafana domain>/login/generic_oauth`
then:
2. Add an App to the Grafana Connector:
- Display Name: Grafana
then:
3. Under the SSO tab on the Grafana App details page you'll find the Client ID and Client Secret.
Your OneLogin Domain will match the url you use to access OneLogin.
Configure Grafana as follows:
```bash
[auth.generic_oauth]
name = OneLogin
enabled = true
allow_sign_up = true
client_id = <client id>
client_secret = <client secret>
scopes = openid email name
auth_url = https://<onelogin domain>.onelogin.com/oidc/auth
token_url = https://<onelogin domain>.onelogin.com/oidc/token
api_url = https://<onelogin domain>.onelogin.com/oidc/me
team_ids =
allowed_organizations =
```
### Set up oauth2 with Auth0
1. Create a new Client in Auth0
- Name: Grafana
- Type: Regular Web Application
2. Go to the Settings tab and set:
- Allowed Callback URLs: `https://<grafana domain>/login/generic_oauth`
3. Click Save Changes, then use the values at the top of the page to configure Grafana:
```bash
[auth.generic_oauth]
enabled = true
allow_sign_up = true
team_ids =
allowed_organizations =
name = Auth0
client_id = <client id>
client_secret = <client secret>
scopes = openid profile email
auth_url = https://<domain>/authorize
token_url = https://<domain>/oauth/token
api_url = https://<domain>/userinfo
```
### Set up oauth2 with Azure Active Directory
1. Log in to portal.azure.com and click "Azure Active Directory" in the side menu, then click the "Properties" sub-menu item.
2. Copy the "Directory ID", this is needed for setting URLs later
3. Click "App Registrations" and add a new application registration:
- Name: Grafana
- Application type: Web app / API
- Sign-on URL: `https://<grafana domain>/login/generic_oauth`
4. Click the name of the new application to open the application details page.
5. Note down the "Application ID", this will be the OAuth client id.
6. Click "Settings", then click "Keys" and add a new entry under Passwords
- Key Description: Grafana OAuth
- Duration: Never Expires
7. Click Save then copy the key value, this will be the OAuth client secret.
8. Configure Grafana as follows:
```bash
[auth.generic_oauth]
name = Azure AD
enabled = true
allow_sign_up = true
client_id = <application id>
client_secret = <key value>
scopes = openid email name
auth_url = https://login.microsoftonline.com/<directory id>/oauth2/authorize
token_url = https://login.microsoftonline.com/<directory id>/oauth2/token
api_url =
team_ids =
allowed_organizations =
```
<hr>
## [auth.basic]
### enabled
When enabled is `true` (default) the http api will accept basic authentication.
<hr>
## [auth.ldap]
### enabled
Set to `true` to enable LDAP integration (default: `false`)
### config_file
Path to the LDAP specific configuration file (default: `/etc/grafana/ldap.toml`)
### allow_sign_up
Allow sign up should almost always be true (default) to allow new Grafana users to be created (if ldap authentication is ok). If set to
false only pre-existing Grafana users will be able to login (if ldap authentication is ok).
> For details on LDAP Configuration, go to the [LDAP Integration]({{< relref "ldap.md" >}}) page.
<hr>
## [auth.proxy]
This feature allows you to handle authentication in a http reverse proxy.
### enabled
Defaults to `false`
### header_name
Defaults to X-WEBAUTH-USER
#### header_property
Defaults to username but can also be set to email
### auto_sign_up
Set to `true` to enable auto sign up of users who do not exist in Grafana DB. Defaults to `true`.
### whitelist
Limit where auth proxy requests come from by configuring a list of IP addresses. This can be used to prevent users spoofing the X-WEBAUTH-USER header.
### headers
Used to define additional headers for `Name`, `Email` and/or `Login`, for example if the user's name is sent in the X-WEBAUTH-NAME header and their email address in the X-WEBAUTH-EMAIL header, set `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL`.
<hr>
Grafana provides many ways to authenticate users. The docs for authentication has been split in to many different pages
below.
- [Authentication Overview]({{< relref "auth/overview.md" >}}) (anonymous access options, hide login and more)
- [Google OAuth]({{< relref "auth/google.md" >}}) (auth.google)
- [GitHub OAuth]({{< relref "auth/github.md" >}}) (auth.github)
- [Gitlab OAuth]({{< relref "auth/gitlab.md" >}}) (auth.gitlab)
- [Generic OAuth]({{< relref "auth/generic-oauth.md" >}}) (auth.generic_oauth, okta2, auth0, bitbucket, azure)
- [Basic Authentication]({{< relref "auth/overview.md" >}}) (auth.basic)
- [LDAP Authentication]({{< relref "auth/ldap.md" >}}) (auth.ldap)
- [Auth Proxy]({{< relref "auth/auth-proxy.md" >}}) (auth.proxy)
## [session]
@@ -1009,3 +556,13 @@ Defaults to true. Set to false to disable alerting engine and hide Alerting from
### execute_alerts
Makes it possible to turn off alert rule execution.
### error_or_timeout
> Available in 5.3 and above
Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
### nodata_or_nullvalues
> Available in 5.3 and above
Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)

View File

@@ -20,7 +20,7 @@ $ docker run -d -p 3000:3000 grafana/grafana
## Configuration
All options defined in conf/grafana.ini can be overridden using environment
All options defined in `conf/grafana.ini` can be overridden using environment
variables by using the syntax `GF_<SectionName>_<KeyName>`.
For example:
@@ -40,6 +40,19 @@ those options.
> For any changes to `conf/grafana.ini` (or corresponding environment variables) to take effect you need to restart Grafana by restarting the Docker container.
### Default Paths
The following settings are hard-coded when launching the Grafana Docker container and can only be overridden using environment variables, not in `conf/grafana.ini`.
Setting | Default value
----------------------|---------------------------
GF_PATHS_CONFIG | /etc/grafana/grafana.ini
GF_PATHS_DATA | /var/lib/grafana
GF_PATHS_HOME | /usr/share/grafana
GF_PATHS_LOGS | /var/log/grafana
GF_PATHS_PLUGINS | /var/lib/grafana/plugins
GF_PATHS_PROVISIONING | /etc/grafana/provisioning
## Running a Specific Version of Grafana
```bash

View File

@@ -13,7 +13,7 @@ dev environment. Grafana ships with its own required backend server; also comple
## Dependencies
- [Go 1.10](https://golang.org/dl/)
- [Go 1.11](https://golang.org/dl/)
- [Git](https://git-scm.com/downloads)
- [NodeJS LTS](https://nodejs.org/download/)
- node-gyp is the Node.js native addon build tool and it requires extra dependencies: python 2.7, make and GCC. These are already installed for most Linux distros and MacOS. See the Building On Windows section or the [node-gyp installation instructions](https://github.com/nodejs/node-gyp#installation) for more details.

View File

@@ -245,7 +245,7 @@ Grafana has global built-in variables that can be used in expressions in the que
### The $__interval Variable
This $__interval variable is similar to the `auto` interval variable that is described above. It can be used as a parameter to group by time (for InfluxDB), Date histogram interval (for Elasticsearch) or as a *summarize* function parameter (for Graphite).
This $__interval variable is similar to the `auto` interval variable that is described above. It can be used as a parameter to group by time (for InfluxDB, MySQL, Postgres, MSSQL), Date histogram interval (for Elasticsearch) or as a *summarize* function parameter (for Graphite).
Grafana automatically calculates an interval that can be used to group by time in queries. When there are more data points than can be shown on a graph then queries can be made more efficient by grouping by a larger interval. It is more efficient to group by 1 day than by 10s when looking at 3 months of data and the graph will look the same and the query will be faster. The `$__interval` is calculated using the time range and the width of the graph (the number of pixels).

View File

@@ -15,6 +15,7 @@
"@types/jest": "^21.1.4",
"@types/node": "^8.0.31",
"@types/react": "^16.0.25",
"@types/react-custom-scrollbars": "^4.0.5",
"@types/react-dom": "^16.0.3",
"angular-mocks": "1.6.6",
"autoprefixer": "^6.4.0",
@@ -154,6 +155,7 @@
"prop-types": "^15.6.0",
"rc-cascader": "^0.14.0",
"react": "^16.2.0",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^16.2.0",
"react-grid-layout": "0.16.6",
"react-highlight-words": "^0.10.0",

View File

@@ -132,20 +132,22 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
}
jsonObj := map[string]interface{}{
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,
"appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,
"appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled,
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,

View File

@@ -70,7 +70,7 @@ func (c *connection) readPump() {
func (c *connection) handleMessage(message []byte) {
json, err := simplejson.NewJson(message)
if err != nil {
log.Error(3, "Unreadable message on websocket channel:", err)
log.Error(3, "Unreadable message on websocket channel. error: %v", err)
}
msgType := json.Get("action").MustString()

View File

@@ -63,7 +63,7 @@ func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
var data m.PluginRepo
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error: %v", err)
logger.Info("Failed to unmarshal graphite response error:", err)
return m.PluginRepo{}, err
}
@@ -140,7 +140,7 @@ func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
var data m.Plugin
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error: %v", err)
logger.Info("Failed to unmarshal graphite response error:", err)
return m.Plugin{}, err
}

View File

@@ -60,7 +60,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
s3_endpoint, _ := endpoints.DefaultResolver().EndpointFor("s3", u.region)
key := u.path + util.GetRandomString(20) + ".png"
image_url := s3_endpoint.URL + "/" + u.bucket + "/" + key
log.Debug("Uploading image to s3", "url = ", image_url)
log.Debug("Uploading image to s3. url = %s", image_url)
file, err := os.Open(imageDiskPath)
if err != nil {

View File

@@ -105,7 +105,7 @@ func Critical(skip int, format string, v ...interface{}) {
}
func Fatal(skip int, format string, v ...interface{}) {
Root.Crit(fmt.Sprintf(format, v))
Root.Crit(fmt.Sprintf(format, v...))
Close()
os.Exit(1)
}

View File

@@ -2,7 +2,6 @@ package login
import (
"errors"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
@@ -14,6 +13,7 @@ var (
ErrProviderDeniedRequest = errors.New("Login provider denied login request")
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrTooManyLoginAttempts = errors.New("Too many consecutive incorrect login attempts for user. Login for user temporarily blocked")
ErrPasswordEmpty = errors.New("No password provided.")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrGettingUserQuota = errors.New("Error getting user quota")
)
@@ -28,6 +28,10 @@ func AuthenticateUser(query *m.LoginUserQuery) error {
return err
}
if err := validatePasswordSet(query.Password); err != nil {
return err
}
err := loginUsingGrafanaDB(query)
if err == nil || (err != m.ErrUserNotFound && err != ErrInvalidCredentials) {
return err
@@ -52,3 +56,10 @@ func AuthenticateUser(query *m.LoginUserQuery) error {
return err
}
func validatePasswordSet(password string) error {
if len(password) == 0 {
return ErrPasswordEmpty
}
return nil
}

View File

@@ -10,6 +10,24 @@ import (
func TestAuthenticateUser(t *testing.T) {
Convey("Authenticate user", t, func() {
authScenario("When a user authenticates without setting a password", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(nil, sc)
mockLoginUsingLdap(false, nil, sc)
loginQuery := m.LoginUserQuery{
Username: "user",
Password: "",
}
err := AuthenticateUser(&loginQuery)
Convey("login should fail", func() {
So(sc.grafanaLoginWasCalled, ShouldBeFalse)
So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(err, ShouldEqual, ErrPasswordEmpty)
})
})
authScenario("When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
mockLoginAttemptValidation(ErrTooManyLoginAttempts, sc)
mockLoginUsingGrafanaDB(nil, sc)

View File

@@ -35,7 +35,7 @@ func UpsertUser(cmd *m.UpsertUserCommand) error {
limitReached, err := quota.QuotaReached(cmd.ReqContext, "user")
if err != nil {
log.Warn("Error getting user quota", "err", err)
log.Warn("Error getting user quota. error: %v", err)
return ErrGettingUserQuota
}
if limitReached {
@@ -135,7 +135,7 @@ func updateUser(user *m.User, extUser *m.ExternalUserInfo) error {
return nil
}
log.Debug("Syncing user info", "id", user.Id, "update", updateCmd)
log.Debug2("Syncing user info", "id", user.Id, "update", updateCmd)
return bus.Dispatch(updateCmd)
}

View File

@@ -440,6 +440,16 @@ func sendUsageStats() {
metrics["stats.ds_access.other."+access+".count"] = count
}
anStats := models.GetAlertNotifierUsageStatsQuery{}
if err := bus.Dispatch(&anStats); err != nil {
metricsLogger.Error("Failed to get alert notification stats", "error", err)
return
}
for _, stats := range anStats.Result {
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
}
out, _ := json.MarshalIndent(report, "", " ")
data := bytes.NewBuffer(out)

View File

@@ -115,6 +115,24 @@ func TestMetrics(t *testing.T) {
return nil
})
var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery
bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error {
query.Result = []*models.NotifierUsageStats{
{
Type: "slack",
Count: 1,
},
{
Type: "webhook",
Count: 2,
},
}
getAlertNotifierUsageStatsQuery = query
return nil
})
var wg sync.WaitGroup
var responseBuffer *bytes.Buffer
var req *http.Request
@@ -157,6 +175,7 @@ func TestMetrics(t *testing.T) {
So(getSystemStatsQuery, ShouldNotBeNil)
So(getDataSourceStatsQuery, ShouldNotBeNil)
So(getDataSourceAccessStatsQuery, ShouldNotBeNil)
So(getAlertNotifierUsageStatsQuery, ShouldNotBeNil)
So(req, ShouldNotBeNil)
So(req.Method, ShouldEqual, http.MethodPost)
So(req.Header.Get("Content-Type"), ShouldEqual, "application/json")
@@ -198,6 +217,9 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.ds_access."+models.DS_PROMETHEUS+".proxy.count").MustInt(), ShouldEqual, 3)
So(metrics.Get("stats.ds_access.other.direct.count").MustInt(), ShouldEqual, 6+7)
So(metrics.Get("stats.ds_access.other.proxy.count").MustInt(), ShouldEqual, 4+8)
So(metrics.Get("stats.alert_notifiers.slack.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.alert_notifiers.webhook.count").MustInt(), ShouldEqual, 2)
})
})

View File

@@ -36,7 +36,7 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
// initialize session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session", err)
log.Error(3, "Failed to start session. error %v", err)
return false
}
@@ -146,12 +146,12 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
if getRequestUserId(ctx) > 0 && getRequestUserId(ctx) != query.Result.UserId {
// remove session
if err := ctx.Session.Destory(ctx.Context); err != nil {
log.Error(3, "Failed to destroy session, err")
log.Error(3, "Failed to destroy session. error: %v", err)
}
// initialize a new session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session", err)
log.Error(3, "Failed to start session. error: %v", err)
}
}

View File

@@ -59,22 +59,22 @@ type DataSource struct {
}
var knownDatasourcePlugins = map[string]bool{
DS_ES: true,
DS_GRAPHITE: true,
DS_INFLUXDB: true,
DS_INFLUXDB_08: true,
DS_KAIROSDB: true,
DS_CLOUDWATCH: true,
DS_PROMETHEUS: true,
DS_OPENTSDB: true,
DS_POSTGRES: true,
DS_MYSQL: true,
DS_MSSQL: true,
"opennms": true,
"abhisant-druid-datasource": true,
"dalmatinerdb-datasource": true,
"gnocci": true,
"zabbix": true,
DS_ES: true,
DS_GRAPHITE: true,
DS_INFLUXDB: true,
DS_INFLUXDB_08: true,
DS_KAIROSDB: true,
DS_CLOUDWATCH: true,
DS_PROMETHEUS: true,
DS_OPENTSDB: true,
DS_POSTGRES: true,
DS_MYSQL: true,
DS_MSSQL: true,
"opennms": true,
"abhisant-druid-datasource": true,
"dalmatinerdb-datasource": true,
"gnocci": true,
"zabbix": true,
"alexanderzobnin-zabbix-datasource": true,
"newrelic-app": true,
"grafana-datadog-datasource": true,

View File

@@ -40,6 +40,15 @@ type GetDataSourceAccessStatsQuery struct {
Result []*DataSourceAccessStats
}
type NotifierUsageStats struct {
Type string
Count int64
}
type GetAlertNotifierUsageStatsQuery struct {
Result []*NotifierUsageStats
}
type AdminStats struct {
Users int `json:"users"`
Orgs int `json:"orgs"`

View File

@@ -96,14 +96,26 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
},
},
"text": message,
"potentialAction": []map[string]interface{}{
},
},
"potentialAction": []map[string]interface{}{
{
"@context": "http://schema.org",
"@type": "OpenUri",
"name": "View Rule",
"targets": []map[string]interface{}{
{
"@context": "http://schema.org",
"@type": "ViewAction",
"name": "View Rule",
"target": []string{
ruleUrl,
},
"os": "default", "uri": ruleUrl,
},
},
},
{
"@context": "http://schema.org",
"@type": "OpenUri",
"name": "View Graph",
"targets": []map[string]interface{}{
{
"os": "default", "uri": evalContext.ImagePublicUrl,
},
},
},

View File

@@ -216,7 +216,7 @@ func appendIfPossible(message string, extra string, sizeLimit int) string {
if len(extra)+len(message) <= sizeLimit {
return message + extra
}
log.Debug("Line too long for image caption.", "value", extra)
log.Debug("Line too long for image caption. value: %s", extra)
return message
}

View File

@@ -106,7 +106,7 @@ func (ss *SqlStore) inTransactionWithRetryCtx(ctx context.Context, callback dbTr
if len(sess.events) > 0 {
for _, e := range sess.events {
if err = bus.Publish(e); err != nil {
log.Error(3, "Failed to publish event after commit", err)
log.Error(3, "Failed to publish event after commit. error: %v", err)
}
}
}

View File

@@ -13,11 +13,19 @@ func init() {
bus.AddHandler("sql", GetDataSourceStats)
bus.AddHandler("sql", GetDataSourceAccessStats)
bus.AddHandler("sql", GetAdminStats)
bus.AddHandlerCtx("sql", GetAlertNotifiersUsageStats)
bus.AddHandlerCtx("sql", GetSystemUserCountStats)
}
var activeUserTimeLimit = time.Hour * 24 * 30
func GetAlertNotifiersUsageStats(ctx context.Context, query *m.GetAlertNotifierUsageStatsQuery) error {
var rawSql = `SELECT COUNT(*) as count, type FROM alert_notification GROUP BY type`
query.Result = make([]*m.NotifierUsageStats, 0)
err := x.SQL(rawSql).Find(&query.Result)
return err
}
func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
var rawSql = `SELECT COUNT(*) as count, type FROM data_source GROUP BY type`
query.Result = make([]*m.DataSourceStats, 0)

View File

@@ -36,5 +36,11 @@ func TestStatsDataAccess(t *testing.T) {
err := GetDataSourceAccessStats(&query)
So(err, ShouldBeNil)
})
Convey("Get alert notifier stats should not results in error", func() {
query := m.GetAlertNotifierUsageStatsQuery{}
err := GetAlertNotifiersUsageStats(context.Background(), &query)
So(err, ShouldBeNil)
})
})
}

View File

@@ -89,7 +89,7 @@ func inTransactionWithRetryCtx(ctx context.Context, callback dbTransactionFunc,
if len(sess.events) > 0 {
for _, e := range sess.events {
if err = bus.Publish(e); err != nil {
log.Error(3, "Failed to publish event after commit", err)
log.Error(3, "Failed to publish event after commit. error: %v", err)
}
}
}

View File

@@ -164,8 +164,10 @@ var (
Quota QuotaSettings
// Alerting
AlertingEnabled bool
ExecuteAlerts bool
AlertingEnabled bool
ExecuteAlerts bool
AlertingErrorOrTimeout string
AlertingNoDataOrNullValues string
// Explore UI
ExploreEnabled bool
@@ -325,7 +327,7 @@ func getCommandLineProperties(args []string) map[string]string {
trimmed := strings.TrimPrefix(arg, "cfg:")
parts := strings.Split(trimmed, "=")
if len(parts) != 2 {
log.Fatal(3, "Invalid command line argument", arg)
log.Fatal(3, "Invalid command line argument. argument: %v", arg)
return nil
}
@@ -672,6 +674,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
alerting := iniFile.Section("alerting")
AlertingEnabled = alerting.Key("enabled").MustBool(true)
ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true)
AlertingErrorOrTimeout = alerting.Key("error_or_timeout").MustString("alerting")
AlertingNoDataOrNullValues = alerting.Key("nodata_or_nullvalues").MustString("no_data")
explore := iniFile.Section("explore")
ExploreEnabled = explore.Key("enabled").MustBool(false)

View File

@@ -16,7 +16,7 @@ export function parseTime(value, isUtc = false, asString = false) {
return value;
}
if (!isNaN(value)) {
const epoch = parseInt(value);
const epoch = parseInt(value, 10);
const m = isUtc ? moment.utc(epoch) : moment(epoch);
return asString ? m.format(DATE_FORMAT) : m;
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import renderer from 'react-test-renderer';
import CustomScrollbar from './CustomScrollbar';
describe('CustomScrollbar', () => {
it('renders correctly', () => {
const tree = renderer
.create(
<CustomScrollbar>
<p>Scrollable content</p>
</CustomScrollbar>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,47 @@
import React, { PureComponent } from 'react';
import Scrollbars from 'react-custom-scrollbars';
interface Props {
customClassName?: string;
autoHide?: boolean;
autoHideTimeout?: number;
autoHideDuration?: number;
hideTracksWhenNotNeeded?: boolean;
}
/**
* Wraps component into <Scrollbars> component from `react-custom-scrollbars`
*/
class CustomScrollbar extends PureComponent<Props> {
static defaultProps: Partial<Props> = {
customClassName: 'custom-scrollbars',
autoHide: true,
autoHideTimeout: 200,
autoHideDuration: 200,
hideTracksWhenNotNeeded: false,
};
render() {
const { customClassName, children, ...scrollProps } = this.props;
return (
<Scrollbars
className={customClassName}
autoHeight={true}
autoHeightMin={'100%'}
autoHeightMax={'100%'}
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
renderTrackVertical={props => <div {...props} className="track-vertical" />}
renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
renderView={props => <div {...props} className="view" />}
{...scrollProps}
>
{children}
</Scrollbars>
);
}
}
export default CustomScrollbar;

View File

@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomScrollbar renders correctly 1`] = `
<div
className="custom-scrollbars"
style={
Object {
"height": "auto",
"maxHeight": "100%",
"minHeight": "100%",
"overflow": "hidden",
"position": "relative",
"width": "100%",
}
}
>
<div
className="view"
style={
Object {
"WebkitOverflowScrolling": "touch",
"bottom": undefined,
"left": undefined,
"marginBottom": 0,
"marginRight": 0,
"maxHeight": "calc(100% + 0px)",
"minHeight": "calc(100% + 0px)",
"overflow": "scroll",
"position": "relative",
"right": undefined,
"top": undefined,
}
}
>
<p>
Scrollable content
</p>
</div>
<div
className="track-horizontal"
style={
Object {
"display": "none",
"height": 6,
"opacity": 0,
"position": "absolute",
"transition": "opacity 200ms",
}
}
>
<div
className="thumb-horizontal"
style={
Object {
"display": "block",
"height": "100%",
"position": "relative",
}
}
/>
</div>
<div
className="track-vertical"
style={
Object {
"display": "none",
"opacity": 0,
"position": "absolute",
"transition": "opacity 200ms",
"width": 6,
}
}
>
<div
className="thumb-vertical"
style={
Object {
"display": "block",
"position": "relative",
"width": "100%",
}
}
/>
</div>
</div>
`;

View File

@@ -55,7 +55,7 @@ export function sqlPartEditorDirective($compile, templateSrv) {
}
function inputBlur($input, paramIndex) {
cancelBlur = setTimeout(function() {
cancelBlur = setTimeout(() => {
switchToLink($input, paramIndex);
}, 200);
}
@@ -95,20 +95,20 @@ export function sqlPartEditorDirective($compile, templateSrv) {
return;
}
const typeaheadSource = function(query, callback) {
const typeaheadSource = (query, callback) => {
if (param.options) {
let options = param.options;
if (param.type === 'int') {
options = _.map(options, function(val) {
options = _.map(options, val => {
return val.toString();
});
}
return options;
}
$scope.$apply(function() {
$scope.handleEvent({ $event: { name: 'get-param-options', param: param } }).then(function(result) {
const dynamicOptions = _.map(result, function(op) {
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'get-param-options', param: param } }).then(result => {
const dynamicOptions = _.map(result, op => {
return op.value;
});
@@ -128,7 +128,7 @@ export function sqlPartEditorDirective($compile, templateSrv) {
source: typeaheadSource,
minLength: 0,
items: 1000,
updater: function(value) {
updater: value => {
if (value === part.params[paramIndex]) {
clearTimeout(cancelBlur);
$input.focus();
@@ -150,18 +150,18 @@ export function sqlPartEditorDirective($compile, templateSrv) {
}
}
$scope.showActionsMenu = function() {
$scope.showActionsMenu = () => {
$scope.handleEvent({ $event: { name: 'get-part-actions' } }).then(res => {
$scope.partActions = res;
});
};
$scope.triggerPartAction = function(action) {
$scope.triggerPartAction = action => {
$scope.handleEvent({ $event: { name: 'action', action: action } });
};
function addElementsAndCompile() {
_.each(partDef.params, function(param, index) {
_.each(partDef.params, (param, index) => {
if (param.optional && part.params.length <= index) {
return;
}

View File

@@ -22,6 +22,8 @@ export class Settings {
disableLoginForm: boolean;
defaultDatasource: string;
alertingEnabled: boolean;
alertingErrorOrTimeout: string;
alertingNoDataOrNullValues: string;
authProxyEnabled: boolean;
exploreEnabled: boolean;
ldapEnabled: boolean;

View File

@@ -14,8 +14,8 @@ export class Analytics {
});
const ga = ((window as any).ga =
(window as any).ga ||
//tslint:disable-next-line:only-arrow-functions
function() {
//tslint:disable-line:only-arrow-functions
(ga.q = ga.q || []).push(arguments);
});
ga.l = +new Date();

View File

@@ -52,8 +52,8 @@ function applied(fn, scope) {
if (fn.wrappedInApply) {
return fn;
}
//tslint:disable-next-line:only-arrow-functions
const wrapped: any = function() {
//tslint:disable-line:only-arrow-functions
const args = arguments;
const phase = scope.$root.$$phase;
if (phase === '$apply' || phase === '$digest') {

View File

@@ -111,7 +111,7 @@ export function describeTextRange(expr: any) {
const parts = /^now([-+])(\d+)(\w)/.exec(expr);
if (parts) {
const unit = parts[3];
const amount = parseInt(parts[2]);
const amount = parseInt(parts[2], 10);
const span = spans[unit];
if (span) {
opt.display = isLast ? 'Last ' : 'Next ';

View File

@@ -164,8 +164,8 @@ export class AlertTabCtrl {
alert.conditions.push(this.buildDefaultCondition());
}
alert.noDataState = alert.noDataState || 'no_data';
alert.executionErrorState = alert.executionErrorState || 'alerting';
alert.noDataState = alert.noDataState || config.alertingNoDataOrNullValues;
alert.executionErrorState = alert.executionErrorState || config.alertingErrorOrTimeout;
alert.frequency = alert.frequency || '60s';
alert.handler = alert.handler || 1;
alert.notifications = alert.notifications || [];

View File

@@ -70,7 +70,7 @@ export class TimeSrv {
}
if (!isNaN(value)) {
const epoch = parseInt(value);
const epoch = parseInt(value, 10);
return moment.utc(epoch);
}

View File

@@ -49,7 +49,7 @@ export class DashboardViewState {
getQueryStringState() {
const state = this.$location.search();
state.panelId = parseInt(state.panelId) || null;
state.panelId = parseInt(state.panelId, 10) || null;
state.fullscreen = state.fullscreen ? true : null;
state.edit = state.edit === 'true' || state.edit === true || null;
state.editview = state.editview || null;

View File

@@ -12,7 +12,7 @@ export class SoloPanelCtrl {
appEvents.emit('toggle-sidemenu-hidden');
const params = $location.search();
panelId = parseInt(params.panelId);
panelId = parseInt(params.panelId, 10);
$scope.onAppEvent('dashboard-initialized', $scope.initPanelScope);

View File

@@ -37,7 +37,7 @@ export class PlaylistEditCtrl {
filterFoundPlaylistItems() {
this.filteredDashboards = _.reject(this.dashboardresult, playlistItem => {
return _.find(this.playlistItems, listPlaylistItem => {
return parseInt(listPlaylistItem.value) === playlistItem.id;
return parseInt(listPlaylistItem.value, 10) === playlistItem.id;
});
});

View File

@@ -208,7 +208,7 @@ export class ElasticBucketAggCtrl {
const id = _.reduce(
$scope.target.bucketAggs.concat($scope.target.metrics),
(max, val) => {
return parseInt(val.id) > max ? parseInt(val.id) : max;
return parseInt(val.id, 10) > max ? parseInt(val.id, 10) : max;
},
0
);

View File

@@ -177,7 +177,7 @@ export class ElasticMetricAggCtrl {
const id = _.reduce(
$scope.target.bucketAggs.concat($scope.target.metrics),
(max, val) => {
return parseInt(val.id) > max ? parseInt(val.id) : max;
return parseInt(val.id, 10) > max ? parseInt(val.id, 10) : max;
},
0
);

View File

@@ -314,7 +314,7 @@ export default class InfluxDatasource {
const parts = /^now-(\d+)([d|h|m|s])$/.exec(date);
if (parts) {
const amount = parseInt(parts[1]);
const amount = parseInt(parts[1], 10);
const unit = parts[2];
return 'now() - ' + amount + unit;
}

View File

@@ -5,12 +5,14 @@ export class MssqlDatasource {
id: any;
name: any;
responseParser: ResponseParser;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
this.name = instanceSettings.name;
this.id = instanceSettings.id;
this.responseParser = new ResponseParser(this.$q);
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -29,6 +29,21 @@
</div>
</div>
<h3 class="page-heading">MSSQL details</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>User Permission</h5>

View File

@@ -17,5 +17,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -5,12 +5,14 @@ export class MysqlDatasource {
id: any;
name: any;
responseParser: ResponseParser;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
this.name = instanceSettings.name;
this.id = instanceSettings.id;
this.responseParser = new ResponseParser(this.$q);
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -24,6 +24,21 @@
</div>
</div>
<h3 class="page-heading">MySQL details</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>User Permission</h5>

View File

@@ -18,5 +18,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -408,11 +408,11 @@ export default class OpenTsDatasource {
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
query.rateOptions.counterMax = parseInt(target.counterMax, 10);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
query.rateOptions.resetValue = parseInt(target.counterResetValue, 10);
}
if (tsdbVersion >= 2) {

View File

@@ -8,6 +8,7 @@ export class PostgresDatasource {
jsonData: any;
responseParser: ResponseParser;
queryModel: PostgresQuery;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv, private timeSrv) {
@@ -16,6 +17,7 @@ export class PostgresDatasource {
this.jsonData = instanceSettings.jsonData;
this.responseParser = new ResponseParser(this.$q);
this.queryModel = new PostgresQuery({});
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -61,6 +61,16 @@
</label>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
<div class="grafana-info-box alert alert-info" ng-show="ctrl.showTimescaleDBHelp">
<div class="alert-body">
<p>

View File

@@ -18,6 +18,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -96,7 +96,7 @@ export class PostgresQueryCtrl extends QueryCtrl {
}
updateProjection() {
this.selectParts = _.map(this.target.select, function(parts: any) {
this.selectParts = _.map(this.target.select, (parts: any) => {
return _.map(parts, sqlPart.create).filter(n => n);
});
this.whereParts = _.map(this.target.where, sqlPart.create).filter(n => n);
@@ -104,15 +104,15 @@ export class PostgresQueryCtrl extends QueryCtrl {
}
updatePersistedParts() {
this.target.select = _.map(this.selectParts, function(selectParts) {
return _.map(selectParts, function(part: any) {
this.target.select = _.map(this.selectParts, selectParts => {
return _.map(selectParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, params: part.params };
});
});
this.target.where = _.map(this.whereParts, function(part: any) {
this.target.where = _.map(this.whereParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, name: part.name, params: part.params };
});
this.target.group = _.map(this.groupParts, function(part: any) {
this.target.group = _.map(this.groupParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, params: part.params };
});
}
@@ -355,7 +355,7 @@ export class PostgresQueryCtrl extends QueryCtrl {
switch (partType) {
case 'column':
const parts = _.map(selectParts, function(part: any) {
const parts = _.map(selectParts, (part: any) => {
return sqlPart.create({ type: part.def.type, params: _.clone(part.params) });
});
this.selectParts.push(parts);

View File

@@ -1,22 +1,22 @@
import PostgresQuery from '../postgres_query';
describe('PostgresQuery', function() {
describe('PostgresQuery', () => {
const templateSrv = {
replace: jest.fn(text => text),
};
describe('When initializing', function() {
it('should not be in SQL mode', function() {
describe('When initializing', () => {
it('should not be in SQL mode', () => {
const query = new PostgresQuery({}, templateSrv);
expect(query.target.rawQuery).toBe(false);
});
it('should be in SQL mode for pre query builder queries', function() {
it('should be in SQL mode for pre query builder queries', () => {
const query = new PostgresQuery({ rawSql: 'SELECT 1' }, templateSrv);
expect(query.target.rawQuery).toBe(true);
});
});
describe('When generating time column SQL', function() {
describe('When generating time column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.timeColumn = 'time';
@@ -25,7 +25,7 @@ describe('PostgresQuery', function() {
expect(query.buildTimeColumn()).toBe('"time" AS "time"');
});
describe('When generating time column SQL with group by time', function() {
describe('When generating time column SQL with group by time', () => {
let query = new PostgresQuery(
{ timeColumn: 'time', group: [{ type: 'time', params: ['5m', 'none'] }] },
templateSrv
@@ -44,7 +44,7 @@ describe('PostgresQuery', function() {
expect(query.buildTimeColumn(false)).toBe('$__unixEpochGroup(time,5m)');
});
describe('When generating metric column SQL', function() {
describe('When generating metric column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.metricColumn = 'host';
@@ -53,7 +53,7 @@ describe('PostgresQuery', function() {
expect(query.buildMetricColumn()).toBe('"host" AS metric');
});
describe('When generating value column SQL', function() {
describe('When generating value column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
let column = [{ type: 'column', params: ['value'] }];
@@ -76,7 +76,7 @@ describe('PostgresQuery', function() {
);
});
describe('When generating value column SQL with metric column', function() {
describe('When generating value column SQL with metric column', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.metricColumn = 'host';
@@ -110,7 +110,7 @@ describe('PostgresQuery', function() {
);
});
describe('When generating WHERE clause', function() {
describe('When generating WHERE clause', () => {
const query = new PostgresQuery({ where: [] }, templateSrv);
expect(query.buildWhereClause()).toBe('');
@@ -126,7 +126,7 @@ describe('PostgresQuery', function() {
expect(query.buildWhereClause()).toBe('\nWHERE\n $__timeFilter(t) AND\n v = 1');
});
describe('When generating GROUP BY clause', function() {
describe('When generating GROUP BY clause', () => {
const query = new PostgresQuery({ group: [], metricColumn: 'none' }, templateSrv);
expect(query.buildGroupClause()).toBe('');
@@ -136,7 +136,7 @@ describe('PostgresQuery', function() {
expect(query.buildGroupClause()).toBe('\nGROUP BY 1,2');
});
describe('When generating complete statement', function() {
describe('When generating complete statement', () => {
const target = {
timeColumn: 't',
table: 'table',

View File

@@ -37,7 +37,7 @@ export class ResultTransformer {
metricLabel = this.createMetricLabel(metricData.metric, options);
const stepMs = parseInt(options.step) * 1000;
const stepMs = parseInt(options.step, 10) * 1000;
let baseTimestamp = start * 1000;
if (metricData.values === undefined) {

View File

@@ -53,7 +53,7 @@ export class ThresholdManager {
function stopped() {
// calculate graph level
let graphValue = plot.c2p({ left: 0, top: posTop }).y;
graphValue = parseInt(graphValue.toFixed(0));
graphValue = parseInt(graphValue.toFixed(0), 10);
model.value = graphValue;
handleElem.off('mousemove', dragging);

View File

@@ -271,7 +271,7 @@ function pushToYBuckets(buckets, bucketNum, value, point, bounds) {
let count = 1;
// Use the 3rd argument as scale/count
if (point.length > 3) {
count = parseInt(point[2]);
count = parseInt(point[2], 10);
}
if (buckets[bucketNum]) {
buckets[bucketNum].values.push(value);

View File

@@ -493,7 +493,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
const bgColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
const fontScale = parseInt(panel.valueFontSize) / 100;
const fontScale = parseInt(panel.valueFontSize, 10) / 100;
const fontSize = Math.min(dimension / 5, 100) * fontScale;
// Reduce gauge width if threshold labels enabled
const gaugeWidthReduceRatio = panel.gauge.thresholdLabels ? 1.5 : 1;

View File

@@ -294,3 +294,46 @@
padding-top: 1px;
}
}
// Custom styles for 'react-custom-scrollbars'
.custom-scrollbars {
// Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
// make scroll working it should fit outer container size (scroll appears only when inner container size is
// greater than outer one).
display: flex;
flex-grow: 1;
.view {
display: flex;
flex-grow: 1;
}
.track-vertical {
border-radius: 3px;
width: 6px !important;
right: 2px;
bottom: 2px;
top: 2px;
}
.track-horizontal {
border-radius: 3px;
height: 6px !important;
right: 2px;
bottom: 2px;
left: 2px;
}
.thumb-vertical {
@include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
border-radius: 6px;
}
.thumb-horizontal {
@include gradient-horizontal($scrollbarBackground, $scrollbarBackground2);
border-radius: 6px;
}
}

View File

@@ -21,7 +21,7 @@ RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A170311380
RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - && \
yum install -y nodejs --nogpgcheck
ENV GOLANG_VERSION 1.10
ENV GOLANG_VERSION 1.11
RUN wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo && \
yum install -y yarn --nogpgcheck && \

View File

@@ -45,6 +45,10 @@ else
fi
echo "Building frontend"
go run build.go ${OPT} build-frontend
# Load ruby, needed for packing with fpm
source /etc/profile.d/rvm.sh
echo "Packaging"
go run build.go -goos linux -pkg-arch amd64 ${OPT} package-only latest
#removing amd64 phantomjs bin for armv7/arm64 packages

View File

@@ -33,5 +33,8 @@ fi
echo "Building frontend"
go run build.go ${OPT} build-frontend
# Load ruby, needed for packing with fpm
source /etc/profile.d/rvm.sh
echo "Packaging"
go run build.go -goos linux -pkg-arch amd64 ${OPT} package-only latest

View File

@@ -49,8 +49,9 @@
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [true, "check-open-brace", "check-catch", "check-else"],
"only-arrow-functions": [true, "allow-declarations", "allow-named-functions"],
"prefer-const": true,
"radix": false,
"radix": true,
"typedef-whitespace": [
true,
{

View File

@@ -240,6 +240,12 @@
version "8.10.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3"
"@types/react-custom-scrollbars@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.5.tgz#8629b4b3164914d27df7cb0ca0a086c0ad406389"
dependencies:
"@types/react" "*"
"@types/react-dom@^16.0.3":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.6.tgz#f1a65a4e7be8ed5d123f8b3b9eacc913e35a1a3c"
@@ -473,6 +479,10 @@ add-dom-event-listener@1.x:
dependencies:
object-assign "4.x"
add-px-to-style@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a"
agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
@@ -3410,6 +3420,14 @@ dom-converter@~0.1:
dependencies:
utila "~0.3"
dom-css@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/dom-css/-/dom-css-2.1.0.tgz#fdbc2d5a015d0a3e1872e11472bbd0e7b9e6a202"
dependencies:
add-px-to-style "1.0.0"
prefix-style "2.0.1"
to-camel-case "1.0.0"
dom-helpers@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
@@ -9145,6 +9163,10 @@ prebuild-install@^2.3.0:
tunnel-agent "^0.6.0"
which-pm-runs "^1.0.0"
prefix-style@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/prefix-style/-/prefix-style-2.0.1.tgz#66bba9a870cfda308a5dc20e85e9120932c95a06"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -9396,7 +9418,7 @@ qw@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
raf@^3.4.0:
raf@^3.1.0, raf@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
dependencies:
@@ -9504,6 +9526,14 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-custom-scrollbars@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db"
dependencies:
dom-css "^2.0.0"
prop-types "^15.5.10"
raf "^3.1.0"
react-dom@^16.2.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e"
@@ -11371,10 +11401,20 @@ to-buffer@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
to-camel-case@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46"
dependencies:
to-space-case "^1.0.0"
to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
to-no-case@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a"
to-object-path@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -11397,6 +11437,12 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
to-space-case@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17"
dependencies:
to-no-case "^1.0.0"
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"