Merge remote-tracking branch 'grafana/master'

This commit is contained in:
Ryan
2018-11-27 10:00:24 -08:00
276 changed files with 14088 additions and 2013 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
[run]
init_cmds = [
["go", "run", "build.go", "-dev", "build-server"],
["./bin/grafana-server", "cfg:app_mode=development"]
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
]
watch_all = true
follow_symlinks = true
@@ -14,5 +14,5 @@ watch_exts = [".go", ".ini", ".toml", ".template.html"]
build_delay = 1500
cmds = [
["go", "run", "build.go", "-dev", "build-server"],
["./bin/grafana-server", "cfg:app_mode=development"]
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
]
+15 -7
View File
@@ -85,12 +85,13 @@ jobs:
- run: 'go get -u github.com/tsenart/deadcode'
- run: 'go get -u github.com/jgautheron/goconst/cmd/goconst'
- run: 'go get -u github.com/gordonklaus/ineffassign'
- run: 'go get -u honnef.co/go/tools/cmd/megacheck'
- run: 'go get -u github.com/opennota/check/cmd/structcheck'
- run: 'go get -u github.com/mdempsky/unconvert'
- run: 'go get -u github.com/opennota/check/cmd/varcheck'
- run:
name: run linters
command: 'gometalinter --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=goconst --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...'
command: 'gometalinter --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=goconst --enable=gofmt --enable=ineffassign --enable=megacheck --enable=structcheck --enable=unconvert --enable=varcheck ./...'
- run:
name: run go vet
command: 'go vet ./pkg/...'
@@ -126,7 +127,7 @@ jobs:
build-all:
docker:
- image: grafana/build-container:1.2.0
- image: grafana/build-container:1.2.1
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -161,8 +162,8 @@ jobs:
name: Build Grafana.com master publisher
command: 'go build -o scripts/publish scripts/build/publish.go'
- run:
name: Build Grafana.com release publisher
command: 'cd scripts/build/release_publisher && go build -o release_publisher .'
name: Test and build Grafana.com release publisher
command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
- persist_to_workspace:
root: .
paths:
@@ -174,7 +175,7 @@ jobs:
build:
docker:
- image: grafana/build-container:1.2.0
- image: grafana/build-container:1.2.1
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -190,6 +191,9 @@ jobs:
- run:
name: sha-sum packages
command: 'go run build.go sha-dist'
- run:
name: Test Grafana.com release publisher
command: 'cd scripts/build/release_publisher && go test .'
- persist_to_workspace:
root: .
paths:
@@ -240,7 +244,7 @@ jobs:
build-enterprise:
docker:
- image: grafana/build-container:1.2.0
- image: grafana/build-container:1.2.1
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -272,7 +276,7 @@ jobs:
build-all-enterprise:
docker:
- image: grafana/build-container:1.2.0
- image: grafana/build-container:1.2.1
working_directory: /go/src/github.com/grafana/grafana
steps:
- checkout
@@ -358,6 +362,9 @@ jobs:
- run:
name: deploy to gcp
command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/release'
- run:
name: Deploy to Grafana.com
command: './scripts/build/publish.sh --enterprise'
deploy-master:
docker:
@@ -506,6 +513,7 @@ workflows:
- grafana-docker-release:
requires:
- build-all
- build-all-enterprise
- test-backend
- test-frontend
- codespell
+42 -7
View File
@@ -1,37 +1,72 @@
# 5.4.0 (unreleased)
* **Cloudwatch**: Fix invalid time range causes segmentation fault [#14150](https://github.com/grafana/grafana/issues/14150)
### 5.4.0-beta1 fixes
* **Graph**: Fix legend always visible even if configured to be hidden [#14144](https://github.com/grafana/grafana/issues/14144)
* **Elasticsearch**: Fix regression when using datasource version 6.0+ and alerting [#14175](https://github.com/grafana/grafana/pull/14175)
# 5.4.0-beta1 (2018-11-20)
### New Features
* **Alerting**: Introduce alert debouncing with the `FOR` setting. [#7886](https://github.com/grafana/grafana/issues/7886) & [#6202](https://github.com/grafana/grafana/issues/6202)
* **Alerting**: Option to disable OK alert notifications [#12330](https://github.com/grafana/grafana/issues/12330) & [#6696](https://github.com/grafana/grafana/issues/6696), thx [@davewat](https://github.com/davewat)
* **Postgres/MySQL/MSSQL**: Adds support for configuration of max open/idle connections and connection max lifetime. Also, panels with multiple SQL queries will now be executed concurrently [#11711](https://github.com/grafana/grafana/issues/11711), thx [@connection-reset](https://github.com/connection-reset)
* **MySQL**: Graphical query builder [#13762](https://github.com/grafana/grafana/issues/13762), thx [svenklemm](https://github.com/svenklemm)
* **MySQL**: Support connecting thru Unix socket for MySQL datasource [#12342](https://github.com/grafana/grafana/issues/12342), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
* **MSSQL**: Add encrypt setting to allow configuration of how data sent between client and server are encrypted [#13629](https://github.com/grafana/grafana/issues/13629), thx [@ramiro](https://github.com/ramiro)
* **Stackdriver**: Not possible to authenticate using GCE metadata server [#13669](https://github.com/grafana/grafana/issues/13669)
* **Teams**: Team preferences (theme, home dashboard, timezone) support [#12550](https://github.com/grafana/grafana/issues/12550)
* **Graph**: Time regions support enabling highlight of weekdays and/or certain timespans [#5930](https://github.com/grafana/grafana/issues/5930)
* **OAuth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller](https://github.com/Nick-Triller)
* **Stackdriver**: Template query editor [#13561](https://github.com/grafana/grafana/issues/13561)
### Minor
* **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043)
* **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy)
* **Cloudwatch**: CloudHSM metrics and dimensions [#14129](https://github.com/grafana/grafana/pull/14129), thx [@daktari](https://github.com/daktari)
* **Cloudwatch**: Enable using variables in the stats field [#13810](https://github.com/grafana/grafana/issues/13810), thx [@mtanda](https://github.com/mtanda)
* **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm)
* **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg)
* **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu)
* **DingDing**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
* **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876)
* **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945)
* **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367)
* **Elasticsearch**: Fix deprecation warning about terms aggregation order key in Elasticsearch 6.x [#11977](https://github.com/grafana/grafana/issues/11977)
* **Graph**: Render dots when no connecting line can be made [#13605](https://github.com/grafana/grafana/issues/13605), thx [@jsferrei](https://github.com/jsferrei)
* **Table**: Fix CSS alpha background-color applied twice in table cell with link [#13606](https://github.com/grafana/grafana/issues/13606), thx [@grisme](https://github.com/grisme)
* **Singlestat**: Fix XSS in prefix/postfix [#13946](https://github.com/grafana/grafana/issues/13946), thx [@cinaglia](https://github.com/cinaglia)
* **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg)
* **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945)
* **Alerting**: More options for the Slack Alert notifier [#13993](https://github.com/grafana/grafana/issues/13993), thx [@andreykaipov](https://github.com/andreykaipov)
* **Alerting**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
* **Alerting**: Increase Telegram captions length limit [#13876](https://github.com/grafana/grafana/pull/13876), thx [@skgsergio](https://github.com/skgsergio)
* **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876)
* **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu)
* **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo)
* **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat)
* **Dashboard**: Fix render dashboard row drag handle only in edit mode [#13555](https://github.com/grafana/grafana/issues/13555), thx [@praveensastry](https://github.com/praveensastry)
* **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425)
* **Render**: Support full height screenshots using phantomjs render script [#13352](https://github.com/grafana/grafana/pull/13352), thx [@amuraru](https://github.com/amuraru)
* **HTTP API**: Support retrieving teams by user [#14120](https://github.com/grafana/grafana/pull/14120), thx [@supercharlesliu](https://github.com/supercharlesliu)
* **Metrics**: Add basic authentication to metrics endpoint [#13577](https://github.com/grafana/grafana/issues/13577), thx [@bobmshannon](https://github.com/bobmshannon)
### Breaking changes
* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited)
* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited).
# 5.3.3 (unreleased)
# 5.3.4 (2018-11-13)
* **Alerting**: Delete alerts when parent folder was deleted [#13322](https://github.com/grafana/grafana/issues/13322)
* **MySQL**: Fix `$__timeFilter()` should respect local time zone [#13769](https://github.com/grafana/grafana/issues/13769)
* **Dashboard**: Fix datasource selection in panel by enter key [#13932](https://github.com/grafana/grafana/issues/13932)
* **Graph**: Fix table legend height when positioned below graph and using Internet Explorer 11 [#13903](https://github.com/grafana/grafana/issues/13903)
* **Dataproxy**: Drop origin and referer http headers [#13328](https://github.com/grafana/grafana/issues/13328) [#13949](https://github.com/grafana/grafana/issues/13949), thx [@roidelapluie](https://github.com/roidelapluie)
# 5.3.3 (2018-11-13)
### File Exfiltration vulnerability Security fix
See [security announcement](https://community.grafana.com/t/grafana-5-3-3-and-4-6-5-security-update/11961) for details.
# 5.3.2 (2018-10-24)
+2 -1
View File
@@ -50,7 +50,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
WORKDIR $GF_PATHS_HOME
RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates && \
RUN apt-get update && apt-get upgrade -y && \
apt-get install -qq -y libfontconfig ca-certificates && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
Generated
+271 -25
View File
@@ -2,30 +2,39 @@
[[projects]]
digest = "1:f8ad8a53fa865a70efbe215b0ca34735523f50ea39e0efde319ab6fc80089b44"
name = "cloud.google.com/go"
packages = ["compute/metadata"]
pruneopts = "NUT"
revision = "056a55f54a6cc77b440b31a56a5e7c3982d32811"
version = "v0.22.0"
[[projects]]
digest = "1:167b6f65a6656de568092189ae791253939f076df60231fdd64588ac703892a1"
name = "github.com/BurntSushi/toml"
packages = ["."]
pruneopts = "NUT"
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:7d23e6e1889b8bb4bbb37a564708fdab4497ce232c3a99d66406c975b642a6ff"
name = "github.com/Unknwon/com"
packages = ["."]
pruneopts = "NUT"
revision = "7677a1d7c1137cd3dd5ba7a076d0c898a1ef4520"
[[projects]]
branch = "master"
digest = "1:1610787cd9726e29d8fecc2a80e43e4fced008a1f560fec6688fc4d946f17835"
name = "github.com/VividCortex/mysqlerr"
packages = ["."]
pruneopts = "NUT"
revision = "6c6b55f8796f578c870b7e19bafb16103bc40095"
[[projects]]
digest = "1:58294d68772aab5a8941b7d5d228eff7cccf63f895e914bc9bc38fda80471ea5"
name = "github.com/aws/aws-sdk-go"
packages = [
"aws",
@@ -61,405 +70,523 @@
"service/ec2",
"service/ec2/ec2iface",
"service/s3",
"service/sts"
"service/sts",
]
pruneopts = "NUT"
revision = "fde4ded7becdeae4d26bf1212916aabba79349b4"
version = "v1.14.12"
[[projects]]
branch = "master"
digest = "1:79cad073c7be02632d3fa52f62486848b089f560db1e94536de83a408c0f4726"
name = "github.com/benbjohnson/clock"
packages = ["."]
pruneopts = "NUT"
revision = "7dc76406b6d3c05b5f71a86293cbcf3c4ea03b19"
[[projects]]
branch = "master"
digest = "1:707ebe952a8b3d00b343c01536c79c73771d100f63ec6babeaed5c79e2b8a8dd"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "NUT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
branch = "master"
digest = "1:433a2ff0ef4e2f8634614aab3174783c5ff80120b487712db96cc3712f409583"
name = "github.com/bmizerany/assert"
packages = ["."]
pruneopts = "NUT"
revision = "b7ed37b82869576c289d7d97fb2bbd8b64a0cb28"
[[projects]]
branch = "master"
digest = "1:d8f9145c361920507a4f85ffb7f70b96beaedacba2ce8c00aa663adb08689d3e"
name = "github.com/bradfitz/gomemcache"
packages = ["memcache"]
pruneopts = "NUT"
revision = "1952afaa557dc08e8e0d89eafab110fb501c1a2b"
[[projects]]
branch = "master"
digest = "1:8ecb89af7dfe3ac401bdb0c9390b134ef96a97e85f732d2b0604fb7b3977839f"
name = "github.com/codahale/hdrhistogram"
packages = ["."]
pruneopts = "NUT"
revision = "3a0bb77429bd3a61596f5e8a3172445844342120"
[[projects]]
digest = "1:5dba68a1600a235630e208cb7196b24e58fcbb77bb7a6bec08fcd23f081b0a58"
name = "github.com/codegangsta/cli"
packages = ["."]
pruneopts = "NUT"
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
version = "v1.20.0"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "NUT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
digest = "1:1b318d2dd6cea8a1a8d8ec70348852303bd3e491df74e8bca6e32eb5a4d06970"
name = "github.com/denisenkom/go-mssqldb"
packages = [
".",
"internal/cp"
"internal/cp",
]
pruneopts = "NUT"
revision = "270bc3860bb94dd3a3ffd047377d746c5e276726"
[[projects]]
branch = "master"
digest = "1:2da5f11ad66ff01a27a5c3dba4620b7eee2327be75b32c9ee9f87c9a8001ecbf"
name = "github.com/facebookgo/inject"
packages = ["."]
pruneopts = "NUT"
revision = "cc1aa653e50f6a9893bcaef89e673e5b24e1e97b"
[[projects]]
branch = "master"
digest = "1:1108df7f658c90db041e0d6174d55be689aaeb0585913b9c3c7aab51a3a6b2b1"
name = "github.com/facebookgo/structtag"
packages = ["."]
pruneopts = "NUT"
revision = "217e25fb96916cc60332e399c9aa63f5c422ceed"
[[projects]]
digest = "1:ade392a843b2035effb4b4a2efa2c3bab3eb29b992e98bacf9c898b0ecb54e45"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "NUT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:e05711632e1515319b014e8fe4cbe1d30ab024c473403f60cf0fdeb4c586a474"
name = "github.com/go-ini/ini"
packages = ["."]
pruneopts = "NUT"
revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
version = "v1.36.0"
[[projects]]
digest = "1:7e1c00b9959544fa1ccca7cf0407a5b29ac6d5201059c4fac6f599cb99bfd24d"
name = "github.com/go-ldap/ldap"
packages = ["."]
pruneopts = "NUT"
revision = "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9"
version = "v2.5.1"
[[projects]]
branch = "master"
digest = "1:682a0aca743a1a4a36697f3d7f86c0ed403c4e3a780db9935f633242855eac9c"
name = "github.com/go-macaron/binding"
packages = ["."]
pruneopts = "NUT"
revision = "ac54ee249c27dca7e76fad851a4a04b73bd1b183"
[[projects]]
branch = "master"
digest = "1:6326b27f8e0c8e135c8674ddbc619fae879664ac832e8e6fa6a23ce0d279ed4d"
name = "github.com/go-macaron/gzip"
packages = ["."]
pruneopts = "NUT"
revision = "cad1c6580a07c56f5f6bc52d66002a05985c5854"
[[projects]]
branch = "master"
digest = "1:fb8711b648d1ff03104fc1d9593a13cb1d5120be7ba2b01641c14ccae286a9e3"
name = "github.com/go-macaron/inject"
packages = ["."]
pruneopts = "NUT"
revision = "d8a0b8677191f4380287cfebd08e462217bac7ad"
[[projects]]
branch = "master"
digest = "1:21577aafe885f088e8086a3415f154c63c0b7ce956a6994df2ac5776bc01b7e3"
name = "github.com/go-macaron/session"
packages = [
".",
"memcache",
"postgres",
"redis"
"redis",
]
revision = "b8e286a0dba8f4999042d6b258daf51b31d08938"
pruneopts = "NUT"
revision = "068d408f9c54c7fa7fcc5e2bdd3241ab21280c9e"
[[projects]]
digest = "1:fddd4bada6100d6fc49a9f32f18ba5718db45a58e4b00aa6377e1cfbf06af34f"
name = "github.com/go-sql-driver/mysql"
packages = ["."]
pruneopts = "NUT"
revision = "2cc627ac8defc45d65066ae98f898166f580f9a4"
[[projects]]
digest = "1:a1efdbc2762667c8a41cbf02b19a0549c846bf2c1d08cad4f445e3344089f1f0"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "NUT"
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
digest = "1:b9d4f09cdaaa9e7dca5ed0b501ca5519eb2168dd01fc5d174c54edfe42a7d5ed"
name = "github.com/go-xorm/builder"
packages = ["."]
pruneopts = "NUT"
revision = "bad0a612f0d6277b953910822ab5dfb30dd18237"
version = "v0.2.0"
[[projects]]
digest = "1:30fb106b0cd6d64ea6fccded579c8f7d788460092f885fcc8f3edd02fc2085a4"
name = "github.com/go-xorm/core"
packages = ["."]
pruneopts = "NUT"
revision = "da1adaf7a28ca792961721a34e6e04945200c890"
version = "v0.5.7"
[[projects]]
digest = "1:007d1354e4f44e6a393337e7623bcf911dfe75d6ef30fb767a6a0b65d302f5ed"
name = "github.com/go-xorm/xorm"
packages = ["."]
pruneopts = "NUT"
revision = "1933dd69e294c0a26c0266637067f24dbb25770c"
version = "v0.6.4"
[[projects]]
branch = "master"
digest = "1:ffbb19fb66f140b5ea059428d1f84246a055d1bc3d9456c1e5c3d143611f03d0"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
"ptypes/timestamp",
]
pruneopts = "NUT"
revision = "927b65914520a8b7d44f5c9057611cfec6b2e2d0"
[[projects]]
branch = "master"
digest = "1:f14d1b50e0075fb00177f12a96dd7addf93d1e2883c25befd17285b779549795"
name = "github.com/gopherjs/gopherjs"
packages = ["js"]
pruneopts = "NUT"
revision = "8dffc02ea1cb8398bb73f30424697c60fcf8d4c5"
[[projects]]
digest = "1:3b708ebf63bfa9ba3313bedb8526bc0bb284e51474e65e958481476a9d4a12aa"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "NUT"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:4e771d1c6e15ca4516ad971c34205c822b5cff2747179679d7b321e4e1bfe431"
name = "github.com/gosimple/slug"
packages = ["."]
pruneopts = "NUT"
revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:08e53c69cd267ef7d71eeae5d953153d0d2bc1b8e0b498731fe9acaead7001b6"
name = "github.com/grafana/grafana-plugin-model"
packages = [
"go/datasource",
"go/renderer"
"go/renderer",
]
pruneopts = "NUT"
revision = "84176c64269d8060f99e750ee8aba6f062753336"
[[projects]]
branch = "master"
digest = "1:58ba5285227b0f635652cd4aa82c4cfd00b590191eadd823462f0c9f64e3ae07"
name = "github.com/hashicorp/go-hclog"
packages = ["."]
pruneopts = "NUT"
revision = "69ff559dc25f3b435631604f573a5fa1efdb6433"
[[projects]]
digest = "1:532090ffc3b05a7e4c0229dd2698d79149f2e0683df993224a8b202f607fb605"
name = "github.com/hashicorp/go-plugin"
packages = ["."]
pruneopts = "NUT"
revision = "e8d22c780116115ae5624720c9af0c97afe4f551"
[[projects]]
branch = "master"
digest = "1:8925116d1edcd85fc0c014e1aa69ce12892489b48ee633a605c46d893b8c151f"
name = "github.com/hashicorp/go-version"
packages = ["."]
pruneopts = "NUT"
revision = "23480c0665776210b5fbbac6eaaee40e3e6a96b7"
[[projects]]
branch = "master"
digest = "1:8deb0c5545c824dfeb0ac77ab8eb67a3d541eab76df5c85ce93064ef02d44cd0"
name = "github.com/hashicorp/yamux"
packages = ["."]
pruneopts = "NUT"
revision = "7221087c3d281fda5f794e28c2ea4c6e4d5c4558"
[[projects]]
digest = "1:efbe016b6d198cf44f1db0ed2fbdf1b36ebf1f6956cc9b76d6affa96f022d368"
name = "github.com/inconshreveable/log15"
packages = ["."]
pruneopts = "NUT"
revision = "0decfc6c20d9ca0ad143b0e89dcaa20f810b4fb3"
version = "v2.13"
[[projects]]
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
name = "github.com/jmespath/go-jmespath"
packages = ["."]
pruneopts = "NUT"
revision = "0b12d6b5"
[[projects]]
digest = "1:6ddab442e52381bab82fb6c07ef3f4b565ff7ec4b8fae96d8dd4b8573a460597"
name = "github.com/jtolds/gls"
packages = ["."]
pruneopts = "NUT"
revision = "77f18212c9c7edc9bd6a33d383a7b545ce62f064"
version = "v4.2.1"
[[projects]]
digest = "1:1da1796a71eb70f1e3e085984d044f67840bb0326816ec8276231aa87b1b9fc3"
name = "github.com/klauspost/compress"
packages = [
"flate",
"gzip"
"gzip",
]
pruneopts = "NUT"
revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf"
version = "v1.2.1"
[[projects]]
digest = "1:5e55a8699c9ff7aba1e4c8952aeda209685d88d4cb63a8766c338e333b8e65d6"
name = "github.com/klauspost/cpuid"
packages = ["."]
pruneopts = "NUT"
revision = "ae7887de9fa5d2db4eaa8174a7eff2c1ac00f2da"
version = "v1.1"
[[projects]]
digest = "1:b95da1293525625ef6f07be79d537b9bf2ecd7901efcf9a92193edafbd55b9ef"
name = "github.com/klauspost/crc32"
packages = ["."]
pruneopts = "NUT"
revision = "cb6bfca970f6908083f26f39a79009d608efd5cd"
version = "v1.1"
[[projects]]
digest = "1:7b21c7fc5551b46d1308b4ffa9e9e49b66c7a8b0ba88c0130474b0e7a20d859f"
name = "github.com/kr/pretty"
packages = ["."]
pruneopts = "NUT"
revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
version = "v0.1.0"
[[projects]]
digest = "1:c3a7836b5904db0f8b609595b619916a6831cb35b8b714aec39f96d00c6155d8"
name = "github.com/kr/text"
packages = ["."]
pruneopts = "NUT"
revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f"
version = "v0.1.0"
[[projects]]
branch = "master"
digest = "1:7a1e592f0349d56fac8ce47f28469e4e7f4ce637cb26f40c88da9dff25db1c98"
name = "github.com/lib/pq"
packages = [
".",
"oid"
"oid",
]
pruneopts = "NUT"
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
[[projects]]
digest = "1:08c231ec84231a7e23d67e4b58f975e1423695a32467a362ee55a803f9de8061"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "NUT"
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
digest = "1:bc4f7eec3b7be8c6cb1f0af6c1e3333d5bb71072951aaaae2f05067b0803f287"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "NUT"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:536979f1c56397dbf91c2785159b37dec37e35d3bffa3cd1cfe66d25f51f8088"
name = "github.com/mattn/go-sqlite3"
packages = ["."]
pruneopts = "NUT"
revision = "323a32be5a2421b8c7087225079c6c900ec397cd"
version = "v1.7.0"
[[projects]]
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = "NUT"
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:18b773b92ac82a451c1276bd2776c1e55ce057ee202691ab33c8d6690efcc048"
name = "github.com/mitchellh/go-testing-interface"
packages = ["."]
pruneopts = "NUT"
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
[[projects]]
digest = "1:3b517122f3aad1ecce45a630ea912b3092b4729f25532a911d0cb2935a1f9352"
name = "github.com/oklog/run"
packages = ["."]
pruneopts = "NUT"
revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39"
version = "v1.0.0"
[[projects]]
digest = "1:7da29c22bcc5c2ffb308324377dc00b5084650348c2799e573ed226d8cc9faf0"
name = "github.com/opentracing/opentracing-go"
packages = [
".",
"ext",
"log"
"log",
]
pruneopts = "NUT"
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
version = "v1.0.2"
[[projects]]
digest = "1:748946761cf99c8b73cef5a3c0ee3e040859dd713a20cece0d0e0dc04e6ceca7"
name = "github.com/patrickmn/go-cache"
packages = ["."]
pruneopts = "NUT"
revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0"
version = "v2.1.0"
[[projects]]
digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "NUT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:4759bed95e3a52febc18c071db28790a5c6e9e106ee201a37add6f6a056f8f9c"
name = "github.com/prometheus/client_golang"
packages = [
"api",
"api/prometheus/v1",
"prometheus",
"prometheus/promhttp"
"prometheus/promhttp",
]
pruneopts = "NUT"
revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
version = "v0.9.0-pre1"
[[projects]]
branch = "master"
digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "NUT"
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
digest = "1:768b555b86742de2f28beb37f1dedce9a75f91f871d75b5717c96399c1a78c08"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
"model",
]
pruneopts = "NUT"
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
[[projects]]
branch = "master"
digest = "1:c4a213a8d73fbb0b13f717ba7996116602ef18ecb42b91d77405877914cb0349"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs"
"xfs",
]
pruneopts = "NUT"
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
branch = "master"
digest = "1:16e2136a67ec44aa2d1d6b0fd65394b3c4a8b2a1b6730c77967f7b7b06b179b2"
name = "github.com/rainycape/unidecode"
packages = ["."]
pruneopts = "NUT"
revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c"
[[projects]]
digest = "1:d917313f309bda80d27274d53985bc65651f81a5b66b820749ac7f8ef061fd04"
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
pruneopts = "NUT"
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
version = "v1.0.0"
[[projects]]
digest = "1:1f0b284a6858827de4c27c66b49b2b25df3e16b031c2b57b7892273131e7dd2b"
name = "github.com/smartystreets/assertions"
packages = [
".",
"internal/go-render/render",
"internal/oglematchers"
"internal/oglematchers",
]
pruneopts = "NUT"
revision = "7678a5452ebea5b7090a6b163f844c133f523da2"
version = "1.8.3"
[[projects]]
digest = "1:7efd0b2309cdd6468029fa30c808c50a820c9344df07e1a4bbdaf18f282907aa"
name = "github.com/smartystreets/goconvey"
packages = [
"convey",
"convey/gotest",
"convey/reporting"
"convey/reporting",
]
pruneopts = "NUT"
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
version = "1.6.3"
[[projects]]
branch = "master"
digest = "1:a66add8dd963bfc72649017c1b321198f596cb4958cb1a11ff91a1be8691020b"
name = "github.com/teris-io/shortid"
packages = ["."]
pruneopts = "NUT"
revision = "771a37caa5cf0c81f585d7b6df4dfc77e0615b5c"
[[projects]]
digest = "1:3d48c38e0eca8c66df62379c5ae7a83fb5cd839b94f241354c07ba077da7bc45"
name = "github.com/uber/jaeger-client-go"
packages = [
".",
@@ -477,45 +604,55 @@
"thrift-gen/jaeger",
"thrift-gen/sampling",
"thrift-gen/zipkincore",
"utils"
"utils",
]
pruneopts = "NUT"
revision = "b043381d944715b469fd6b37addfd30145ca1758"
version = "v2.14.0"
[[projects]]
digest = "1:0f09db8429e19d57c8346ad76fbbc679341fa86073d3b8fb5ac919f0357d8f4c"
name = "github.com/uber/jaeger-lib"
packages = ["metrics"]
pruneopts = "NUT"
revision = "ed3a127ec5fef7ae9ea95b01b542c47fbd999ce5"
version = "v1.5.0"
[[projects]]
digest = "1:4c7d12ad3ef47bb03892a52e2609dc9a9cff93136ca9c7d31c00b79fcbc23c7b"
name = "github.com/yudai/gojsondiff"
packages = [
".",
"formatter"
"formatter",
]
pruneopts = "NUT"
revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6"
version = "1.0.0"
[[projects]]
branch = "master"
digest = "1:e50cbf8eba568d59b71e08c22c2a77809ed4646ae06ef4abb32b3d3d3fdb1a77"
name = "github.com/yudai/golcs"
packages = ["."]
pruneopts = "NUT"
revision = "ecda9a501e8220fae3b4b600c3db4b0ba22cfc68"
[[projects]]
branch = "master"
digest = "1:758f363e0dff33cf00b234be2efb12f919d79b42d5ae3909ff9eb69ef2c3cca5"
name = "golang.org/x/crypto"
packages = [
"ed25519",
"ed25519/internal/edwards25519",
"md4",
"pbkdf2"
"pbkdf2",
]
pruneopts = "NUT"
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
[[projects]]
branch = "master"
digest = "1:0b3fee9c4472022a0982ee0d81e08b3cc3e595f50befd7a4b358b48540d9d8c5"
name = "golang.org/x/net"
packages = [
"context",
@@ -525,35 +662,43 @@
"http2/hpack",
"idna",
"internal/timeseries",
"trace"
"trace",
]
pruneopts = "NUT"
revision = "2491c5de3490fced2f6cff376127c667efeed857"
[[projects]]
branch = "master"
digest = "1:46bd4e66bfce5e77f08fc2e8dcacc3676e679241ce83d9c150ff0397d686dd44"
name = "golang.org/x/oauth2"
packages = [
".",
"google",
"internal",
"jws",
"jwt"
"jwt",
]
pruneopts = "NUT"
revision = "cdc340f7c179dbbfa4afd43b7614e8fcadde4269"
[[projects]]
branch = "master"
digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239"
name = "golang.org/x/sync"
packages = ["errgroup"]
pruneopts = "NUT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:ec21c5bf0572488865b93e30ffd9132afbf85bec0b20c2d6cbcf349cf2031ed5"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "NUT"
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
[[projects]]
digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619"
name = "golang.org/x/text"
packages = [
"collate",
@@ -569,12 +714,14 @@
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
"unicode/rangetable",
]
pruneopts = "NUT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
digest = "1:dbd5568923513ee74aa626d027e2a8a352cf8f35df41d19f4e34491d1858c38b"
name = "google.golang.org/appengine"
packages = [
".",
@@ -587,18 +734,22 @@
"internal/modules",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
"urlfetch",
]
pruneopts = "NUT"
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:3c24554c312721e98fa6b76403e7100cf974eb46b1255ea7fc6471db9a9ce498"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "NUT"
revision = "7bb2a897381c9c5ab2aeb8614f758d7766af68ff"
[[projects]]
digest = "1:840b77b6eb539b830bb760b6e30b688ed2ff484bd83466fce2395835ed9367fe"
name = "google.golang.org/grpc"
packages = [
".",
@@ -625,72 +776,167 @@
"stats",
"status",
"tap",
"transport"
"transport",
]
pruneopts = "NUT"
revision = "1e2570b1b19ade82d8dbb31bba4e65e9f9ef5b34"
version = "v1.11.1"
[[projects]]
branch = "v3"
digest = "1:1244a9b3856f70d5ffb74bbfd780fc9d47f93f2049fa265c6fb602878f507bf8"
name = "gopkg.in/alexcesaro/quotedprintable.v3"
packages = ["."]
pruneopts = "NUT"
revision = "2caba252f4dc53eaf6b553000885530023f54623"
[[projects]]
digest = "1:aea6e9483c167cc6fdf1274c442558c5dda8fd3373372be04d98c79100868da1"
name = "gopkg.in/asn1-ber.v1"
packages = ["."]
pruneopts = "NUT"
revision = "379148ca0225df7a432012b8df0355c2a2063ac0"
version = "v1.2"
[[projects]]
digest = "1:24bfc2e8bf971485cb5ba0f0e5b08a1b806cca5828134df76b32d1ea50f2ab49"
name = "gopkg.in/bufio.v1"
packages = ["."]
pruneopts = "NUT"
revision = "567b2bfa514e796916c4747494d6ff5132a1dfce"
version = "v1"
[[projects]]
digest = "1:e05711632e1515319b014e8fe4cbe1d30ab024c473403f60cf0fdeb4c586a474"
name = "gopkg.in/ini.v1"
packages = ["."]
pruneopts = "NUT"
revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
version = "v1.36.0"
[[projects]]
digest = "1:3b0cf3a465fd07f76e5fc1a9d0783c662dac0de9fc73d713ebe162768fd87b5f"
name = "gopkg.in/macaron.v1"
packages = ["."]
pruneopts = "NUT"
revision = "c1be95e6d21e769e44e1ec33cec9da5837861c10"
version = "v1.3.1"
[[projects]]
branch = "v2"
digest = "1:d52332f9e9f2c6343652e13aa3fd40cfd03353520c9a48d90f21215d3012d50f"
name = "gopkg.in/mail.v2"
packages = ["."]
pruneopts = "NUT"
revision = "5bc5c8bb07bd8d2803831fbaf8cbd630fcde2c68"
[[projects]]
digest = "1:00126f697efdcab42f07c89ac8bf0095fb2328aef6464e070055154088cea859"
name = "gopkg.in/redis.v2"
packages = ["."]
pruneopts = "NUT"
revision = "e6179049628164864e6e84e973cfb56335748dea"
version = "v2.3.2"
[[projects]]
digest = "1:a50fabe7a46692dc7c656310add3d517abe7914df02afd151ef84da884605dc8"
name = "gopkg.in/square/go-jose.v2"
packages = [
".",
"cipher",
"json"
"json",
]
pruneopts = "NUT"
revision = "ef984e69dd356202fd4e4910d4d9c24468bdf0b8"
version = "v2.1.9"
[[projects]]
branch = "v2"
digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "NUT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "6f7f271afd27f78b7d8ebe27436fee72c9925fb82a978bdc57fde44e01f3ca51"
input-imports = [
"github.com/BurntSushi/toml",
"github.com/Unknwon/com",
"github.com/VividCortex/mysqlerr",
"github.com/aws/aws-sdk-go/aws",
"github.com/aws/aws-sdk-go/aws/awserr",
"github.com/aws/aws-sdk-go/aws/awsutil",
"github.com/aws/aws-sdk-go/aws/credentials",
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds",
"github.com/aws/aws-sdk-go/aws/defaults",
"github.com/aws/aws-sdk-go/aws/ec2metadata",
"github.com/aws/aws-sdk-go/aws/endpoints",
"github.com/aws/aws-sdk-go/aws/request",
"github.com/aws/aws-sdk-go/aws/session",
"github.com/aws/aws-sdk-go/service/cloudwatch",
"github.com/aws/aws-sdk-go/service/ec2",
"github.com/aws/aws-sdk-go/service/ec2/ec2iface",
"github.com/aws/aws-sdk-go/service/s3",
"github.com/aws/aws-sdk-go/service/sts",
"github.com/benbjohnson/clock",
"github.com/bmizerany/assert",
"github.com/codegangsta/cli",
"github.com/davecgh/go-spew/spew",
"github.com/denisenkom/go-mssqldb",
"github.com/facebookgo/inject",
"github.com/fatih/color",
"github.com/go-ldap/ldap",
"github.com/go-macaron/binding",
"github.com/go-macaron/gzip",
"github.com/go-macaron/session",
"github.com/go-macaron/session/memcache",
"github.com/go-macaron/session/postgres",
"github.com/go-macaron/session/redis",
"github.com/go-sql-driver/mysql",
"github.com/go-stack/stack",
"github.com/go-xorm/core",
"github.com/go-xorm/xorm",
"github.com/gorilla/websocket",
"github.com/gosimple/slug",
"github.com/grafana/grafana-plugin-model/go/datasource",
"github.com/grafana/grafana-plugin-model/go/renderer",
"github.com/hashicorp/go-hclog",
"github.com/hashicorp/go-plugin",
"github.com/hashicorp/go-version",
"github.com/inconshreveable/log15",
"github.com/lib/pq",
"github.com/mattn/go-isatty",
"github.com/mattn/go-sqlite3",
"github.com/opentracing/opentracing-go",
"github.com/opentracing/opentracing-go/ext",
"github.com/opentracing/opentracing-go/log",
"github.com/patrickmn/go-cache",
"github.com/pkg/errors",
"github.com/prometheus/client_golang/api",
"github.com/prometheus/client_golang/api/prometheus/v1",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/prometheus/client_model/go",
"github.com/prometheus/common/expfmt",
"github.com/prometheus/common/model",
"github.com/smartystreets/goconvey/convey",
"github.com/teris-io/shortid",
"github.com/uber/jaeger-client-go/config",
"github.com/yudai/gojsondiff",
"github.com/yudai/gojsondiff/formatter",
"golang.org/x/net/context/ctxhttp",
"golang.org/x/oauth2",
"golang.org/x/oauth2/google",
"golang.org/x/oauth2/jwt",
"golang.org/x/sync/errgroup",
"gopkg.in/ini.v1",
"gopkg.in/macaron.v1",
"gopkg.in/mail.v2",
"gopkg.in/square/go-jose.v2",
"gopkg.in/yaml.v2",
]
solver-name = "gps-cdcl"
solver-version = 1
+1 -1
View File
@@ -25,7 +25,7 @@ build: build-go build-js
build-docker-dev:
@echo "\033[92mInfo:\033[0m the frontend code is expected to be built already."
go run build.go -goos linux -pkg-arch amd64 ${OPT} build package-only latest
go run build.go -goos linux -pkg-arch amd64 ${OPT} build pkg-archive latest
cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
cd packaging/docker && docker build --tag grafana/grafana:dev .
+3 -1
View File
@@ -128,6 +128,8 @@ func main() {
if goos == linux {
createLinuxPackages()
}
case "pkg-archive":
grunt(gruntBuildArg("package")...)
case "pkg-rpm":
grunt(gruntBuildArg("release")...)
@@ -639,7 +641,7 @@ func shaFile(file string) error {
func shortenBuildId(buildId string) string {
buildId = strings.Replace(buildId, "-", "", -1)
if (len(buildId) < 9) {
if len(buildId) < 9 {
return buildId
}
return buildId[0:8]
+4
View File
@@ -490,6 +490,10 @@ enabled = false
enabled = true
interval_seconds = 10
#If both are set, basic auth will be required for the metrics endpoint.
basic_auth_username =
basic_auth_password =
# Send internal Grafana metrics to graphite
[metrics.graphite]
# Enable by setting the address setting (ex localhost:2003)
@@ -0,0 +1,511 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"fill": 2,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenarioId": "random_walk",
"target": ""
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [
{
"colorMode": "gray",
"fill": true,
"fillColor": "rgba(255, 255, 255, 0.03)",
"from": "08:30",
"fromDayOfWeek": 1,
"line": false,
"lineColor": "rgba(255, 255, 255, 0.2)",
"op": "time",
"to": "16:45",
"toDayOfWeek": 5
}
],
"timeShift": null,
"title": "Business Hours",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"fill": 2,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 8
},
"id": 4,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "",
"format": "time_series",
"intervalFactor": 1,
"refId": "A",
"scenarioId": "random_walk",
"target": ""
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [
{
"colorMode": "red",
"fill": true,
"fillColor": "rgba(255, 255, 255, 0.03)",
"from": "20:00",
"fromDayOfWeek": 7,
"line": false,
"lineColor": "rgba(255, 255, 255, 0.2)",
"op": "time",
"to": "23:00",
"toDayOfWeek": 7
}
],
"timeShift": null,
"title": "Sunday's 20-23",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {
"A-series": "#d683ce"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"fill": 2,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 16
},
"id": 3,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 0.5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenarioId": "random_walk",
"target": ""
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(255, 0, 0, 0.22)",
"from": "",
"fromDayOfWeek": 1,
"line": true,
"lineColor": "rgba(255, 0, 0, 0.32)",
"op": "time",
"to": "",
"toDayOfWeek": 1
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(255, 127, 0, 0.22)",
"fromDayOfWeek": 2,
"line": true,
"lineColor": "rgba(255, 127, 0, 0.32)",
"op": "time",
"toDayOfWeek": 2
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(255, 255, 0, 0.22)",
"fromDayOfWeek": 3,
"line": true,
"lineColor": "rgba(255, 255, 0, 0.22)",
"op": "time",
"toDayOfWeek": 3
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(0, 255, 0, 0.22)",
"fromDayOfWeek": 4,
"line": true,
"lineColor": "rgba(0, 255, 0, 0.32)",
"op": "time",
"toDayOfWeek": 4
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(0, 0, 255, 0.22)",
"fromDayOfWeek": 5,
"line": true,
"lineColor": "rgba(0, 0, 255, 0.32)",
"op": "time",
"toDayOfWeek": 5
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(75, 0, 130, 0.22)",
"fromDayOfWeek": 6,
"line": true,
"lineColor": "rgba(75, 0, 130, 0.32)",
"op": "time",
"toDayOfWeek": 6
},
{
"colorMode": "custom",
"fill": true,
"fillColor": "rgba(148, 0, 211, 0.22)",
"fromDayOfWeek": 7,
"line": true,
"lineColor": "rgba(148, 0, 211, 0.32)",
"op": "time",
"toDayOfWeek": 7
}
],
"timeShift": null,
"title": "Each day of week",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"fill": 2,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 24
},
"id": 5,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "",
"format": "time_series",
"intervalFactor": 1,
"refId": "A",
"scenarioId": "random_walk",
"target": ""
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [
{
"colorMode": "red",
"fill": false,
"from": "05:00",
"line": true,
"op": "time"
}
],
"timeShift": null,
"title": "05:00",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": false,
"schemaVersion": 16,
"style": "dark",
"tags": [
"gdev",
"panel-tests"
],
"templating": {
"list": []
},
"time": {
"from": "now-30d",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "Panel Tests - Graph (Time Regions)",
"uid": "XMjIZPmik",
"version": 1
}
+678 -253
View File
@@ -1,250 +1,681 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
60
],
"type": "gt"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"enabled": true,
"frequency": "60s",
"handler": 1,
"name": "TestData - Always OK",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 0
},
"id": 3,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,0",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 60
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Always OK",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": "125",
"min": "0",
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
177
],
"type": "gt"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"enabled": true,
"executionErrorState": "alerting",
"for": "0m",
"frequency": "60s",
"handler": 1,
"name": "TestData - Always Alerting",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 0
},
"id": 4,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "200,445,100,150,200,220,190",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 177
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Always Alerting",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": "0",
"show": true
},
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
1
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"15m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "5m",
"frequency": "1m",
"handler": 1,
"name": "TestData - No data",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 7
},
"id": 5,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "no_data_points",
"stringInput": "",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 1
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "No data",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": "0",
"show": true
},
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
177
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"15m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "1m",
"frequency": "1m",
"handler": 1,
"name": "TestData - Always Pending",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 7
},
"id": 6,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "200,445,100,150,200,220,190",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 177
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Always Alerting with For",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": "0",
"show": true
},
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
100
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "900000h",
"frequency": "1m",
"handler": 1,
"name": "Always Pending",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 14
},
"id": 7,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "200,445,100,150,200,220,190",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 100
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Always Alerting with For",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": "0",
"show": true
},
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"revision": 2,
"title": "Alerting with TestData",
"schemaVersion": 16,
"style": "dark",
"tags": [
"grafana-test"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": 255.625,
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
60
],
"type": "gt"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"enabled": true,
"frequency": "60s",
"handler": 1,
"name": "TestData - Always OK",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"id": 3,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,0",
"target": ""
}
],
"thresholds": [
{
"value": 60,
"op": "gt",
"fill": true,
"line": true,
"colorMode": "critical"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Always OK",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": "125",
"min": "0",
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
177
],
"type": "gt"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"enabled": true,
"frequency": "60s",
"handler": 1,
"name": "TestData - Always Alerting",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"datasource": "gdev-testdata",
"editable": true,
"error": false,
"fill": 1,
"id": 4,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"scenario": "random_walk",
"scenarioId": "csv_metric_values",
"stringInput": "200,445,100,150,200,220,190",
"target": ""
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 177
}
],
"timeFrom": null,
"timeShift": null,
"title": "Always Alerting",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": "0",
"show": true
},
{
"format": "short",
"label": "",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
}
],
"title": "New row"
}
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
@@ -274,14 +705,8 @@
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 13,
"version": 4,
"links": [],
"gnetId": null
}
"timezone": "browser",
"title": "Alerting with TestData",
"uid": "7MeksYbmk",
"version": 7
}
@@ -0,0 +1,5 @@
memcached:
image: redis:latest
ports:
- "6379:6379"
+1 -1
View File
@@ -9,7 +9,7 @@ services:
- /var/run/docker.sock:/tmp/docker.sock:ro
db:
image: mysql
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: grafana
@@ -39,6 +39,7 @@ local alertDashboardTemplate = {
"executionErrorState": "alerting",
"frequency": "10s",
"handler": 1,
"for": "1m",
"name": "bulk alerting",
"noDataState": "no_data",
"notifications": [
+4
View File
@@ -55,6 +55,10 @@ Alert rule evaluation interval | Send reminders every | Reminder sent every (aft
<div class="clearfix"></div>
### Disable resolve message
When checked, this option will disable resolve message [OK] that is sent when alerting state returns to false.
## Supported Notification Types
Grafana ships with the following set of notification types:
+16 -3
View File
@@ -39,7 +39,7 @@ Currently alerting supports a limited form of high availability. Since v4.2.0 of
## Rule Config
{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}}
Currently only the graph panel supports alert rules but this will be added to the **Singlestat** and **Table**
panels as well in a future release.
@@ -48,6 +48,19 @@ panels as well in a future release.
Here you can specify the name of the alert rule and how often the scheduler should evaluate the alert rule.
### For
> This setting is available in Grafana 5.4 and above.
If an alert rule has a configured `For` and the query violates the configured threshold it will first go from `OK` to `Pending`. Going from `OK` to `Pending` Grafana will not send any notifications. Once the alert rule has been firing for more than `For` duration, it will change to `Alerting` and send alert notifications.
Typically, it's always a good idea to use this setting since it's often worse to get false positive than wait a few minutes before the alert notification triggers. Looking at the `Alert list` or `Alert list panels` you will be able to see alerts in pending state.
Below you can see an example timeline of an alert using the `For` setting. At ~16:04 the alert state changes to `Pending` and after 4 minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`.
{{< imgbox img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}}
{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}}
### Conditions
Currently the only condition type that exists is a `Query` condition that allows you to
@@ -57,11 +70,11 @@ specify a query letter, time range and an aggregation function.
### Query condition example
```sql
avg() OF query(A, 5m, now) IS BELOW 14
avg() OF query(A, 15m, now) IS BELOW 14
```
- `avg()` Controls how the values for **each** series should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function.
- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `5m, now` means 5 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data.
- `query(A, 15m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `15m, now` means 15 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data.
- `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold.
The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially.
+1
View File
@@ -3,6 +3,7 @@ title = "LDAP Authentication"
description = "Grafana LDAP Authentication Guide "
keywords = ["grafana", "configuration", "documentation", "ldap", "active directory"]
type = "docs"
aliases = ["/installation/ldap/"]
[menu.docs]
name = "LDAP"
identifier = "ldap"
+21 -1
View File
@@ -73,7 +73,18 @@ You can hide the Grafana login form using the below configuration settings.
```bash
[auth]
disable_login_form ⁼ true
disable_login_form = true
```
### Automatic OAuth login
Set to true to attempt login with OAuth automatically, skipping the login screen.
This setting is ignored if multiple OAuth providers are configured.
Defaults to `false`.
```bash
[auth]
oauth_auto_login = true
```
### Hide sign-out menu
@@ -84,3 +95,12 @@ Set to the option detailed below to true to hide sign-out menu link. Useful if y
[auth]
disable_signout_menu = true
```
### URL redirect after signing out
URL to redirect the user to after signing out from Grafana. This can for example be used to enable signout from oauth provider.
```bash
[auth]
signout_redirect_url =
```
@@ -158,9 +158,9 @@ Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod`
It is also possible to resolve the name of the Monitored Resource Type.
| Alias Pattern Format | Description | Example Result |
| ------------------------ | ------------------------------------------------| ---------------- |
| `{{resource.type}}` | returns the name of the monitored resource type | `gce_instance` |
| Alias Pattern Format | Description | Example Result |
| -------------------- | ----------------------------------------------- | -------------- |
| `{{resource.type}}` | returns the name of the monitored resource type | `gce_instance` |
Example Alias By: `{{resource.type}} - {{metric.type}}`
@@ -177,7 +177,17 @@ types of template variables.
### Query Variable
Writing variable queries is not supported yet.
Variable of the type *Query* allows you to query Stackdriver for various types of data. The Stackdriver data source plugin provides the following `Query Types`.
| Name | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| *Metric Types* | Returns a list of metric type names that are available for the specified service. |
| *Labels Keys* | Returns a list of keys for `metric label` and `resource label` in the specified metric. |
| *Labels Values* | Returns a list of values for the label in the specified metric. |
| *Resource Types* | Returns a list of resource types for the the specified metric. |
| *Aggregations* | Returns a list of aggregations (cross series reducers) for the the specified metric. |
| *Aligners* | Returns a list of aligners (per series aligners) for the the specified metric. |
| *Alignment periods* | Returns a list of all alignment periods that are available in Stackdriver query editor in Grafana |
### Using variables in queries
+8
View File
@@ -186,6 +186,14 @@ There is an option under Series overrides to draw lines as dashes. Set Dashes to
Thresholds allow you to add arbitrary lines or sections to the graph to make it easier to see when
the graph crosses a particular threshold.
### Time Regions
> Only available in Grafana v5.4 and above.
{{< docs-imagebox img="/img/docs/v54/graph_time_regions.png" max-width= "800px" >}}
Time regions allow you to highlight certain time regions of the graph to make it easier to see for example weekends, business hours and/or off work hours.
## Time Range
{{< docs-imagebox img="/img/docs/v51/graph-time-range.png" max-width= "900px" >}}
+83
View File
@@ -0,0 +1,83 @@
+++
title = "What's New in Grafana v5.4"
description = "Feature & improvement highlights for Grafana v5.4"
keywords = ["grafana", "new", "documentation", "5.4"]
type = "docs"
[menu.docs]
name = "Version 5.4"
identifier = "v5.4"
parent = "whatsnew"
weight = -10
+++
# What's New in Grafana v5.4
Grafana v5.4 brings new features, many enhancements and bug fixes. This article will detail the major new features and enhancements.
- [Alerting]({{< relref "#alerting" >}}) Limit false positives with the new `For` setting
- [Google Stackdriver]({{< relref "#google-stackdriver" >}}) Now with support for templating queries
- [MySQL]({{< relref "#mysql-query-builder" >}}) gets a new query builder!
- [Graph Panel]({{< relref "#graph-panel-enhancements" >}}) Highlight time regions and more
- [Team Preferences]({{< relref "#team-preferences" >}}) Give your teams their own home dashboard
## Alerting
{{< docs-imagebox img="/img/docs/v54/alerting-for-dark-theme.png" max-width="600px" class="docs-image--right" >}}
Grafana v5.4 ships with a new alert rule setting named `For` which is great for removing false positives. If an alert rule has a configured `For` and the query violates the configured threshold it will first go from `OK` to `Pending`. Going from `OK` to `Pending` Grafana will not send any notifications. Once the alert rule has been firing for more than `For` duration, it will change to `Alerting` and send alert notifications. Typically, it's always a good idea to use this setting since it's often worse to get false positive than wait a few minutes before the alert notification triggers.
In the screenshot you can see an example timeline of an alert using the `For` setting. At ~16:04 the alert state changes to `Pending` and after 4 minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`. [Learn more](/alerting/rules/#for).
Additionally, there's now support for disable the sending of `OK` alert notifications. [Learn more](/alerting/notifications/#disable-resolve-message).
<div class="clearfix"></div>
## Google Stackdriver
{{< docs-imagebox img="/img/docs/v54/stackdriver_template_query.png" max-width="600px" class="docs-image--right" >}}
Grafana v5.3 included built-in support for [Google Stackdriver](https://cloud.google.com/stackdriver/) which enables you to visualize your Stackdriver metrics in Grafana.
One important feature missing was support for templating queries. This is now included together with a brand new templating query editor for Stackdriver.
The Stackdriver templating query editor lets you choose from a set of different Query Types. This will in turn reveal additional drop downs to help you
find, filter and select the templating values you're interested in, see screenshot for details. The templating query editor also supports chaining multiple variables
making it easy to define variables that's dependent on other variables.
Stackdriver is the first datasource which has support for a custom templating query editor. But starting from Grafana v5.4 it's now possible for all datasources, including plugin datasources, to
create their very own templating query editor.
Additionally, if Grafana is running on a Google Compute Engine (GCE) virtual machine, it is now possible for Grafana to automatically retrieve default credentials from the metadata server.
This has the advantage of not needing to generate a private key file for the service account and also not having to upload the file to Grafana. [Learn more](/features/datasources/stackdriver/#using-gce-default-service-account).
Please read [Using Google Stackdriver in Grafana](/features/datasources/stackdriver/) for more detailed information on how to get started and use it.
<div class="clearfix"></div>
## MySQL Query Builder
Grafana v5.4 comes with a new graphical query builder for MySQL. This brings MySQL integration more in line with some of the other datasources and makes it easier for both advanced users and beginners to work with timeseries in MySQL. Learn more about it in the [documentation](/features/datasources/mysql/#query-editor).
{{< docs-imagebox img="/img/docs/v54/mysql_query_still.png" animated-gif="/img/docs/v54/mysql_query.gif" >}}
## Graph Panel Enhancements
Grafana v5.4 adds support for highlighting weekdays and/or certain timespans in the graph panel. This should make it easier to compare for example weekends, business hours and/or off work hours.
{{< docs-imagebox img="/img/docs/v54/graph_time_regions.png" max-width= "800px" >}}
Additionally, when rendering series as lines in the graph panel, should there be only one data point available for one series so that a connecting line cannot be established, a point will
automatically be rendered for that data point. This should make it easier to understand what's going on when only receiving a single data point.
{{< docs-imagebox img="/img/docs/v54/graph_dot_single_point.png" max-width= "800px" >}}
## Team Preferences
Grafana v5.4 adds support for customizing home dashboard, timezone and theme for teams, in addition to the existing customization on Organization and user Profile level.
1. Specifying a preference on User Profile level will override preference on Team and/or Organization level
2. Specifying a preference on Team level will override preference on Organization level.
## 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.
+4 -1
View File
@@ -160,11 +160,14 @@ Content-Type: application/json
`POST /api/admin/pause-all-alerts`
Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
**Example Request**:
```http
POST /api/admin/pause-all-alerts HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"paused": true
+65 -1
View File
@@ -30,7 +30,7 @@ Authorization: Basic YWRtaW46YWRtaW4=
### Using the query parameter
Default value for the `perpage` parameter is `1000` and for the `page` parameter is `1`.
Default value for the `perpage` parameter is `1000` and for the `page` parameter is `1`.
The `totalCount` field in the response can be used for pagination of the teams list E.g. if `totalCount` is equal to 100 teams and the `perpage` parameter is set to 10 then there are 10 pages of teams.
@@ -314,3 +314,67 @@ Status Codes:
- **401** - Unauthorized
- **403** - Permission denied
- **404** - Team not found/Team member not found
## Get Team Preferences
`GET /api/teams/:teamId/preferences`
**Example Request**:
```http
GET /api/teams/2/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"theme": "",
"homeDashboardId": 0,
"timezone": ""
}
```
## Update Team Preferences
`PUT /api/teams/:teamId/preferences`
**Example Request**:
```http
PUT /api/teams/2/preferences HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"theme": "dark",
"homeDashboardId": 39,
"timezone": "utc"
}
```
JSON Body Schema:
- **theme** - One of: ``light``, ``dark``, or an empty string for the default theme
- **homeDashboardId** - The numerical ``:id`` of a dashboard, default: ``0``
- **timezone** - One of: ``utc``, ``browser``, or an empty string for the default
Omitting a key will cause the current value to be replaced with the system default value.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: text/plain; charset=utf-8
{
"message":"Preferences updated"
}
```
+34
View File
@@ -226,6 +226,40 @@ Content-Type: application/json
]
```
## Get Teams for user
`GET /api/users/:id/teams`
**Example Request**:
```http
GET /api/users/1/teams HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"id":1,
"orgId":1,
"name":"team1",
"email":"",
"avatarUrl":"/avatar/3fcfe295eae3bcb67a49349377428a66",
"memberCount":1
}
]
```
## User
## Actual User
+3 -3
View File
@@ -60,9 +60,9 @@ aliases = ["v1.1", "guides/reference/admin"]
<h4>Provisioning</h4>
<p>A guide to help you automate your Grafana setup & configuration.</p>
</a>
<a href="{{< relref "guides/whats-new-in-v5-3.md" >}}" class="nav-cards__item nav-cards__item--guide">
<h4>What's new in v5.3</h4>
<p>Article on all the new cool features and enhancements in v5.3</p>
<a href="{{< relref "guides/whats-new-in-v5-4.md" >}}" class="nav-cards__item nav-cards__item--guide">
<h4>What's new in v5.4</h4>
<p>Article on all the new cool features and enhancements in v5.4</p>
</a>
<a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
<h4>Screencasts</h4>
@@ -454,6 +454,12 @@ Ex `filters = sqlstore:debug`
### enabled
Enable metrics reporting. defaults true. Available via HTTP API `/metrics`.
### basic_auth_username
If set configures the username to use for basic authentication on the metrics endpoint.
### basic_auth_password
If set configures the password to use for basic authentication on the metrics endpoint.
### interval_seconds
Flush/Write interval when sending metrics to external TSDB. Defaults to 10s.
+1 -1
View File
@@ -25,7 +25,7 @@ the value, using the dropdown at the top of the dashboard, your panel's metric q
Panel titles and metric queries can refer to variables using two different syntaxes:
- `$<varname>` Example: apps.frontend.$server.requests.count
- `$varname` Example: apps.frontend.$server.requests.count
- `[[varname]]` Example: apps.frontend.[[server]].requests.count
Why two ways? The first syntax is easier to read and write but does not allow you to use a variable in the middle of word. Use
+2 -2
View File
@@ -1,4 +1,4 @@
{
"stable": "5.3.2",
"testing": "5.3.2"
"stable": "5.3.4",
"testing": "5.3.4"
}
+1 -1
View File
@@ -56,7 +56,7 @@ if [ -f "$DEFAULT" ]; then
. "$DEFAULT"
fi
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} --packaging=deb cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
function checkUser() {
if [ `id -u` -ne 0 ]; then
@@ -17,6 +17,7 @@ RuntimeDirectoryMode=0750
ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE_DIR}/grafana-server.pid \
--packaging=deb \
cfg:default.paths.logs=${LOG_DIR} \
cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.plugins=${PLUGINS_DIR} \
+2 -1
View File
@@ -25,7 +25,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
WORKDIR $GF_PATHS_HOME
RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates curl && \
RUN apt-get update && apt-get -y upgrade && \
apt-get install -qq -y libfontconfig ca-certificates curl && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
+9 -1
View File
@@ -1,9 +1,17 @@
#!/bin/sh
set -e
_grafana_tag=$1
_raw_grafana_tag=$1
_docker_repo=${2:-grafana/grafana-enterprise}
if echo "$_raw_grafana_tag" | grep -q "^v"; then
_grafana_tag=$(echo "${_raw_grafana_tag}" | cut -d "v" -f 2)
else
_grafana_tag="${_raw_grafana_tag}"
fi
echo "Building and deploying ${_docker_repo}:${_grafana_tag}"
docker build \
--tag "${_docker_repo}:${_grafana_tag}"\
--no-cache=true \
+1
View File
@@ -80,6 +80,7 @@ fi
exec grafana-server \
--homepath="$GF_PATHS_HOME" \
--config="$GF_PATHS_CONFIG" \
--packaging=docker \
"$@" \
cfg:default.log.mode="console" \
cfg:default.paths.data="$GF_PATHS_DATA" \
+1 -1
View File
@@ -60,7 +60,7 @@ fi
# overwrite settings from default file
[ -e /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} --packaging=rpm cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
function isRunning() {
status -p $PID_FILE $NAME > /dev/null 2>&1
@@ -17,6 +17,7 @@ RuntimeDirectoryMode=0750
ExecStart=/usr/sbin/grafana-server \
--config=${CONF_FILE} \
--pidfile=${PID_FILE_DIR}/grafana-server.pid \
--packaging=rpm \
cfg:default.paths.logs=${LOG_DIR} \
cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.plugins=${PLUGINS_DIR} \
+1 -1
View File
@@ -295,7 +295,7 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
return Error(500, "", err)
}
var response m.AlertStateType = m.AlertStatePending
var response m.AlertStateType = m.AlertStateUnknown
pausedState := "un-paused"
if cmd.Paused {
response = m.AlertStatePaused
+4 -1
View File
@@ -140,6 +140,7 @@ func (hs *HTTPServer) registerRoutes() {
usersRoute.Get("/", Wrap(SearchUsers))
usersRoute.Get("/search", Wrap(SearchUsersWithPaging))
usersRoute.Get("/:id", Wrap(GetUserByID))
usersRoute.Get("/:id/teams", Wrap(GetUserTeams))
usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com
usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail))
@@ -155,6 +156,8 @@ func (hs *HTTPServer) registerRoutes() {
teamsRoute.Get("/:teamId/members", Wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), Wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", Wrap(RemoveTeamMember))
teamsRoute.Get("/:teamId/preferences", Wrap(GetTeamPreferences))
teamsRoute.Put("/:teamId/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateTeamPreferences))
}, reqOrgAdmin)
// team without requirement of user to be org admin
@@ -242,7 +245,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIdByName), reqSignedIn)
apiRoute.Get("/plugins", Wrap(GetPluginList))
apiRoute.Get("/plugins", Wrap(hs.GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", Wrap(GetPluginMarkdown))
+19
View File
@@ -0,0 +1,19 @@
package api
import (
"crypto/subtle"
macaron "gopkg.in/macaron.v1"
)
// BasicAuthenticatedRequest parses the provided HTTP request for basic authentication credentials
// and returns true if the provided credentials match the expected username and password.
// Returns false if the request is unauthenticated.
// Uses constant-time comparison in order to mitigate timing attacks.
func BasicAuthenticatedRequest(req macaron.Request, expectedUser, expectedPass string) bool {
user, pass, ok := req.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(expectedUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(expectedPass)) != 1 {
return false
}
return true
}
+45
View File
@@ -0,0 +1,45 @@
package api
import (
"encoding/base64"
"fmt"
"net/http"
"testing"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/macaron.v1"
)
func TestBasicAuthenticatedRequest(t *testing.T) {
expectedUser := "prometheus"
expectedPass := "password"
Convey("Given a valid set of basic auth credentials", t, func() {
httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
So(err, ShouldBeNil)
req := macaron.Request{
Request: httpReq,
}
encodedCreds := encodeBasicAuthCredentials(expectedUser, expectedPass)
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
So(authenticated, ShouldBeTrue)
})
Convey("Given an invalid set of basic auth credentials", t, func() {
httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
So(err, ShouldBeNil)
req := macaron.Request{
Request: httpReq,
}
encodedCreds := encodeBasicAuthCredentials("invaliduser", "invalidpass")
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
So(authenticated, ShouldBeFalse)
})
}
func encodeBasicAuthCredentials(user, pass string) string {
creds := fmt.Sprintf("%s:%s", user, pass)
return base64.StdEncoding.EncodeToString([]byte(creds))
}
+1 -5
View File
@@ -277,10 +277,6 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
return Error(500, "Failed to save dashboard", err)
}
if err == m.ErrDashboardFailedToUpdateAlertData {
return Error(500, "Invalid alert data. Cannot save dashboard", err)
}
c.TimeRequest(metrics.M_Api_Dashboard_Save)
return JSON(200, util.DynMap{
"status": "success",
@@ -293,7 +289,7 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
}
func GetHomeDashboard(c *m.ReqContext) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
prefsQuery := m.GetPreferencesWithDefaultsQuery{User: c.SignedInUser}
if err := bus.Dispatch(&prefsQuery); err != nil {
return Error(500, "Failed to get preferences", err)
}
-1
View File
@@ -727,7 +727,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},
+4 -4
View File
@@ -19,9 +19,9 @@ type PluginSetting struct {
JsonData map[string]interface{} `json:"jsonData"`
DefaultNavUrl string `json:"defaultNavUrl"`
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
State string `json:"state"`
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
State plugins.PluginState `json:"state"`
}
type PluginListItem struct {
@@ -34,7 +34,7 @@ type PluginListItem struct {
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
DefaultNavUrl string `json:"defaultNavUrl"`
State string `json:"state"`
State plugins.PluginState `json:"state"`
}
type PluginList []PluginListItem
+1 -1
View File
@@ -133,7 +133,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
panels := map[string]interface{}{}
for _, panel := range enabledPlugins.Panels {
if panel.State == "alpha" && !hs.Cfg.EnableAlphaPanels {
if panel.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
continue
}
+9
View File
@@ -245,6 +245,11 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
return
}
if hs.metricsEndpointBasicAuthEnabled() && !BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) {
ctx.Resp.WriteHeader(http.StatusUnauthorized)
return
}
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
ServeHTTP(ctx.Resp, ctx.Req.Request)
}
@@ -299,3 +304,7 @@ func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string,
},
))
}
func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool {
return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != ""
}
+30
View File
@@ -0,0 +1,30 @@
package api
import (
"testing"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestHTTPServer(t *testing.T) {
Convey("Given a HTTPServer", t, func() {
ts := &HTTPServer{
Cfg: setting.NewCfg(),
}
Convey("Given that basic auth on the metrics endpoint is enabled", func() {
ts.Cfg.MetricsEndpointBasicAuthUsername = "foo"
ts.Cfg.MetricsEndpointBasicAuthPassword = "bar"
So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeTrue)
})
Convey("Given that basic auth on the metrics endpoint is disabled", func() {
ts.Cfg.MetricsEndpointBasicAuthUsername = ""
ts.Cfg.MetricsEndpointBasicAuthPassword = ""
So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeFalse)
})
})
}
+1 -1
View File
@@ -23,7 +23,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
return nil, err
}
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
prefsQuery := m.GetPreferencesWithDefaultsQuery{User: c.SignedInUser}
if err := bus.Dispatch(&prefsQuery); err != nil {
return nil, err
}
+22
View File
@@ -39,6 +39,10 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
viewData.Settings["loginError"] = loginError
}
if tryOAuthAutoLogin(c) {
return
}
if !tryLoginUsingRememberCookie(c) {
c.HTML(200, ViewIndex, viewData)
return
@@ -53,6 +57,24 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
c.Redirect(setting.AppSubUrl + "/")
}
func tryOAuthAutoLogin(c *m.ReqContext) bool {
if !setting.OAuthAutoLogin {
return false
}
oauthInfos := setting.OAuthService.OAuthInfos
if len(oauthInfos) != 1 {
log.Warn("Skipping OAuth auto login because multiple OAuth providers are configured.")
return false
}
for key := range setting.OAuthService.OAuthInfos {
redirectUrl := setting.AppSubUrl + "/login/" + key
log.Info("OAuth auto login enabled. Redirecting to " + redirectUrl)
c.Redirect(redirectUrl, 307)
return true
}
return false
}
func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
// Check auto-login.
uname := c.GetCookie(setting.CookieUserName)
+4
View File
@@ -195,6 +195,10 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
req.Header.Del("X-Forwarded-Proto")
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// Clear Origin and Referer to avoir CORS issues
req.Header.Del("Origin")
req.Header.Del("Referer")
// set X-Forwarded-For header
if req.RemoteAddr != "" {
remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr)
+10 -1
View File
@@ -371,13 +371,22 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/")
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req.Header.Add("Origin", "grafana.com")
req.Header.Add("Referer", "grafana.com")
req.Header.Add("X-Canary", "stillthere")
So(err, ShouldBeNil)
proxy.getDirector()(req)
Convey("Shoudl keep user request (including trailing slash)", func() {
Convey("Should keep user request (including trailing slash)", func() {
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
})
Convey("Origin and Referer headers should be dropped", func() {
So(req.Header.Get("Origin"), ShouldEqual, "")
So(req.Header.Get("Referer"), ShouldEqual, "")
So(req.Header.Get("X-Canary"), ShouldEqual, "stillthere")
})
})
})
}
+5 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func GetPluginList(c *m.ReqContext) Response {
func (hs *HTTPServer) GetPluginList(c *m.ReqContext) Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
@@ -39,6 +39,10 @@ func GetPluginList(c *m.ReqContext) Response {
continue
}
if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
continue
}
listItem := dtos.PluginListItem{
Id: pluginDef.Id,
Name: pluginDef.Name,
+8 -7
View File
@@ -21,11 +21,11 @@ func SetHomeDashboard(c *m.ReqContext, cmd m.SavePreferencesCommand) Response {
// GET /api/user/preferences
func GetUserPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, c.UserId)
return getPreferencesFor(c.OrgId, c.UserId, 0)
}
func getPreferencesFor(orgID int64, userID int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID}
func getPreferencesFor(orgID, userID, teamID int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID, TeamId: teamID}
if err := bus.Dispatch(&prefsQuery); err != nil {
return Error(500, "Failed to get preferences", err)
@@ -42,13 +42,14 @@ func getPreferencesFor(orgID int64, userID int64) Response {
// PUT /api/user/preferences
func UpdateUserPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
return updatePreferencesFor(c.OrgId, c.UserId, 0, &dtoCmd)
}
func updatePreferencesFor(orgID int64, userID int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
func updatePreferencesFor(orgID, userID, teamId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
saveCmd := m.SavePreferencesCommand{
UserId: userID,
OrgId: orgID,
TeamId: teamId,
Theme: dtoCmd.Theme,
Timezone: dtoCmd.Timezone,
HomeDashboardId: dtoCmd.HomeDashboardID,
@@ -63,10 +64,10 @@ func updatePreferencesFor(orgID int64, userID int64, dtoCmd *dtos.UpdatePrefsCmd
// GET /api/org/preferences
func GetOrgPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, 0)
return getPreferencesFor(c.OrgId, 0, 0)
}
// PUT /api/org/preferences
func UpdateOrgPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
return updatePreferencesFor(c.OrgId, 0, &dtoCmd)
return updatePreferencesFor(c.OrgId, 0, 0, &dtoCmd)
}
+10
View File
@@ -96,3 +96,13 @@ func GetTeamByID(c *m.ReqContext) Response {
query.Result.AvatarUrl = dtos.GetGravatarUrlWithDefault(query.Result.Email, query.Result.Name)
return JSON(200, &query.Result)
}
// GET /api/teams/:teamId/preferences
func GetTeamPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, 0, c.ParamsInt64(":teamId"))
}
// PUT /api/teams/:teamId/preferences
func UpdateTeamPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response {
return updatePreferencesFor(c.OrgId, 0, c.ParamsInt64(":teamId"), &dtoCmd)
}
+11 -3
View File
@@ -113,7 +113,16 @@ func GetSignedInUserOrgList(c *m.ReqContext) Response {
// GET /api/user/teams
func GetSignedInUserTeamList(c *m.ReqContext) Response {
query := m.GetTeamsByUserQuery{OrgId: c.OrgId, UserId: c.UserId}
return getUserTeamList(c.OrgId, c.UserId)
}
// GET /api/users/:id/teams
func GetUserTeams(c *m.ReqContext) Response {
return getUserTeamList(c.OrgId, c.ParamsInt64(":id"))
}
func getUserTeamList(userID int64, orgID int64) Response {
query := m.GetTeamsByUserQuery{OrgId: orgID, UserId: userID}
if err := bus.Dispatch(&query); err != nil {
return Error(500, "Failed to get user teams", err)
@@ -122,11 +131,10 @@ func GetSignedInUserTeamList(c *m.ReqContext) Response {
for _, team := range query.Result {
team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
}
return JSON(200, query.Result)
}
// GET /api/user/:id/orgs
// GET /api/users/:id/orgs
func GetUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.ParamsInt64(":id"))
}
+17 -2
View File
@@ -13,7 +13,7 @@ import (
"syscall"
"time"
extensions "github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
@@ -39,6 +39,7 @@ var buildstamp string
var configFile = flag.String("config", "", "path to config file")
var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory")
var pidFile = flag.String("pidfile", "", "path to pid file")
var packaging = flag.String("packaging", "unknown", "describes the way Grafana was installed")
func main() {
v := flag.Bool("v", false, "prints current version and exits")
@@ -53,7 +54,10 @@ func main() {
if *profile {
runtime.SetBlockProfileRate(1)
go func() {
http.ListenAndServe(fmt.Sprintf("localhost:%d", *profilePort), nil)
err := http.ListenAndServe(fmt.Sprintf("localhost:%d", *profilePort), nil)
if err != nil {
panic(err)
}
}()
f, err := os.Create("trace.out")
@@ -79,6 +83,7 @@ func main() {
setting.BuildStamp = buildstampInt64
setting.BuildBranch = buildBranch
setting.IsEnterprise = extensions.IsEnterprise
setting.Packaging = validPackaging(*packaging)
metrics.SetBuildInformation(version, commit, buildBranch)
@@ -95,6 +100,16 @@ func main() {
os.Exit(code)
}
func validPackaging(packaging string) string {
validTypes := []string{"dev", "deb", "rpm", "docker", "brew", "hosted", "unknown"}
for _, vt := range validTypes {
if packaging == vt {
return packaging
}
}
return "unknown"
}
func listenToSystemSignals(server *GrafanaServerImpl) {
signalChan := make(chan os.Signal, 1)
sighupChan := make(chan os.Signal, 1)
+26 -6
View File
@@ -67,6 +67,7 @@ type GrafanaServerImpl struct {
}
func (g *GrafanaServerImpl) Run() error {
var err error
g.loadConfiguration()
g.writePIDFile()
@@ -74,20 +75,38 @@ func (g *GrafanaServerImpl) Run() error {
social.NewOAuthService()
serviceGraph := inject.Graph{}
serviceGraph.Provide(&inject.Object{Value: bus.GetBus()})
serviceGraph.Provide(&inject.Object{Value: g.cfg})
serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)})
serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)})
err = serviceGraph.Provide(&inject.Object{Value: bus.GetBus()})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
err = serviceGraph.Provide(&inject.Object{Value: g.cfg})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
err = serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
err = serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
// self registered services
services := registry.GetServices()
// Add all services to dependency graph
for _, service := range services {
serviceGraph.Provide(&inject.Object{Value: service.Instance})
err = serviceGraph.Provide(&inject.Object{Value: service.Instance})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
}
serviceGraph.Provide(&inject.Object{Value: g})
err = serviceGraph.Provide(&inject.Object{Value: g})
if err != nil {
return fmt.Errorf("Failed to provide object to the graph: %v", err)
}
// Inject dependencies to services
if err := serviceGraph.Populate(); err != nil {
@@ -144,6 +163,7 @@ func (g *GrafanaServerImpl) Run() error {
}
sendSystemdNotification("READY=1")
return g.childRoutines.Wait()
}
+9 -6
View File
@@ -313,7 +313,7 @@ func init() {
// SetBuildInformation sets the build information for this binary
func SetBuildInformation(version, revision, branch string) {
// We export this info twice for backwards compability.
// We export this info twice for backwards compatibility.
// Once this have been released for some time we should be able to remote `M_Grafana_Version`
// The reason we added a new one is that its common practice in the prometheus community
// to name this metric `*_build_info` so its easy to do aggregation on all programs.
@@ -397,11 +397,12 @@ func sendUsageStats(oauthProviders map[string]bool) {
metrics := map[string]interface{}{}
report := map[string]interface{}{
"version": version,
"metrics": metrics,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"edition": getEdition(),
"version": version,
"metrics": metrics,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"edition": getEdition(),
"packaging": setting.Packaging,
}
statsQuery := models.GetSystemStatsQuery{}
@@ -447,6 +448,8 @@ func sendUsageStats(oauthProviders map[string]bool) {
}
metrics["stats.ds.other.count"] = dsOtherCount
metrics["stats.packaging."+setting.Packaging+".count"] = 1
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
if err := bus.Dispatch(&dsAccessStats); err != nil {
metricsLogger.Error("Failed to get datasource access stats", "error", err)
+3
View File
@@ -176,6 +176,7 @@ func TestMetrics(t *testing.T) {
setting.BasicAuthEnabled = true
setting.LdapEnabled = true
setting.AuthProxyEnabled = true
setting.Packaging = "deb"
wg.Add(1)
sendUsageStats(oauthProviders)
@@ -243,6 +244,8 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.auth_enabled.oauth_google.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_generic_oauth.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1)
})
})
+1
View File
@@ -115,6 +115,7 @@ func Recovery() macaron.Handler {
c.Data["Title"] = "Server Error"
c.Data["AppSubUrl"] = setting.AppSubUrl
c.Data["Theme"] = setting.DefaultTheme
if setting.Env == setting.DEV {
if theErr, ok := err.(error); ok {
+10 -3
View File
@@ -19,6 +19,7 @@ const (
AlertStateAlerting AlertStateType = "alerting"
AlertStateOK AlertStateType = "ok"
AlertStatePending AlertStateType = "pending"
AlertStateUnknown AlertStateType = "unknown"
)
const (
@@ -39,7 +40,12 @@ var (
)
func (s AlertStateType) IsValid() bool {
return s == AlertStateOK || s == AlertStateNoData || s == AlertStatePaused || s == AlertStatePending
return s == AlertStateOK ||
s == AlertStateNoData ||
s == AlertStatePaused ||
s == AlertStatePending ||
s == AlertStateAlerting ||
s == AlertStateUnknown
}
func (s NoDataOption) IsValid() bool {
@@ -66,12 +72,13 @@ type Alert struct {
PanelId int64
Name string
Message string
Severity string
Severity string //Unused
State AlertStateType
Handler int64
Handler int64 //Unused
Silenced bool
ExecutionError string
Frequency int64
For time.Duration
EvalData *simplejson.Json
NewStateDate time.Time
-1
View File
@@ -21,7 +21,6 @@ var (
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
+4 -3
View File
@@ -14,6 +14,7 @@ type Preferences struct {
Id int64
OrgId int64
UserId int64
TeamId int64
Version int
HomeDashboardId int64
Timezone string
@@ -29,14 +30,13 @@ type GetPreferencesQuery struct {
Id int64
OrgId int64
UserId int64
TeamId int64
Result *Preferences
}
type GetPreferencesWithDefaultsQuery struct {
Id int64
OrgId int64
UserId int64
User *SignedInUser
Result *Preferences
}
@@ -46,6 +46,7 @@ type GetPreferencesWithDefaultsQuery struct {
type SavePreferencesCommand struct {
UserId int64
OrgId int64
TeamId int64
HomeDashboardId int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
+8 -1
View File
@@ -17,6 +17,13 @@ var (
PluginTypeDashboard = "dashboard"
)
type PluginState string
var (
PluginStateAlpha PluginState = "alpha"
PluginStateBeta PluginState = "beta"
)
type PluginNotFoundError struct {
PluginId string
}
@@ -39,7 +46,7 @@ type PluginBase struct {
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
HideFromList bool `json:"hideFromList,omitempty"`
State string `json:"state,omitempty"`
State PluginState `json:"state,omitempty"`
IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
+29 -3
View File
@@ -68,8 +68,13 @@ func (c *EvalContext) GetStateModel() *StateDescription {
Color: "#D63232",
Text: "Alerting",
}
case m.AlertStateUnknown:
return &StateDescription{
Color: "#888888",
Text: "Unknown",
}
default:
panic("Unknown rule state " + c.Rule.State)
panic("Unknown rule state for alert " + c.Rule.State)
}
}
@@ -113,7 +118,26 @@ func (c *EvalContext) GetRuleUrl() (string, error) {
return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil
}
// GetNewState returns the new state from the alert rule evaluation
func (c *EvalContext) GetNewState() m.AlertStateType {
ns := getNewStateInternal(c)
if ns != m.AlertStateAlerting || c.Rule.For == 0 {
return ns
}
since := time.Since(c.Rule.LastStateChange)
if c.PrevAlertState == m.AlertStatePending && since > c.Rule.For {
return m.AlertStateAlerting
}
if c.PrevAlertState == m.AlertStateAlerting {
return m.AlertStateAlerting
}
return m.AlertStatePending
}
func getNewStateInternal(c *EvalContext) m.AlertStateType {
if c.Error != nil {
c.log.Error("Alert Rule Result Error",
"ruleId", c.Rule.Id,
@@ -125,11 +149,13 @@ func (c *EvalContext) GetNewState() m.AlertStateType {
return c.PrevAlertState
}
return c.Rule.ExecutionErrorState.ToAlertState()
}
} else if c.Firing {
if c.Firing {
return m.AlertStateAlerting
}
} else if c.NoDataFound {
if c.NoDataFound {
c.log.Info("Alert Rule returned no data",
"ruleId", c.Rule.Id,
"name", c.Rule.Name,
+172 -67
View File
@@ -2,11 +2,11 @@ package alerting
import (
"context"
"fmt"
"errors"
"testing"
"time"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestStateIsUpdatedWhenNeeded(t *testing.T) {
@@ -31,71 +31,176 @@ func TestStateIsUpdatedWhenNeeded(t *testing.T) {
})
}
func TestAlertingEvalContext(t *testing.T) {
Convey("Should compute and replace properly new rule state", t, func() {
func TestGetStateFromEvalContext(t *testing.T) {
tcs := []struct {
name string
expected models.AlertStateType
applyFn func(ec *EvalContext)
}{
{
name: "ok -> alerting",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.Firing = true
ec.PrevAlertState = models.AlertStateOK
},
},
{
name: "ok -> error(alerting)",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Error = errors.New("test error")
ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
},
},
{
name: "ok -> pending. since its been firing for less than FOR",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Firing = true
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
ec.Rule.For = time.Minute * 5
},
},
{
name: "ok -> pending. since it has to be pending longer than FOR and prev state is ok",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Firing = true
ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5))
ec.Rule.For = time.Minute * 2
},
},
{
name: "pending -> alerting. since its been firing for more than FOR and prev state is pending",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Firing = true
ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5))
ec.Rule.For = time.Minute * 2
},
},
{
name: "alerting -> alerting. should not update regardless of FOR",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateAlerting
ec.Firing = true
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
ec.Rule.For = time.Minute * 2
},
},
{
name: "ok -> ok. should not update regardless of FOR",
expected: models.AlertStateOK,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
ec.Rule.For = time.Minute * 2
},
},
{
name: "ok -> error(keep_last)",
expected: models.AlertStateOK,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Error = errors.New("test error")
ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
},
},
{
name: "pending -> error(keep_last)",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Error = errors.New("test error")
ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
},
},
{
name: "ok -> no_data(alerting)",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Rule.NoDataState = models.NoDataSetAlerting
ec.NoDataFound = true
},
},
{
name: "ok -> no_data(keep_last)",
expected: models.AlertStateOK,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStateOK
ec.Rule.NoDataState = models.NoDataKeepState
ec.NoDataFound = true
},
},
{
name: "pending -> no_data(keep_last)",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Rule.NoDataState = models.NoDataKeepState
ec.NoDataFound = true
},
},
{
name: "pending -> no_data(alerting) with for duration have not passed",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Rule.NoDataState = models.NoDataSetAlerting
ec.NoDataFound = true
ec.Rule.For = time.Minute * 5
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
},
},
{
name: "pending -> no_data(alerting) should set alerting since time passed FOR",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Rule.NoDataState = models.NoDataSetAlerting
ec.NoDataFound = true
ec.Rule.For = time.Minute * 2
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
},
},
{
name: "pending -> error(alerting) with for duration have not passed ",
expected: models.AlertStatePending,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
ec.Error = errors.New("test error")
ec.Rule.For = time.Minute * 5
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
},
},
{
name: "pending -> error(alerting) should set alerting since time passed FOR",
expected: models.AlertStateAlerting,
applyFn: func(ec *EvalContext) {
ec.PrevAlertState = models.AlertStatePending
ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
ec.Error = errors.New("test error")
ec.Rule.For = time.Minute * 2
ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
},
},
}
for _, tc := range tcs {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
dummieError := fmt.Errorf("dummie error")
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Firing = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
Convey("ok -> no_data(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetAlerting
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
})
tc.applyFn(ctx)
have := ctx.GetNewState()
if have != tc.expected {
t.Errorf("failed: %s \n expected '%s' have '%s'\n", tc.name, tc.expected, string(have))
}
}
}
+11 -1
View File
@@ -2,8 +2,8 @@ package alerting
import (
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -115,6 +115,15 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
return nil, ValidationError{Reason: "Could not parse frequency"}
}
rawFor := jsonAlert.Get("for").MustString()
var forValue time.Duration
if rawFor != "" {
forValue, err = time.ParseDuration(rawFor)
if err != nil {
return nil, ValidationError{Reason: "Could not parse for"}
}
}
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgID,
@@ -124,6 +133,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
Handler: jsonAlert.Get("handler").MustInt64(),
Message: jsonAlert.Get("message").MustString(),
Frequency: frequency,
For: forValue,
}
for _, condition := range jsonAlert.Get("conditions").MustArray() {
+13 -7
View File
@@ -3,6 +3,7 @@ package alerting
import (
"io/ioutil"
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -46,7 +47,7 @@ func TestAlertRuleExtraction(t *testing.T) {
return nil
})
json, err := ioutil.ReadFile("./test-data/graphite-alert.json")
json, err := ioutil.ReadFile("./testdata/graphite-alert.json")
So(err, ShouldBeNil)
Convey("Extractor should not modify the original json", func() {
@@ -118,6 +119,11 @@ func TestAlertRuleExtraction(t *testing.T) {
So(alerts[1].PanelId, ShouldEqual, 4)
})
Convey("should extract for param", func() {
So(alerts[0].For, ShouldEqual, time.Minute*2)
So(alerts[1].For, ShouldEqual, time.Duration(0))
})
Convey("should extract name and desc", func() {
So(alerts[0].Name, ShouldEqual, "name1")
So(alerts[0].Message, ShouldEqual, "desc1")
@@ -140,7 +146,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Panels missing id should return error", func() {
panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json")
panelWithoutId, err := ioutil.ReadFile("./testdata/panels-missing-id.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(panelWithoutId)
@@ -156,7 +162,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Panel with id set to zero should return error", func() {
panelWithIdZero, err := ioutil.ReadFile("./test-data/panel-with-id-0.json")
panelWithIdZero, err := ioutil.ReadFile("./testdata/panel-with-id-0.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(panelWithIdZero)
@@ -172,7 +178,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Parse alerts from dashboard without rows", func() {
json, err := ioutil.ReadFile("./test-data/v5-dashboard.json")
json, err := ioutil.ReadFile("./testdata/v5-dashboard.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(json)
@@ -192,7 +198,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Parse and validate dashboard containing influxdb alert", func() {
json, err := ioutil.ReadFile("./test-data/influxdb-alert.json")
json, err := ioutil.ReadFile("./testdata/influxdb-alert.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(json)
@@ -221,7 +227,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Should be able to extract collapsed panels", func() {
json, err := ioutil.ReadFile("./test-data/collapsed-panels.json")
json, err := ioutil.ReadFile("./testdata/collapsed-panels.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(json)
@@ -242,7 +248,7 @@ func TestAlertRuleExtraction(t *testing.T) {
})
Convey("Parse and validate dashboard without id and containing an alert", func() {
json, err := ioutil.ReadFile("./test-data/dash-without-id.json")
json, err := ioutil.ReadFile("./testdata/dash-without-id.json")
So(err, ShouldBeNil)
dashJSON, err := simplejson.NewJson(json)
@@ -1,13 +1,60 @@
package notifiers
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
)
func TestWhenAlertManagerShouldNotify(t *testing.T) {
tcs := []struct {
prevState m.AlertStateType
newState m.AlertStateType
expect bool
}{
{
prevState: m.AlertStatePending,
newState: m.AlertStateOK,
expect: false,
},
{
prevState: m.AlertStateAlerting,
newState: m.AlertStateOK,
expect: true,
},
{
prevState: m.AlertStateOK,
newState: m.AlertStatePending,
expect: false,
},
{
prevState: m.AlertStateUnknown,
newState: m.AlertStatePending,
expect: false,
},
}
for _, tc := range tcs {
am := &AlertmanagerNotifier{log: log.New("test.logger")}
evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: tc.prevState,
})
evalContext.Rule.State = tc.newState
res := am.ShouldNotify(context.TODO(), evalContext, &m.AlertNotificationState{})
if res != tc.expect {
t.Errorf("got %v expected %v", res, tc.expect)
}
}
}
func TestAlertmanagerNotifier(t *testing.T) {
Convey("Alertmanager notifier tests", t, func() {
+10
View File
@@ -67,6 +67,16 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC
}
// Do not notify when we become OK for the first time.
if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStateOK {
return false
}
// Do not notify when we become OK for the first time.
if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStatePending {
return false
}
// Do not notify when we become OK from pending
if context.PrevAlertState == models.AlertStatePending && context.Rule.State == models.AlertStateOK {
return false
}
+25 -7
View File
@@ -29,7 +29,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStateOK,
prevState: m.AlertStatePending,
sendReminder: false,
state: &m.AlertNotificationState{},
expect: false,
},
@@ -38,7 +37,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStateAlerting,
prevState: m.AlertStateOK,
sendReminder: false,
state: &m.AlertNotificationState{},
expect: true,
},
@@ -47,7 +45,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStatePending,
prevState: m.AlertStateOK,
sendReminder: false,
state: &m.AlertNotificationState{},
expect: false,
},
@@ -56,7 +53,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
sendReminder: false,
state: &m.AlertNotificationState{},
expect: false,
},
@@ -65,7 +61,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
sendReminder: true,
state: &m.AlertNotificationState{},
expect: false,
},
@@ -74,7 +69,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
sendReminder: false,
state: &m.AlertNotificationState{},
expect: true,
},
@@ -94,7 +88,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
prevState: m.AlertStateAlerting,
frequency: time.Minute * 10,
sendReminder: true,
state: &m.AlertNotificationState{},
expect: true,
},
@@ -132,6 +125,27 @@ func TestShouldSendAlertNotification(t *testing.T) {
prevState: m.AlertStateOK,
state: &m.AlertNotificationState{State: m.AlertNotificationStatePending, UpdatedAt: tnow.Add(-2 * time.Minute).Unix()},
expect: true,
},
{
name: "unknown -> ok",
prevState: m.AlertStateUnknown,
newState: m.AlertStateOK,
expect: false,
},
{
name: "unknown -> pending",
prevState: m.AlertStateUnknown,
newState: m.AlertStatePending,
expect: false,
},
{
name: "unknown -> alerting",
prevState: m.AlertStateUnknown,
newState: m.AlertStateAlerting,
expect: true,
},
}
@@ -141,6 +155,10 @@ func TestShouldSendAlertNotification(t *testing.T) {
State: tc.prevState,
})
if tc.state == nil {
tc.state = &m.AlertNotificationState{}
}
evalContext.Rule.State = tc.newState
nb := &NotifierBase{SendReminder: tc.sendReminder, Frequency: tc.frequency}
+3
View File
@@ -73,6 +73,9 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
// when two servers are raising. This makes sure that the server
// with the last state change always sends a notification.
evalContext.Rule.StateChanges = cmd.Result.StateChanges
// Update the last state change of the alert rule in memory
evalContext.Rule.LastStateChange = time.Now()
}
// save annotation
+5
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"regexp"
"strconv"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -18,6 +19,8 @@ type Rule struct {
Frequency int64
Name string
Message string
LastStateChange time.Time
For time.Duration
NoDataState m.NoDataOption
ExecutionErrorState m.ExecutionErrorOption
State m.AlertStateType
@@ -100,6 +103,8 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
model.Message = ruleDef.Message
model.Frequency = ruleDef.Frequency
model.State = ruleDef.State
model.LastStateChange = ruleDef.NewStateDate
model.For = ruleDef.For
model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data"))
model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
model.StateChanges = ruleDef.StateChanges
@@ -23,6 +23,7 @@
"message": "desc1",
"handler": 1,
"frequency": "60s",
"for": "2m",
"conditions": [
{
"type": "query",
+1 -1
View File
@@ -165,7 +165,7 @@ func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand,
}
if err := bus.Dispatch(&alertCmd); err != nil {
return models.ErrDashboardFailedToUpdateAlertData
return err
}
return nil
+5 -4
View File
@@ -193,7 +193,8 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
if alertToUpdate.ContainsUpdates(alert) {
alert.Updated = timeNow()
alert.State = alertToUpdate.State
sess.MustCols("message")
sess.MustCols("message", "for")
_, err := sess.ID(alert.Id).Update(alert)
if err != nil {
return err
@@ -204,7 +205,7 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
} else {
alert.Updated = timeNow()
alert.Created = timeNow()
alert.State = m.AlertStatePending
alert.State = m.AlertStateUnknown
alert.NewStateDate = timeNow()
_, err := sess.Insert(alert)
@@ -299,7 +300,7 @@ func PauseAlert(cmd *m.PauseAlertCommand) error {
params = append(params, string(m.AlertStatePaused))
params = append(params, timeNow())
} else {
params = append(params, string(m.AlertStatePending))
params = append(params, string(m.AlertStateUnknown))
params = append(params, timeNow())
}
@@ -323,7 +324,7 @@ func PauseAllAlerts(cmd *m.PauseAllAlertCommand) error {
if cmd.Paused {
newState = string(m.AlertStatePaused)
} else {
newState = string(m.AlertStatePending)
newState = string(m.AlertStateUnknown)
}
res, err := sess.Exec(`UPDATE alert SET state = ?, new_state_date = ?`, newState, timeNow())
+2 -2
View File
@@ -109,7 +109,7 @@ func TestAlertingDataAccess(t *testing.T) {
So(alert.DashboardId, ShouldEqual, testDash.Id)
So(alert.PanelId, ShouldEqual, 1)
So(alert.Name, ShouldEqual, "Alerting title")
So(alert.State, ShouldEqual, "pending")
So(alert.State, ShouldEqual, m.AlertStateUnknown)
So(alert.NewStateDate, ShouldNotBeNil)
So(alert.EvalData, ShouldNotBeNil)
So(alert.EvalData.Get("test").MustString(), ShouldEqual, "test")
@@ -154,7 +154,7 @@ func TestAlertingDataAccess(t *testing.T) {
So(query.Result[0].Name, ShouldEqual, "Name")
Convey("Alert state should not be updated", func() {
So(query.Result[0].State, ShouldEqual, "pending")
So(query.Result[0].State, ShouldEqual, m.AlertStateUnknown)
})
})
@@ -133,4 +133,8 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("create alert_notification_state table v1", NewAddTableMigration(alert_notification_state))
mg.AddMigration("add index alert_notification_state org_id & alert_id & notifier_id",
NewAddIndexMigration(alert_notification_state, alert_notification_state.Indices[0]))
mg.AddMigration("Add for to alert table", NewAddColumnMigration(alertV1, &Column{
Name: "for", Type: DB_BigInt, Nullable: true,
}))
}
@@ -34,4 +34,13 @@ func addPreferencesMigrations(mg *Migrator) {
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: false},
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: false},
}))
mg.AddMigration("Add column team_id in preferences", NewAddColumnMigration(preferencesV2, &Column{
Name: "team_id", Type: DB_BigInt, Nullable: true,
}))
mg.AddMigration("Update team_id column values in preferences", NewRawSqlMigration("").
Sqlite("UPDATE preferences SET team_id=0 WHERE team_id IS NULL;").
Postgres("UPDATE preferences SET team_id=0 WHERE team_id IS NULL;").
Mysql("UPDATE preferences SET team_id=0 WHERE team_id IS NULL;"))
}
+19 -7
View File
@@ -1,6 +1,7 @@
package sqlstore
import (
"strings"
"time"
"github.com/grafana/grafana/pkg/bus"
@@ -16,11 +17,22 @@ func init() {
}
func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error {
params := make([]interface{}, 0)
filter := ""
if len(query.User.Teams) > 0 {
filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.User.Teams)-1) + ")) OR "
params = append(params, query.User.OrgId)
for _, v := range query.User.Teams {
params = append(params, v)
}
}
filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)"
params = append(params, query.User.OrgId)
params = append(params, query.User.UserId)
params = append(params, query.User.OrgId)
prefs := make([]*m.Preferences, 0)
filter := "(org_id=? AND user_id=?) OR (org_id=? AND user_id=0)"
err := x.Where(filter, query.OrgId, query.UserId, query.OrgId).
OrderBy("user_id ASC").
err := x.Where(filter, params...).
OrderBy("user_id ASC, team_id ASC").
Find(&prefs)
if err != nil {
@@ -50,9 +62,8 @@ func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error
}
func GetPreferences(query *m.GetPreferencesQuery) error {
var prefs m.Preferences
exists, err := x.Where("org_id=? AND user_id=?", query.OrgId, query.UserId).Get(&prefs)
exists, err := x.Where("org_id=? AND user_id=? AND team_id=?", query.OrgId, query.UserId, query.TeamId).Get(&prefs)
if err != nil {
return err
@@ -71,7 +82,7 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error {
return inTransaction(func(sess *DBSession) error {
var prefs m.Preferences
exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
exists, err := sess.Where("org_id=? AND user_id=? AND team_id=?", cmd.OrgId, cmd.UserId, cmd.TeamId).Get(&prefs)
if err != nil {
return err
}
@@ -80,6 +91,7 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error {
prefs = m.Preferences{
UserId: cmd.UserId,
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
HomeDashboardId: cmd.HomeDashboardId,
Timezone: cmd.Timezone,
Theme: cmd.Theme,
+91
View File
@@ -0,0 +1,91 @@
package sqlstore
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func TestPreferencesDataAccess(t *testing.T) {
Convey("Testing preferences data access", t, func() {
InitTestDB(t)
Convey("GetPreferencesWithDefaults with no saved preferences should return defaults", func() {
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.Theme, ShouldEqual, setting.DefaultTheme)
So(query.Result.Timezone, ShouldEqual, "browser")
So(query.Result.HomeDashboardId, ShouldEqual, 0)
})
Convey("GetPreferencesWithDefaults with saved org and user home dashboard should return user home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 1}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 4)
})
Convey("GetPreferencesWithDefaults with saved org and other user home dashboard should return org home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 2}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 1)
})
Convey("GetPreferencesWithDefaults with saved org and teams home dashboard should return last team home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, Teams: []int64{2, 3}}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 3)
})
Convey("GetPreferencesWithDefaults with saved org and other teams home dashboard should return org home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 1)
})
Convey("GetPreferencesWithDefaults with saved org, teams and user home dashboard should return user home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 1, Teams: []int64{2, 3}}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 4)
})
Convey("GetPreferencesWithDefaults with saved org, other teams and user home dashboard should return org home dashboard", func() {
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3})
SavePreferences(&models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4})
query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 2}}
err := GetPreferencesWithDefaults(query)
So(err, ShouldBeNil)
So(query.Result.HomeDashboardId, ShouldEqual, 1)
})
})
}
+9
View File
@@ -57,6 +57,9 @@ var (
IsEnterprise bool
ApplicationName string
// packaging
Packaging = "unknown"
// Paths
HomePath string
PluginsPath string
@@ -112,6 +115,7 @@ var (
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
OAuthAutoLogin bool
ViewersCanEdit bool
// Http auth
@@ -215,6 +219,8 @@ type Cfg struct {
DisableBruteForceLoginProtection bool
TempDataLifetime time.Duration
MetricsEndpointEnabled bool
MetricsEndpointBasicAuthUsername string
MetricsEndpointBasicAuthPassword string
EnableAlphaPanels bool
EnterpriseLicensePath string
}
@@ -626,6 +632,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
auth := iniFile.Section("auth")
DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false)
OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
SignoutRedirectUrl = auth.Key("signout_redirect_url").String()
// anonymous access
@@ -676,6 +683,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
cfg.MetricsEndpointBasicAuthUsername = iniFile.Section("metrics").Key("basic_auth_username").String()
cfg.MetricsEndpointBasicAuthPassword = iniFile.Section("metrics").Key("basic_auth_password").String()
analytics := iniFile.Section("analytics")
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
+2
View File
@@ -32,6 +32,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool {
func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
@@ -47,6 +48,7 @@ func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
}
return &BasicUserInfo{
Id: data.Id,
Name: data.Name,
Email: data.Email,
Login: data.Email,
+25 -2
View File
@@ -126,6 +126,18 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
}
eg.Go(func() error {
defer func() {
if err := recover(); err != nil {
plog.Error("Execute Query Panic", "error", err, "stack", log.Stack(1))
if theErr, ok := err.(error); ok {
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
Error: theErr,
}
}
}
}()
queryRes, err := e.executeQuery(ectx, query, queryContext)
if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
return err
@@ -146,6 +158,17 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
for region, getMetricDataQuery := range getMetricDataQueries {
q := getMetricDataQuery
eg.Go(func() error {
defer func() {
if err := recover(); err != nil {
plog.Error("Execute Get Metric Data Query Panic", "error", err, "stack", log.Stack(1))
if theErr, ok := err.(error); ok {
resultChan <- &tsdb.QueryResult{
Error: theErr,
}
}
}
}()
queryResponses, err := e.executeGetMetricDataQuery(ectx, region, q, queryContext)
if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
return err
@@ -188,8 +211,8 @@ func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatch
return nil, err
}
if endTime.Before(startTime) {
return nil, fmt.Errorf("Invalid time range: End time can't be before start time")
if !startTime.Before(endTime) {
return nil, fmt.Errorf("Invalid time range: Start time must be before end time")
}
params := &cloudwatch.GetMetricStatisticsInput{
+22
View File
@@ -1,9 +1,13 @@
package cloudwatch
import (
"context"
"testing"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/grafana/grafana/pkg/components/null"
@@ -14,6 +18,24 @@ import (
func TestCloudWatch(t *testing.T) {
Convey("CloudWatch", t, func() {
Convey("executeQuery", func() {
e := &CloudWatchExecutor{
DataSource: &models.DataSource{
JsonData: simplejson.New(),
},
}
Convey("End time before start time should result in error", func() {
_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-2h")})
So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
})
Convey("End time equals start time should result in error", func() {
_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-1h")})
So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
})
})
Convey("can parse cloudwatch json model", func() {
json := `
{
+2
View File
@@ -46,6 +46,7 @@ func init() {
"AWS/Billing": {"EstimatedCharges"},
"AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
"AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
"AWS/CloudHSM": {"HsmUnhealthy", "HsmTemperature", "HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSslCtxsOccupied", "HsmSessionCount", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"},
"AWS/Connect": {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"},
"AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"},
"AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"},
@@ -121,6 +122,7 @@ func init() {
"AWS/Billing": {"ServiceName", "LinkedAccount", "Currency"},
"AWS/CloudFront": {"DistributionId", "Region"},
"AWS/CloudSearch": {},
"AWS/CloudHSM": {"Region", "ClusterId", "HsmId"},
"AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"},
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
"AWS/DX": {"ConnectionId"},
+1 -1
View File
@@ -65,7 +65,7 @@ var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange *tsdb
clientLog.Debug("Creating new client", "version", version, "timeField", timeField, "indices", strings.Join(indices, ", "))
switch version {
case 2, 5, 56:
case 2, 5, 56, 60:
return &baseClientImpl{
ctx: ctx,
ds: ds,
+13 -6
View File
@@ -90,6 +90,19 @@ func TestClient(t *testing.T) {
So(err, ShouldBeNil)
So(c.GetVersion(), ShouldEqual, 56)
})
Convey("When version 60 should return v6.0 client", func() {
ds := &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
"esVersion": 60,
"timeField": "@timestamp",
}),
}
c, err := NewClient(context.Background(), ds, nil)
So(err, ShouldBeNil)
So(c.GetVersion(), ShouldEqual, 60)
})
})
Convey("Given a fake http client", func() {
@@ -153,8 +166,6 @@ func TestClient(t *testing.T) {
jBody, err := simplejson.NewJson(bodyBytes)
So(err, ShouldBeNil)
fmt.Println("body", string(headerBytes))
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
So(jHeader.Get("search_type").MustString(), ShouldEqual, "count")
@@ -209,8 +220,6 @@ func TestClient(t *testing.T) {
jBody, err := simplejson.NewJson(bodyBytes)
So(err, ShouldBeNil)
fmt.Println("body", string(headerBytes))
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
So(jHeader.Get("search_type").MustString(), ShouldEqual, "query_then_fetch")
@@ -265,8 +274,6 @@ func TestClient(t *testing.T) {
jBody, err := simplejson.NewJson(bodyBytes)
So(err, ShouldBeNil)
fmt.Println("body", string(headerBytes))
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
So(jHeader.Get("search_type").MustString(), ShouldEqual, "query_then_fetch")
+4 -5
View File
@@ -96,16 +96,15 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
}
func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Query, context *tsdb.TsdbQuery) (*Query, error) {
for _, v := range queries {
query, err := e.QueryParser.Parse(v.Model, dsInfo)
// The model supports multiple queries, but right now this is only used from
// alerting so we only needed to support batch executing 1 query at a time.
if len(queries) > 0 {
query, err := e.QueryParser.Parse(queries[0].Model, dsInfo)
if err != nil {
return nil, err
}
return query, nil
}
return nil, fmt.Errorf("query request contains no queries")
}
-28
View File
@@ -1,28 +0,0 @@
import { ThunkAction } from 'redux-thunk';
import { getBackendSrv } from '../services/backend_srv';
import { DashboardAcl, DashboardSearchHit, StoreState } from '../../types';
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
export type Action = LoadStarredDashboardsAction;
export enum ActionTypes {
LoadStarredDashboards = 'LOAD_STARRED_DASHBOARDS',
}
interface LoadStarredDashboardsAction {
type: ActionTypes.LoadStarredDashboards;
payload: DashboardSearchHit[];
}
const starredDashboardsLoaded = (dashboards: DashboardAcl[]) => ({
type: ActionTypes.LoadStarredDashboards,
payload: dashboards,
});
export function loadStarredDashboards(): ThunkResult<void> {
return async dispatch => {
const starredDashboards = await getBackendSrv().search({ starred: true });
dispatch(starredDashboardsLoaded(starredDashboards));
};
}
@@ -5,13 +5,14 @@ import ResetStyles from './ResetStyles';
interface Props {
className?: string;
defaultValue: any;
defaultValue?: any;
getOptionLabel: (item: any) => string;
getOptionValue: (item: any) => string;
onSelected: (item: any) => {} | void;
options: any[];
placeholder?: string;
width: number;
value: any;
}
const SimplePicker: SFC<Props> = ({
@@ -23,6 +24,7 @@ const SimplePicker: SFC<Props> = ({
options,
placeholder,
width,
value,
}) => {
return (
<Select
@@ -32,6 +34,7 @@ const SimplePicker: SFC<Props> = ({
Option: DescriptionOption,
}}
defaultValue={defaultValue}
value={value}
getOptionLabel={getOptionLabel}
getOptionValue={getOptionValue}
isSearchable={false}
@@ -14,3 +14,4 @@ exports[`PickerOption renders correctly 1`] = `
</div>
</div>
`;
@@ -0,0 +1,141 @@
import React, { PureComponent } from 'react';
import { Label } from 'app/core/components/Label/Label';
import SimplePicker from 'app/core/components/Picker/SimplePicker';
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSearchHit } from 'app/types';
export interface Props {
resourceUri: string;
}
export interface State {
homeDashboardId: number;
theme: string;
timezone: string;
dashboards: DashboardSearchHit[];
}
const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
const timezones = [
{ value: '', text: 'Default' },
{ value: 'browser', text: 'Local browser time' },
{ value: 'utc', text: 'UTC' },
];
export class SharedPreferences extends PureComponent<Props, State> {
backendSrv: BackendSrv = getBackendSrv();
constructor(props) {
super(props);
this.state = {
homeDashboardId: 0,
theme: '',
timezone: '',
dashboards: [],
};
}
async componentDidMount() {
const prefs = await this.backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
const dashboards = await this.backendSrv.search({ starred: true });
if (prefs.homeDashboardId > 0 && !dashboards.find(d => d.id === prefs.homeDashboardId)) {
const missing = await this.backendSrv.search({ dashboardIds: [prefs.homeDashboardId] });
if (missing && missing.length > 0) {
dashboards.push(missing[0]);
}
}
this.setState({
homeDashboardId: prefs.homeDashboardId,
theme: prefs.theme,
timezone: prefs.timezone,
dashboards: [{ id: 0, title: 'Default', tags: [], type: '', uid: '', uri: '', url: '' }, ...dashboards],
});
}
onSubmitForm = async event => {
event.preventDefault();
const { homeDashboardId, theme, timezone } = this.state;
await this.backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
homeDashboardId,
theme,
timezone,
});
window.location.reload();
};
onThemeChanged = (theme: string) => {
this.setState({ theme });
};
onTimeZoneChanged = (timezone: string) => {
this.setState({ timezone });
};
onHomeDashboardChanged = (dashboardId: number) => {
this.setState({ homeDashboardId: dashboardId });
};
render() {
const { theme, timezone, homeDashboardId, dashboards } = this.state;
return (
<form className="section gf-form-group" onSubmit={this.onSubmitForm}>
<h3 className="page-heading">Preferences</h3>
<div className="gf-form">
<span className="gf-form-label width-11">UI Theme</span>
<SimplePicker
value={themes.find(item => item.value === theme)}
options={themes}
getOptionValue={i => i.value}
getOptionLabel={i => i.text}
onSelected={theme => this.onThemeChanged(theme.value)}
width={20}
/>
</div>
<div className="gf-form">
<Label
width={11}
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
>
Home Dashboard
</Label>
<SimplePicker
value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
getOptionValue={i => i.id}
getOptionLabel={i => i.title}
onSelected={(dashboard: DashboardSearchHit) => this.onHomeDashboardChanged(dashboard.id)}
options={dashboards}
placeholder="Chose default dashboard"
width={20}
/>
</div>
<div className="gf-form">
<label className="gf-form-label width-11">Timezone</label>
<SimplePicker
value={timezones.find(item => item.value === timezone)}
getOptionValue={i => i.value}
getOptionLabel={i => i.text}
onSelected={timezone => this.onTimeZoneChanged(timezone.value)}
options={timezones}
width={20}
/>
</div>
<div className="gf-form-button-row">
<button type="submit" className="btn btn-success">
Save
</button>
</div>
</form>
);
}
}
export default SharedPreferences;
+76 -9
View File
@@ -3,25 +3,26 @@ import { TimeSeries } from 'app/core/core';
import colors from 'app/core/utils/colors';
export enum LogLevel {
crit = 'crit',
warn = 'warn',
crit = 'critical',
critical = 'critical',
warn = 'warning',
warning = 'warning',
err = 'error',
error = 'error',
info = 'info',
debug = 'debug',
trace = 'trace',
none = 'none',
unkown = 'unkown',
}
export const LogLevelColor = {
[LogLevel.crit]: colors[7],
[LogLevel.warn]: colors[1],
[LogLevel.err]: colors[4],
[LogLevel.critical]: colors[7],
[LogLevel.warning]: colors[1],
[LogLevel.error]: colors[4],
[LogLevel.info]: colors[0],
[LogLevel.debug]: colors[3],
[LogLevel.trace]: colors[3],
[LogLevel.none]: '#eee',
[LogLevel.debug]: colors[5],
[LogLevel.trace]: colors[2],
[LogLevel.unkown]: '#ddd',
};
export interface LogSearchMatch {
@@ -31,6 +32,7 @@ export interface LogSearchMatch {
}
export interface LogRow {
duplicates?: number;
entry: string;
key: string; // timestamp + labels
labels: string;
@@ -71,6 +73,71 @@ export interface LogsStreamLabels {
[key: string]: string;
}
export enum LogsDedupStrategy {
none = 'none',
exact = 'exact',
numbers = 'numbers',
signature = 'signature',
}
const isoDateRegexp = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-6]\d[,\.]\d+([+-][0-2]\d:[0-5]\d|Z)/g;
function isDuplicateRow(row: LogRow, other: LogRow, strategy: LogsDedupStrategy): boolean {
switch (strategy) {
case LogsDedupStrategy.exact:
// Exact still strips dates
return row.entry.replace(isoDateRegexp, '') === other.entry.replace(isoDateRegexp, '');
case LogsDedupStrategy.numbers:
return row.entry.replace(/\d/g, '') === other.entry.replace(/\d/g, '');
case LogsDedupStrategy.signature:
return row.entry.replace(/\w/g, '') === other.entry.replace(/\w/g, '');
default:
return false;
}
}
export function dedupLogRows(logs: LogsModel, strategy: LogsDedupStrategy): LogsModel {
if (strategy === LogsDedupStrategy.none) {
return logs;
}
const dedupedRows = logs.rows.reduce((result: LogRow[], row: LogRow, index, list) => {
const previous = result[result.length - 1];
if (index > 0 && isDuplicateRow(row, previous, strategy)) {
previous.duplicates++;
} else {
row.duplicates = 0;
result.push(row);
}
return result;
}, []);
return {
...logs,
rows: dedupedRows,
};
}
export function filterLogLevels(logs: LogsModel, hiddenLogLevels: Set<LogLevel>): LogsModel {
if (hiddenLogLevels.size === 0) {
return logs;
}
const filteredRows = logs.rows.reduce((result: LogRow[], row: LogRow, index, list) => {
if (!hiddenLogLevels.has(row.logLevel)) {
result.push(row);
}
return result;
}, []);
return {
...logs,
rows: filteredRows,
};
}
export function makeSeriesForLogs(rows: LogRow[], intervalMs: number): TimeSeries[] {
// Graph time series by log level
const seriesByLevel = {};
-2
View File
@@ -1,11 +1,9 @@
import { navIndexReducer as navIndex } from './navModel';
import { locationReducer as location } from './location';
import { appNotificationsReducer as appNotifications } from './appNotification';
import { userReducer as user } from './user';
export default {
navIndex,
location,
appNotifications,
user,
};
-15
View File
@@ -1,15 +0,0 @@
import { DashboardSearchHit, UserState } from '../../types';
import { Action, ActionTypes } from '../actions/user';
const initialState: UserState = {
starredDashboards: [] as DashboardSearchHit[],
};
export const userReducer = (state: UserState = initialState, action: Action): UserState => {
switch (action.type) {
case ActionTypes.LoadStarredDashboards:
return { ...state, starredDashboards: action.payload };
}
return state;
};
@@ -21,7 +21,6 @@ class DynamicDirectiveSrv {
}
if (!directiveInfo.fn.registered) {
console.log('register panel tab');
coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn);
directiveInfo.fn.registered = true;
}

Some files were not shown because too many files have changed in this diff Show More