mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into jquery-flot-events-to-ts
This commit is contained in:
commit
608303f4ce
@ -1,6 +1,59 @@
|
||||
aliases:
|
||||
# Workflow filters
|
||||
- &filter-only-release
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
- &filter-not-release
|
||||
tags:
|
||||
ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
- &filter-only-master
|
||||
branches:
|
||||
only: master
|
||||
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
mysql-integration-test:
|
||||
docker:
|
||||
- image: circleci/golang:1.10
|
||||
- image: circleci/mysql:5.6-ram
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_DATABASE: grafana_tests
|
||||
MYSQL_USER: grafana
|
||||
MYSQL_PASSWORD: password
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install -y mysql-client
|
||||
- run: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s
|
||||
- run: cat docker/blocks/mysql_tests/setup.sql | mysql -h 127.0.0.1 -P 3306 -u root -prootpass
|
||||
- run:
|
||||
name: mysql integration tests
|
||||
command: 'GRAFANA_TEST_DB=mysql go test ./pkg/services/sqlstore/... ./pkg/tsdb/mysql/... '
|
||||
|
||||
postgres-integration-test:
|
||||
docker:
|
||||
- image: circleci/golang:1.10
|
||||
- image: circleci/postgres:9.3-ram
|
||||
environment:
|
||||
POSTGRES_USER: grafanatest
|
||||
POSTGRES_PASSWORD: grafanatest
|
||||
POSTGRES_DB: grafanatest
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install -y postgresql-client
|
||||
- run: dockerize -wait tcp://127.0.0.1:5432 -timeout 120s
|
||||
- run: 'PGPASSWORD=grafanatest psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f docker/blocks/postgres_tests/setup.sql'
|
||||
- run:
|
||||
name: postgres integration tests
|
||||
command: 'GRAFANA_TEST_DB=postgres go test ./pkg/services/sqlstore/... ./pkg/tsdb/postgres/...'
|
||||
|
||||
codespell:
|
||||
docker:
|
||||
- image: circleci/python
|
||||
@ -35,18 +88,17 @@ jobs:
|
||||
- run:
|
||||
name: run linters
|
||||
command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...'
|
||||
- run:
|
||||
name: run go vet
|
||||
command: 'go vet ./pkg/...'
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
- image: circleci/node:6.11.4
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: install yarn
|
||||
command: 'sudo npm install -g yarn --quiet'
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
# Could we skip this step if the cache has been restored? `[ -d node_modules ] || yarn install ...` should be able to apply to build step as well
|
||||
- run:
|
||||
name: yarn install
|
||||
command: 'yarn install --pure-lockfile --no-progress'
|
||||
@ -68,15 +120,27 @@ jobs:
|
||||
name: build backend and run go tests
|
||||
command: './scripts/circle-test-backend.sh'
|
||||
|
||||
build:
|
||||
build-all:
|
||||
docker:
|
||||
- image: grafana/build-container:v0.1
|
||||
- image: grafana/build-container:1.0.0
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- restore_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
- run:
|
||||
name: download phantomjs binaries
|
||||
command: './scripts/build/download-phantomjs.sh'
|
||||
- save_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
paths:
|
||||
- /tmp/phantomjs
|
||||
- run:
|
||||
name: build and package grafana
|
||||
command: './scripts/build/build.sh'
|
||||
command: './scripts/build/build-all.sh'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
@ -92,6 +156,8 @@ jobs:
|
||||
- dist/grafana*
|
||||
- scripts/*.sh
|
||||
- scripts/publish
|
||||
- store_artifacts:
|
||||
path: dist
|
||||
|
||||
build-enterprise:
|
||||
docker:
|
||||
@ -100,7 +166,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: build and package grafana
|
||||
name: build, test and package grafana enterprise
|
||||
command: './scripts/build/build_enterprise.sh'
|
||||
- run:
|
||||
name: sign packages
|
||||
@ -108,6 +174,26 @@ jobs:
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: move enterprise packages into their own folder
|
||||
command: 'mv dist enterprise-dist'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- enterprise-dist/grafana-enterprise*
|
||||
|
||||
deploy-enterprise-master:
|
||||
docker:
|
||||
- image: circleci/python:2.7-stretch
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: install awscli
|
||||
command: 'sudo pip install awscli'
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: 'aws s3 sync ./enterprise-dist s3://$ENTERPRISE_BUCKET_NAME/master'
|
||||
|
||||
deploy-master:
|
||||
docker:
|
||||
@ -120,16 +206,21 @@ jobs:
|
||||
command: 'sudo pip install awscli'
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: 'aws s3 sync ./dist s3://$BUCKET_NAME/master'
|
||||
command: |
|
||||
# Also
|
||||
cp dist/grafana-latest.linux-x64.tar.gz dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
|
||||
aws s3 sync ./dist s3://$BUCKET_NAME/master
|
||||
- run:
|
||||
name: Trigger Windows build
|
||||
command: './scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master'
|
||||
- run:
|
||||
name: Trigger Docker build
|
||||
command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}'
|
||||
command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} master-$(echo "${CIRCLE_SHA1}" | cut -b1-7)'
|
||||
- run:
|
||||
name: Publish to Grafana.com
|
||||
command: './scripts/publish -apiKey ${GRAFANA_COM_API_KEY}'
|
||||
command: |
|
||||
rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
|
||||
./scripts/publish -apiKey ${GRAFANA_COM_API_KEY}
|
||||
|
||||
deploy-release:
|
||||
docker:
|
||||
@ -154,45 +245,67 @@ workflows:
|
||||
version: 2
|
||||
test-and-build:
|
||||
jobs:
|
||||
- build-all:
|
||||
filters: *filter-only-master
|
||||
- build-enterprise:
|
||||
filters: *filter-only-master
|
||||
- codespell:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
filters: *filter-not-release
|
||||
- gometalinter:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
filters: *filter-not-release
|
||||
- test-frontend:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
filters: *filter-not-release
|
||||
- test-backend:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
filters: *filter-not-release
|
||||
- mysql-integration-test:
|
||||
filters: *filter-not-release
|
||||
- postgres-integration-test:
|
||||
filters: *filter-not-release
|
||||
- deploy-master:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- codespell
|
||||
- gometalinter
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-master
|
||||
- deploy-enterprise-master:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- gometalinter
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- build-enterprise
|
||||
filters: *filter-only-master
|
||||
|
||||
release:
|
||||
jobs:
|
||||
- build-all:
|
||||
filters: *filter-only-release
|
||||
- codespell:
|
||||
filters: *filter-only-release
|
||||
- gometalinter:
|
||||
filters: *filter-only-release
|
||||
- test-frontend:
|
||||
filters: *filter-only-release
|
||||
- test-backend:
|
||||
filters: *filter-only-release
|
||||
- mysql-integration-test:
|
||||
filters: *filter-only-release
|
||||
- postgres-integration-test:
|
||||
filters: *filter-only-release
|
||||
- deploy-release:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
# - build-enterprise:
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
- codespell
|
||||
- gometalinter
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-release
|
||||
|
@ -11,8 +11,5 @@ dump.rdb
|
||||
node_modules
|
||||
/local
|
||||
/tmp
|
||||
/vendor
|
||||
*.yml
|
||||
*.md
|
||||
/vendor
|
||||
/tmp
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -33,6 +33,7 @@ public/css/*.min.css
|
||||
*.tmp
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
/data/*
|
||||
/bin/*
|
||||
@ -42,6 +43,8 @@ fig.yml
|
||||
docker-compose.yml
|
||||
docker-compose.yaml
|
||||
/conf/provisioning/**/custom.yaml
|
||||
/conf/provisioning/**/dev.yaml
|
||||
/conf/ldap_dev.toml
|
||||
profile.cov
|
||||
/grafana
|
||||
/local
|
||||
@ -65,4 +68,6 @@ debug.test
|
||||
/vendor/**/*_test.go
|
||||
/vendor/**/.editorconfig
|
||||
/vendor/**/appengine*
|
||||
*.orig
|
||||
*.orig
|
||||
|
||||
/devenv/dashboards/bulk-testing/*.json
|
||||
|
127
CHANGELOG.md
127
CHANGELOG.md
@ -1,17 +1,139 @@
|
||||
# 5.2.0 (unreleased)
|
||||
# 5.3.0 (unreleased)
|
||||
|
||||
* **Dataproxy**: Pass configured/auth headers to a Datasource [#10971](https://github.com/grafana/grafana/issues/10971), thx [@mrsiano](https://github.com/mrsiano)
|
||||
* **Cleanup**: Make temp file time to live configurable [#11607](https://github.com/grafana/grafana/issues/11607), thx [@xapon](https://github.com/xapon)
|
||||
* **LDAP**: Define Grafana Admin permission in ldap group mappings [#2469](https://github.com/grafana/grafana/issues/2496), PR [#12622](https://github.com/grafana/grafana/issues/12622)
|
||||
* **Cloudwatch**: CloudWatch GetMetricData support [#11487](https://github.com/grafana/grafana/issues/11487), thx [@mtanda](https://github.com/mtanda)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Api**: Delete nonexistent datasource should return 404 [#12313](https://github.com/grafana/grafana/issues/12313), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
|
||||
* **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248)
|
||||
* **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps)
|
||||
* **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2)
|
||||
* **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379)
|
||||
* **Prometheus**: Heatmap - fix unhandled error when some points are missing [#12484](https://github.com/grafana/grafana/issues/12484)
|
||||
* **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda)
|
||||
* **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm)
|
||||
* **MySQL/MSSQL**: Use datetime format instead of epoch for $__timeFilter, $__timeFrom and $__timeTo macros [#11618](https://github.com/grafana/grafana/issues/11618) [#11619](https://github.com/grafana/grafana/issues/11619), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
|
||||
* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
|
||||
* **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane)
|
||||
|
||||
# 5.2.2 (unreleased)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379)
|
||||
* **Dashboard**: Dashboard links not updated when changing variables [#12506](https://github.com/grafana/grafana/issues/12506)
|
||||
|
||||
# 5.2.1 (2018-06-29)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Auth Proxy**: Important security fix for whitelist of IP address feature [#12444](https://github.com/grafana/grafana/pull/12444)
|
||||
* **UI**: Fix - Grafana footer overlapping page [#12430](https://github.com/grafana/grafana/issues/12430)
|
||||
* **Logging**: Errors should be reported before crashing [#12438](https://github.com/grafana/grafana/issues/12438)
|
||||
|
||||
# 5.2.0-stable (2018-06-27)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Plugins**: Handle errors correctly when loading datasource plugin [#12383](https://github.com/grafana/grafana/pull/12383) thx [@rozetko](https://github.com/rozetko)
|
||||
* **Render**: Enhance error message if phantomjs executable is not found [#11868](https://github.com/grafana/grafana/issues/11868)
|
||||
* **Dashboard**: Set correct text in drop down when variable is present in url [#11968](https://github.com/grafana/grafana/issues/11968)
|
||||
|
||||
### 5.2.0-beta3 fixes
|
||||
|
||||
* **LDAP**: Handle "dn" ldap attribute more gracefully [#12385](https://github.com/grafana/grafana/pull/12385), reverts [#10970](https://github.com/grafana/grafana/pull/10970)
|
||||
|
||||
# 5.2.0-beta3 (2018-06-21)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Build**: All rpm packages should be signed [#12359](https://github.com/grafana/grafana/issues/12359)
|
||||
|
||||
# 5.2.0-beta2 (2018-06-20)
|
||||
|
||||
### New Features
|
||||
|
||||
* **Dashboard**: Import dashboard to folder [#10796](https://github.com/grafana/grafana/issues/10796)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Permissions**: Important security fix for API keys with viewer role [#12343](https://github.com/grafana/grafana/issues/12343)
|
||||
* **Dashboard**: Fix so panel titles doesn't wrap [#11074](https://github.com/grafana/grafana/issues/11074)
|
||||
* **Dashboard**: Prevent double-click when saving dashboard [#11963](https://github.com/grafana/grafana/issues/11963)
|
||||
* **Dashboard**: AutoFocus the add-panel search filter [#12189](https://github.com/grafana/grafana/pull/12189) thx [@ryantxu](https://github.com/ryantxu)
|
||||
* **Units**: W/m2 (energy), l/h (flow) and kPa (pressure) [#11233](https://github.com/grafana/grafana/pull/11233), thx [@flopp999](https://github.com/flopp999)
|
||||
* **Units**: Litre/min (flow) and milliLitre/min (flow) [#12282](https://github.com/grafana/grafana/pull/12282), thx [@flopp999](https://github.com/flopp999)
|
||||
* **Alerting**: Fix mobile notifications for Microsoft Teams alert notifier [#11484](https://github.com/grafana/grafana/pull/11484), thx [@manacker](https://github.com/manacker)
|
||||
* **Influxdb**: Add support for mode function [#12286](https://github.com/grafana/grafana/issues/12286)
|
||||
* **Cloudwatch**: Fixes panic caused by bad timerange settings [#12199](https://github.com/grafana/grafana/issues/12199)
|
||||
* **Auth Proxy**: Whitelist proxy IP address instead of client IP address [#10707](https://github.com/grafana/grafana/issues/10707)
|
||||
* **User Management**: Make sure that a user always has a current org assigned [#11076](https://github.com/grafana/grafana/issues/11076)
|
||||
* **Snapshots**: Fix: annotations not properly extracted leading to incorrect rendering of annotations [#12278](https://github.com/grafana/grafana/issues/12278)
|
||||
* **LDAP**: Allow use of DN in group_search_filter_user_attribute and member_of [#3132](https://github.com/grafana/grafana/issues/3132), thx [@mmolnar](https://github.com/mmolnar)
|
||||
* **Graph**: Fix legend decimals precision calculation [#11792](https://github.com/grafana/grafana/issues/11792)
|
||||
* **Dashboard**: Make sure to process panels in collapsed rows when exporting dashboard [#12256](https://github.com/grafana/grafana/issues/12256)
|
||||
|
||||
### 5.2.0-beta1 fixes
|
||||
|
||||
* **Dashboard**: Dashboard link doesn't work when "As dropdown" option is checked [#12315](https://github.com/grafana/grafana/issues/12315)
|
||||
* **Dashboard**: Fix regressions after save modal changes, including adhoc template issues [#12240](https://github.com/grafana/grafana/issues/12240)
|
||||
* **Docker**: Config keys ending with _FILE are not respected [#170](https://github.com/grafana/grafana-docker/issues/170)
|
||||
|
||||
# 5.2.0-beta1 (2018-06-05)
|
||||
|
||||
### New Features
|
||||
|
||||
* **Elasticsearch**: Alerting support [#5893](https://github.com/grafana/grafana/issues/5893), thx [@WPH95](https://github.com/WPH95)
|
||||
* **Build**: Crosscompile and packages Grafana on arm, windows, linux and darwin [#11920](https://github.com/grafana/grafana/pull/11920), thx [@fg2it](https://github.com/fg2it)
|
||||
* **Login**: Change admin password after first login [#11882](https://github.com/grafana/grafana/issues/11882)
|
||||
* **Alert list panel**: Updated to support filtering alerts by name, dashboard title, folder, tags [#11500](https://github.com/grafana/grafana/issues/11500), [#8168](https://github.com/grafana/grafana/issues/8168), [#6541](https://github.com/grafana/grafana/issues/6541)
|
||||
|
||||
### Minor
|
||||
|
||||
* **Dashboard**: Modified time range and variables are now not saved by default [#10748](https://github.com/grafana/grafana/issues/10748), [#8805](https://github.com/grafana/grafana/issues/8805)
|
||||
* **Graph**: Show invisible highest value bucket in histogram [#11498](https://github.com/grafana/grafana/issues/11498)
|
||||
* **Dashboard**: Enable "Save As..." if user has edit permission [#11625](https://github.com/grafana/grafana/issues/11625)
|
||||
* **Prometheus**: Query dates are now step-aligned [#10434](https://github.com/grafana/grafana/pull/10434)
|
||||
* **Prometheus**: Table columns order now changes when rearrange queries [#11690](https://github.com/grafana/grafana/issues/11690), thx [@mtanda](https://github.com/mtanda)
|
||||
* **Variables**: Fix variable interpolation when using multiple formatting types [#11800](https://github.com/grafana/grafana/issues/11800), thx [@svenklemm](https://github.com/svenklemm)
|
||||
* **Dashboard**: Fix date selector styling for dark/light theme in time picker control [#11616](https://github.com/grafana/grafana/issues/11616)
|
||||
* **Discord**: Alert notification channel type for Discord, [#7964](https://github.com/grafana/grafana/issues/7964) thx [@jereksel](https://github.com/jereksel),
|
||||
* **InfluxDB**: Support SELECT queries in templating query, [#5013](https://github.com/grafana/grafana/issues/5013)
|
||||
* **InfluxDB**: Support count distinct aggregation [#11645](https://github.com/grafana/grafana/issues/11645), thx [@kichristensen](https://github.com/kichristensen)
|
||||
* **Dashboard**: JSON Model under dashboard settings can now be updated & changes saved, [#1429](https://github.com/grafana/grafana/issues/1429), thx [@jereksel](https://github.com/jereksel)
|
||||
* **Security**: Fix XSS vulnerabilities in dashboard links [#11813](https://github.com/grafana/grafana/pull/11813)
|
||||
* **Singlestat**: Fix "time of last point" shows local time when dashboard timezone set to UTC [#10338](https://github.com/grafana/grafana/issues/10338)
|
||||
* **Prometheus**: Add support for passing timeout parameter to Prometheus [#11788](https://github.com/grafana/grafana/pull/11788), thx [@mtanda](https://github.com/mtanda)
|
||||
* **Login**: Add optional option sign out url for generic oauth [#9847](https://github.com/grafana/grafana/issues/9847), thx [@roidelapluie](https://github.com/roidelapluie)
|
||||
* **Login**: Use proxy server from environment variable if available [#9703](https://github.com/grafana/grafana/issues/9703), thx [@iyeonok](https://github.com/iyeonok)
|
||||
* **Invite users**: Friendlier error message when smtp is not configured [#12087](https://github.com/grafana/grafana/issues/12087), thx [@thurt](https://github.com/thurt)
|
||||
* **Graphite**: Don't send distributed tracing headers when using direct/browser access mode [#11494](https://github.com/grafana/grafana/issues/11494)
|
||||
* **Sidenav**: Show create dashboard link for viewers if at least editor in one folder [#11858](https://github.com/grafana/grafana/issues/11858)
|
||||
* **SQL**: Second epochs are now correctly converted to ms. [#12085](https://github.com/grafana/grafana/pull/12085)
|
||||
* **Singlestat**: Fix singlestat threshold tooltip [#11971](https://github.com/grafana/grafana/issues/11971)
|
||||
* **Dashboard**: Hide grid controls in fullscreen/low-activity views [#11771](https://github.com/grafana/grafana/issues/11771)
|
||||
* **Dashboard**: Validate uid when importing dashboards [#11515](https://github.com/grafana/grafana/issues/11515)
|
||||
* **Docker**: Support for env variables ending with _FILE [grafana-docker #166](https://github.com/grafana/grafana-docker/pull/166), thx [@efrecon](https://github.com/efrecon)
|
||||
* **Alert list panel**: Show alerts for user with viewer role [#11167](https://github.com/grafana/grafana/issues/11167)
|
||||
* **Provisioning**: Verify checksum of dashboards before updating to reduce load on database [#11670](https://github.com/grafana/grafana/issues/11670)
|
||||
* **Provisioning**: Support symlinked files in dashboard provisioning config files [#11958](https://github.com/grafana/grafana/issues/11958)
|
||||
* **Dashboard list panel**: Search dashboards by folder [#11525](https://github.com/grafana/grafana/issues/11525)
|
||||
* **Sidenav**: Always show server admin link in sidenav if grafana admin [#11657](https://github.com/grafana/grafana/issues/11657)
|
||||
|
||||
# 5.1.5 (2018-06-27)
|
||||
|
||||
* **Docker**: Config keys ending with _FILE are not respected [#170](https://github.com/grafana/grafana-docker/issues/170)
|
||||
|
||||
# 5.1.4 (2018-06-19)
|
||||
|
||||
* **Permissions**: Important security fix for API keys with viewer role [#12343](https://github.com/grafana/grafana/issues/12343)
|
||||
|
||||
# 5.1.3 (2018-05-16)
|
||||
|
||||
* **Scroll**: Graph panel / legend texts shifts on the left each time we move scrollbar on firefox [#11830](https://github.com/grafana/grafana/issues/11830)
|
||||
|
||||
# 5.1.2 (2018-05-09)
|
||||
|
||||
@ -1232,7 +1354,7 @@ Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated
|
||||
**New features**
|
||||
- [Issue #1623](https://github.com/grafana/grafana/issues/1623). Share Dashboard: Dashboard snapshot sharing (dash and data snapshot), save to local or save to public snapshot dashboard snapshots.raintank.io site
|
||||
- [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site
|
||||
- [Issue #718](https://github.com/grafana/grafana/issues/718). Dashboard: When saving a dashboard and another user has made changes in between the user is promted with a warning if he really wants to overwrite the other's changes
|
||||
- [Issue #718](https://github.com/grafana/grafana/issues/718). Dashboard: When saving a dashboard and another user has made changes in between the user is prompted with a warning if he really wants to overwrite the other's changes
|
||||
- [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
|
||||
- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, useful when you want to ignore last minute because it contains incomplete data
|
||||
- [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
|
||||
@ -1690,3 +1812,4 @@ Thanks to everyone who contributed fixes and provided feedback :+1:
|
||||
# 1.0.0 (2014-01-19)
|
||||
|
||||
First public release
|
||||
|
||||
|
140
Gopkg.lock
generated
140
Gopkg.lock
generated
@ -4,8 +4,8 @@
|
||||
[[projects]]
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["compute/metadata"]
|
||||
revision = "767c40d6a2e058483c25fa193e963a22da17236d"
|
||||
version = "v0.18.0"
|
||||
revision = "056a55f54a6cc77b440b31a56a5e7c3982d32811"
|
||||
version = "v0.22.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
@ -19,12 +19,6 @@
|
||||
packages = ["."]
|
||||
revision = "7677a1d7c1137cd3dd5ba7a076d0c898a1ef4520"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/apache/thrift"
|
||||
packages = ["lib/go/thrift"]
|
||||
revision = "b2a4d4ae21c789b689dd162deb819665567f481c"
|
||||
version = "0.10.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = [
|
||||
@ -38,15 +32,20 @@
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/csm",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/sdkio",
|
||||
"internal/sdkrand",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/ec2query",
|
||||
"private/protocol/eventstream",
|
||||
"private/protocol/eventstream/eventstreamapi",
|
||||
"private/protocol/query",
|
||||
"private/protocol/query/queryutil",
|
||||
"private/protocol/rest",
|
||||
@ -58,8 +57,8 @@
|
||||
"service/s3",
|
||||
"service/sts"
|
||||
]
|
||||
revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
|
||||
version = "v1.12.67"
|
||||
revision = "fde4ded7becdeae4d26bf1212916aabba79349b4"
|
||||
version = "v1.14.12"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -71,7 +70,7 @@
|
||||
branch = "master"
|
||||
name = "github.com/beorn7/perks"
|
||||
packages = ["quantile"]
|
||||
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -126,14 +125,14 @@
|
||||
[[projects]]
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260"
|
||||
version = "v1.5.0"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
|
||||
version = "v1.32.0"
|
||||
revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
|
||||
version = "v1.36.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-ldap/ldap"
|
||||
@ -182,10 +181,10 @@
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/go-xorm/builder"
|
||||
packages = ["."]
|
||||
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
|
||||
revision = "bad0a612f0d6277b953910822ab5dfb30dd18237"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-xorm/core"
|
||||
@ -209,13 +208,13 @@
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237"
|
||||
revision = "927b65914520a8b7d44f5c9057611cfec6b2e2d0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gopherjs/gopherjs"
|
||||
packages = ["js"]
|
||||
revision = "178c176a91fe05e3e6c58fa5c989bad19e6cdcb3"
|
||||
revision = "8dffc02ea1cb8398bb73f30424697c60fcf8d4c5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
@ -231,32 +230,35 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/grafana/grafana_plugin_model"
|
||||
packages = ["go/datasource"]
|
||||
revision = "dfe5dc0a6ce05825ba7fe2d0323d92e631bffa89"
|
||||
name = "github.com/grafana/grafana-plugin-model"
|
||||
packages = [
|
||||
"go/datasource",
|
||||
"go/renderer"
|
||||
]
|
||||
revision = "84176c64269d8060f99e750ee8aba6f062753336"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-hclog"
|
||||
packages = ["."]
|
||||
revision = "5bcb0f17e36442247290887cc914a6e507afa5c4"
|
||||
revision = "69ff559dc25f3b435631604f573a5fa1efdb6433"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hashicorp/go-plugin"
|
||||
packages = ["."]
|
||||
revision = "3e6d191694b5a3a2b99755f31b47fa209e4bcd09"
|
||||
revision = "e8d22c780116115ae5624720c9af0c97afe4f551"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-version"
|
||||
packages = ["."]
|
||||
revision = "4fe82ae3040f80a03d04d2cccb5606a626b8e1ee"
|
||||
revision = "23480c0665776210b5fbbac6eaaee40e3e6a96b7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/yamux"
|
||||
packages = ["."]
|
||||
revision = "683f49123a33db61abfb241b7ac5e4af4dc54d55"
|
||||
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/log15"
|
||||
@ -297,16 +299,16 @@
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/pretty"
|
||||
packages = ["."]
|
||||
revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4"
|
||||
revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/text"
|
||||
packages = ["."]
|
||||
revision = "7cafcd837844e784b526369c9bce262804aebc60"
|
||||
revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -315,7 +317,7 @@
|
||||
".",
|
||||
"oid"
|
||||
]
|
||||
revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
|
||||
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
@ -332,8 +334,8 @@
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
revision = "6c771bb9887719704b210e87e934f08be014bdb1"
|
||||
version = "v1.6.0"
|
||||
revision = "323a32be5a2421b8c7087225079c6c900ec397cd"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
@ -347,6 +349,12 @@
|
||||
packages = ["."]
|
||||
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/oklog/run"
|
||||
packages = ["."]
|
||||
revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
packages = [
|
||||
@ -394,7 +402,7 @@
|
||||
"internal/bitbucket.org/ww/goautoneg",
|
||||
"model"
|
||||
]
|
||||
revision = "89604d197083d4781071d3c65855d24ecfb0a563"
|
||||
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -402,10 +410,10 @@
|
||||
packages = [
|
||||
".",
|
||||
"internal/util",
|
||||
"nfsd",
|
||||
"nfs",
|
||||
"xfs"
|
||||
]
|
||||
revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1"
|
||||
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -414,10 +422,16 @@
|
||||
revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/sergi/go-diff"
|
||||
packages = ["diffmatchpatch"]
|
||||
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||
packages = ["."]
|
||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/assertions"
|
||||
@ -426,8 +440,8 @@
|
||||
"internal/go-render/render",
|
||||
"internal/oglematchers"
|
||||
]
|
||||
revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea"
|
||||
version = "1.8.1"
|
||||
revision = "7678a5452ebea5b7090a6b163f844c133f523da2"
|
||||
version = "1.8.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
@ -453,8 +467,11 @@
|
||||
"internal/baggage",
|
||||
"internal/baggage/remote",
|
||||
"internal/spanlog",
|
||||
"internal/throttler",
|
||||
"internal/throttler/remote",
|
||||
"log",
|
||||
"rpcmetrics",
|
||||
"thrift",
|
||||
"thrift-gen/agent",
|
||||
"thrift-gen/baggage",
|
||||
"thrift-gen/jaeger",
|
||||
@ -462,14 +479,14 @@
|
||||
"thrift-gen/zipkincore",
|
||||
"utils"
|
||||
]
|
||||
revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7"
|
||||
version = "v2.11.2"
|
||||
revision = "b043381d944715b469fd6b37addfd30145ca1758"
|
||||
version = "v2.14.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/uber/jaeger-lib"
|
||||
packages = ["metrics"]
|
||||
revision = "7f95f4f7e80028096410abddaae2556e4c61b59f"
|
||||
version = "v1.3.1"
|
||||
revision = "ed3a127ec5fef7ae9ea95b01b542c47fbd999ce5"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/yudai/gojsondiff"
|
||||
@ -493,7 +510,7 @@
|
||||
"md4",
|
||||
"pbkdf2"
|
||||
]
|
||||
revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
|
||||
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -501,14 +518,14 @@
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"lex/httplex",
|
||||
"trace"
|
||||
]
|
||||
revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||
revision = "2491c5de3490fced2f6cff376127c667efeed857"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -520,22 +537,21 @@
|
||||
"jws",
|
||||
"jwt"
|
||||
]
|
||||
revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067"
|
||||
revision = "cdc340f7c179dbbfa4afd43b7614e8fcadde4269"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
revision = "fd80eb99c8f653c847d294a001bdf2a3a6f768f5"
|
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "af50095a40f9041b3b38960738837185c26e9419"
|
||||
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
@ -553,7 +569,8 @@
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
@ -577,7 +594,7 @@
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
revision = "a8101f21cf983e773d0c1133ebc5424792003214"
|
||||
revision = "7bb2a897381c9c5ab2aeb8614f758d7766af68ff"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
@ -590,6 +607,7 @@
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"health",
|
||||
@ -607,8 +625,8 @@
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef"
|
||||
version = "v1.9.2"
|
||||
revision = "1e2570b1b19ade82d8dbb31bba4e65e9f9ef5b34"
|
||||
version = "v1.11.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
@ -631,14 +649,14 @@
|
||||
[[projects]]
|
||||
name = "gopkg.in/ini.v1"
|
||||
packages = ["."]
|
||||
revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
|
||||
version = "v1.32.0"
|
||||
revision = "6529cf7c58879c08d927016dde4477f18a0634cb"
|
||||
version = "v1.36.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/macaron.v1"
|
||||
packages = ["."]
|
||||
revision = "75f2e9b42e99652f0d82b28ccb73648f44615faa"
|
||||
version = "v1.2.4"
|
||||
revision = "c1be95e6d21e769e44e1ec33cec9da5837861c10"
|
||||
version = "v1.3.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
@ -653,14 +671,14 @@
|
||||
version = "v2.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "bd54a1a836599d90b36d4ac1af56d716ef9ca5be4865e217bddd49e3d32a1997"
|
||||
inputs-digest = "cb8e7fd81f23ec987fc4d5dd9d31ae0f1164bc2f30cbea2fe86e0d97dd945beb"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
14
Gopkg.toml
14
Gopkg.toml
@ -36,7 +36,7 @@ ignored = [
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
version = "1.12.65"
|
||||
version = "1.13.56"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@ -85,11 +85,11 @@ ignored = [
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-xorm/core"
|
||||
version = "0.5.7"
|
||||
version = "=0.5.7"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-xorm/xorm"
|
||||
version = "0.6.4"
|
||||
version = "=0.6.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
@ -101,12 +101,16 @@ ignored = [
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/grafana/grafana_plugin_model"
|
||||
name = "github.com/grafana/grafana-plugin-model"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-hclog"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hashicorp/go-plugin"
|
||||
revision = "e8d22c780116115ae5624720c9af0c97afe4f551"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-version"
|
||||
@ -125,7 +129,7 @@ ignored = [
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
|
@ -12,6 +12,10 @@ module.exports = function (grunt) {
|
||||
platform: process.platform.replace('win32', 'windows'),
|
||||
};
|
||||
|
||||
if (grunt.option('platform')) {
|
||||
config.platform = grunt.option('platform');
|
||||
}
|
||||
|
||||
if (grunt.option('arch')) {
|
||||
config.arch = grunt.option('arch');
|
||||
} else {
|
||||
|
@ -64,8 +64,6 @@ Run karma tests
|
||||
npm run karma
|
||||
```
|
||||
|
||||
Run
|
||||
|
||||
### Recompile backend on source change
|
||||
|
||||
To rebuild on source change.
|
||||
|
17
ROADMAP.md
17
ROADMAP.md
@ -1,28 +1,21 @@
|
||||
# Roadmap (2018-05-06)
|
||||
# Roadmap (2018-06-26)
|
||||
|
||||
This roadmap is a tentative plan for the core development team. Things change constantly as PRs come in and priorities change.
|
||||
But it will give you an idea of our current vision and plan.
|
||||
|
||||
### Short term (1-2 months)
|
||||
|
||||
- Elasticsearch alerting
|
||||
- Crossplatform builds
|
||||
- Backend service refactorings
|
||||
- Explore UI
|
||||
- First login registration view
|
||||
|
||||
### Mid term (2-4 months)
|
||||
- Multi-Stat panel
|
||||
- Metrics & Log Explore UI
|
||||
|
||||
### Mid term (2-4 months)
|
||||
- React Panels
|
||||
- Change visualization (panel type) on the fly.
|
||||
- Templating Query Editor UI Plugin hook
|
||||
|
||||
### Long term (4 - 8 months)
|
||||
|
||||
- Alerting improvements (silence, per series tracking, etc)
|
||||
- Progress on React migration
|
||||
- Change visualization (panel type) on the fly.
|
||||
- Multi stat panel (vertical version of singlestat with bars/graph mode with big number etc)
|
||||
- Repeat panel by query results
|
||||
|
||||
### In a distant future far far away
|
||||
|
||||
|
13
appveyor.yml
13
appveyor.yml
@ -38,16 +38,3 @@ artifacts:
|
||||
- path: grafana-*windows-*.*
|
||||
name: binzip
|
||||
type: zip
|
||||
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: GrafanaReleaseMaster
|
||||
on:
|
||||
buildType: master
|
||||
|
||||
- provider: Environment
|
||||
name: GrafanaReleaseRelease
|
||||
on:
|
||||
buildType: release
|
||||
|
||||
|
||||
|
80
build.go
80
build.go
@ -27,8 +27,7 @@ var (
|
||||
goarch string
|
||||
goos string
|
||||
gocc string
|
||||
gocxx string
|
||||
cgo string
|
||||
cgo bool
|
||||
pkgArch string
|
||||
version string = "v1"
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
@ -53,8 +52,7 @@ func main() {
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.StringVar(&gocc, "cc", "", "CC")
|
||||
flag.StringVar(&gocxx, "cxx", "", "CXX")
|
||||
flag.StringVar(&cgo, "cgo-enabled", "", "CGO_ENABLED")
|
||||
flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo")
|
||||
flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH")
|
||||
flag.StringVar(&phjsToRelease, "phjs", "", "PhantomJS binary")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
@ -93,20 +91,24 @@ func main() {
|
||||
build("grafana-server", "./pkg/cmd/grafana-server", []string{})
|
||||
|
||||
case "build":
|
||||
clean()
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
build(binary, "./pkg/cmd/"+binary, []string{})
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
grunt(gruntBuildArg("build")...)
|
||||
|
||||
case "test":
|
||||
test("./pkg/...")
|
||||
grunt("test")
|
||||
|
||||
case "package":
|
||||
grunt(gruntBuildArg("release")...)
|
||||
if runtime.GOOS != "windows" {
|
||||
createLinuxPackages()
|
||||
}
|
||||
grunt(gruntBuildArg("build")...)
|
||||
packageGrafana()
|
||||
|
||||
case "package-only":
|
||||
packageGrafana()
|
||||
|
||||
case "pkg-rpm":
|
||||
grunt(gruntBuildArg("release")...)
|
||||
@ -131,6 +133,22 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func packageGrafana() {
|
||||
platformArg := fmt.Sprintf("--platform=%v", goos)
|
||||
previousPkgArch := pkgArch
|
||||
if pkgArch == "" {
|
||||
pkgArch = goarch
|
||||
}
|
||||
postProcessArgs := gruntBuildArg("package")
|
||||
postProcessArgs = append(postProcessArgs, platformArg)
|
||||
grunt(postProcessArgs...)
|
||||
pkgArch = previousPkgArch
|
||||
|
||||
if goos == "linux" {
|
||||
createLinuxPackages()
|
||||
}
|
||||
}
|
||||
|
||||
func makeLatestDistCopies() {
|
||||
files, err := ioutil.ReadDir("dist")
|
||||
if err != nil {
|
||||
@ -138,9 +156,9 @@ func makeLatestDistCopies() {
|
||||
}
|
||||
|
||||
latestMapping := map[string]string{
|
||||
".deb": "dist/grafana_latest_amd64.deb",
|
||||
".rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||
".tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
@ -211,6 +229,10 @@ type linuxPackageOptions struct {
|
||||
}
|
||||
|
||||
func createDebPackages() {
|
||||
previousPkgArch := pkgArch
|
||||
if pkgArch == "armv7" {
|
||||
pkgArch = "armhf"
|
||||
}
|
||||
createPackage(linuxPackageOptions{
|
||||
packageType: "deb",
|
||||
homeDir: "/usr/share/grafana",
|
||||
@ -228,9 +250,17 @@ func createDebPackages() {
|
||||
|
||||
depends: []string{"adduser", "libfontconfig"},
|
||||
})
|
||||
pkgArch = previousPkgArch
|
||||
}
|
||||
|
||||
func createRpmPackages() {
|
||||
previousPkgArch := pkgArch
|
||||
switch {
|
||||
case pkgArch == "armv7":
|
||||
pkgArch = "armhfp"
|
||||
case pkgArch == "arm64":
|
||||
pkgArch = "aarch64"
|
||||
}
|
||||
createPackage(linuxPackageOptions{
|
||||
packageType: "rpm",
|
||||
homeDir: "/usr/share/grafana",
|
||||
@ -248,6 +278,7 @@ func createRpmPackages() {
|
||||
|
||||
depends: []string{"/sbin/service", "fontconfig", "freetype", "urw-fonts"},
|
||||
})
|
||||
pkgArch = previousPkgArch
|
||||
}
|
||||
|
||||
func createLinuxPackages() {
|
||||
@ -299,6 +330,7 @@ func createPackage(options linuxPackageOptions) {
|
||||
name := "grafana"
|
||||
if enterprise {
|
||||
name += "-enterprise"
|
||||
args = append(args, "--replaces", "grafana")
|
||||
}
|
||||
args = append(args, "--name", name)
|
||||
|
||||
@ -386,7 +418,12 @@ func test(pkg string) {
|
||||
}
|
||||
|
||||
func build(binaryName, pkg string, tags []string) {
|
||||
binary := "./bin/" + binaryName
|
||||
binary := fmt.Sprintf("./bin/%s-%s/%s", goos, goarch, binaryName)
|
||||
if isDev {
|
||||
//dont include os and arch in output path in dev environment
|
||||
binary = fmt.Sprintf("./bin/%s", binaryName)
|
||||
}
|
||||
|
||||
if goos == "windows" {
|
||||
binary += ".exe"
|
||||
}
|
||||
@ -408,6 +445,7 @@ func build(binaryName, pkg string, tags []string) {
|
||||
if !isDev {
|
||||
setBuildEnv()
|
||||
runPrint("go", "version")
|
||||
fmt.Printf("Targeting %s/%s\n", goos, goarch)
|
||||
}
|
||||
|
||||
runPrint("go", args...)
|
||||
@ -428,7 +466,6 @@ func ldflags() string {
|
||||
b.WriteString(fmt.Sprintf(" -X main.version=%s", version))
|
||||
b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.enterprise=%t", enterprise))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@ -451,6 +488,14 @@ func clean() {
|
||||
|
||||
func setBuildEnv() {
|
||||
os.Setenv("GOOS", goos)
|
||||
if goos == "windows" {
|
||||
// require windows >=7
|
||||
os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601")
|
||||
}
|
||||
if goarch != "amd64" || goos != "linux" {
|
||||
// needed for all other archs
|
||||
cgo = true
|
||||
}
|
||||
if strings.HasPrefix(goarch, "armv") {
|
||||
os.Setenv("GOARCH", "arm")
|
||||
os.Setenv("GOARM", goarch[4:])
|
||||
@ -460,15 +505,12 @@ func setBuildEnv() {
|
||||
if goarch == "386" {
|
||||
os.Setenv("GO386", "387")
|
||||
}
|
||||
if cgo != "" {
|
||||
os.Setenv("CGO_ENABLED", cgo)
|
||||
if cgo {
|
||||
os.Setenv("CGO_ENABLED", "1")
|
||||
}
|
||||
if gocc != "" {
|
||||
os.Setenv("CC", gocc)
|
||||
}
|
||||
if gocxx != "" {
|
||||
os.Setenv("CXX", gocxx)
|
||||
}
|
||||
}
|
||||
|
||||
func getGitSha() string {
|
||||
|
@ -14,6 +14,9 @@ instance_name = ${HOSTNAME}
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
data = data
|
||||
|
||||
# Temporary files in `data` directory older than given duration will be removed
|
||||
temp_data_lifetime = 24h
|
||||
|
||||
# Directory where grafana can store logs
|
||||
logs = data/log
|
||||
|
||||
@ -237,6 +240,9 @@ disable_login_form = false
|
||||
# Set to true to disable the signout link in the side menu. useful if you use auth.proxy
|
||||
disable_signout_menu = false
|
||||
|
||||
# URL to redirect the user to after sign out
|
||||
signout_redirect_url =
|
||||
|
||||
#################################### Anonymous Auth ######################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
@ -72,6 +72,8 @@ email = "email"
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=admins,dc=grafana,dc=org"
|
||||
org_role = "Admin"
|
||||
# To make user an instance admin (Grafana Admin) uncomment line below
|
||||
# grafana_admin = true
|
||||
# The Grafana organization database id, optional, if left out the default org (id 1) will be used
|
||||
# org_id = 1
|
||||
|
||||
|
@ -40,11 +40,14 @@ apiVersion: 1
|
||||
# graphiteVersion: "1.1"
|
||||
# tlsAuth: true
|
||||
# tlsAuthWithCACert: true
|
||||
# httpHeaderName1: "Authorization"
|
||||
# # <string> json object of data that will be encrypted.
|
||||
# secureJsonData:
|
||||
# tlsCACert: "..."
|
||||
# tlsClientCert: "..."
|
||||
# tlsClientKey: "..."
|
||||
# # <openshift\kubernetes token example>
|
||||
# httpHeaderValue1: "Bearer xf5yhfkpsnmgo"
|
||||
# version: 1
|
||||
# # <bool> allow users to edit datasources from the UI.
|
||||
# editable: false
|
||||
|
@ -14,6 +14,9 @@
|
||||
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
|
||||
;data = /var/lib/grafana
|
||||
|
||||
# Temporary files in `data` directory older than given duration will be removed
|
||||
;temp_data_lifetime = 24h
|
||||
|
||||
# Directory where grafana can store logs
|
||||
;logs = /var/log/grafana
|
||||
|
||||
@ -217,6 +220,9 @@ log_queries =
|
||||
# Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
|
||||
;disable_signout_menu = false
|
||||
|
||||
# URL to redirect the user to after sign out
|
||||
;signout_redirect_url =
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
16
devenv/README.md
Normal file
16
devenv/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
This folder contains useful scripts and configuration for...
|
||||
|
||||
* Configuring dev datasources in Grafana
|
||||
* Configuring dev & test scenarios dashboards.
|
||||
|
||||
```bash
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
After restarting grafana server there should now be a number of datasources named `gdev-<type>` provisioned as well as a dashboard folder named `gdev dashboards`. This folder contains dashboard & panel features tests dashboards.
|
||||
|
||||
# Dev dashboards
|
||||
|
||||
Please update these dashboards or make new ones as new panels & dashboards features are developed or new bugs are found. The dashboards are located in the `devenv/dev-dashboards` folder.
|
||||
|
||||
|
9
devenv/bulk-dashboards/bulk-dashboards.yaml
Normal file
9
devenv/bulk-dashboards/bulk-dashboards.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'Bulk dashboards'
|
||||
folder: 'Bulk dashboards'
|
||||
type: file
|
||||
options:
|
||||
path: devenv/dashboards/bulk-testing
|
||||
|
1140
devenv/bulk-dashboards/bulkdash.jsonnet
Normal file
1140
devenv/bulk-dashboards/bulkdash.jsonnet
Normal file
File diff suppressed because it is too large
Load Diff
9
devenv/dashboards.yaml
Normal file
9
devenv/dashboards.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'gdev dashboards'
|
||||
folder: 'gdev dashboards'
|
||||
type: file
|
||||
options:
|
||||
path: devenv/dev-dashboards
|
||||
|
78
devenv/datasources.yaml
Normal file
78
devenv/datasources.yaml
Normal file
@ -0,0 +1,78 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: gdev-graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
jsonData:
|
||||
graphiteVersion: "1.1"
|
||||
|
||||
- name: gdev-prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
isDefault: true
|
||||
url: http://localhost:9090
|
||||
|
||||
- name: gdev-testdata
|
||||
type: testdata
|
||||
|
||||
- name: gdev-influxdb
|
||||
type: influxdb
|
||||
access: proxy
|
||||
database: site
|
||||
user: grafana
|
||||
password: grafana
|
||||
url: http://localhost:8086
|
||||
jsonData:
|
||||
timeInterval: "15s"
|
||||
|
||||
- name: gdev-opentsdb
|
||||
type: opentsdb
|
||||
access: proxy
|
||||
url: http://localhost:4242
|
||||
jsonData:
|
||||
tsdbResolution: 1
|
||||
tsdbVersion: 1
|
||||
|
||||
- name: gdev-elasticsearch-metrics
|
||||
type: elasticsearch
|
||||
access: proxy
|
||||
database: "[metrics-]YYYY.MM.DD"
|
||||
url: http://localhost:9200
|
||||
jsonData:
|
||||
interval: Daily
|
||||
timeField: "@timestamp"
|
||||
|
||||
- name: gdev-mysql
|
||||
type: mysql
|
||||
url: localhost:3306
|
||||
database: grafana
|
||||
user: grafana
|
||||
password: password
|
||||
|
||||
- name: gdev-mssql
|
||||
type: mssql
|
||||
url: localhost:1433
|
||||
database: grafana
|
||||
user: grafana
|
||||
password: "Password!"
|
||||
|
||||
- name: gdev-postgres
|
||||
type: postgres
|
||||
url: localhost:5432
|
||||
database: grafana
|
||||
user: grafana
|
||||
secureJsonData:
|
||||
password: password
|
||||
jsonData:
|
||||
sslmode: "disable"
|
||||
|
||||
- name: gdev-cloudwatch
|
||||
type: cloudwatch
|
||||
editable: true
|
||||
jsonData:
|
||||
authType: credentials
|
||||
defaultRegion: eu-west-2
|
||||
|
||||
|
1558
devenv/dev-dashboards/panel_tests_graph.json
Normal file
1558
devenv/dev-dashboards/panel_tests_graph.json
Normal file
File diff suppressed because it is too large
Load Diff
574
devenv/dev-dashboards/panel_tests_singlestat.json
Normal file
574
devenv/dev-dashboards/panel_tests_singlestat.json
Normal file
@ -0,0 +1,574 @@
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "postfix",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "prefix",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": true
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "prefix 3 ms (green) postfixt + sparkline",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#d44a3a",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#299c46"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": true
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "3 ms (red) + full height sparkline",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "200%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": true,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": [
|
||||
"#d44a3a",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#299c46"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "3 ms + red background",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "200%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": true,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 7
|
||||
},
|
||||
"id": 5,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 7
|
||||
},
|
||||
"id": 6,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90, no labels",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": [
|
||||
"#299c46",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"#d44a3a"
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": false
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 7
|
||||
},
|
||||
"id": 7,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90, no markers or labels",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"revision": 8,
|
||||
"schemaVersion": 16,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"panel-tests"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"timezone": "browser",
|
||||
"title": "Panel Tests - Singlestat",
|
||||
"uid": "singlestat",
|
||||
"version": 14
|
||||
}
|
453
devenv/dev-dashboards/panel_tests_table.json
Normal file
453
devenv/dev-dashboards/panel_tests_table.json
Normal file
@ -0,0 +1,453 @@
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"columns": [],
|
||||
"datasource": "gdev-testdata",
|
||||
"fontSize": "100%",
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"links": [],
|
||||
"pageSize": 10,
|
||||
"scroll": true,
|
||||
"showHeader": true,
|
||||
"sort": {
|
||||
"col": 0,
|
||||
"desc": true
|
||||
},
|
||||
"styles": [
|
||||
{
|
||||
"alias": "Time",
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"pattern": "Time",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "cell",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorCell",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "currencyUSD"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "value",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorValue",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "Bps"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": null,
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"decimals": 2,
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [],
|
||||
"type": "number",
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"alias": "server1",
|
||||
"expr": "",
|
||||
"format": "table",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0,20,10"
|
||||
},
|
||||
{
|
||||
"alias": "server2",
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
],
|
||||
"title": "Time series to rows (2 pages)",
|
||||
"transform": "timeseries_to_rows",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"text": "Avg",
|
||||
"value": "avg"
|
||||
},
|
||||
{
|
||||
"text": "Max",
|
||||
"value": "max"
|
||||
},
|
||||
{
|
||||
"text": "Current",
|
||||
"value": "current"
|
||||
}
|
||||
],
|
||||
"datasource": "gdev-testdata",
|
||||
"fontSize": "100%",
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"links": [],
|
||||
"pageSize": 10,
|
||||
"scroll": true,
|
||||
"showHeader": true,
|
||||
"sort": {
|
||||
"col": 0,
|
||||
"desc": true
|
||||
},
|
||||
"styles": [
|
||||
{
|
||||
"alias": "Time",
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"pattern": "Time",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "cell",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorCell",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "currencyUSD"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "value",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorValue",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "Bps"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": null,
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"decimals": 2,
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [],
|
||||
"type": "number",
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"alias": "server1",
|
||||
"expr": "",
|
||||
"format": "table",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0,20,10"
|
||||
},
|
||||
{
|
||||
"alias": "server2",
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
],
|
||||
"title": "Time series aggregations",
|
||||
"transform": "timeseries_aggregations",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"columns": [],
|
||||
"datasource": "gdev-testdata",
|
||||
"fontSize": "100%",
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"id": 5,
|
||||
"links": [],
|
||||
"pageSize": null,
|
||||
"scroll": true,
|
||||
"showHeader": true,
|
||||
"sort": {
|
||||
"col": 0,
|
||||
"desc": true
|
||||
},
|
||||
"styles": [
|
||||
{
|
||||
"alias": "Time",
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"pattern": "Time",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "row",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "/Color/",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "currencyUSD"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": null,
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"decimals": 2,
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [],
|
||||
"type": "number",
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"alias": "ColorValue",
|
||||
"expr": "",
|
||||
"format": "table",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0,20,10"
|
||||
}
|
||||
],
|
||||
"title": "color row by threshold",
|
||||
"transform": "timeseries_to_columns",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"columns": [],
|
||||
"datasource": "gdev-testdata",
|
||||
"fontSize": "100%",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"id": 2,
|
||||
"links": [],
|
||||
"pageSize": null,
|
||||
"scroll": true,
|
||||
"showHeader": true,
|
||||
"sort": {
|
||||
"col": 0,
|
||||
"desc": true
|
||||
},
|
||||
"styles": [
|
||||
{
|
||||
"alias": "Time",
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"pattern": "Time",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "cell",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorCell",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "currencyUSD"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": "value",
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||
"decimals": 2,
|
||||
"mappingType": 1,
|
||||
"pattern": "ColorValue",
|
||||
"thresholds": [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
"type": "number",
|
||||
"unit": "Bps"
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"colorMode": null,
|
||||
"colors": [
|
||||
"rgba(245, 54, 54, 0.9)",
|
||||
"rgba(237, 129, 40, 0.89)",
|
||||
"rgba(50, 172, 45, 0.97)"
|
||||
],
|
||||
"decimals": 2,
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [],
|
||||
"type": "number",
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"alias": "ColorValue",
|
||||
"expr": "",
|
||||
"format": "table",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0,20,10"
|
||||
},
|
||||
{
|
||||
"alias": "ColorCell",
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "5,1,2,3,4,5,10,20"
|
||||
}
|
||||
],
|
||||
"title": "Column style thresholds & units",
|
||||
"transform": "timeseries_to_columns",
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"revision": 8,
|
||||
"schemaVersion": 16,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"gdev",
|
||||
"panel-tests"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"timezone": "browser",
|
||||
"title": "Panel Tests - Table",
|
||||
"uid": "pttable",
|
||||
"version": 1
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"revision": 2,
|
||||
"title": "TestData - Alerts",
|
||||
"title": "Alerting with TestData",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
],
|
||||
@ -48,7 +48,7 @@
|
||||
},
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"datasource": "gdev-testdata",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
@ -161,7 +161,7 @@
|
||||
},
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"datasource": "gdev-testdata",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
64
devenv/setup.sh
Executable file
64
devenv/setup.sh
Executable file
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
bulkDashboard() {
|
||||
|
||||
requiresJsonnet
|
||||
|
||||
COUNTER=0
|
||||
MAX=400
|
||||
while [ $COUNTER -lt $MAX ]; do
|
||||
jsonnet -o "dashboards/bulk-testing/dashboard${COUNTER}.json" -e "local bulkDash = import 'dashboards/bulk-testing/bulkdash.jsonnet'; bulkDash + { uid: 'uid-${COUNTER}', title: 'title-${COUNTER}' }"
|
||||
let COUNTER=COUNTER+1
|
||||
done
|
||||
|
||||
ln -s -f -r ./dashboards/bulk-testing/bulk-dashboards.yaml ../conf/provisioning/dashboards/custom.yaml
|
||||
}
|
||||
|
||||
requiresJsonnet() {
|
||||
if ! type "jsonnet" > /dev/null; then
|
||||
echo "you need you install jsonnet to run this script"
|
||||
echo "follow the instructions on https://github.com/google/jsonnet"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
devDashboards() {
|
||||
echo -e "\xE2\x9C\x94 Setting up all dev dashboards using provisioning"
|
||||
ln -s -f ../../../devenv/dashboards.yaml ../conf/provisioning/dashboards/dev.yaml
|
||||
}
|
||||
|
||||
devDatasources() {
|
||||
echo -e "\xE2\x9C\x94 Setting up all dev datasources using provisioning"
|
||||
|
||||
ln -s -f ../../../devenv/datasources.yaml ../conf/provisioning/datasources/dev.yaml
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo -e "\n"
|
||||
echo "Usage:"
|
||||
echo " bulk-dashboards - create and provisioning 400 dashboards"
|
||||
echo " no args - provisiong core datasources and dev dashboards"
|
||||
}
|
||||
|
||||
main() {
|
||||
echo -e "------------------------------------------------------------------"
|
||||
echo -e "This script setups provisioning for dev datasources and dashboards"
|
||||
echo -e "------------------------------------------------------------------"
|
||||
echo -e "\n"
|
||||
|
||||
local cmd=$1
|
||||
|
||||
if [[ $cmd == "bulk-dashboards" ]]; then
|
||||
bulkDashboard
|
||||
else
|
||||
devDashboards
|
||||
devDatasources
|
||||
fi
|
||||
|
||||
if [[ -z "$cmd" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
main "$@"
|
15
docker/blocks/elastic6/docker-compose.yaml
Normal file
15
docker/blocks/elastic6/docker-compose.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# You need to run 'sysctl -w vm.max_map_count=262144' on the host machine
|
||||
|
||||
elasticsearch6:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4
|
||||
command: elasticsearch
|
||||
ports:
|
||||
- "11200:9200"
|
||||
- "11300:9300"
|
||||
|
||||
fake-elastic6-data:
|
||||
image: grafana/fake-data-gen
|
||||
network_mode: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: elasticsearch6
|
||||
FD_PORT: 11200
|
2
docker/blocks/elastic6/elasticsearch.yml
Normal file
2
docker/blocks/elastic6/elasticsearch.yml
Normal file
@ -0,0 +1,2 @@
|
||||
script.inline: on
|
||||
script.indexed: on
|
@ -1,5 +1,5 @@
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
image: mysql:5.6
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_DATABASE: grafana
|
||||
|
@ -1,3 +1,3 @@
|
||||
FROM mysql:latest
|
||||
FROM mysql:5.6
|
||||
ADD setup.sql /docker-entrypoint-initdb.d
|
||||
CMD ["mysqld"]
|
||||
CMD ["mysqld"]
|
||||
|
@ -1,3 +1,4 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY htpasswd /etc/nginx/htpasswd
|
||||
|
3
docker/blocks/nginx_proxy/htpasswd
Executable file
3
docker/blocks/nginx_proxy/htpasswd
Executable file
@ -0,0 +1,3 @@
|
||||
user1:$apr1$1odeeQb.$kwV8D/VAAGUDU7pnHuKoV0
|
||||
user2:$apr1$A2kf25r.$6S0kp3C7vIuixS5CL0XA9.
|
||||
admin:$apr1$IWn4DoRR$E2ol7fS/dkI18eU4bXnBO1
|
@ -13,7 +13,26 @@ http {
|
||||
listen 10080;
|
||||
|
||||
location /grafana/ {
|
||||
################################################################
|
||||
# Enable these settings to test with basic auth and an auth proxy header
|
||||
# the htpasswd file contains an admin user with password admin and
|
||||
# user1: grafana and user2: grafana
|
||||
################################################################
|
||||
|
||||
# auth_basic "Restricted Content";
|
||||
# auth_basic_user_file /etc/nginx/htpasswd;
|
||||
|
||||
################################################################
|
||||
# To use the auth proxy header, set the following in custom.ini:
|
||||
# [auth.proxy]
|
||||
# enabled = true
|
||||
# header_name = X-WEBAUTH-USER
|
||||
# header_property = username
|
||||
################################################################
|
||||
|
||||
# proxy_set_header X-WEBAUTH-USER $remote_user;
|
||||
|
||||
proxy_pass http://localhost:3000/;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Fork of https://github.com/dinkel/docker-openldap
|
||||
|
||||
FROM debian:jessie
|
||||
|
||||
LABEL maintainer="Christian Luginbühl <dinke@pimprecords.com>"
|
||||
@ -6,7 +8,8 @@ ENV OPENLDAP_VERSION 2.4.40
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
slapd=${OPENLDAP_VERSION}* && \
|
||||
slapd=${OPENLDAP_VERSION}* \
|
||||
ldap-utils && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@ -20,6 +23,7 @@ COPY modules/ /etc/ldap.dist/modules
|
||||
COPY prepopulate/ /etc/ldap.dist/prepopulate
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY prepopulate.sh /prepopulate.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
|
@ -76,13 +76,14 @@ EOF
|
||||
IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES); unset IFS
|
||||
|
||||
for module in "${modules[@]}"; do
|
||||
slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1
|
||||
echo "Adding module ${module}"
|
||||
slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1
|
||||
done
|
||||
fi
|
||||
|
||||
for file in `ls /etc/ldap/prepopulate/*.ldif`; do
|
||||
slapadd -F /etc/ldap/slapd.d -l "$file"
|
||||
done
|
||||
# This needs to run in background
|
||||
# Will prepopulate entries after ldap daemon has started
|
||||
./prepopulate.sh &
|
||||
|
||||
chown -R openldap:openldap /etc/ldap/slapd.d/ /var/lib/ldap/ /var/run/slapd/
|
||||
else
|
||||
|
85
docker/blocks/openldap/ldap_dev.toml
Normal file
85
docker/blocks/openldap/ldap_dev.toml
Normal file
@ -0,0 +1,85 @@
|
||||
# To troubleshoot and get more log info enable ldap debug logging in grafana.ini
|
||||
# [log]
|
||||
# filters = ldap:debug
|
||||
|
||||
[[servers]]
|
||||
# Ldap server host (specify multiple hosts space separated)
|
||||
host = "127.0.0.1"
|
||||
# Default port is 389 or 636 if use_ssl = true
|
||||
port = 389
|
||||
# Set to true if ldap server supports TLS
|
||||
use_ssl = false
|
||||
# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS)
|
||||
start_tls = false
|
||||
# set to true if you want to skip ssl cert validation
|
||||
ssl_skip_verify = false
|
||||
# set to the path to your root CA certificate or leave unset to use system defaults
|
||||
# root_ca_cert = "/path/to/certificate.crt"
|
||||
|
||||
# Search user bind dn
|
||||
bind_dn = "cn=admin,dc=grafana,dc=org"
|
||||
# Search user bind password
|
||||
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
|
||||
bind_password = 'grafana'
|
||||
|
||||
# User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"
|
||||
search_filter = "(cn=%s)"
|
||||
|
||||
# An array of base dns to search through
|
||||
search_base_dns = ["dc=grafana,dc=org"]
|
||||
|
||||
# In POSIX LDAP schemas, without memberOf attribute a secondary query must be made for groups.
|
||||
# This is done by enabling group_search_filter below. You must also set member_of= "cn"
|
||||
# in [servers.attributes] below.
|
||||
|
||||
# Users with nested/recursive group membership and an LDAP server that supports LDAP_MATCHING_RULE_IN_CHAIN
|
||||
# can set group_search_filter, group_search_filter_user_attribute, group_search_base_dns and member_of
|
||||
# below in such a way that the user's recursive group membership is considered.
|
||||
#
|
||||
# Nested Groups + Active Directory (AD) Example:
|
||||
#
|
||||
# AD groups store the Distinguished Names (DNs) of members, so your filter must
|
||||
# recursively search your groups for the authenticating user's DN. For example:
|
||||
#
|
||||
# group_search_filter = "(member:1.2.840.113556.1.4.1941:=%s)"
|
||||
# group_search_filter_user_attribute = "distinguishedName"
|
||||
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
|
||||
#
|
||||
# [servers.attributes]
|
||||
# ...
|
||||
# member_of = "distinguishedName"
|
||||
|
||||
## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available)
|
||||
# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
|
||||
## Group search filter user attribute defines what user attribute gets substituted for %s in group_search_filter.
|
||||
## Defaults to the value of username in [server.attributes]
|
||||
## Valid options are any of your values in [servers.attributes]
|
||||
## If you are using nested groups you probably want to set this and member_of in
|
||||
## [servers.attributes] to "distinguishedName"
|
||||
# group_search_filter_user_attribute = "distinguishedName"
|
||||
## An array of the base DNs to search through for groups. Typically uses ou=groups
|
||||
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
|
||||
|
||||
# Specify names of the ldap attributes your ldap uses
|
||||
[servers.attributes]
|
||||
name = "givenName"
|
||||
surname = "sn"
|
||||
username = "cn"
|
||||
member_of = "memberOf"
|
||||
email = "email"
|
||||
|
||||
# Map ldap groups to grafana org roles
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=admins,ou=groups,dc=grafana,dc=org"
|
||||
org_role = "Admin"
|
||||
# The Grafana organization database id, optional, if left out the default org (id 1) will be used
|
||||
# org_id = 1
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=editors,ou=groups,dc=grafana,dc=org"
|
||||
org_role = "Editor"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
# If you want to match all (or no ldap groups) then you can use wildcard
|
||||
group_dn = "*"
|
||||
org_role = "Viewer"
|
@ -1,6 +1,6 @@
|
||||
# Notes on OpenLdap Docker Block
|
||||
|
||||
Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database.
|
||||
Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database.
|
||||
|
||||
The ldif files add three users, `ldapviewer`, `ldapeditor` and `ldapadmin`. Two groups, `admins` and `users`, are added that correspond with the group mappings in the default conf/ldap.toml. `ldapadmin` is a member of `admins` and `ldapeditor` is a member of `users`.
|
||||
|
||||
@ -11,3 +11,35 @@ After adding ldif files to `prepopulate`:
|
||||
1. Remove your current docker image: `docker rm docker_openldap_1`
|
||||
2. Build: `docker-compose build`
|
||||
3. `docker-compose up`
|
||||
|
||||
## Enabling LDAP in Grafana
|
||||
|
||||
Copy the ldap_dev.toml file in this folder into your `conf` folder (it is gitignored already). To enable it in the .ini file to get Grafana to use this block:
|
||||
|
||||
```ini
|
||||
[auth.ldap]
|
||||
enabled = true
|
||||
config_file = conf/ldap_dev.toml
|
||||
; allow_sign_up = true
|
||||
```
|
||||
|
||||
Test groups & users
|
||||
|
||||
admins
|
||||
ldap-admin
|
||||
ldap-torkel
|
||||
ldap-daniel
|
||||
backend
|
||||
ldap-carl
|
||||
ldap-torkel
|
||||
ldap-leo
|
||||
frontend
|
||||
ldap-torkel
|
||||
ldap-tobias
|
||||
ldap-daniel
|
||||
editors
|
||||
ldap-editors
|
||||
|
||||
|
||||
no groups
|
||||
ldap-viewer
|
||||
|
14
docker/blocks/openldap/prepopulate.sh
Executable file
14
docker/blocks/openldap/prepopulate.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Pre-populating ldap entries, first waiting for ldap to start"
|
||||
|
||||
sleep 3
|
||||
|
||||
adminUserDn="cn=admin,dc=grafana,dc=org"
|
||||
adminPassword="grafana"
|
||||
|
||||
for file in `ls /etc/ldap/prepopulate/*.ldif`; do
|
||||
ldapadd -x -D $adminUserDn -w $adminPassword -f "$file"
|
||||
done
|
||||
|
||||
|
9
docker/blocks/openldap/prepopulate/1_units.ldif
Normal file
9
docker/blocks/openldap/prepopulate/1_units.ldif
Normal file
@ -0,0 +1,9 @@
|
||||
dn: ou=groups,dc=grafana,dc=org
|
||||
ou: Groups
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
|
||||
dn: ou=users,dc=grafana,dc=org
|
||||
ou: Users
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
80
docker/blocks/openldap/prepopulate/2_users.ldif
Normal file
80
docker/blocks/openldap/prepopulate/2_users.ldif
Normal file
@ -0,0 +1,80 @@
|
||||
# ldap-admin
|
||||
dn: cn=ldap-admin,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-admin@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-admin
|
||||
cn: ldap-admin
|
||||
|
||||
dn: cn=ldap-editor,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-editor@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-editor
|
||||
cn: ldap-editor
|
||||
|
||||
dn: cn=ldap-viewer,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-viewer@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-viewer
|
||||
cn: ldap-viewer
|
||||
|
||||
dn: cn=ldap-carl,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-carl@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-carl
|
||||
cn: ldap-carl
|
||||
|
||||
dn: cn=ldap-daniel,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-daniel@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-daniel
|
||||
cn: ldap-daniel
|
||||
|
||||
dn: cn=ldap-leo,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-leo@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-leo
|
||||
cn: ldap-leo
|
||||
|
||||
dn: cn=ldap-tobias,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-tobias@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-tobias
|
||||
cn: ldap-tobias
|
||||
|
||||
dn: cn=ldap-torkel,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-torkel@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-torkel
|
||||
cn: ldap-torkel
|
25
docker/blocks/openldap/prepopulate/3_groups.ldif
Normal file
25
docker/blocks/openldap/prepopulate/3_groups.ldif
Normal file
@ -0,0 +1,25 @@
|
||||
dn: cn=admins,ou=groups,dc=grafana,dc=org
|
||||
cn: admins
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
||||
member: cn=ldap-admin,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-torkel,ou=users,dc=grafana,dc=org
|
||||
|
||||
dn: cn=editors,ou=groups,dc=grafana,dc=org
|
||||
cn: editors
|
||||
objectClass: groupOfNames
|
||||
member: cn=ldap-editor,ou=users,dc=grafana,dc=org
|
||||
|
||||
dn: cn=backend,ou=groups,dc=grafana,dc=org
|
||||
cn: backend
|
||||
objectClass: groupOfNames
|
||||
member: cn=ldap-carl,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-leo,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-torkel,ou=users,dc=grafana,dc=org
|
||||
|
||||
dn: cn=frontend,ou=groups,dc=grafana,dc=org
|
||||
cn: frontend
|
||||
objectClass: groupOfNames
|
||||
member: cn=ldap-torkel,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-daniel,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-leo,ou=users,dc=grafana,dc=org
|
@ -1,10 +0,0 @@
|
||||
dn: cn=ldapadmin,dc=grafana,dc=org
|
||||
mail: ldapadmin@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldapadmin
|
||||
cn: ldapadmin
|
||||
memberOf: cn=admins,dc=grafana,dc=org
|
@ -1,5 +0,0 @@
|
||||
dn: cn=admins,dc=grafana,dc=org
|
||||
cn: admins
|
||||
member: cn=ldapadmin,dc=grafana,dc=org
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
@ -1,10 +0,0 @@
|
||||
dn: cn=ldapeditor,dc=grafana,dc=org
|
||||
mail: ldapeditor@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldapeditor
|
||||
cn: ldapeditor
|
||||
memberOf: cn=users,dc=grafana,dc=org
|
@ -1,5 +0,0 @@
|
||||
dn: cn=users,dc=grafana,dc=org
|
||||
cn: users
|
||||
member: cn=ldapeditor,dc=grafana,dc=org
|
||||
objectClass: groupOfNames
|
||||
objectClass: top
|
@ -1,9 +0,0 @@
|
||||
dn: cn=ldapviewer,dc=grafana,dc=org
|
||||
mail: ldapviewer@grafana.com
|
||||
userPassword: grafana
|
||||
objectClass: person
|
||||
objectClass: top
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldapviewer
|
||||
cn: ldapviewer
|
@ -1,5 +1,5 @@
|
||||
postgrestest:
|
||||
image: postgres:latest
|
||||
image: postgres:9.3
|
||||
environment:
|
||||
POSTGRES_USER: grafana
|
||||
POSTGRES_PASSWORD: password
|
||||
@ -13,4 +13,4 @@
|
||||
network_mode: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: postgres
|
||||
FD_PORT: 5432
|
||||
FD_PORT: 5432
|
||||
|
@ -1,3 +1,3 @@
|
||||
FROM postgres:latest
|
||||
FROM postgres:9.3
|
||||
ADD setup.sql /docker-entrypoint-initdb.d
|
||||
CMD ["postgres"]
|
||||
CMD ["postgres"]
|
||||
|
@ -1,3 +1,3 @@
|
||||
CREATE DATABASE grafanadstest;
|
||||
REVOKE CONNECT ON DATABASE grafanadstest FROM PUBLIC;
|
||||
GRANT CONNECT ON DATABASE grafanadstest TO grafanatest;
|
||||
GRANT CONNECT ON DATABASE grafanadstest TO grafanatest;
|
||||
|
@ -76,7 +76,7 @@ Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://gith
|
||||
|
||||
> This feature is available from v5.0
|
||||
|
||||
It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `delete_datasources`. Grafana will delete datasources listed in `delete_datasources` before inserting/updating those in the `datasource` list.
|
||||
It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `deleteDatasources`. Grafana will delete datasources listed in `deleteDatasources` before inserting/updating those in the `datasource` list.
|
||||
|
||||
### Running Multiple Grafana Instances
|
||||
|
||||
@ -94,7 +94,7 @@ deleteDatasources:
|
||||
orgId: 1
|
||||
|
||||
# list of datasources to insert/update depending
|
||||
# whats available in the database
|
||||
# what's available in the database
|
||||
datasources:
|
||||
# <string, required> name of the datasource. Required
|
||||
- name: Graphite
|
||||
@ -154,7 +154,7 @@ Since not all datasources have the same configuration settings we only have the
|
||||
| tlsAuthWithCACert | boolean | *All* | Enable TLS authentication using CA cert |
|
||||
| tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. |
|
||||
| graphiteVersion | string | Graphite | Graphite version |
|
||||
| timeInterval | string | Elastic, Influxdb & Prometheus | Lowest interval/step value that should be used for this data source |
|
||||
| timeInterval | string | Elastic, InfluxDB & Prometheus | Lowest interval/step value that should be used for this data source |
|
||||
| esVersion | string | Elastic | Elasticsearch version as an number (2/5/56) |
|
||||
| timeField | string | Elastic | Which field that should be used as timestamp |
|
||||
| interval | string | Elastic | Index date time format |
|
||||
@ -162,9 +162,9 @@ Since not all datasources have the same configuration settings we only have the
|
||||
| assumeRoleArn | string | Cloudwatch | ARN of Assume Role |
|
||||
| defaultRegion | string | Cloudwatch | AWS region |
|
||||
| customMetricsNamespaces | string | Cloudwatch | Namespaces of Custom Metrics |
|
||||
| tsdbVersion | string | OpenTsdb | Version |
|
||||
| tsdbResolution | string | OpenTsdb | Resolution |
|
||||
| sslmode | string | Postgre | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' |
|
||||
| tsdbVersion | string | OpenTSDB | Version |
|
||||
| tsdbResolution | string | OpenTSDB | Resolution |
|
||||
| sslmode | string | PostgreSQL | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' |
|
||||
|
||||
#### Secure Json Data
|
||||
|
||||
@ -177,8 +177,8 @@ Secure json data is a map of settings that will be encrypted with [secret key](/
|
||||
| tlsCACert | string | *All* |CA cert for out going requests |
|
||||
| tlsClientCert | string | *All* |TLS Client cert for outgoing requests |
|
||||
| tlsClientKey | string | *All* |TLS Client key for outgoing requests |
|
||||
| password | string | Postgre | password |
|
||||
| user | string | Postgre | user |
|
||||
| password | string | PostgreSQL | password |
|
||||
| user | string | PostgreSQL | user |
|
||||
| accessKey | string | Cloudwatch | Access key for connecting to Cloudwatch |
|
||||
| secretKey | string | Cloudwatch | Secret key for connecting to Cloudwatch |
|
||||
|
||||
@ -197,6 +197,7 @@ providers:
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 3 #how often Grafana will scan for changed dashboards
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
```
|
||||
|
@ -27,7 +27,9 @@ and the conditions that need to be met for the alert to change state and trigger
|
||||
## Execution
|
||||
|
||||
The alert rules are evaluated in the Grafana backend in a scheduler and query execution engine that is part
|
||||
of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `InfluxDB`, `OpenTSDB`, `MySQL`, `Postgres` and `Cloudwatch`.
|
||||
of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `Elasticsearch`, `InfluxDB`, `OpenTSDB`, `MySQL`, `Postgres` and `Cloudwatch`.
|
||||
|
||||
> Alerting support for Elasticsearch is only available in Grafana v5.2 and above.
|
||||
|
||||
### Clustering
|
||||
|
||||
@ -152,6 +154,8 @@ filters = alerting.scheduler:debug \
|
||||
tsdb.prometheus:debug \
|
||||
tsdb.opentsdb:debug \
|
||||
tsdb.influxdb:debug \
|
||||
tsdb.elasticsearch:debug \
|
||||
tsdb.elasticsearch.client:debug \
|
||||
```
|
||||
|
||||
If you want to log raw query sent to your TSDB and raw response in log you also have to set grafana.ini option `app_mode` to
|
||||
|
@ -20,7 +20,7 @@ queries through the use of query references.
|
||||
## Adding the data source
|
||||
|
||||
1. Open the side menu by clicking the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
2. In the side menu under the `Configuration` link you should find a link named `Data Sources`.
|
||||
3. Click the `+ Add data source` button in the top header.
|
||||
4. Select `Graphite` from the *Type* dropdown.
|
||||
|
||||
|
@ -188,7 +188,7 @@ queries via the Dashboard menu / Annotations view.
|
||||
An example query:
|
||||
|
||||
```SQL
|
||||
SELECT title, description from events WHERE $timeFilter order asc
|
||||
SELECT title, description from events WHERE $timeFilter ORDER BY time ASC
|
||||
```
|
||||
|
||||
For InfluxDB you need to enter a query like in the above example. You need to have the ```where $timeFilter```
|
||||
|
@ -77,9 +77,9 @@ Macro example | Description
|
||||
------------ | -------------
|
||||
*$__time(dateColumn)* | Will be replaced by an expression to rename the column to *time*. For example, *dateColumn as time*
|
||||
*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename it to *time*. <br/>For example, *DATEDIFF(second, '1970-01-01', dateColumn) AS time*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. <br/>For example, *dateColumn >= DATEADD(s, 1494410783, '1970-01-01') AND dateColumn <= DATEADD(s, 1494410783, '1970-01-01')*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *DATEADD(second, 1494410783, '1970-01-01')*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *DATEADD(second, 1494410783, '1970-01-01')*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. <br/>For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
|
||||
*$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value. <br/>For example, *CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)\*300*.
|
||||
*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example).
|
||||
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
||||
|
@ -60,9 +60,9 @@ Macro example | Description
|
||||
------------ | -------------
|
||||
*$__time(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
|
||||
*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > FROM_UNIXTIME(1494410783) AND dateColumn < FROM_UNIXTIME(1494497183)*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494497183)*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
|
||||
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),*
|
||||
*$__timeGroup(dateColumn,'5m',0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example).
|
||||
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
||||
|
@ -14,11 +14,53 @@ weight = 2
|
||||
|
||||
<img class="screenshot" src="/assets/img/features/table-panel.png">
|
||||
|
||||
The new table panel is very flexible, supporting both multiple modes for time series as well as for
|
||||
The table panel is very flexible, supporting both multiple modes for time series as well as for
|
||||
table, annotation and raw JSON data. It also provides date formatting and value formatting and coloring options.
|
||||
|
||||
To view table panels in action and test different configurations with sample data, check out the [Table Panel Showcase in the Grafana Playground](http://play.grafana.org/dashboard/db/table-panel-showcase).
|
||||
|
||||
## Querying Data
|
||||
|
||||
The table panel displays the results of a query specified in the **Metrics** tab.
|
||||
The result being displayed depends on the datasource and the query, but generally there is one row per datapoint, with extra columns for associated keys and values, as well as one column for the numeric value of the datapoint.
|
||||
You can change the behavior in the section **Data to Table** below.
|
||||
|
||||
### Merge Multiple Queries per Table
|
||||
|
||||
> Only available in Grafana v5.0+.
|
||||
|
||||
Sometimes it is useful to display the results of multiple queries in the same table on corresponding rows, e.g., when comparing capacity and actual usage of resources.
|
||||
In this example usage and capacity are metrics that will have corresponding datapoints, while their associated keys and values can be used to match them.
|
||||
(This matching is only available with the **Table Transform** set to **Table**.)
|
||||
|
||||
In its simplest case, both queries return time-series data with a numeric value and a timestamp.
|
||||
If the timestamps are the same, datapoints will be matched and rendered on the same row.
|
||||
Some datasources return keys and values (labels, tags) associated with the datapoint.
|
||||
These are being matched as well if they are present in both results and have the same value.
|
||||
The following datapoints will end up on the same row with one time column, two label columns ("host" and "job") and two value columns:
|
||||
|
||||
```
|
||||
Datapoint for query A: {time: 1, host: "node-2", job: "job-8", value: 3}
|
||||
Datapoint for query B: {time: 1, host: "node-2", value: 4}
|
||||
```
|
||||
|
||||
The following two results cannot be matched and will be rendered on separate rows:
|
||||
|
||||
```
|
||||
Different time
|
||||
Datapoint for query A: {time: 1, host: "node-2", job: "job-8", value: 3}
|
||||
Datapoint for query B: {time: 2, host: "node-2", value: 4}
|
||||
|
||||
Different label "host"
|
||||
Datapoint for query A: {time: 1, host: "node-2", job: "job-8", value: 3}
|
||||
Datapoint for query B: {time: 1, host: "node-9", value: 4}
|
||||
```
|
||||
|
||||
You can still merge both of the above cases by changing the conflicting column's **Type** to **hidden** in the **Column Styles**.
|
||||
|
||||
Note that if each datapoint of your query results have multiple value fields like max, min, mean, etc., they will likely have different values and therefore will not match and render on separate rows.
|
||||
If you intend for rows to be merged but see them rendered on separate rows, check the query results in the **Query Inspector** for field values being identical across datapoints that should be merged into a row.
|
||||
|
||||
## Options overview
|
||||
|
||||
The table panel has many ways to manipulate your data for optimal presentation.
|
||||
|
@ -11,7 +11,7 @@ weight = 3
|
||||
+++
|
||||
|
||||
|
||||
## Whats new in Grafana v4.1
|
||||
## What's new in Grafana v4.1
|
||||
- **Graph**: Support for shared tooltip on all graphs as you hover over one graph. [#1578](https://github.com/grafana/grafana/pull/1578), [#6274](https://github.com/grafana/grafana/pull/6274)
|
||||
- **Victorops**: Add VictorOps notification integration [#6411](https://github.com/grafana/grafana/issues/6411), thx [@ichekrygin](https://github.com/ichekrygin)
|
||||
- **Opsgenie**: Add OpsGenie notification integratiion [#6687](https://github.com/grafana/grafana/issues/6687), thx [@kylemcc](https://github.com/kylemcc)
|
||||
@ -24,7 +24,7 @@ weight = 3
|
||||
|
||||
{{< imgbox max-width="60%" img="/img/docs/v41/shared_tooltip.gif" caption="Shared tooltip" >}}
|
||||
|
||||
Showing the tooltip on all panels at the same time has been a long standing request in Grafana and we are really happy to finally be able to release it.
|
||||
Showing the tooltip on all panels at the same time has been a long standing request in Grafana and we are really happy to finally be able to release it.
|
||||
You can enable/disable the shared tooltip from the dashboard settings menu or cycle between default, shared tooltip and shared crosshair by pressing `CTRL + O` or `CMD + O`.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
@ -50,7 +50,7 @@ Panels with a help text available have a little indicator in the top left corner
|
||||
In Grafana 4.1.0 you can configure your Cloudwatch data source with `access key` and `secret key` directly in the data source configuration page.
|
||||
This enables people to use the Cloudwatch data source without having access to the filesystem where Grafana is running.
|
||||
|
||||
Once the `access key` and `secret key` have been saved the user will no longer be able to view them.
|
||||
Once the `access key` and `secret key` have been saved the user will no longer be able to view them.
|
||||
<div class="clearfix"></div>
|
||||
|
||||
## Upgrade & Breaking changes
|
||||
|
@ -10,7 +10,7 @@ parent = "whatsnew"
|
||||
weight = -1
|
||||
+++
|
||||
|
||||
## Whats new in Grafana v4.2
|
||||
## What's new in Grafana v4.2
|
||||
|
||||
Grafana v4.2 Beta is now [available for download](https://grafana.com/grafana/download/4.2.0).
|
||||
Just like the last release this one contains lots bug fixes and minor improvements.
|
||||
|
@ -64,7 +64,7 @@ This makes exploring and filtering Prometheus data much easier.
|
||||
* **Dataproxy**: Allow grafan to renegotiate tls connection [#9250](https://github.com/grafana/grafana/issues/9250)
|
||||
* **HTTP**: set net.Dialer.DualStack to true for all http clients [#9367](https://github.com/grafana/grafana/pull/9367)
|
||||
* **Alerting**: Add diff and percent diff as series reducers [#9386](https://github.com/grafana/grafana/pull/9386), thx [@shanhuhai5739](https://github.com/shanhuhai5739)
|
||||
* **Slack**: Allow images to be uploaded to slack when Token is precent [#7175](https://github.com/grafana/grafana/issues/7175), thx [@xginn8](https://github.com/xginn8)
|
||||
* **Slack**: Allow images to be uploaded to slack when Token is present [#7175](https://github.com/grafana/grafana/issues/7175), thx [@xginn8](https://github.com/xginn8)
|
||||
* **Opsgenie**: Use their latest API instead of old version [#9399](https://github.com/grafana/grafana/pull/9399), thx [@cglrkn](https://github.com/cglrkn)
|
||||
* **Table**: Add support for displaying the timestamp with milliseconds [#9429](https://github.com/grafana/grafana/pull/9429), thx [@s1061123](https://github.com/s1061123)
|
||||
* **Hipchat**: Add metrics, message and image to hipchat notifications [#9110](https://github.com/grafana/grafana/issues/9110), thx [@eloo](https://github.com/eloo)
|
||||
|
@ -115,7 +115,7 @@ Grafana v5.1 brings an improved workflow for provisioned dashboards:
|
||||
|
||||
|
||||
Available options in the dialog will let you `Copy JSON to Clipboard` and/or `Save JSON to file` which can help you synchronize your dashboard changes back to the provisioning source.
|
||||
More information in the [Provisioning documentation](/features/datasources/prometheus/).
|
||||
More information in the [Provisioning documentation](/administration/provisioning/).
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
|
101
docs/sources/guides/whats-new-in-v5-2.md
Normal file
101
docs/sources/guides/whats-new-in-v5-2.md
Normal file
@ -0,0 +1,101 @@
|
||||
+++
|
||||
title = "What's New in Grafana v5.2"
|
||||
description = "Feature & improvement highlights for Grafana v5.2"
|
||||
keywords = ["grafana", "new", "documentation", "5.2"]
|
||||
type = "docs"
|
||||
[menu.docs]
|
||||
name = "Version 5.2"
|
||||
identifier = "v5.2"
|
||||
parent = "whatsnew"
|
||||
weight = -8
|
||||
+++
|
||||
|
||||
# What's New in Grafana v5.2
|
||||
|
||||
Grafana v5.2 brings new features, many enhancements and bug fixes. This article will detail the major new features and enhancements.
|
||||
|
||||
- [Elasticsearch alerting]({{< relref "#elasticsearch-alerting" >}}) it's finally here!
|
||||
- [Native builds for ARM]({{< relref "#native-builds-for-arm" >}}) native builds of Grafana for many more platforms!
|
||||
- [Improved Docker image]({{< relref "#improved-docker-image" >}}) with support for docker secrets
|
||||
- [Security]({{< relref "#security" >}}) make your Grafana instance more secure
|
||||
- [Prometheus]({{< relref "#prometheus" >}}) with alignment enhancements
|
||||
- [InfluxDB]({{< relref "#influxdb" >}}) now supports the `mode` function
|
||||
- [Alerting]({{< relref "#alerting" >}}) with alert notification channel type for Discord
|
||||
- [Dashboards & Panels]({{< relref "#dashboards-panels" >}}) with save & import enhancements
|
||||
|
||||
## Elasticsearch alerting
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v52/elasticsearch_alerting.png" max-width="800px" class="docs-image--right" >}}
|
||||
|
||||
Grafana v5.2 ships with an updated Elasticsearch datasource with support for alerting. Alerting support for Elasticsearch has been one of
|
||||
the most requested features by our community and now it's finally here. Please try it out and let us know what you think.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
## Native builds for ARM
|
||||
|
||||
Grafana v5.2 brings an improved build pipeline with cross-platform support. This enables native builds of Grafana for ARMv7 (x32) and ARM64 (x64).
|
||||
We've been longing for native ARM build support for ages. With the help from our amazing community this is now finally available.
|
||||
Please try it out and let us know what you think.
|
||||
|
||||
Another great addition with the improved build pipeline is that binaries for MacOS/Darwin (x64) and Windows (x64) are now automatically built and
|
||||
published for both stable and nightly builds.
|
||||
|
||||
## Improved Docker image
|
||||
|
||||
The Grafana docker image adds support for Docker secrets which enables you to supply Grafana with configuration through files. More
|
||||
information in the [Installing using Docker documentation](/installation/docker/#reading-secrets-from-files-support-for-docker-secrets).
|
||||
|
||||
## Security
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v52/login_change_password.png" max-width="800px" class="docs-image--right" >}}
|
||||
|
||||
Starting from Grafana v5.2, when you login with the administrator account using the default password you'll be presented with a form to change the password.
|
||||
We hope this encourages users to follow Grafana's best practices and change the default administrator password.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
## Prometheus
|
||||
|
||||
The Prometheus datasource now aligns the start/end of the query sent to Prometheus with the step, which ensures PromQL expressions with *rate*
|
||||
functions get consistent results, and thus avoids graphs jumping around on reload.
|
||||
|
||||
## InfluxDB
|
||||
|
||||
The InfluxDB datasource now includes support for the *mode* function which returns the most frequent value in a list of field values.
|
||||
|
||||
## Alerting
|
||||
|
||||
By popular demand Grafana now includes support for an alert notification channel type for [Discord](https://discordapp.com/).
|
||||
|
||||
## Dashboards & Panels
|
||||
|
||||
### Modified time range and variables are no longer saved by default
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v52/dashboard_save_modal.png" max-width="800px" class="docs-image--right" >}}
|
||||
|
||||
Starting from Grafana v5.2, a modified time range or variable are no longer saved by default. To save a modified
|
||||
time range or variable, you'll need to actively select that when saving a dashboard, see screenshot.
|
||||
This should hopefully make it easier to have sane defaults for time and variables in dashboards and make it more explicit
|
||||
when you actually want to overwrite those settings.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
### Import dashboard enhancements
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v52/dashboard_import.png" max-width="800px" class="docs-image--right" >}}
|
||||
|
||||
Grafana v5.2 adds support for specifying an existing folder or creating a new one when importing a dashboard - a long-awaited feature since
|
||||
Grafana v5.0 introduced support for dashboard folders and permissions. The import dashboard page has also got some general improvements
|
||||
and should now make it more clear if a possible import will overwrite an existing dashboard, or not.
|
||||
|
||||
This release also adds some improvements for those users only having editor or admin permissions in certain folders. The links to
|
||||
*Create Dashboard* and *Import Dashboard* are now available in the side navigation, in dashboard search and on the manage dashboards/folder page for a
|
||||
user that has editor role in an organization or the edit permission in at least one folder.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
## 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.
|
@ -36,11 +36,10 @@ HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"DEFAULT":
|
||||
{
|
||||
"app_mode":"production"},
|
||||
"analytics":
|
||||
{
|
||||
"DEFAULT": {
|
||||
"app_mode":"production"
|
||||
},
|
||||
"analytics": {
|
||||
"google_analytics_ua_id":"",
|
||||
"reporting_enabled":"false"
|
||||
},
|
||||
@ -195,15 +194,16 @@ HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"user_count":2,
|
||||
"org_count":1,
|
||||
"dashboard_count":4,
|
||||
"db_snapshot_count":2,
|
||||
"db_tag_count":6,
|
||||
"data_source_count":1,
|
||||
"playlist_count":1,
|
||||
"starred_db_count":2,
|
||||
"grafana_admin_count":2
|
||||
"users":2,
|
||||
"orgs":1,
|
||||
"dashboards":4,
|
||||
"snapshots":2,
|
||||
"tags":6,
|
||||
"datasources":1,
|
||||
"playlists":1,
|
||||
"stars":2,
|
||||
"alerts":2,
|
||||
"activeUsers":1
|
||||
}
|
||||
```
|
||||
|
||||
@ -340,4 +340,4 @@ HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{state: "new state", message: "alerts pause/un paused", "alertsAffected": 100}
|
||||
```
|
||||
```
|
||||
|
@ -35,10 +35,15 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
`/api/alerts?dashboardId=1`
|
||||
|
||||
- **dashboardId** – Return alerts for a specified dashboard.
|
||||
- **panelId** – Return alerts for a specified panel on a dashboard.
|
||||
- **limit** - Limit response to x number of alerts.
|
||||
- **dashboardId** – Limit response to alerts in specified dashboard(s). You can specify multiple dashboards, e.g. dashboardId=23&dashboardId=35.
|
||||
- **panelId** – Limit response to alert for a specified panel on a dashboard.
|
||||
- **query** - Limit response to alerts having a name like this value.
|
||||
- **state** - Return alerts with one or more of the following alert states: `ALL`,`no_data`, `paused`, `alerting`, `ok`, `pending`. To specify multiple states use the following format: `?state=paused&state=alerting`
|
||||
- **limit** - Limit response to *X* number of alerts.
|
||||
- **folderId** – Limit response to alerts of dashboards in specified folder(s). You can specify multiple folders, e.g. folderId=23&folderId=35.
|
||||
- **dashboardQuery** - Limit response to alerts having a dashboard name like this value.
|
||||
- **dashboardTag** - Limit response to alerts of dashboards with specified tags. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. dashboardTag=tag1&dashboardTag=tag2.
|
||||
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -49,18 +54,15 @@ Content-Type: application/json
|
||||
{
|
||||
"id": 1,
|
||||
"dashboardId": 1,
|
||||
"dashboardUId": "ABcdEFghij"
|
||||
"dashboardSlug": "sensors",
|
||||
"panelId": 1,
|
||||
"name": "fire place sensor",
|
||||
"message": "Someone is trying to break in through the fire place",
|
||||
"state": "alerting",
|
||||
"message": "Someone is trying to break in through the fire place",
|
||||
"newStateDate": "2018-05-14T05:55:20+02:00",
|
||||
"evalDate": "0001-01-01T00:00:00Z",
|
||||
"evalData": [
|
||||
{
|
||||
"metric": "fire",
|
||||
"tags": null,
|
||||
"value": 5.349999999999999
|
||||
}
|
||||
"newStateDate": "2016-12-25",
|
||||
"evalData": null,
|
||||
"executionError": "",
|
||||
"url": "http://grafana.com/dashboard/db/sensors"
|
||||
}
|
||||
@ -88,16 +90,35 @@ Content-Type: application/json
|
||||
{
|
||||
"id": 1,
|
||||
"dashboardId": 1,
|
||||
"dashboardUId": "ABcdEFghij"
|
||||
"dashboardSlug": "sensors",
|
||||
"panelId": 1,
|
||||
"name": "fire place sensor",
|
||||
"message": "Someone is trying to break in through the fire place",
|
||||
"state": "alerting",
|
||||
"newStateDate": "2016-12-25",
|
||||
"message": "Someone is trying to break in through the fire place",
|
||||
"newStateDate": "2018-05-14T05:55:20+02:00",
|
||||
"evalDate": "0001-01-01T00:00:00Z",
|
||||
"evalData": "evalMatches": [
|
||||
{
|
||||
"metric": "movement",
|
||||
"tags": {
|
||||
"name": "fireplace_chimney"
|
||||
},
|
||||
"value": 98.765
|
||||
}
|
||||
],
|
||||
"executionError": "",
|
||||
"url": "http://grafana.com/dashboard/db/sensors"
|
||||
}
|
||||
```
|
||||
|
||||
**Important Note**:
|
||||
"evalMatches" data is cached in the db when and only when the state of the alert changes
|
||||
(e.g. transitioning from "ok" to "alerting" state).
|
||||
|
||||
If data from one server triggers the alert first and, before that server is seen leaving alerting state,
|
||||
a second server also enters a state that would trigger the alert, the second server will not be visible in "evalMatches" data.
|
||||
|
||||
## Pause alert
|
||||
|
||||
`POST /api/alerts/:id/pause`
|
||||
|
@ -44,6 +44,14 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
The `Authorization` header value should be `Bearer <your api key>`.
|
||||
|
||||
The API Token can also be passed as a Basic authorization password with the special username `api_key`:
|
||||
|
||||
curl example:
|
||||
```bash
|
||||
?curl http://api_key:eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk@localhost:3000/api/org
|
||||
{"id":1,"name":"Main Org."}
|
||||
```
|
||||
|
||||
# Auth HTTP resources / actions
|
||||
|
||||
## Api Keys
|
||||
|
@ -19,6 +19,10 @@ The unique identifier (uid) of a folder can be used for uniquely identify folder
|
||||
|
||||
The uid can have a maximum length of 40 characters.
|
||||
|
||||
## A note about the General folder
|
||||
|
||||
The General folder (id=0) is special and is not part of the Folder API which means
|
||||
that you cannot use this API for retrieving information about the General folder.
|
||||
|
||||
## Get all folders
|
||||
|
||||
@ -273,14 +277,14 @@ Status Codes:
|
||||
|
||||
## Get folder by id
|
||||
|
||||
`GET /api/folders/:id`
|
||||
`GET /api/folders/id/:id`
|
||||
|
||||
Will return the folder identified by id.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/folders/1 HTTP/1.1
|
||||
GET /api/folders/id/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
@ -12,7 +12,13 @@ parent = "http_api"
|
||||
|
||||
# Organisation API
|
||||
|
||||
## Get current Organisation
|
||||
The Organisation HTTP API is divided in two resources, `/api/org` (current organisation)
|
||||
and `/api/orgs` (admin organisations). One big difference between these are that
|
||||
the admin of all organisations API only works with basic authentication, see [Admin Organisations API](#admin-organisations-api) for more information.
|
||||
|
||||
## Current Organisation API
|
||||
|
||||
### Get current Organisation
|
||||
|
||||
`GET /api/org/`
|
||||
|
||||
@ -37,135 +43,7 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
## Get Organisation by Id
|
||||
|
||||
`GET /api/orgs/:orgId`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/1
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id":1,
|
||||
"name":"Main Org.",
|
||||
"address":{
|
||||
"address1":"",
|
||||
"address2":"",
|
||||
"city":"",
|
||||
"zipCode":"",
|
||||
"state":"",
|
||||
"country":""
|
||||
}
|
||||
}
|
||||
```
|
||||
## Get Organisation by Name
|
||||
|
||||
`GET /api/orgs/name/:orgName`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs/name/Main%20Org%2E HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/name/Main%20Org%2E
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id":1,
|
||||
"name":"Main Org.",
|
||||
"address":{
|
||||
"address1":"",
|
||||
"address2":"",
|
||||
"city":"",
|
||||
"zipCode":"",
|
||||
"state":"",
|
||||
"country":""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create Organisation
|
||||
|
||||
`POST /api/orgs`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/orgs HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name":"New Org."
|
||||
}
|
||||
```
|
||||
Note: The api will work in the following two ways
|
||||
1) Need to set GF_USERS_ALLOW_ORG_CREATE=true
|
||||
2) Set the config users.allow_org_create to true in ini file
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orgId":"1",
|
||||
"message":"Organization created"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Update current Organisation
|
||||
|
||||
`PUT /api/org`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
PUT /api/org HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name":"Main Org."
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Organization updated"}
|
||||
```
|
||||
|
||||
## Get all users within the actual organisation
|
||||
### Get all users within the current organisation
|
||||
|
||||
`GET /api/org/users`
|
||||
|
||||
@ -195,36 +73,7 @@ Content-Type: application/json
|
||||
]
|
||||
```
|
||||
|
||||
## Add a new user to the actual organisation
|
||||
|
||||
`POST /api/org/users`
|
||||
|
||||
Adds a global user to the actual organisation.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/org/users HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"role": "Admin",
|
||||
"loginOrEmail": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"User added to organization"}
|
||||
```
|
||||
|
||||
## Updates the given user
|
||||
### Updates the given user
|
||||
|
||||
`PATCH /api/org/users/:userId`
|
||||
|
||||
@ -250,7 +99,7 @@ Content-Type: application/json
|
||||
{"message":"Organization user updated"}
|
||||
```
|
||||
|
||||
## Delete user in actual organisation
|
||||
### Delete user in current organisation
|
||||
|
||||
`DELETE /api/org/users/:userId`
|
||||
|
||||
@ -272,19 +121,181 @@ Content-Type: application/json
|
||||
{"message":"User removed from organization"}
|
||||
```
|
||||
|
||||
# Organisations
|
||||
### Update current Organisation
|
||||
|
||||
## Search all Organisations
|
||||
`PUT /api/org`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
PUT /api/org HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name":"Main Org."
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Organization updated"}
|
||||
```
|
||||
|
||||
### Add a new user to the current organisation
|
||||
|
||||
`POST /api/org/users`
|
||||
|
||||
Adds a global user to the current organisation.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/org/users HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"role": "Admin",
|
||||
"loginOrEmail": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"User added to organization"}
|
||||
```
|
||||
|
||||
## Admin Organisations API
|
||||
|
||||
The Admin Organisations HTTP API does not currently work with an API Token. API Tokens are currently
|
||||
only linked to an organization and an organization role. They cannot be given the permission of server
|
||||
admin, only users can be given that permission. So in order to use these API calls you will have to
|
||||
use Basic Auth and the Grafana user must have the Grafana Admin permission (The default admin user
|
||||
is called `admin` and has permission to use this API).
|
||||
|
||||
### Get Organisation by Id
|
||||
|
||||
`GET /api/orgs/:orgId`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id":1,
|
||||
"name":"Main Org.",
|
||||
"address":{
|
||||
"address1":"",
|
||||
"address2":"",
|
||||
"city":"",
|
||||
"zipCode":"",
|
||||
"state":"",
|
||||
"country":""
|
||||
}
|
||||
}
|
||||
```
|
||||
### Get Organisation by Name
|
||||
|
||||
`GET /api/orgs/name/:orgName`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs/name/Main%20Org%2E HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id":1,
|
||||
"name":"Main Org.",
|
||||
"address":{
|
||||
"address1":"",
|
||||
"address2":"",
|
||||
"city":"",
|
||||
"zipCode":"",
|
||||
"state":"",
|
||||
"country":""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Create Organisation
|
||||
|
||||
`POST /api/orgs`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/orgs HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name":"New Org."
|
||||
}
|
||||
```
|
||||
Note: The api will work in the following two ways
|
||||
1) Need to set GF_USERS_ALLOW_ORG_CREATE=true
|
||||
2) Set the config users.allow_org_create to true in ini file
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orgId":"1",
|
||||
"message":"Organization created"
|
||||
}
|
||||
```
|
||||
|
||||
### Search all Organisations
|
||||
|
||||
`GET /api/orgs`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs
|
||||
@ -303,11 +314,12 @@ Content-Type: application/json
|
||||
]
|
||||
```
|
||||
|
||||
## Update Organisation
|
||||
### Update Organisation
|
||||
|
||||
`PUT /api/orgs/:orgId`
|
||||
|
||||
Update Organisation, fields *Address 1*, *Address 2*, *City* are not implemented yet.
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
@ -315,7 +327,6 @@ Update Organisation, fields *Address 1*, *Address 2*, *City* are not implemented
|
||||
PUT /api/orgs/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name":"Main Org 2."
|
||||
@ -331,17 +342,40 @@ Content-Type: application/json
|
||||
{"message":"Organization updated"}
|
||||
```
|
||||
|
||||
## Get Users in Organisation
|
||||
### Delete Organisation
|
||||
|
||||
`DELETE /api/orgs/:orgId`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
DELETE /api/orgs/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Organization deleted"}
|
||||
```
|
||||
|
||||
### Get Users in Organisation
|
||||
|
||||
`GET /api/orgs/:orgId/users`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/orgs/1/users HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/1/users
|
||||
@ -363,25 +397,24 @@ Content-Type: application/json
|
||||
]
|
||||
```
|
||||
|
||||
## Add User in Organisation
|
||||
### Add User in Organisation
|
||||
|
||||
`POST /api/orgs/:orgId/users`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/orgs/1/users HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"loginOrEmail":"user",
|
||||
"role":"Viewer"
|
||||
}
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/1/users
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -392,17 +425,18 @@ Content-Type: application/json
|
||||
{"message":"User added to organization"}
|
||||
```
|
||||
|
||||
## Update Users in Organisation
|
||||
### Update Users in Organisation
|
||||
|
||||
`PATCH /api/orgs/:orgId/users/:userId`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
PATCH /api/orgs/1/users/2 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"role":"Admin"
|
||||
@ -418,17 +452,18 @@ Content-Type: application/json
|
||||
{"message":"Organization user updated"}
|
||||
```
|
||||
|
||||
## Delete User in Organisation
|
||||
### Delete User in Organisation
|
||||
|
||||
`DELETE /api/orgs/:orgId/users/:userId`
|
||||
|
||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
DELETE /api/orgs/1/users/2 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
286
docs/sources/http_api/playlist.md
Normal file
286
docs/sources/http_api/playlist.md
Normal file
@ -0,0 +1,286 @@
|
||||
+++
|
||||
title = "Playlist HTTP API "
|
||||
description = "Playlist Admin HTTP API"
|
||||
keywords = ["grafana", "http", "documentation", "api", "playlist"]
|
||||
aliases = ["/http_api/playlist/"]
|
||||
type = "docs"
|
||||
[menu.docs]
|
||||
name = "Playlist"
|
||||
parent = "http_api"
|
||||
+++
|
||||
|
||||
# Playlist API
|
||||
|
||||
## Search Playlist
|
||||
|
||||
`GET /api/playlists`
|
||||
|
||||
Get all existing playlist for the current organization using pagination
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
GET /api/playlists HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
Querystring Parameters:
|
||||
|
||||
These parameters are used as querystring parameters.
|
||||
|
||||
- **query** - Limit response to playlist having a name like this value.
|
||||
- **limit** - Limit response to *X* number of playlist.
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "my playlist",
|
||||
"interval": "5m"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get one playlist
|
||||
|
||||
`GET /api/playlists/:id`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
GET /api/playlists/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
{
|
||||
"id" : 1,
|
||||
"name": "my playlist",
|
||||
"interval": "5m",
|
||||
"orgId": "my org",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_id",
|
||||
"value": "3",
|
||||
"order": 1,
|
||||
"title":"my third dasboard"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_tag",
|
||||
"value": "myTag",
|
||||
"order": 2,
|
||||
"title":"my other dasboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get Playlist items
|
||||
|
||||
`GET /api/playlists/:id/items`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
GET /api/playlists/1/items HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_id",
|
||||
"value": "3",
|
||||
"order": 1,
|
||||
"title":"my third dasboard"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_tag",
|
||||
"value": "myTag",
|
||||
"order": 2,
|
||||
"title":"my other dasboard"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get Playlist dashboards
|
||||
|
||||
`GET /api/playlists/:id/dashboards`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
GET /api/playlists/1/dashboards HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
[
|
||||
{
|
||||
"id": 3,
|
||||
"title": "my third dasboard",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title":"my other dasboard"
|
||||
"order": 2,
|
||||
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Create a playlist
|
||||
|
||||
`POST /api/playlists/`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
PUT /api/playlists/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
{
|
||||
"name": "my playlist",
|
||||
"interval": "5m",
|
||||
"items": [
|
||||
{
|
||||
"type": "dashboard_by_id",
|
||||
"value": "3",
|
||||
"order": 1,
|
||||
"title":"my third dasboard"
|
||||
},
|
||||
{
|
||||
"type": "dashboard_by_tag",
|
||||
"value": "myTag",
|
||||
"order": 2,
|
||||
"title":"my other dasboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "my playlist",
|
||||
"interval": "5m"
|
||||
}
|
||||
```
|
||||
|
||||
## Update a playlist
|
||||
|
||||
`PUT /api/playlists/:id`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
PUT /api/playlists/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
{
|
||||
"name": "my playlist",
|
||||
"interval": "5m",
|
||||
"items": [
|
||||
{
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_id",
|
||||
"value": "3",
|
||||
"order": 1,
|
||||
"title":"my third dasboard"
|
||||
},
|
||||
{
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_tag",
|
||||
"value": "myTag",
|
||||
"order": 2,
|
||||
"title":"my other dasboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
{
|
||||
"id" : 1,
|
||||
"name": "my playlist",
|
||||
"interval": "5m",
|
||||
"orgId": "my org",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_id",
|
||||
"value": "3",
|
||||
"order": 1,
|
||||
"title":"my third dasboard"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"playlistId": 1,
|
||||
"type": "dashboard_by_tag",
|
||||
"value": "myTag",
|
||||
"order": 2,
|
||||
"title":"my other dasboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Delete a playlist
|
||||
|
||||
`DELETE /api/playlists/:id`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
DELETE /api/playlists/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
{}
|
||||
```
|
@ -70,7 +70,7 @@ JSON Body schema:
|
||||
Content-Type: application/json
|
||||
{
|
||||
"deleteKey":"XXXXXXX",
|
||||
"deleteUrl":"myurl/dashboard/snapshot/XXXXXXX",
|
||||
"deleteUrl":"myurl/api/snapshots-delete/XXXXXXX",
|
||||
"key":"YYYYYYY",
|
||||
"url":"myurl/dashboard/snapshot/YYYYYYY"
|
||||
}
|
||||
@ -81,7 +81,46 @@ Keys:
|
||||
- **deleteKey** – Key generated to delete the snapshot
|
||||
- **key** – Key generated to share the dashboard
|
||||
|
||||
## Get Snapshot by Id
|
||||
## Get list of Snapshots
|
||||
|
||||
`GET /api/dashboard/snapshots`
|
||||
|
||||
Query parameters:
|
||||
|
||||
- **query** – Search Query
|
||||
- **limit** – Limit the number of returned results
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/dashboard/snapshots HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"id":8,
|
||||
"name":"Home",
|
||||
"key":"YYYYYYY",
|
||||
"orgId":1,
|
||||
"userId":1,
|
||||
"external":false,
|
||||
"externalUrl":"",
|
||||
"expires":"2200-13-32T25:23:23+02:00",
|
||||
"created":"2200-13-32T28:24:23+02:00",
|
||||
"updated":"2200-13-32T28:24:23+02:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get Snapshot by Key
|
||||
|
||||
`GET /api/snapshots/:key`
|
||||
|
||||
@ -90,7 +129,6 @@ Keys:
|
||||
```http
|
||||
GET /api/snapshots/YYYYYYY HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
@ -140,16 +178,15 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Snapshot by deleteKey
|
||||
## Delete Snapshot by Key
|
||||
|
||||
`GET /api/snapshots-delete/:deleteKey`
|
||||
`DELETE /api/snapshots/:key`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/snapshots/YYYYYYY HTTP/1.1
|
||||
DELETE /api/snapshots/YYYYYYY HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
@ -159,5 +196,27 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Snapshot deleted. It might take an hour before it's cleared from a CDN cache."}
|
||||
{"message":"Snapshot deleted. It might take an hour before it's cleared from any CDN caches."}
|
||||
```
|
||||
|
||||
## Delete Snapshot by deleteKey
|
||||
|
||||
This API call can be used without authentication by using the secret delete key for the snapshot.
|
||||
|
||||
`GET /api/snapshots-delete/:deleteKey`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/snapshots-delete/XXXXXXX HTTP/1.1
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Snapshot deleted. It might take an hour before it's cleared from any CDN caches."}
|
||||
```
|
@ -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.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||
<h4>What's new in v5.0</h4>
|
||||
<p>Article on all the new cool features and enhancements in v5.0</p>
|
||||
<a href="{{< relref "guides/whats-new-in-v5-2.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||
<h4>What's new in v5.2</h4>
|
||||
<p>Article on all the new cool features and enhancements in v5.2</p>
|
||||
</a>
|
||||
<a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||
<h4>Screencasts</h4>
|
||||
|
@ -26,7 +26,7 @@ Otherwise Grafana will not behave correctly. See example below.
|
||||
## Examples
|
||||
Here are some example configurations for running Grafana behind a reverse proxy.
|
||||
|
||||
### Grafana configuration (ex http://foo.bar.com)
|
||||
### Grafana configuration (ex http://foo.bar)
|
||||
|
||||
```bash
|
||||
[server]
|
||||
@ -47,7 +47,7 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
### Examples with **sub path** (ex http://foo.bar.com/grafana)
|
||||
### Examples with **sub path** (ex http://foo.bar/grafana)
|
||||
|
||||
#### Grafana configuration with sub path
|
||||
```bash
|
||||
|
@ -80,6 +80,11 @@ Path to where Grafana stores the sqlite3 database (if used), file based
|
||||
sessions (if used), and other data. This path is usually specified via
|
||||
command line in the init.d script or the systemd service file.
|
||||
|
||||
### temp_data_lifetime
|
||||
|
||||
How long temporary images in `data` directory should be kept. Defaults to: `24h`. Supported modifiers: `h` (hours),
|
||||
`m` (minutes), for example: `168h`, `30m`, `10h30m`. Use `0` to never clean up temporary files.
|
||||
|
||||
### logs
|
||||
|
||||
Path to where Grafana will store logs. This path is usually specified via
|
||||
@ -291,6 +296,12 @@ Set to `true` to automatically add new users to the main organization
|
||||
(id 1). When set to `false`, new users will automatically cause a new
|
||||
organization to be created for that new user.
|
||||
|
||||
### auto_assign_org_id
|
||||
|
||||
Set this value to automatically add new users to the provided org.
|
||||
This requires `auto_assign_org` to be set to `true`. Please make sure
|
||||
that this organization does already exists.
|
||||
|
||||
### auto_assign_org_role
|
||||
|
||||
The role new users will be assigned for the main organization (if the
|
||||
@ -419,25 +430,33 @@ allowed_organizations = github google
|
||||
|
||||
## [auth.google]
|
||||
|
||||
You need to create a Google project. You can do this in the [Google
|
||||
Developer Console](https://console.developers.google.com/project). When
|
||||
you create the project you will need to specify a callback URL. Specify
|
||||
this as callback:
|
||||
First, you need to create a Google OAuth Client:
|
||||
|
||||
```bash
|
||||
http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/google
|
||||
```
|
||||
1. Go to https://console.developers.google.com/apis/credentials
|
||||
|
||||
This callback URL must match the full HTTP address that you use in your
|
||||
browser to access Grafana, but with the prefix path of `/login/google`.
|
||||
When the Google project is created you will get a Client ID and a Client
|
||||
Secret. Specify these in the Grafana configuration file. For example:
|
||||
2. Click the 'Create Credentials' button, then click 'OAuth Client ID' in the
|
||||
menu that drops down
|
||||
|
||||
3. Enter the following:
|
||||
|
||||
- Application Type: Web Application
|
||||
- Name: Grafana
|
||||
- Authorized Javascript Origins: https://grafana.mycompany.com
|
||||
- Authorized Redirect URLs: https://grafana.mycompany.com/login/google
|
||||
|
||||
Replace https://grafana.mycompany.com with the URL of your Grafana instance.
|
||||
|
||||
4. Click Create
|
||||
|
||||
5. Copy the Client ID and Client Secret from the 'OAuth Client' modal
|
||||
|
||||
Specify the Client ID and Secret in the Grafana configuration file. For example:
|
||||
|
||||
```bash
|
||||
[auth.google]
|
||||
enabled = true
|
||||
client_id = YOUR_GOOGLE_APP_CLIENT_ID
|
||||
client_secret = YOUR_GOOGLE_APP_CLIENT_SECRET
|
||||
client_id = CLIENT_ID
|
||||
client_secret = CLIENT_SECRET
|
||||
scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
|
||||
auth_url = https://accounts.google.com/o/oauth2/auth
|
||||
token_url = https://accounts.google.com/o/oauth2/token
|
||||
@ -844,7 +863,7 @@ Secret key. e.g. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
Url to where Grafana will send PUT request with images
|
||||
|
||||
### public_url
|
||||
Optional parameter. Url to send to users in notifications, directly appended with the resulting uploaded file name.
|
||||
Optional parameter. Url to send to users in notifications. If the string contains the sequence ${file}, it will be replaced with the uploaded filename. Otherwise, the file name will be appended to the path part of the url, leaving any query string unchanged.
|
||||
|
||||
### username
|
||||
basic auth username
|
||||
|
@ -15,10 +15,9 @@ weight = 1
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable for Debian-based Linux | [grafana_5.1.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.2_amd64.deb)
|
||||
<!--
|
||||
Beta for Debian-based Linux | [grafana_5.1.0-beta1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.0-beta1_amd64.deb)
|
||||
-->
|
||||
Stable for Debian-based Linux | [x86-64](https://grafana.com/grafana/download?platform=linux)
|
||||
Stable for Debian-based Linux | [ARM64](https://grafana.com/grafana/download?platform=arm)
|
||||
Stable for Debian-based Linux | [ARMv7](https://grafana.com/grafana/download?platform=arm)
|
||||
|
||||
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
|
||||
installation.
|
||||
@ -27,17 +26,18 @@ installation.
|
||||
|
||||
|
||||
```bash
|
||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.2_amd64.deb
|
||||
wget <debian package url>
|
||||
sudo apt-get install -y adduser libfontconfig
|
||||
sudo dpkg -i grafana_5.1.2_amd64.deb
|
||||
sudo dpkg -i grafana_5.1.4_amd64.deb
|
||||
```
|
||||
|
||||
<!-- ## Install Latest Beta
|
||||
Example:
|
||||
|
||||
```bash
|
||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.0-beta1_amd64.deb
|
||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.4_amd64.deb
|
||||
sudo apt-get install -y adduser libfontconfig
|
||||
sudo dpkg -i grafana_5.1.0-beta1_amd64.deb
|
||||
``` -->
|
||||
sudo dpkg -i grafana_5.1.4_amd64.deb
|
||||
```
|
||||
|
||||
## APT Repository
|
||||
|
||||
|
@ -49,6 +49,11 @@ $ docker run \
|
||||
grafana/grafana:5.1.0
|
||||
```
|
||||
|
||||
## Running of the master branch
|
||||
|
||||
For every successful commit we publish a Grafana container to [`grafana/grafana`](https://hub.docker.com/r/grafana/grafana/tags/) and [`grafana/grafana-dev`](https://hub.docker.com/r/grafana/grafana-dev/tags/). In `grafana/grafana` container we will always overwrite the `master` tag with the latest version. In `grafana/grafana-dev` we will include
|
||||
the git commit in the tag. If you run Grafana master in production we **strongly** recommend that you use the later since different machines might run different version of grafana if they pull the master tag at different times.
|
||||
|
||||
## Installing Plugins for Grafana
|
||||
|
||||
Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma separated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}` and install them when Grafana starts.
|
||||
@ -130,6 +135,20 @@ ID=$(id -u) # saves your user id in the ID variable
|
||||
docker run -d --user $ID --volume "$PWD/data:/var/lib/grafana" -p 3000:3000 grafana/grafana:5.1.0
|
||||
```
|
||||
|
||||
## Reading secrets from files (support for Docker Secrets)
|
||||
|
||||
> Only available in Grafana v5.2+.
|
||||
|
||||
It's possible to supply Grafana with configuration through files. This works well with [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) as the secrets by default gets mapped into `/run/secrets/<name of secret>` of the container.
|
||||
|
||||
You can do this with any of the configuration options in conf/grafana.ini by setting `GF_<SectionName>_<KeyName>__FILE` to the path of the file holding the secret.
|
||||
|
||||
Let's say you want to set the admin password this way.
|
||||
|
||||
- Admin password secret: `/run/secrets/admin_password`
|
||||
- Environment variable: `GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/admin_password`
|
||||
|
||||
|
||||
## Migration from a previous version of the docker container to 5.1 or later
|
||||
|
||||
The docker container for Grafana has seen a major rewrite for 5.1.
|
||||
@ -147,7 +166,7 @@ The docker container for Grafana has seen a major rewrite for 5.1.
|
||||
|
||||
Previously `/var/lib/grafana`, `/etc/grafana` and `/var/log/grafana` were defined as volumes in the `Dockerfile`. This led to the creation of three volumes each time a new instance of the Grafana container started, whether you wanted it or not.
|
||||
|
||||
You should always be careful to define your own named volume for storage, but if you depended on these volumes you should be aware that an upgraded container will no longer have them.
|
||||
You should always be careful to define your own named volume for storage, but if you depended on these volumes you should be aware that an upgraded container will no longer have them.
|
||||
|
||||
**Warning**: when migrating from an earlier version to 5.1 or later using docker compose and implicit volumes you need to use `docker inspect` to find out which volumes your container is mapped to so that you can map them to the upgraded container as well. You will also have to change file ownership (or user) as documented below.
|
||||
|
||||
@ -182,7 +201,7 @@ services:
|
||||
|
||||
#### Modifying permissions
|
||||
|
||||
The commands below will run bash inside the Grafana container with your volume mapped in. This makes it possible to modify the file ownership to match the new container. Always be careful when modifying permissions.
|
||||
The commands below will run bash inside the Grafana container with your volume mapped in. This makes it possible to modify the file ownership to match the new container. Always be careful when modifying permissions.
|
||||
|
||||
```bash
|
||||
$ docker run -ti --user root --volume "<your volume mapping here>" --entrypoint bash grafana/grafana:5.1.0
|
||||
|
@ -23,8 +23,9 @@ specific configuration file (default: `/etc/grafana/ldap.toml`).
|
||||
### Example config
|
||||
|
||||
```toml
|
||||
# Set to true to log user information returned from LDAP
|
||||
verbose_logging = false
|
||||
# To troubleshoot and get more log info enable ldap debug logging in grafana.ini
|
||||
# [log]
|
||||
# filters = ldap:debug
|
||||
|
||||
[[servers]]
|
||||
# Ldap server host (specify multiple hosts space separated)
|
||||
@ -73,6 +74,8 @@ email = "email"
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=admins,dc=grafana,dc=org"
|
||||
org_role = "Admin"
|
||||
# To make user an instance admin (Grafana Admin) uncomment line below
|
||||
# grafana_admin = true
|
||||
# The Grafana organization database id, optional, if left out the default org (id 1) will be used. Setting this allows for multiple group_dn's to be assigned to the same org_role provided the org_id differs
|
||||
# org_id = 1
|
||||
|
||||
@ -132,6 +135,10 @@ Users page, this change will be reset the next time the user logs in. If you
|
||||
change the LDAP groups of a user, the change will take effect the next
|
||||
time the user logs in.
|
||||
|
||||
### Grafana Admin
|
||||
with a servers.group_mappings section you can set grafana_admin = true or false to sync Grafana Admin permission. A Grafana server admin has admin access over all orgs &
|
||||
users.
|
||||
|
||||
### Priority
|
||||
The first group mapping that an LDAP user is matched to will be used for the sync. If you have LDAP users that fit multiple mappings, the topmost mapping in the TOML config will be used.
|
||||
|
||||
|
@ -11,6 +11,8 @@ weight = 4
|
||||
|
||||
# Installing on Mac
|
||||
|
||||
## Install using homebrew
|
||||
|
||||
Installation can be done using [homebrew](http://brew.sh/)
|
||||
|
||||
Install latest stable:
|
||||
@ -75,3 +77,18 @@ If you want to manually install a plugin place it here: `/usr/local/var/lib/graf
|
||||
|
||||
The default sqlite database is located at `/usr/local/var/lib/grafana`
|
||||
|
||||
## Installing from binary tar file
|
||||
|
||||
Download [the latest `.tar.gz` file](https://grafana.com/get) and
|
||||
extract it. This will extract into a folder named after the version you
|
||||
downloaded. This folder contains all files required to run Grafana. There are
|
||||
no init scripts or install scripts in this package.
|
||||
|
||||
To configure Grafana add a configuration file named `custom.ini` to the
|
||||
`conf` folder and override any of the settings defined in
|
||||
`conf/defaults.ini`.
|
||||
|
||||
Start Grafana by executing `./bin/grafana-server web`. The `grafana-server`
|
||||
binary needs the working directory to be the root install directory (where the
|
||||
binary and the `public` folder is located).
|
||||
|
||||
|
@ -15,42 +15,49 @@ weight = 2
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.1.2 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.2-1.x86_64.rpm)
|
||||
<!--
|
||||
Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [5.1.0-beta1 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0-beta1.x86_64.rpm)
|
||||
-->
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [x86-64](https://grafana.com/grafana/download?platform=linux)
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [ARM64](https://grafana.com/grafana/download?platform=arm)
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [ARMv7](https://grafana.com/grafana/download?platform=arm)
|
||||
|
||||
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
|
||||
installation.
|
||||
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation.
|
||||
|
||||
## Install Stable
|
||||
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
```bash
|
||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.2-1.x86_64.rpm
|
||||
$ sudo yum install <rpm package url>
|
||||
```
|
||||
|
||||
<!-- ## Install Beta
|
||||
Example:
|
||||
|
||||
```bash
|
||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.0-beta1.x86_64.rpm
|
||||
``` -->
|
||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm
|
||||
```
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
Or install manually using `rpm`. First execute
|
||||
|
||||
```bash
|
||||
$ wget <rpm package url>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm
|
||||
```
|
||||
|
||||
### On CentOS / Fedora / Redhat:
|
||||
|
||||
```bash
|
||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.2-1.x86_64.rpm
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-5.1.2-1.x86_64.rpm
|
||||
$ sudo rpm -Uvh <local rpm package>
|
||||
```
|
||||
|
||||
#### On OpenSuse:
|
||||
### On OpenSuse:
|
||||
|
||||
```bash
|
||||
$ sudo rpm -i --nodeps grafana-5.1.2-1.x86_64.rpm
|
||||
$ sudo rpm -i --nodeps <local rpm package>
|
||||
```
|
||||
|
||||
## Install via YUM Repository
|
||||
|
@ -21,7 +21,7 @@ the data source response.
|
||||
|
||||
To check this you should use Query Inspector (new in Grafana v4.5). The query Inspector shows query requests and responses.
|
||||
|
||||
For more on the query insector read [this guide here](https://community.grafana.com/t/using-grafanas-query-inspector-to-troubleshoot-issues/2630). For
|
||||
For more on the query inspector read [this guide here](https://community.grafana.com/t/using-grafanas-query-inspector-to-troubleshoot-issues/2630). For
|
||||
older versions of Grafana read the [how troubleshoot metric query issue](https://community.grafana.com/t/how-to-troubleshoot-metric-query-issues/50/2) article.
|
||||
|
||||
## Logging
|
||||
|
@ -12,17 +12,15 @@ weight = 3
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Latest stable package for Windows | [grafana-5.1.2.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.2.windows-x64.zip)
|
||||
|
||||
<!--
|
||||
Latest beta package for Windows | [grafana.5.1.0-beta1.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta5.windows-x64.zip)
|
||||
-->
|
||||
Latest stable package for Windows | [x64](https://grafana.com/grafana/download?platform=windows)
|
||||
|
||||
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
|
||||
installation.
|
||||
|
||||
## Configure
|
||||
|
||||
**Important:** After you've downloaded the zip file and before extracting it, make sure to open properties for that file (right-click Properties) and check the `unblock` checkbox and `Ok`.
|
||||
|
||||
The zip file contains a folder with the current Grafana version. Extract
|
||||
this folder to anywhere you want Grafana to run from. Go into the
|
||||
`conf` directory and copy `sample.ini` to `custom.ini`. You should edit
|
||||
|
99
docs/sources/plugins/developing/auth-for-datasources.md
Normal file
99
docs/sources/plugins/developing/auth-for-datasources.md
Normal file
@ -0,0 +1,99 @@
|
||||
+++
|
||||
title = "Authentication for Datasource Plugins"
|
||||
type = "docs"
|
||||
[menu.docs]
|
||||
name = "Authentication for Datasource Plugins"
|
||||
parent = "developing"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
# Authentication for Datasource Plugins
|
||||
|
||||
Grafana has a proxy feature that proxies all data requests through the Grafana backend. This is very useful when your datasource plugin calls an external/thirdy-party API. The Grafana proxy adds CORS headers and can authenticate against the external API. This means that a datasource plugin that proxies all requests via Grafana can enable token authentication and the token will be renewed automatically for the user when it expires.
|
||||
|
||||
The plugin config page should save the API key/password to be encrypted (using the `secureJsonData` feature) and then when a request from the datasource is made, the Grafana Proxy will:
|
||||
|
||||
1. decrypt the API key/password on the backend.
|
||||
2. carry out authentication and generate an OAuth token that will be added as an `Authorization` HTTP header to all requests (or it will add a HTTP header with the API key).
|
||||
3. renew the token if it expires.
|
||||
|
||||
This means that users that access the datasource config page cannot access the API key or password after is saved the first time and that no secret keys are sent in plain text through the browser where they can be spied on.
|
||||
|
||||
For backend authentication to work, the external/third-party API must either have an OAuth endpoint or that the API accepts an API key as a HTTP header for authentication.
|
||||
|
||||
## Plugin Routes
|
||||
|
||||
You can specify routes in the `plugin.json` file for your datasource plugin. [Here is an example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L30-L95) with lots of routes (though most plugins will just have one route).
|
||||
|
||||
When you build your url to the third-party API in your datasource class, the url should start with the text specified in the path field for a route. The proxy will strip out the path text and replace it with the value in the url field.
|
||||
|
||||
For example, if my code makes a call to url `azuremonitor/foo/bar` with this code:
|
||||
|
||||
```js
|
||||
this.backendSrv.datasourceRequest({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
})
|
||||
```
|
||||
|
||||
and this route:
|
||||
|
||||
```json
|
||||
"routes": [{
|
||||
"path": "azuremonitor",
|
||||
"method": "GET",
|
||||
"url": "https://management.azure.com",
|
||||
...
|
||||
}]
|
||||
```
|
||||
|
||||
then the Grafana proxy will transform it into "https://management.azure.com/foo/bar" and add CORS headers.
|
||||
|
||||
The `method` parameter is optional. It can be set to any HTTP verb to provide more fine-grained control.
|
||||
|
||||
## Encrypting Sensitive Data
|
||||
|
||||
When a user saves a password or secret with your datasource plugin's Config page, then you can save data to a column in the datasource table called `secureJsonData` that is an encrypted blob. Any data saved in the blob is encrypted by Grafana and can only be decrypted by the Grafana server on the backend. This means once a password is saved, no sensitive data is sent to the browser. If the password is saved in the `jsonData` blob or the `password` field then it is unencrypted and anyone with Admin access (with the help of Chrome Developer Tools) can read it.
|
||||
|
||||
This is an example of using the `secureJsonData` blob to save a property called `password`:
|
||||
|
||||
```html
|
||||
<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>
|
||||
```
|
||||
|
||||
## API Key/HTTP Header Authentication
|
||||
|
||||
Some third-party API's accept a HTTP Header for authentication. The [example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L91-L93) below has a `headers` section that defines the name of the HTTP Header that the API expects and it uses the `SecureJSONData` blob to fetch an encrypted API key. The Grafana server proxy will decrypt the key, add the `X-API-Key` header to the request and forward it to the third-party API.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "appinsights",
|
||||
"method": "GET",
|
||||
"url": "https://api.applicationinsights.io",
|
||||
"headers": [
|
||||
{"name": "X-API-Key", "content": "{{.SecureJsonData.appInsightsApiKey}}"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## How Token Authentication Works
|
||||
|
||||
The token auth section in the `plugin.json` file looks like this:
|
||||
|
||||
```json
|
||||
"tokenAuth": {
|
||||
"url": "https://login.microsoftonline.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
"params": {
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": "{{.JsonData.clientId}}",
|
||||
"client_secret": "{{.SecureJsonData.clientSecret}}",
|
||||
"resource": "https://management.azure.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This interpolates in data from both `jsonData` and `secureJsonData` to generate the token request to the third-party API. It is common for tokens to have a short expiry period (30 minutes). The proxy in Grafana server will automatically renew the token if it has expired.
|
||||
|
||||
## Always Restart the Grafana Server After Route Changes
|
||||
|
||||
The plugin.json files are only loaded when the Grafana server starts so when a route is added or changed then the Grafana server has to be restarted for the changes to take effect.
|
@ -25,7 +25,6 @@ To interact with the rest of grafana the plugins module file can export 5 differ
|
||||
- Datasource (Required)
|
||||
- QueryCtrl (Required)
|
||||
- ConfigCtrl (Required)
|
||||
- QueryOptionsCtrl
|
||||
- AnnotationsQueryCtrl
|
||||
|
||||
## Plugin json
|
||||
@ -182,12 +181,6 @@ A JavaScript class that will be instantiated and treated as an Angular controlle
|
||||
|
||||
Requires a static template or templateUrl variable which will be rendered as the view for this controller.
|
||||
|
||||
## QueryOptionsCtrl
|
||||
|
||||
A JavaScript class that will be instantiated and treated as an Angular controller when the user edits metrics in a panel. This controller is responsible for handling panel wide settings for the datasource, such as interval, rate and aggregations if needed.
|
||||
|
||||
Requires a static template or templateUrl variable which will be rendered as the view for this controller.
|
||||
|
||||
## AnnotationsQueryCtrl
|
||||
|
||||
A JavaScript class that will be instantiated and treated as an Angular controller when the user choose this type of datasource in the templating menu in the dashboard.
|
||||
|
@ -25,7 +25,7 @@ export class MyPanelCtrl extends PanelCtrl {
|
||||
...
|
||||
```
|
||||
|
||||
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modifiy that element adding a scrollbar.
|
||||
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
|
||||
|
||||
|
||||
|
||||
|
175
docs/sources/plugins/developing/plugin-review-guidelines.md
Normal file
175
docs/sources/plugins/developing/plugin-review-guidelines.md
Normal file
@ -0,0 +1,175 @@
|
||||
+++
|
||||
title = "Plugin Review Guidelines"
|
||||
type = "docs"
|
||||
[menu.docs]
|
||||
name = "Plugin Review Guidelines"
|
||||
parent = "developing"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
# Plugin Review Guidelines
|
||||
|
||||
The Grafana team reviews all plugins that are published on Grafana.com. There are two areas we review, the metadata for the plugin and the plugin functionality.
|
||||
|
||||
## Metadata
|
||||
|
||||
The plugin metadata consists of a `plugin.json` file and the README.md file. These `plugin.json` file is used by Grafana to load the plugin and the README.md file is shown in the plugins section of Grafana and the plugins section of Grafana.com.
|
||||
|
||||
### README.md
|
||||
|
||||
The README.md file is shown on the plugins page in Grafana and the plugin page on Grafana.com. There are some differences between the GitHub markdown and the markdown allowed in Grafana/Grafana.com:
|
||||
|
||||
- Cannot contain inline HTML.
|
||||
- Any image links should be absolute links. For example: https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/dist/img/grafana_cloud_install.png
|
||||
|
||||
The README should:
|
||||
|
||||
- describe the purpose of the plugin.
|
||||
- contain steps on how to get started.
|
||||
|
||||
### Plugin.json
|
||||
|
||||
The `plugin.json` file is the same concept as the `package.json` file for an npm package. When the Grafana server starts it will scan the plugin folders (all folders in the data/plugins subfolder) and load every folder that contains a `plugin.json` file unless the folder contains a subfolder named `dist`. In that case, the Grafana server will load the `dist` folder instead.
|
||||
|
||||
A minimal `plugin.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Clock",
|
||||
"id": "yourorg-clock-panel",
|
||||
|
||||
"info": {
|
||||
"description": "Clock panel for grafana",
|
||||
"author": {
|
||||
"name": "Author Name",
|
||||
"url": "http://yourwebsite.com"
|
||||
},
|
||||
"keywords": ["clock", "panel"],
|
||||
"version": "1.0.0",
|
||||
"updated": "2018-03-24"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "3.x.x",
|
||||
"plugins": [ ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The convention for the plugin id is [github username/org]-[plugin name]-[datasource|app|panel] and it has to be unique. Although if org and plugin name are the same then [plugin name]-[datasource|app|panel] is also valid. The org **cannot** be `grafana` unless it is a plugin created by the Grafana core team.
|
||||
|
||||
Examples:
|
||||
|
||||
- raintank-worldping-app
|
||||
- ryantxu-ajax-panel
|
||||
- alexanderzobnin-zabbix-app
|
||||
- hawkular-datasource
|
||||
|
||||
- The `type` field should be either `datasource` `app` or `panel`.
|
||||
- The `version` field should be in the form: x.x.x e.g. `1.0.0` or `0.4.1`.
|
||||
|
||||
The full file format for the `plugin.json` file is described [here](http://docs.grafana.org/plugins/developing/plugin.json/).
|
||||
|
||||
## Plugin Language
|
||||
|
||||
JavaScript, TypeScript, ES6 (or any other language) are all fine as long as the contents of the `dist` subdirectory are transpiled to JavaScript (ES5).
|
||||
|
||||
## File and Directory Structure Conventions
|
||||
|
||||
Here is a typical directory structure for a plugin.
|
||||
|
||||
```bash
|
||||
johnnyb-awesome-datasource
|
||||
|-- dist
|
||||
|-- src
|
||||
| |-- img
|
||||
| | |-- logo.svg
|
||||
| |-- partials
|
||||
| | |-- annotations.editor.html
|
||||
| | |-- config.html
|
||||
| | |-- query.editor.html
|
||||
| |-- datasource.js
|
||||
| |-- module.js
|
||||
| |-- plugin.json
|
||||
| |-- query_ctrl.js
|
||||
|-- Gruntfile.js
|
||||
|-- LICENSE
|
||||
|-- package.json
|
||||
|-- README.md
|
||||
```
|
||||
|
||||
Most JavaScript projects have a build step. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
|
||||
|
||||
Directories:
|
||||
|
||||
- `src/` contains plugin source files.
|
||||
- `src/partials` contains html templates.
|
||||
- `src/img` contains plugin logos and other images.
|
||||
- `dist/` contains built content.
|
||||
|
||||
## HTML and CSS
|
||||
|
||||
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
|
||||
|
||||
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
|
||||
|
||||
```html
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">My Plugin Options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label1</label>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.mySelectProperty" ng-options="t for t in ['option1', 'option2', 'option3']" ng-change="ctrl.onSelectChange()"></select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Label2</label>
|
||||
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.myProperty" ng-change="ctrl.onFieldChange()" placeholder="suggestion for user" ng-model-onblur />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
|
||||
|
||||
## Data Sources
|
||||
|
||||
A basic guide for data sources can be found [here](http://docs.grafana.org/plugins/developing/datasources/).
|
||||
|
||||
### Config Page Guidelines
|
||||
|
||||
- It should be as easy as possible for a user to configure a url. If the data source is using the `datasource-http-settings` component, it should use the `suggest-url` attribute to suggest the default url or a url that is similar to what it should be (especially important if the url refers to a REST endpoint that is not common knowledge for most users e.g. `https://yourserver:4000/api/custom-endpoint`).
|
||||
|
||||
```html
|
||||
<datasource-http-settings
|
||||
current="ctrl.current"
|
||||
suggest-url="http://localhost:8080">
|
||||
</datasource-http-settings>
|
||||
```
|
||||
|
||||
- The `testDatasource` function should make a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
|
||||
|
||||
#### Password Security
|
||||
|
||||
If possible, any passwords or secrets should be be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API then this will not be possible and sensitive data will not be encrypted.
|
||||
|
||||
Read more here about how [Authentication for Datasources]({{< relref "auth-for-datasources.md" >}}) works.
|
||||
|
||||
If using the proxy feature then the Config page should use the `secureJsonData` blob like this:
|
||||
|
||||
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
|
||||
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
|
||||
|
||||
### Query Editor
|
||||
|
||||
Each query editor is unique and can have a unique style. It should be adapted to what the users of the data source are used to.
|
||||
|
||||
- Should use the Grafana CSS `gf-form` classes.
|
||||
- Should be neat and tidy. Labels and fields in columns should be aligned and should be the same width if possible.
|
||||
- The datasource should be able to handle when a user toggles a query (by clicking on the eye icon) and not execute the query. This is done by checking the `hide` property - an [example](https://github.com/grafana/grafana/blob/master/public/app/plugins/datasource/postgres/datasource.ts#L35-L38).
|
||||
- Should not execute queries if fields in the Query Editor are empty and the query will throw an exception (defensive programming).
|
||||
- Should handle errors. There are two main ways to do this:
|
||||
- use the notification system in Grafana to show a toaster popup with the error message. Example [here](https://github.com/alexanderzobnin/grafana-zabbix/blob/fdbbba2fb03f5f2a4b3b0715415e09d5a4cf6cde/src/panel-triggers/triggers_panel_ctrl.js#L467-L471).
|
||||
- provide an error notification in the query editor like the MySQL/Postgres data sources do. Example code in the `query_ctrl` [here](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/query_ctrl.ts#L36-L51) and in the [html](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/partials/query.editor.html#L190-L193).
|
@ -13,7 +13,7 @@ dev environment. Grafana ships with its own required backend server; also comple
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [Go 1.9.2](https://golang.org/dl/)
|
||||
- [Go 1.10](https://golang.org/dl/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS LTS](https://nodejs.org/download/)
|
||||
- node-gyp is the Node.js native addon build tool and it requires extra dependencies: python 2.7, make and GCC. These are already installed for most Linux distros and MacOS. See the Building On Windows section or the [node-gyp installation instructions](https://github.com/nodejs/node-gyp#installation) for more details.
|
||||
@ -66,13 +66,13 @@ You can run a local instance of Grafana by running:
|
||||
```bash
|
||||
./bin/grafana-server
|
||||
```
|
||||
If you built the binary with `go run build.go build`, run `./bin/grafana-server`
|
||||
Or, if you built the binary with `go run build.go build`, run `./bin/<os>-<architecture>/grafana-server`
|
||||
|
||||
If you built it with `go build .`, run `./grafana`
|
||||
|
||||
Open grafana in your browser (default [http://localhost:3000](http://localhost:3000)) and login with admin user (default user/pass = admin/admin).
|
||||
|
||||
## Developing Grafana
|
||||
# Developing Grafana
|
||||
|
||||
To add features, customize your config, etc, you'll need to rebuild the backend when you change the source code. We use a tool named `bra` that
|
||||
does this.
|
||||
@ -124,7 +124,7 @@ Learn more about Grafana config options in the [Configuration section](/installa
|
||||
## Create a pull requests
|
||||
Please contribute to the Grafana project and submit a pull request! Build new features, write or update documentation, fix bugs and generally make Grafana even more awesome.
|
||||
|
||||
## Troubleshooting
|
||||
# Troubleshooting
|
||||
|
||||
**Problem**: PhantomJS or node-sass errors when running grunt
|
||||
|
||||
|
@ -21,42 +21,32 @@ If you open scripted.js you can see how it reads url parameters from ARGS variab
|
||||
## Example
|
||||
|
||||
```javascript
|
||||
var rows = 1;
|
||||
var seriesName = 'argName';
|
||||
|
||||
if(!_.isUndefined(ARGS.rows)) {
|
||||
rows = parseInt(ARGS.rows, 10);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ARGS.name)) {
|
||||
seriesName = ARGS.name;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rows; i++) {
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Scripted Graph ' + i,
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Events',
|
||||
type: 'graph',
|
||||
span: 12,
|
||||
fill: 1,
|
||||
linewidth: 2,
|
||||
targets: [
|
||||
{
|
||||
'target': "randomWalk('" + seriesName + "')"
|
||||
},
|
||||
{
|
||||
'target': "randomWalk('random walk2')"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
dashboard.panels.push({
|
||||
title: 'Events',
|
||||
type: 'graph',
|
||||
fill: 1,
|
||||
linewidth: 2,
|
||||
gridPos: {
|
||||
h: 10,
|
||||
w: 24,
|
||||
x: 0,
|
||||
y: 10,
|
||||
},
|
||||
targets: [
|
||||
{
|
||||
'target': "randomWalk('" + seriesName + "')"
|
||||
},
|
||||
{
|
||||
'target': "randomWalk('random walk2')"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return dashboard;
|
||||
```
|
||||
|
@ -11,7 +11,7 @@ weight = 1
|
||||
# Variables
|
||||
|
||||
Variables allows for more interactive and dynamic dashboards. Instead of hard-coding things like server, application
|
||||
and sensor name in you metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of
|
||||
and sensor name in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of
|
||||
the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard.
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v50/variables_dashboard.png" >}}
|
||||
|
@ -94,7 +94,7 @@ weight = 10
|
||||
</a>
|
||||
<figcaption>
|
||||
<a href="https://youtu.be/FC13uhFRsVw?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" target="_blank" rel="noopener noreferrer">
|
||||
#3 Whats New In Grafana 2.0
|
||||
#3 What's New In Grafana 2.0
|
||||
</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
@ -1,5 +1,6 @@
|
||||
[
|
||||
{ "version": "v5.1", "path": "/", "archived": false, "current": true },
|
||||
{ "version": "v5.2", "path": "/", "archived": false, "current": true },
|
||||
{ "version": "v5.1", "path": "/v5.1", "archived": true },
|
||||
{ "version": "v5.0", "path": "/v5.0", "archived": true },
|
||||
{ "version": "v4.6", "path": "/v4.6", "archived": true },
|
||||
{ "version": "v4.5", "path": "/v4.5", "archived": true },
|
||||
|
@ -19,8 +19,8 @@ module.exports = function(config) {
|
||||
},
|
||||
|
||||
webpack: webpackTestConfig,
|
||||
webpackServer: {
|
||||
noInfo: true, // please don't spam the console when running in karma!
|
||||
webpackMiddleware: {
|
||||
stats: 'minimal',
|
||||
},
|
||||
|
||||
// list of files to exclude
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "5.0.4",
|
||||
"testing": "5.0.4"
|
||||
"stable": "5.2.0",
|
||||
"testing": "5.2.0"
|
||||
}
|
||||
|
62
package.json
62
package.json
@ -4,7 +4,7 @@
|
||||
"company": "Grafana Labs"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "5.2.0-pre1",
|
||||
"version": "5.3.0-pre1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
@ -16,12 +16,11 @@
|
||||
"@types/node": "^8.0.31",
|
||||
"@types/react": "^16.0.25",
|
||||
"@types/react-dom": "^16.0.3",
|
||||
"angular-mocks": "^1.6.6",
|
||||
"angular-mocks": "1.6.6",
|
||||
"autoprefixer": "^6.4.0",
|
||||
"awesome-typescript-loader": "^3.2.3",
|
||||
"axios": "^0.17.1",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
@ -33,8 +32,9 @@
|
||||
"es6-shim": "^0.35.3",
|
||||
"expect.js": "~0.2.0",
|
||||
"expose-loader": "^0.7.3",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^0.11.2",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.1",
|
||||
"gaze": "^1.1.2",
|
||||
"glob": "~7.0.0",
|
||||
"grunt": "1.0.1",
|
||||
@ -57,11 +57,10 @@
|
||||
"grunt-webpack": "^3.0.2",
|
||||
"html-loader": "^0.5.1",
|
||||
"html-webpack-harddisk-plugin": "^0.2.0",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.0.4",
|
||||
"jshint-stylish": "~2.2.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"karma": "1.7.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-expect": "~1.1.3",
|
||||
@ -69,7 +68,7 @@
|
||||
"karma-phantomjs-launcher": "1.0.4",
|
||||
"karma-sinon": "^1.0.5",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^2.0.4",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"lint-staged": "^6.0.0",
|
||||
"load-grunt-tasks": "3.5.2",
|
||||
"mobx-react-devtools": "^4.2.15",
|
||||
@ -83,30 +82,32 @@
|
||||
"postcss-loader": "^2.0.6",
|
||||
"postcss-reporter": "^5.0.0",
|
||||
"prettier": "1.9.2",
|
||||
"react-hot-loader": "^4.0.1",
|
||||
"react-hot-loader": "^4.2.0",
|
||||
"react-test-renderer": "^16.0.0",
|
||||
"sass-lint": "^1.10.2",
|
||||
"sass-loader": "^6.0.6",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sinon": "1.17.6",
|
||||
"style-loader": "^0.20.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "^0.1.36",
|
||||
"ts-jest": "^22.0.0",
|
||||
"ts-loader": "^3.2.0",
|
||||
"ts-loader": "^4.3.0",
|
||||
"ts-jest": "^22.4.6",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-loader": "^3.5.3",
|
||||
"typescript": "^2.6.2",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack": "^4.8.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-cleanup-plugin": "^0.5.1",
|
||||
"webpack-dev-server": "2.11.1",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.2",
|
||||
"webpack-cli": "^2.1.4",
|
||||
"webpack-dev-server": "^3.1.0",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"zone.js": "^0.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --progress --colors --config scripts/webpack/webpack.dev.js",
|
||||
"start": "webpack-dev-server --progress --colors --config scripts/webpack/webpack.dev.js",
|
||||
"watch": "webpack --progress --colors --watch --config scripts/webpack/webpack.dev.js",
|
||||
"dev": "webpack --progress --colors --mode development --config scripts/webpack/webpack.dev.js",
|
||||
"start": "webpack-dev-server --progress --colors --mode development --config scripts/webpack/webpack.hot.js",
|
||||
"watch": "webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js",
|
||||
"build": "grunt build",
|
||||
"test": "grunt test",
|
||||
"test:coverage": "grunt test --coverage=true",
|
||||
@ -137,34 +138,36 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"angular": "^1.6.6",
|
||||
"angular-bindonce": "^0.3.1",
|
||||
"angular-native-dragdrop": "^1.2.2",
|
||||
"angular-route": "^1.6.6",
|
||||
"angular-sanitize": "^1.6.6",
|
||||
"angular": "1.6.6",
|
||||
"angular-bindonce": "0.3.1",
|
||||
"angular-native-dragdrop": "1.2.2",
|
||||
"angular-route": "1.6.6",
|
||||
"angular-sanitize": "1.6.6",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"baron": "^3.0.3",
|
||||
"brace": "^0.10.0",
|
||||
"classnames": "^2.2.5",
|
||||
"clipboard": "^1.7.1",
|
||||
"d3": "^4.11.0",
|
||||
"d3-scale-chromatic": "^1.1.1",
|
||||
"d3-scale-chromatic": "^1.3.0",
|
||||
"eventemitter3": "^2.0.3",
|
||||
"file-saver": "^1.3.3",
|
||||
"immutable": "^3.8.2",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.10",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
"mobx": "^3.4.1",
|
||||
"mobx-react": "^4.3.5",
|
||||
"mobx-state-tree": "^1.3.1",
|
||||
"moment": "^2.18.1",
|
||||
"moment": "^2.22.2",
|
||||
"mousetrap": "^1.6.0",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||
"prismjs": "^1.6.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-grid-layout-grafana": "0.16.0",
|
||||
"react-grid-layout": "0.16.6",
|
||||
"react-highlight-words": "^0.10.0",
|
||||
"react-popper": "^0.7.5",
|
||||
"react-select": "^1.1.0",
|
||||
@ -178,7 +181,8 @@
|
||||
"slate-react": "^0.12.4",
|
||||
"tether": "^1.4.0",
|
||||
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||
"tinycolor2": "^1.4.1"
|
||||
"tinycolor2": "^1.4.1",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7"
|
||||
},
|
||||
"resolutions": {
|
||||
"caniuse-db": "1.0.30000772"
|
||||
|
@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
func ValidateOrgAlert(c *m.ReqContext) {
|
||||
@ -46,12 +48,64 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response {
|
||||
|
||||
// GET /api/alerts
|
||||
func GetAlerts(c *m.ReqContext) Response {
|
||||
dashboardQuery := c.Query("dashboardQuery")
|
||||
dashboardTags := c.QueryStrings("dashboardTag")
|
||||
stringDashboardIDs := c.QueryStrings("dashboardId")
|
||||
stringFolderIDs := c.QueryStrings("folderId")
|
||||
|
||||
dashboardIDs := make([]int64, 0)
|
||||
for _, id := range stringDashboardIDs {
|
||||
dashboardID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
dashboardIDs = append(dashboardIDs, dashboardID)
|
||||
}
|
||||
}
|
||||
|
||||
if dashboardQuery != "" || len(dashboardTags) > 0 || len(stringFolderIDs) > 0 {
|
||||
folderIDs := make([]int64, 0)
|
||||
for _, id := range stringFolderIDs {
|
||||
folderID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
folderIDs = append(folderIDs, folderID)
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery := search.Query{
|
||||
Title: dashboardQuery,
|
||||
Tags: dashboardTags,
|
||||
SignedInUser: c.SignedInUser,
|
||||
Limit: 1000,
|
||||
OrgId: c.OrgId,
|
||||
DashboardIds: dashboardIDs,
|
||||
Type: string(search.DashHitDB),
|
||||
FolderIds: folderIDs,
|
||||
Permission: m.PERMISSION_VIEW,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
if err != nil {
|
||||
return Error(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
for _, d := range searchQuery.Result {
|
||||
if d.Type == search.DashHitDB && d.Id > 0 {
|
||||
dashboardIDs = append(dashboardIDs, d.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find any dashboards, return empty result
|
||||
if len(dashboardIDs) == 0 {
|
||||
return JSON(200, []*m.AlertListItemDTO{})
|
||||
}
|
||||
}
|
||||
|
||||
query := m.GetAlertsQuery{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: c.QueryInt64("dashboardId"),
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
User: c.SignedInUser,
|
||||
OrgId: c.OrgId,
|
||||
DashboardIDs: dashboardIDs,
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
User: c.SignedInUser,
|
||||
Query: c.Query("query"),
|
||||
}
|
||||
|
||||
states := c.QueryStrings("state")
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
@ -30,7 +31,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -64,6 +65,60 @@ func TestAlertingApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
var searchQuery *search.Query
|
||||
bus.AddHandler("test", func(query *search.Query) error {
|
||||
searchQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
var getAlertsQuery *m.GetAlertsQuery
|
||||
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
|
||||
getAlertsQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = GetAlerts
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(searchQuery, ShouldBeNil)
|
||||
So(getAlertsQuery, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
var searchQuery *search.Query
|
||||
bus.AddHandler("test", func(query *search.Query) error {
|
||||
searchQuery = query
|
||||
query.Result = search.HitList{
|
||||
&search.Hit{Id: 1},
|
||||
&search.Hit{Id: 2},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
var getAlertsQuery *m.GetAlertsQuery
|
||||
bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
|
||||
getAlertsQuery = query
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = GetAlerts
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(searchQuery, ShouldNotBeNil)
|
||||
So(searchQuery.DashboardIds[0], ShouldEqual, 1)
|
||||
So(searchQuery.DashboardIds[1], ShouldEqual, 2)
|
||||
So(searchQuery.FolderIds[0], ShouldEqual, 3)
|
||||
So(searchQuery.Tags[0], ShouldEqual, "abc")
|
||||
So(searchQuery.Title, ShouldEqual, "dbQuery")
|
||||
|
||||
So(getAlertsQuery, ShouldNotBeNil)
|
||||
So(getAlertsQuery.DashboardIDs[0], ShouldEqual, 1)
|
||||
So(getAlertsQuery.DashboardIDs[1], ShouldEqual, 2)
|
||||
So(getAlertsQuery.Limit, ShouldEqual, 5)
|
||||
So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,7 +135,7 @@ func postAlertScenario(desc string, url string, routePattern string, role m.Role
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
|
@ -37,7 +37,6 @@ func GetAnnotations(c *m.ReqContext) Response {
|
||||
if item.Email != "" {
|
||||
item.AvatarUrl = dtos.GetGravatarUrl(item.Email)
|
||||
}
|
||||
item.Time = item.Time
|
||||
}
|
||||
|
||||
return JSON(200, items)
|
||||
@ -214,7 +213,9 @@ func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response
|
||||
repo := annotations.GetRepository()
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
AlertId: cmd.PanelId,
|
||||
OrgId: c.OrgId,
|
||||
Id: cmd.AnnotationId,
|
||||
RegionId: cmd.RegionId,
|
||||
DashboardId: cmd.DashboardId,
|
||||
PanelId: cmd.PanelId,
|
||||
})
|
||||
@ -235,7 +236,8 @@ func DeleteAnnotationByID(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
Id: annotationID,
|
||||
OrgId: c.OrgId,
|
||||
Id: annotationID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -254,6 +256,7 @@ func DeleteAnnotationRegion(c *m.ReqContext) Response {
|
||||
}
|
||||
|
||||
err := repo.Delete(&annotations.DeleteParams{
|
||||
OrgId: c.OrgId,
|
||||
RegionId: regionID,
|
||||
})
|
||||
|
||||
@ -269,9 +272,9 @@ func canSaveByDashboardID(c *m.ReqContext, dashboardID int64) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if dashboardID > 0 {
|
||||
guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
||||
if dashboardID != 0 {
|
||||
guard := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
Id: 1,
|
||||
}
|
||||
|
||||
deleteCmd := dtos.DeleteAnnotationsCmd{
|
||||
DashboardId: 1,
|
||||
PanelId: 1,
|
||||
}
|
||||
|
||||
viewerRole := m.ROLE_VIEWER
|
||||
editorRole := m.ROLE_EDITOR
|
||||
|
||||
@ -114,7 +119,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = []*m.Team{}
|
||||
query.Result = []*m.TeamDTO{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -171,6 +176,25 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Admin", func() {
|
||||
role := m.ROLE_ADMIN
|
||||
Convey("Should be able to do anything", func() {
|
||||
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -199,7 +223,7 @@ func postAnnotationScenario(desc string, url string, routePattern string, role m
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@ -222,7 +246,7 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@ -239,3 +263,26 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func deleteAnnotationsScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.OrgRole = role
|
||||
|
||||
return DeleteAnnotations(c, cmd)
|
||||
})
|
||||
|
||||
fakeAnnoRepo = &fakeAnnotationsRepo{}
|
||||
annotations.SetRepository(fakeAnnoRepo)
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
339
pkg/api/api.go
339
pkg/api/api.go
@ -4,13 +4,12 @@ import (
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// Register adds http routes
|
||||
func (hs *HTTPServer) registerRoutes() {
|
||||
macaronR := hs.macaron
|
||||
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
|
||||
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
@ -20,15 +19,12 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
quota := middleware.Quota
|
||||
bind := binding.Bind
|
||||
|
||||
// automatically set HEAD for every GET
|
||||
macaronR.SetAutoHead(true)
|
||||
|
||||
r := hs.RouteRegister
|
||||
|
||||
// not logged in views
|
||||
r.Get("/", reqSignedIn, Index)
|
||||
r.Get("/logout", Logout)
|
||||
r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost))
|
||||
r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), Wrap(LoginPost))
|
||||
r.Get("/login/:name", quota("session"), OAuthLogin)
|
||||
r.Get("/login", LoginView)
|
||||
r.Get("/invite/:code", Index)
|
||||
@ -77,6 +73,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/dashboards/", reqSignedIn, Index)
|
||||
r.Get("/dashboards/*", reqSignedIn, Index)
|
||||
|
||||
r.Get("/explore", reqEditorRole, Index)
|
||||
|
||||
r.Get("/playlists/", reqSignedIn, Index)
|
||||
r.Get("/playlists/*", reqSignedIn, Index)
|
||||
r.Get("/alerting/", reqSignedIn, Index)
|
||||
@ -84,20 +82,20 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// sign up
|
||||
r.Get("/signup", Index)
|
||||
r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
|
||||
r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp))
|
||||
r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))
|
||||
r.Get("/api/user/signup/options", Wrap(GetSignUpOptions))
|
||||
r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), Wrap(SignUp))
|
||||
r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), Wrap(SignUpStep2))
|
||||
|
||||
// invited
|
||||
r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
|
||||
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))
|
||||
r.Get("/api/user/invite/:code", Wrap(GetInviteInfoByCode))
|
||||
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), Wrap(CompleteInvite))
|
||||
|
||||
// reset password
|
||||
r.Get("/user/password/send-reset-email", Index)
|
||||
r.Get("/user/password/reset", Index)
|
||||
|
||||
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
|
||||
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), Wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), Wrap(ResetPassword))
|
||||
|
||||
// dashboard snapshots
|
||||
r.Get("/dashboard/snapshot/*", Index)
|
||||
@ -107,148 +105,149 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
|
||||
r.Get("/api/snapshot/shared-options/", GetSharingOptions)
|
||||
r.Get("/api/snapshots/:key", GetDashboardSnapshot)
|
||||
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
|
||||
r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey))
|
||||
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
||||
|
||||
// api renew session based on remember cookie
|
||||
r.Get("/api/login/ping", quota("session"), LoginAPIPing)
|
||||
|
||||
// authed api
|
||||
r.Group("/api", func(apiRoute RouteRegister) {
|
||||
r.Group("/api", func(apiRoute routing.RouteRegister) {
|
||||
|
||||
// user (signed in)
|
||||
apiRoute.Group("/user", func(userRoute RouteRegister) {
|
||||
userRoute.Get("/", wrap(GetSignedInUser))
|
||||
userRoute.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||
userRoute.Post("/using/:id", wrap(UserSetUsingOrg))
|
||||
userRoute.Get("/orgs", wrap(GetSignedInUserOrgList))
|
||||
apiRoute.Group("/user", func(userRoute routing.RouteRegister) {
|
||||
userRoute.Get("/", Wrap(GetSignedInUser))
|
||||
userRoute.Put("/", bind(m.UpdateUserCommand{}), Wrap(UpdateSignedInUser))
|
||||
userRoute.Post("/using/:id", Wrap(UserSetUsingOrg))
|
||||
userRoute.Get("/orgs", Wrap(GetSignedInUserOrgList))
|
||||
|
||||
userRoute.Post("/stars/dashboard/:id", wrap(StarDashboard))
|
||||
userRoute.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
|
||||
userRoute.Post("/stars/dashboard/:id", Wrap(StarDashboard))
|
||||
userRoute.Delete("/stars/dashboard/:id", Wrap(UnstarDashboard))
|
||||
|
||||
userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
|
||||
userRoute.Get("/quotas", wrap(GetUserQuotas))
|
||||
userRoute.Put("/helpflags/:id", wrap(SetHelpFlag))
|
||||
userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), Wrap(ChangeUserPassword))
|
||||
userRoute.Get("/quotas", Wrap(GetUserQuotas))
|
||||
userRoute.Put("/helpflags/:id", Wrap(SetHelpFlag))
|
||||
// For dev purpose
|
||||
userRoute.Get("/helpflags/clear", wrap(ClearHelpFlags))
|
||||
userRoute.Get("/helpflags/clear", Wrap(ClearHelpFlags))
|
||||
|
||||
userRoute.Get("/preferences", wrap(GetUserPreferences))
|
||||
userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
|
||||
userRoute.Get("/preferences", Wrap(GetUserPreferences))
|
||||
userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateUserPreferences))
|
||||
})
|
||||
|
||||
// users (admin permission required)
|
||||
apiRoute.Group("/users", func(usersRoute RouteRegister) {
|
||||
usersRoute.Get("/", wrap(SearchUsers))
|
||||
usersRoute.Get("/search", wrap(SearchUsersWithPaging))
|
||||
usersRoute.Get("/:id", wrap(GetUserByID))
|
||||
usersRoute.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
|
||||
usersRoute.Get("/", Wrap(SearchUsers))
|
||||
usersRoute.Get("/search", Wrap(SearchUsersWithPaging))
|
||||
usersRoute.Get("/:id", Wrap(GetUserByID))
|
||||
usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList))
|
||||
// query parameters /users/lookup?loginOrEmail=admin@example.com
|
||||
usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail))
|
||||
usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg))
|
||||
usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail))
|
||||
usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), Wrap(UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", Wrap(UpdateUserActiveOrg))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// team (admin permission required)
|
||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
|
||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), Wrap(CreateTeam))
|
||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), Wrap(UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", Wrap(DeleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", Wrap(GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), Wrap(AddTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", Wrap(RemoveTeamMember))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// team without requirement of user to be org admin
|
||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
|
||||
teamsRoute.Get("/search", wrap(SearchTeams))
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", Wrap(GetTeamByID))
|
||||
teamsRoute.Get("/search", Wrap(SearchTeams))
|
||||
})
|
||||
|
||||
// org information available to all users.
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Get("/", wrap(GetOrgCurrent))
|
||||
orgRoute.Get("/quotas", wrap(GetOrgQuotas))
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/", Wrap(GetOrgCurrent))
|
||||
orgRoute.Get("/quotas", Wrap(GetOrgQuotas))
|
||||
})
|
||||
|
||||
// current org
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
|
||||
|
||||
// invites
|
||||
orgRoute.Get("/invites", wrap(GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||
orgRoute.Get("/invites", Wrap(GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), Wrap(AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", Wrap(RevokeInvite))
|
||||
|
||||
// prefs
|
||||
orgRoute.Get("/preferences", wrap(GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||
orgRoute.Get("/preferences", Wrap(GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateOrgPreferences))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// current org without requirement of user to be org admin
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
|
||||
})
|
||||
|
||||
// create new org
|
||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), Wrap(CreateOrg))
|
||||
|
||||
// search all orgs
|
||||
apiRoute.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))
|
||||
apiRoute.Get("/orgs", reqGrafanaAdmin, Wrap(SearchOrgs))
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) {
|
||||
orgsRoute.Get("/", wrap(GetOrgByID))
|
||||
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", wrap(GetOrgUsers))
|
||||
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", wrap(RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", wrap(GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota))
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
|
||||
orgsRoute.Get("/", Wrap(GetOrgByID))
|
||||
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", Wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", Wrap(GetOrgUsers))
|
||||
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), Wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", Wrap(RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", Wrap(GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), Wrap(UpdateOrgQuota))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/name/:name", func(orgsRoute RouteRegister) {
|
||||
orgsRoute.Get("/", wrap(GetOrgByName))
|
||||
apiRoute.Group("/orgs/name/:name", func(orgsRoute routing.RouteRegister) {
|
||||
orgsRoute.Get("/", Wrap(GetOrgByName))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// auth api keys
|
||||
apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) {
|
||||
keysRoute.Get("/", wrap(GetAPIKeys))
|
||||
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddAPIKey))
|
||||
keysRoute.Delete("/:id", wrap(DeleteAPIKey))
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
keysRoute.Get("/", Wrap(GetAPIKeys))
|
||||
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), Wrap(AddAPIKey))
|
||||
keysRoute.Delete("/:id", Wrap(DeleteAPIKey))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// Preferences
|
||||
apiRoute.Group("/preferences", func(prefRoute RouteRegister) {
|
||||
prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
|
||||
apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) {
|
||||
prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), Wrap(SetHomeDashboard))
|
||||
})
|
||||
|
||||
// Data sources
|
||||
apiRoute.Group("/datasources", func(datasourceRoute RouteRegister) {
|
||||
datasourceRoute.Get("/", wrap(GetDataSources))
|
||||
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource))
|
||||
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
|
||||
datasourceRoute.Delete("/:id", wrap(DeleteDataSourceByID))
|
||||
datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName))
|
||||
datasourceRoute.Get("/:id", wrap(GetDataSourceByID))
|
||||
datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName))
|
||||
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
|
||||
datasourceRoute.Get("/", Wrap(GetDataSources))
|
||||
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource))
|
||||
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource))
|
||||
datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceByID))
|
||||
datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName))
|
||||
datasourceRoute.Get("/:id", Wrap(GetDataSourceByID))
|
||||
datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIDByName), reqSignedIn)
|
||||
apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIDByName), reqSignedIn)
|
||||
|
||||
apiRoute.Get("/plugins", wrap(GetPluginList))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingByID))
|
||||
apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
|
||||
apiRoute.Get("/plugins", Wrap(GetPluginList))
|
||||
apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID))
|
||||
apiRoute.Get("/plugins/:pluginId/markdown/:name", Wrap(GetPluginMarkdown))
|
||||
|
||||
apiRoute.Group("/plugins", func(pluginRoute RouteRegister) {
|
||||
pluginRoute.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
|
||||
pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
|
||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||
pluginRoute.Get("/:pluginId/dashboards/", Wrap(GetPluginDashboards))
|
||||
pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), Wrap(UpdatePluginSetting))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
apiRoute.Get("/frontend/settings/", GetFrontendSettings)
|
||||
@ -256,125 +255,125 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest)
|
||||
|
||||
// Folders
|
||||
apiRoute.Group("/folders", func(folderRoute RouteRegister) {
|
||||
folderRoute.Get("/", wrap(GetFolders))
|
||||
folderRoute.Get("/id/:id", wrap(GetFolderByID))
|
||||
folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder))
|
||||
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
|
||||
folderRoute.Get("/", Wrap(GetFolders))
|
||||
folderRoute.Get("/id/:id", Wrap(GetFolderByID))
|
||||
folderRoute.Post("/", bind(m.CreateFolderCommand{}), Wrap(CreateFolder))
|
||||
|
||||
folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) {
|
||||
folderUidRoute.Get("/", wrap(GetFolderByUID))
|
||||
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder))
|
||||
folderUidRoute.Delete("/", wrap(DeleteFolder))
|
||||
folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) {
|
||||
folderUidRoute.Get("/", Wrap(GetFolderByUID))
|
||||
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), Wrap(UpdateFolder))
|
||||
folderUidRoute.Delete("/", Wrap(DeleteFolder))
|
||||
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute RouteRegister) {
|
||||
folderPermissionRoute.Get("/", wrap(GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateFolderPermissions))
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
|
||||
folderPermissionRoute.Get("/", Wrap(GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateFolderPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Dashboard
|
||||
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
|
||||
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUID))
|
||||
apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) {
|
||||
dashboardRoute.Get("/uid/:uid", Wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/uid/:uid", Wrap(DeleteDashboardByUID))
|
||||
|
||||
dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
|
||||
dashboardRoute.Get("/db/:slug", Wrap(GetDashboard))
|
||||
dashboardRoute.Delete("/db/:slug", Wrap(DeleteDashboard))
|
||||
|
||||
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), wrap(CalculateDashboardDiff))
|
||||
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), Wrap(CalculateDashboardDiff))
|
||||
|
||||
dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), wrap(PostDashboard))
|
||||
dashboardRoute.Get("/home", wrap(GetHomeDashboard))
|
||||
dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), Wrap(PostDashboard))
|
||||
dashboardRoute.Get("/home", Wrap(GetHomeDashboard))
|
||||
dashboardRoute.Get("/tags", GetDashboardTags)
|
||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), Wrap(ImportDashboard))
|
||||
|
||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
|
||||
dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
|
||||
dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
|
||||
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), wrap(RestoreDashboardVersion))
|
||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) {
|
||||
dashIdRoute.Get("/versions", Wrap(GetDashboardVersions))
|
||||
dashIdRoute.Get("/versions/:id", Wrap(GetDashboardVersion))
|
||||
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(RestoreDashboardVersion))
|
||||
|
||||
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute RouteRegister) {
|
||||
dashboardPermissionRoute.Get("/", wrap(GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateDashboardPermissions))
|
||||
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) {
|
||||
dashboardPermissionRoute.Get("/", Wrap(GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateDashboardPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Dashboard snapshots
|
||||
apiRoute.Group("/dashboard/snapshots", func(dashboardRoute RouteRegister) {
|
||||
dashboardRoute.Get("/", wrap(SearchDashboardSnapshots))
|
||||
apiRoute.Group("/dashboard/snapshots", func(dashboardRoute routing.RouteRegister) {
|
||||
dashboardRoute.Get("/", Wrap(SearchDashboardSnapshots))
|
||||
})
|
||||
|
||||
// Playlist
|
||||
apiRoute.Group("/playlists", func(playlistRoute RouteRegister) {
|
||||
playlistRoute.Get("/", wrap(SearchPlaylists))
|
||||
playlistRoute.Get("/:id", ValidateOrgPlaylist, wrap(GetPlaylist))
|
||||
playlistRoute.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems))
|
||||
playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards))
|
||||
playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist))
|
||||
playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
|
||||
playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist))
|
||||
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
|
||||
playlistRoute.Get("/", Wrap(SearchPlaylists))
|
||||
playlistRoute.Get("/:id", ValidateOrgPlaylist, Wrap(GetPlaylist))
|
||||
playlistRoute.Get("/:id/items", ValidateOrgPlaylist, Wrap(GetPlaylistItems))
|
||||
playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, Wrap(GetPlaylistDashboards))
|
||||
playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, Wrap(DeletePlaylist))
|
||||
playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, Wrap(UpdatePlaylist))
|
||||
playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), Wrap(CreatePlaylist))
|
||||
})
|
||||
|
||||
// Search
|
||||
apiRoute.Get("/search/", Search)
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSQLTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/scenarios", Wrap(GetTestDataScenarios))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, Wrap(GenerateSQLTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", Wrap(GetTestDataRandomWalk))
|
||||
|
||||
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest))
|
||||
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), wrap(PauseAlert))
|
||||
alertsRoute.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
||||
alertsRoute.Get("/", wrap(GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard))
|
||||
apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), Wrap(AlertTest))
|
||||
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), Wrap(PauseAlert))
|
||||
alertsRoute.Get("/:alertId", ValidateOrgAlert, Wrap(GetAlert))
|
||||
alertsRoute.Get("/", Wrap(GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
|
||||
})
|
||||
|
||||
apiRoute.Get("/alert-notifications", wrap(GetAlertNotifications))
|
||||
apiRoute.Get("/alert-notifiers", wrap(GetAlertNotifiers))
|
||||
apiRoute.Get("/alert-notifications", Wrap(GetAlertNotifications))
|
||||
apiRoute.Get("/alert-notifiers", Wrap(GetAlertNotifiers))
|
||||
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications RouteRegister) {
|
||||
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
|
||||
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification))
|
||||
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
|
||||
alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationByID))
|
||||
alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification))
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
|
||||
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest))
|
||||
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification))
|
||||
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification))
|
||||
alertNotifications.Get("/:notificationId", Wrap(GetAlertNotificationByID))
|
||||
alertNotifications.Delete("/:notificationId", Wrap(DeleteAlertNotification))
|
||||
}, reqEditorRole)
|
||||
|
||||
apiRoute.Get("/annotations", wrap(GetAnnotations))
|
||||
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations))
|
||||
apiRoute.Get("/annotations", Wrap(GetAnnotations))
|
||||
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations))
|
||||
|
||||
apiRoute.Group("/annotations", func(annotationsRoute RouteRegister) {
|
||||
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation))
|
||||
annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationByID))
|
||||
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), wrap(UpdateAnnotation))
|
||||
annotationsRoute.Delete("/region/:regionId", wrap(DeleteAnnotationRegion))
|
||||
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), wrap(PostGraphiteAnnotation))
|
||||
apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) {
|
||||
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
|
||||
annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
|
||||
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
|
||||
annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
|
||||
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
|
||||
})
|
||||
|
||||
// error test
|
||||
r.Get("/metrics/error", wrap(GenerateError))
|
||||
r.Get("/metrics/error", Wrap(GenerateError))
|
||||
|
||||
}, reqSignedIn)
|
||||
|
||||
// admin api
|
||||
r.Group("/api/admin", func(adminRoute RouteRegister) {
|
||||
r.Group("/api/admin", func(adminRoute routing.RouteRegister) {
|
||||
adminRoute.Get("/settings", AdminGetSettings)
|
||||
adminRoute.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
||||
adminRoute.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
||||
adminRoute.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
||||
adminRoute.Delete("/users/:id", AdminDeleteUser)
|
||||
adminRoute.Get("/users/:id/quotas", wrap(GetUserQuotas))
|
||||
adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
|
||||
adminRoute.Get("/users/:id/quotas", Wrap(GetUserQuotas))
|
||||
adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), Wrap(UpdateUserQuota))
|
||||
adminRoute.Get("/stats", AdminGetStats)
|
||||
adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), wrap(PauseAllAlerts))
|
||||
adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), Wrap(PauseAllAlerts))
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// rendering
|
||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||
r.Get("/render/*", reqSignedIn, hs.RenderToPng)
|
||||
|
||||
// grafana.net proxy
|
||||
r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
|
||||
@ -388,10 +387,4 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// streams
|
||||
//r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)
|
||||
|
||||
r.Register(macaronR)
|
||||
|
||||
InitAppPluginRoutes(macaronR)
|
||||
|
||||
macaronR.NotFound(NotFoundHandler)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
var pluginProxyTransport *http.Transport
|
||||
|
||||
func InitAppPluginRoutes(r *macaron.Macaron) {
|
||||
func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
|
||||
pluginProxyTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: setting.PluginAppsSkipVerifyTLS,
|
||||
|
@ -30,7 +30,7 @@ type NormalResponse struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func wrap(action interface{}) macaron.Handler {
|
||||
func Wrap(action interface{}) macaron.Handler {
|
||||
|
||||
return func(c *m.ReqContext) {
|
||||
var res Response
|
||||
|
@ -23,7 +23,7 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
@ -46,6 +46,31 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
|
||||
})
|
||||
}
|
||||
|
||||
func anonymousUserScenario(desc string, method string, url string, routePattern string, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
if sc.handlerFunc != nil {
|
||||
return sc.handlerFunc(sc.context)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
sc.m.Get(routePattern, sc.defaultHandler)
|
||||
case "DELETE":
|
||||
sc.m.Delete(routePattern, sc.defaultHandler)
|
||||
}
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
|
||||
sc.resp = httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
|
@ -194,7 +194,7 @@ func updateDashboardPermissionScenario(desc string, url string, routePattern str
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
|
||||
sc.defaultHandler = wrap(func(c *m.ReqContext) Response {
|
||||
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.UserId = TestUserID
|
||||
|
@ -91,11 +91,31 @@ func GetDashboardSnapshot(c *m.ReqContext) {
|
||||
c.JSON(200, dto)
|
||||
}
|
||||
|
||||
// GET /api/snapshots-delete/:key
|
||||
// GET /api/snapshots-delete/:deleteKey
|
||||
func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response {
|
||||
key := c.Params(":deleteKey")
|
||||
|
||||
query := &m.GetDashboardSnapshotQuery{DeleteKey: key}
|
||||
|
||||
err := bus.Dispatch(query)
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return Error(500, "Failed to delete dashboard snapshot", err)
|
||||
}
|
||||
|
||||
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
|
||||
}
|
||||
|
||||
// DELETE /api/snapshots/:key
|
||||
func DeleteDashboardSnapshot(c *m.ReqContext) Response {
|
||||
key := c.Params(":key")
|
||||
|
||||
query := &m.GetDashboardSnapshotQuery{DeleteKey: key}
|
||||
query := &m.GetDashboardSnapshotQuery{Key: key}
|
||||
|
||||
err := bus.Dispatch(query)
|
||||
if err != nil {
|
||||
@ -118,13 +138,13 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
|
||||
return Error(403, "Access denied to this snapshot", nil)
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: key}
|
||||
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return Error(500, "Failed to delete dashboard snapshot", err)
|
||||
}
|
||||
|
||||
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
|
||||
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches."})
|
||||
}
|
||||
|
||||
// GET /api/dashboard/snapshots
|
||||
@ -154,7 +174,6 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
|
||||
Id: snapshot.Id,
|
||||
Name: snapshot.Name,
|
||||
Key: snapshot.Key,
|
||||
DeleteKey: snapshot.DeleteKey,
|
||||
OrgId: snapshot.OrgId,
|
||||
UserId: snapshot.UserId,
|
||||
External: snapshot.External,
|
||||
|
@ -39,7 +39,7 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
teamResp := []*m.Team{}
|
||||
teamResp := []*m.TeamDTO{}
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = teamResp
|
||||
return nil
|
||||
@ -47,15 +47,30 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
|
||||
Convey("When user has editor role and is not in the ACL", func() {
|
||||
Convey("Should not be able to delete snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is anonymous", func() {
|
||||
Convey("Should be able to delete snapshot by deleteKey", func() {
|
||||
anonymousUserScenario("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is editor and dashboard has default ACL", func() {
|
||||
aclMockResp = []*m.DashboardAclInfoDTO{
|
||||
{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
|
||||
@ -63,9 +78,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("Should be able to delete a snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
@ -81,9 +96,9 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
|
||||
mockSnapshotResult.UserId = TestUserID
|
||||
|
||||
Convey("Should be able to delete a snapshot", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user