Compare commits

...

422 Commits

Author SHA1 Message Date
Torkel Ödegaard
6a71b199ca Updated version to beta2 2019-02-11 16:55:36 +01:00
Torkel Ödegaard
e50b7b3279 Merge branch 'ui-new-red-green-blue' 2019-02-11 16:51:30 +01:00
Torkel Ödegaard
99df7b87c0 Merge branch 'master' of github.com:grafana/grafana 2019-02-11 16:51:24 +01:00
Torkel Ödegaard
6d11999e20 Merge pull request #15367 from grafana/gauge-max-value
Fix issue with Max value in Gauge
2019-02-11 16:50:42 +01:00
Torkel Ödegaard
5195954681 style tweak to alert 2019-02-11 16:50:12 +01:00
Daniel Lee
18615a3357 Merge pull request #15363 from bergquist/edition_to_build_info
adds edition to build_info metric
2019-02-11 16:46:53 +01:00
Torkel Ödegaard
afc2efa56d Removed plus icons 2019-02-11 16:45:47 +01:00
Torkel Ödegaard
8e93b68e6d restoring green CTA 2019-02-11 16:38:05 +01:00
Torkel Ödegaard
b920ee0ea3 Merge branch 'master' into ui-new-red-green-blue 2019-02-11 16:26:58 +01:00
Torkel Ödegaard
b93cdf56fb Removed double page container 2019-02-11 16:26:02 +01:00
Peter Holmberg
c332e106a2 Removing default thresholds values. 2019-02-11 16:00:08 +01:00
bergquist
e4e42fcd08 adds edition to build_info metric 2019-02-11 15:42:12 +01:00
Torkel Ödegaard
c4fa64e6dc Updated lint-staged 2019-02-11 15:23:50 +01:00
Daniel Lee
93f1a48641 changelog: adds note for #14623 2019-02-11 15:21:02 +01:00
Torkel Ödegaard
4408817e65 Fixed double page class on api keys and org details page 2019-02-11 15:18:11 +01:00
Daniel Lee
1c364b57b5 Merge pull request #15353 from grafana/14623-azure-monitor-alerting
Adds alerting for the Azure Monitor API in the Azure Monitor datasource
2019-02-11 15:16:32 +01:00
Torkel Ödegaard
931bece16e Merge branch 'master' into ui-new-red-green-blue 2019-02-11 15:00:00 +01:00
Torkel Ödegaard
962815169e Color tweaks 2019-02-11 14:58:11 +01:00
Daniel Lee
ac345312a4 azuremonitor: don't use make for maps and array 2019-02-11 14:42:12 +01:00
ijin08
56b35354c7 changed back to old green in light theme 2019-02-11 14:21:43 +01:00
Daniel Lee
52fe6b0316 Merge pull request #15198 from CorpGlory/azure-monitor-refactor-#15087
Azure Monitor: refactor #15087
2019-02-11 14:19:42 +01:00
Marcus Efraimsson
8769b7aa57 changelog: add notes about closing #15258 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
757a98257d changelog: add notes about closing #15223 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
1f0c7727f4 changelog: add notes about closing #15222 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
63f465f0ac changelog: add notes about closing #15122 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
9472d7e600 changelog: add notes about closing #15219 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
a7c44c2ce7 changelog: add notes about closing #14432 2019-02-11 14:17:59 +01:00
Marcus Efraimsson
82e330a1c5 update changelog 2019-02-11 14:17:32 +01:00
Daniel Lee
e53f41e511 changelog: adds note for #15131 2019-02-11 14:10:01 +01:00
Daniel Lee
22d64a8c2a Merge pull request #15301 from grafana/azure-monitor-autocomplete
Azure Monitor: improve autocomplete for Log Analytics and App Insights editor
2019-02-11 14:06:43 +01:00
Marcus Efraimsson
c13e302cde Merge pull request #15356 from grafana/15258_fix
revert ds_proxy timeout and implement dataproxy timeout correctly
2019-02-11 14:05:51 +01:00
Marcus Efraimsson
5e6c746c9b changelog: add notes about closing #15284 2019-02-11 13:58:36 +01:00
Torkel Ödegaard
77ba734491 Fixed issue with light theme introduced by #15333 2019-02-11 13:54:50 +01:00
Torkel Ödegaard
c92cd73bd8 Merge pull request #15355 from grafana/15284_fix_time_range
WIP: Datasources with custom time range handling should always take dashboard timezone into consideration
2019-02-11 13:43:47 +01:00
Torkel Ödegaard
9570394c49 minor style update 2019-02-11 13:42:37 +01:00
Marcus Efraimsson
a1cd550df4 revert ds_proxy timeout and implement dataproxy timeout correctly 2019-02-11 13:42:05 +01:00
bergquist
13f21fffc4 changelog: adds note about closing #15295
[skip ci]
2019-02-11 13:35:37 +01:00
Carl Bergquist
1f7a1f807e Merge pull request #15299 from grafana/15295_fux
Make sure alert notifier provisioning directory are created for deb and rpm packages
2019-02-11 13:31:51 +01:00
Daniel Lee
0b74860f55 azuremonitor: fix auto interval calculation on backend
Not needed for alerting (as the query intervalms will always be 0) but needed
later when being called from the frontend)
2019-02-11 13:27:08 +01:00
Torkel Ödegaard
b14958edef Minor style fixes 2019-02-11 13:24:02 +01:00
Marcus Efraimsson
b9c36e5301 make sure opentsdb takes dashboard timezone into consideration 2019-02-11 13:13:38 +01:00
Marcus Efraimsson
519dfd0899 make sure influx takes dashboard timezone into consideration 2019-02-11 13:11:56 +01:00
Torkel Ödegaard
a4dd63e224 Merge branch 'master' into ui-new-red-green-blue 2019-02-11 13:04:25 +01:00
Carl Bergquist
1b22ceabbe Merge pull request #15347 from bergquist/usage_stats_naming
renames usage state metric for auth_token
2019-02-11 12:52:04 +01:00
Torkel Ödegaard
6f16f70456 Merge pull request #15354 from grafana/fix/plugin-loading-failure
Fix plugin loading failure message not being displayed
2019-02-11 12:49:08 +01:00
Marcus Efraimsson
37a73b6b35 make sure graphite takes dashboard timezone into consideration 2019-02-11 12:04:27 +01:00
Torkel Ödegaard
ba2f698b81 Merge pull request #15352 from grafana/fixed-missing-graph-time-axis
Fixed missing time axis on graph due to width not being passed
2019-02-11 11:50:04 +01:00
Dominik Prokop
9485c67827 Fix plugin loading failure message not being displayed 2019-02-11 11:47:12 +01:00
Torkel Ödegaard
d970c3a686 Merge pull request #15350 from grafana/fix/clear-search-on-vizpicker-close
Clear visualization picker search on picker close
2019-02-11 11:39:07 +01:00
Torkel Ödegaard
5af1bd657f Merge pull request #15351 from grafana/15270_fix
Fix navigate to folder with only uid
2019-02-11 11:38:48 +01:00
bergquist
5dc864b47f fixes invalid folder check
-f check if a file exists. -d checks if the dir exists
2019-02-11 11:36:34 +01:00
bergquist
7ce18ec4f7 extract notifiers folder creation to new if statement 2019-02-11 11:30:41 +01:00
Daniel Lee
a54484638d interval: make the FormatDuration function public
A useful function that was ported from kbn.ts and can be used
to convert milliseconds into a kbn unit
2019-02-11 11:25:51 +01:00
Torkel Ödegaard
b780b6377a Fixed missing time axis on graph due to width not being passed 2019-02-11 11:17:23 +01:00
bergquist
217eb6310e make sure notifiers dir exists for provisioning in docker 2019-02-11 11:17:23 +01:00
Torkel Ödegaard
8f6ccce16f Merge pull request #15346 from grafana/logs-graph-series-names
Fix for logs graph series names (level names)
2019-02-11 11:13:37 +01:00
Marcus Efraimsson
f73f0e69e0 should be able to navigate to folder with only uid 2019-02-11 11:11:21 +01:00
bergquist
3ce99bca66 renames usage state name for auth token
as noted, sessions might not be a good name for this metrics.
while devices would be a better name for users I think we should
align the name with the code as much as possible. The ui listing
all auth_tokens per user should probarbly say "devices" instead
2019-02-11 11:08:31 +01:00
Dominik Prokop
f39fef2a02 Clear visualization picker search on picker close 2019-02-11 11:03:24 +01:00
Torkel Ödegaard
784d4fb70d Update README.md 2019-02-11 10:57:32 +01:00
bergquist
2c8c4729a8 changelog: adds note about closing #15288 2019-02-11 10:47:03 +01:00
Carl Bergquist
396a5a947f Merge pull request #15300 from bergquist/token_usage_stats
adds usage stats for sessions
2019-02-11 10:31:57 +01:00
Torkel Ödegaard
a402a3713f Merge pull request #15339 from grafana/navbar-back-btn
Navbar back btn
2019-02-11 10:28:47 +01:00
ijin08
2a2b242eb0 removed extra semi-colon 2019-02-11 10:27:58 +01:00
Torkel Ödegaard
e75e69a709 Commented out the Loki dashboard query editor 2019-02-11 10:27:04 +01:00
ijin08
1cff59731c added old green to dark-theme 2019-02-11 10:25:33 +01:00
Torkel Ödegaard
3bf0a5ffc6 Fixed issue with logs graph not showing level names 2019-02-11 10:01:43 +01:00
ijin08
9e0c795228 set secondary to new blue 2019-02-11 09:54:14 +01:00
Torkel Ödegaard
58e57a1669 removed unused directive 2019-02-11 09:46:56 +01:00
Torkel Ödegaard
9565e48f03 Fixed issue where double clicking on back button closes sidemenu 2019-02-11 09:37:13 +01:00
Patrick O'Carroll
c34021344a Merge branch 'master' into ui-new-red-green-blue 2019-02-11 09:17:19 +01:00
Marcus Efraimsson
5eea85a3a3 changelog: add notes about closing #8570 2019-02-11 09:06:20 +01:00
Marcus Efraimsson
75f89ecf1f Merge pull request #14888 from bugficks/8570-mysql-ssl-datasource
MySQL SSL CA in datasource connector
2019-02-11 09:00:18 +01:00
Marcus Efraimsson
be11da5b31 update changelog 2019-02-11 08:49:10 +01:00
Marcus Efraimsson
06972144d2 changelog: add notes about closing #14233 2019-02-11 08:48:03 +01:00
Marcus Efraimsson
7b761f0a28 changelog: add notes about closing #15189 2019-02-11 08:44:04 +01:00
Marcus Efraimsson
41217ea110 changelog: add notes about closing #13324 2019-02-11 08:39:33 +01:00
Marcus Efraimsson
bd48408e2d Merge pull request #15215 from thatsparesh/13324-mssql-pass-timerange-for-template-variable-queries
mssql: pass timerange for template variable queries
2019-02-11 08:36:47 +01:00
Daniel Lee
d6904ba9b4 azuremonitor: small refactoring 2019-02-11 01:22:15 +01:00
Daniel Lee
60327953a2 azuremonitor: handles timegrain set to auto on backend 2019-02-11 01:17:37 +01:00
Torkel Ödegaard
9ab06c6eef Merge pull request #15333 from nrichards/nrichards-patch-1
Improve usability of forms, better showing disabled metrics
2019-02-10 20:15:53 +01:00
Torkel Ödegaard
3e583707ca Merge pull request #15335 from tcpatterson/cloudwatch-ec2-api-metrics
Add aws ec2 api metrics for cloudwatch
2019-02-10 20:15:12 +01:00
Torkel Ödegaard
295bc425ff Merge pull request #15337 from grafana/time-range-to-angular-query-controllers
provide time range to angular query controllers
2019-02-10 20:02:41 +01:00
Torkel Ödegaard
f38e64cc5d Navbar back button, no title edit this time 2019-02-10 20:01:22 +01:00
Torkel Ödegaard
a0729b9b50 provide time range to angular query controllers 2019-02-10 17:05:58 +01:00
Daniel Lee
452c4f5b9b azuremonitor: add test for dimension filter 2019-02-10 01:47:38 +01:00
Daniel Lee
b94de101cd azuremonitor: refactor azure monitor api code into own file 2019-02-10 01:18:16 +01:00
Daniel Lee
b816f35c41 azuremonitor: handle multi-dimensions on backend 2019-02-10 00:23:12 +01:00
thatsparesh
105879ab5d use timeSrv in metricFindQuery as timeRange 2019-02-09 14:57:20 -06:00
thatsparesh
716db35fae remove unnecessary spy 2019-02-09 14:56:43 -06:00
Daniel Lee
a5e5db20e1 azuremonitor: add support for aggregations on backend 2019-02-09 21:52:44 +01:00
Connor Patterson
d2aed7e075 Fix formatting 2019-02-09 14:13:15 -05:00
Connor Patterson
2987a47a9b Add aws ec2 api metrics for cloudwatch 2019-02-09 13:47:08 -05:00
Nick Richards
bd6cefa53f Improve usability showing disabled lines in forms
* Use gray-3 instead of gray-2 for text-color-weak in "light" theme
2019-02-08 14:51:50 -08:00
Torkel Ödegaard
89c153d44b Merge pull request #15332 from grafana/style-fixes
Fixed issues with plus button in threshold and panel option header
2019-02-08 22:21:03 +01:00
Torkel Ödegaard
2e050d3337 Merge pull request #15331 from ryantxu/color-parsing
support three letter hex color strings
2019-02-08 22:19:56 +01:00
Torkel Ödegaard
748cb44911 Fixed issues with plus button in threshold and panel option header, and current state in viz picker, fixes #15329 2019-02-08 21:53:51 +01:00
ryan
169732997d support three letter hex color strings 2019-02-08 12:46:30 -08:00
Daniel Lee
10194df112 azuremonitor: simple alerting for Azure Monitor API
Lots of edge cases and functionality left to implement but
a simple query works for alerting now.
2019-02-08 18:15:17 +01:00
Torkel Ödegaard
db8c74c4c3 Merge pull request #15323 from ryantxu/package-license
mark grafana-ui package with Apache license
2019-02-08 18:13:41 +01:00
Marcus Efraimsson
1bc2a0af70 use unique datasource id when registering mysql tls config 2019-02-08 18:08:07 +01:00
Daniel Lee
0e228d582d azuremonitor: builds a query and sends it to Azure on the backend
Lots of edge cases not covered and the response is not parsed. It only
handles one service and will have to be refactored to handle multiple
2019-02-08 17:20:31 +01:00
ryan
3d5ae3dca3 mark packages as Apache license 2019-02-08 08:10:02 -08:00
Torkel Ödegaard
3e317b7d20 Merge pull request #15235 from grafana/core/theming
POC - Enable js defined theme to be used in SASS
2019-02-08 16:10:17 +01:00
Torkel Ödegaard
5436c28448 Minor refactoring around theme access 2019-02-08 15:38:45 +01:00
Dominik Prokop
7e03913d0d Use TS instead of JS to store theme variables@next 2019-02-08 14:06:53 +01:00
Dominik Prokop
71576a634e Do not use js theme variables in sass (poor dev experience for now) 2019-02-08 14:06:06 +01:00
Torkel Ödegaard
bb8bec5aaa Merge branch 'master' into core/theming 2019-02-08 09:05:41 +01:00
Leonard Gram
b32d420a75 ldap: refactoring. 2019-02-08 08:18:24 +01:00
Daniel Lee
21a1507c77 ldap: fixes #14432. Fix for IPA v4.6.4
IPA v4.6.4 introduced a fix that does not allow empty attributes
to be sent in a search request. This fix only adds attributes to
the request if they are mapped in the ldap toml file.
2019-02-08 08:18:24 +01:00
Daniel Lee
13d9acb1ef ldap: adds docker block for freeipa 2019-02-08 08:18:24 +01:00
Torkel Ödegaard
0bd39e5426 Merge pull request #15307 from grafana/fixed-explore-width-0
fixed explore width-0 issue
2019-02-07 19:11:02 +01:00
Torkel Ödegaard
0f96cf8662 slight tweaks 2019-02-07 19:06:51 +01:00
Torkel Ödegaard
df17f7dc45 fixed explore width-0 issue, fixes #15304 2019-02-07 18:20:16 +01:00
bergquist
2be60887ca adds usage stats for sessions 2019-02-07 16:27:40 +01:00
Torkel Ödegaard
e080983147 Panel edit navbar poc 2019-02-07 16:14:11 +01:00
Marcus Efraimsson
9c18aa8684 make sure to create provisioning/notifiers directory for deb and rpm packages 2019-02-07 16:10:39 +01:00
Marcus Efraimsson
b4267eafb8 log root cause error when reading from provisioning directories 2019-02-07 15:46:57 +01:00
bergquist
6e7941d396 moves usage stats sender to new package 2019-02-07 14:59:50 +01:00
corpglory-dev
0cc9cbcb54 Merge branch 'master' into azure-monitor-refactor-#15087 2019-02-07 16:59:49 +03:00
Marcus Efraimsson
c71904e326 Merge pull request #15239 from grafana/auth_token_middleware_refactor
Auth token package and middleware refactoring
2019-02-07 14:24:23 +01:00
Torkel Ödegaard
78ea7ae783 Merge pull request #15212 from grafana/dashboard-react-page
Dashboard react page
2019-02-07 14:23:44 +01:00
Dominik Prokop
ac50d2b31f Merge branch 'master' into core/theming 2019-02-07 14:20:53 +01:00
Marcus Efraimsson
b545f51820 changelog: add notes about closing #15291 2019-02-07 14:18:20 +01:00
Torkel Ödegaard
8f0e9b674d Merge pull request #15282 from CorpGlory/unexpected-semver-comparison-behavior-#15280
Unexpected semver comparison behavior #15280
2019-02-07 14:17:48 +01:00
Dominik Prokop
e7917ce4e0 Removed unnecessary code from ColorPicker and extended theme type 2019-02-07 14:10:56 +01:00
Dominik Prokop
5ba3b0aa2c Selecting theme variable variant helper function 2019-02-07 14:10:20 +01:00
Torkel Ödegaard
00a676ff86 Merge pull request #15292 from mtanda/template_json_format
support json format templating
2019-02-07 14:05:09 +01:00
Torkel Ödegaard
8c65430ea3 Merge pull request #15251 from grafana/14822-use-autosizer
Remove react-sizeme and use AutoSizer
2019-02-07 14:03:25 +01:00
Torkel Ödegaard
2eca4caa5d added reducers tests 2019-02-07 13:58:24 +01:00
Mitsuhiro Tanda
89d69a6f21 update docs 2019-02-07 20:16:20 +09:00
Torkel Ödegaard
61e9148eed added way to test action called from react component 2019-02-07 12:02:01 +01:00
Torkel Ödegaard
5f808ddf22 Added annother initDashboard test 2019-02-07 11:17:31 +01:00
bergquist
487e7b5ea6 removes cleanup setting from docs 2019-02-07 11:07:55 +01:00
bergquist
170783c292 make hourly cleanup the default behavior 2019-02-07 10:51:35 +01:00
Torkel Ödegaard
8b080f0511 Simplified condition 2019-02-07 09:09:59 +01:00
Mitsuhiro Tanda
4bd94b8aba support json format templating 2019-02-07 14:07:41 +09:00
Marcus Efraimsson
3555997f98 devenv: update ha test and load test
better ha setup for many mysql connections
prometheus now scrapes mysql metrics in ha setup
ha setup provisions mysql dashboard
adds configurable virtual users for load test run script
2019-02-06 22:33:48 +01:00
Marcus Efraimsson
1a140ee199 run token cleanup job when grafana starts, then each hour 2019-02-06 22:27:08 +01:00
Torkel Ödegaard
6caae9167e Merge pull request #15278 from grafana/15277-remove-unused-theme-variables
Removed unused theme variables
2019-02-06 21:58:30 +01:00
Torkel Ödegaard
7edc3fdd5c Added another error object message detection 2019-02-06 21:35:01 +01:00
Torkel Ödegaard
baeec495a2 Fixed some remaining issues 2019-02-06 21:32:48 +01:00
Torkel Ödegaard
961695a61f Improved dashboard page test 2019-02-06 21:08:15 +01:00
Torkel Ödegaard
a4841a72d9 Improved dashboard page test 2019-02-06 21:04:18 +01:00
Torkel Ödegaard
dd0afd0a0b Big refactoring for dashboard init redux actions 2019-02-06 19:42:04 +01:00
corpglory-dev
ee132c1091 Fix SemVersion.isGtOrEq 2019-02-06 19:59:28 +03:00
Torkel Ödegaard
8574dca081 making changes suggested in review and improving typings 2019-02-06 17:31:52 +01:00
Marcus Efraimsson
836501186f fix 2019-02-06 17:30:17 +01:00
Dominik Prokop
dc6b27d123 Minor cleanup 2019-02-06 17:05:43 +01:00
Dominik Prokop
1e4c6b4b52 Added test for SASS variable retrieval function from JS definition 2019-02-06 17:05:22 +01:00
Dominik Prokop
7762d72ae3 Updated stories to use new theming 2019-02-06 17:03:42 +01:00
Marcus Efraimsson
8ae066ab5d move authtoken package into auth package 2019-02-06 17:02:57 +01:00
Marcus Efraimsson
8678620730 move UserToken and UserTokenService to models package 2019-02-06 16:55:12 +01:00
corpglory-dev
43b5eba8ee Add failing test 2019-02-06 18:33:24 +03:00
corpglory-dev
eb879062f9 Rename version_test to version.test 2019-02-06 18:32:22 +03:00
Marcus Efraimsson
a60124a88c change UserToken from interface to struct 2019-02-06 16:30:50 +01:00
ijin08
16e3c193ec replaced some hex values with variables 2019-02-06 16:19:56 +01:00
ijin08
0e7b420d6a some changes i forgot to save in first push in variables.dark 2019-02-06 15:51:36 +01:00
ijin08
3ef4d20f77 removed trailing whitespace 2019-02-06 15:47:03 +01:00
ijin08
27a1a9e8c5 removed unused theme variables, removed empty sections, aligned the order of sections in the files 2019-02-06 15:45:40 +01:00
Dominik Prokop
7eb2558fc5 Fix issue with graph legend color picker disapearing on color selection 2019-02-06 15:06:27 +01:00
Marcus Efraimsson
809d4b040a changelog: add notes about closing #12546 2019-02-06 14:52:17 +01:00
Marcus Efraimsson
afa87e6ab4 Merge pull request #15197 from SamuelToh/12546_annotation_nulls_existing_val
Add http patch support for annotations
2019-02-06 14:50:00 +01:00
Torkel Ödegaard
a53c3b45fc Added a basic test for initDashboard thunk 2019-02-06 14:35:53 +01:00
Marcus Efraimsson
6848fe0edf docs: update annotaions http api 2019-02-06 14:26:43 +01:00
corpglory-dev
e58f3a678d Merge branch 'master' into azure-monitor-refactor-#15087 2019-02-06 14:31:23 +03:00
Alexander Zobnin
e4446f0340 azuremonitor: improve autocomplete UX 2019-02-06 13:52:35 +03:00
Torkel Ödegaard
08a86250be Merge branch 'dashboard-react-page' of github.com:grafana/grafana into dashboard-react-page 2019-02-06 11:34:08 +01:00
Torkel Ödegaard
865d1567fc Added DashboardPage tests that tests view mode transition logic 2019-02-06 11:30:42 +01:00
Alexander Zobnin
4caea91164 azuremonitor: fix autocomplete menu height 2019-02-06 13:19:17 +03:00
Johannes Schill
c47c2528aa Revert "chore: Replace sizeMe with AutoSizer in DashboardGrid"
This reverts commit ae0b027d69.
2019-02-06 09:45:09 +01:00
Johannes Schill
9c64e3b4b9 Revert "chore: Remove react-sizeme"
This reverts commit 260b6f5de8.
2019-02-06 09:45:03 +01:00
Marcus Efraimsson
85ef2ca738 fix spelling 2019-02-06 09:43:45 +01:00
Torkel Ödegaard
1fbdd02464 wip: tests 2019-02-06 09:04:38 +01:00
Marcus Efraimsson
44275d9660 middleware fix 2019-02-06 08:45:01 +01:00
Marcus Efraimsson
d8658a765c enhanced expiration logic for lookup token
tokens are not expired if created_at > now - LoginMaxLifetimeDays and
rotated_at > now - LoginMaxInactiveLifetimeDays
2019-02-06 08:30:14 +01:00
Marcus Efraimsson
0be43948e2 changelog: add notes about closing #15265 2019-02-05 22:01:00 +01:00
Marcus Efraimsson
e87ff5a06d Merge pull request #15265 from larsjoergensen/cloudwatch/add-rds-aurora-serverlessdatabasecapacity-metrics
Added ServerlessDatabaseCapacity metric to list of AWS RDS metrics.
2019-02-05 21:56:11 +01:00
SamuelToh
4de9e3598b Address review comments 2019-02-06 06:41:39 +10:00
Marcus Efraimsson
9483506590 auth token clean up job now runs on schedule and deletes all expired tokens
delete tokens having created_at <= LoginMaxLifetimeDays or
rotated_at <= LoginMaxInactiveLifetimeDays
2019-02-05 21:20:11 +01:00
Marcus Efraimsson
871c84d195 changes needed for api/middleware due to configuration settings 2019-02-05 21:14:23 +01:00
Marcus Efraimsson
0915f931ae change configuration settings in auth package 2019-02-05 21:12:30 +01:00
Marcus Efraimsson
80d0943d9d document login, short-lived tokens and secure cookie configurations 2019-02-05 21:10:56 +01:00
Marcus Efraimsson
3c2fd02bc0 refactor login/auth token configuration settings
remove login section and reuse existing sections security and auth
2019-02-05 21:09:55 +01:00
Marcus Efraimsson
1d1b617cee remove unused code 2019-02-05 21:08:55 +01:00
Lars Jørgensen
749320002b Added ServerlessDatabaseCapacity metric to list of AWS RDS metrics. 2019-02-05 20:57:51 +01:00
Marcus Efraimsson
3e129dffa0 changelog: add notes about closing #8207 2019-02-05 19:40:46 +01:00
Torkel Ödegaard
4d2cff41ff Minor code simplification 2019-02-05 19:38:51 +01:00
Marcus Efraimsson
26df20082b Merge pull request #14803 from jeroenvollenbrock/8207-resource-arns
cloudwatch: Add resource_arns template query function
2019-02-05 19:34:59 +01:00
Torkel Ödegaard
2196b4f1c2 Delete template.html 2019-02-05 19:34:31 +01:00
jeroenvollenbrock
28aafcd789 cloudwatch: Add tests for resource_arn template query 2019-02-05 18:37:05 +01:00
jeroenvollenbrock
fa977ce090 cloudwatch: Add resource_arns template query function
Implements feature request #8207
2019-02-05 18:37:04 +01:00
jeroenvollenbrock
6fb76b7c9b update to aws-sdk-go v1.16.15 2019-02-05 18:37:04 +01:00
Torkel Ödegaard
fa32198831 Merge branch 'master' into dashboard-react-page 2019-02-05 17:56:04 +01:00
Torkel Ödegaard
80ccea3d85 Merge pull request #15254 from grafana/update-add-panel-flow
Update add panel flow
2019-02-05 17:51:49 +01:00
Torkel Ödegaard
096751b658 Updated add panel related flows 2019-02-05 17:15:21 +01:00
Dominik Prokop
6b1390b972 Update types and themes usage in components 2019-02-05 17:04:48 +01:00
Dominik Prokop
1bc007e29c Implemented theme context and renamed/moved theme related types 2019-02-05 16:53:19 +01:00
Torkel Ödegaard
a0bd022186 Merge pull request #15259 from grafana/hugoh/clicking-outside-timepicker-should-hide-it
Clicking outside TimePicker (Angular & React versions) should close it
2019-02-05 16:46:47 +01:00
Daniel Lee
c19baaffaa changelog: adds note for #15182 2019-02-05 16:17:22 +01:00
Daniel Lee
9959bbfdfb Merge pull request #15260 from grafana/15182-stackdriver-filter
Changes default interpolation for Stackdriver filter to be regex. Fixes #15182
2019-02-05 16:14:48 +01:00
Torkel Ödegaard
f5249d6033 Breaking init dashboard up in to fetch & init 2019-02-05 15:58:09 +01:00
Daniel Lee
1ecd70e2dd stackdriver: fixes #15182
For the filter expression, a better default is to
interpolate multi variables as a regex rather than
the default globbing pattern.

Also, uses the real TemplateSrv class rather than
stubbing it in the tests.
2019-02-05 15:42:59 +01:00
Hugo Häggmark
e42b670f5c Closing timepicker when clicking outside the picker 2019-02-05 15:41:00 +01:00
Hugo Häggmark
a344091d82 Optimized so we only do checks when dropdown is opened 2019-02-05 15:29:19 +01:00
Daniel Lee
0302c7afa7 stackdriver: add some more typings 2019-02-05 15:28:03 +01:00
Hugo Häggmark
e2ffaef88a Fixed so that we close angular TimePicker when user clicks outside the dropdown 2019-02-05 15:25:19 +01:00
Torkel Ödegaard
6dd1a8e688 Merge pull request #15256 from grafana/hugoh/bug-explore-cut-and-paste
Handle onPaste because of bug in this Slate version
2019-02-05 15:23:14 +01:00
Torkel Ödegaard
49a597fcd0 Moved remove panel logic to dashboard srv 2019-02-05 15:15:15 +01:00
Torkel Ödegaard
a624c9713a Removed unused controllers and services 2019-02-05 15:09:56 +01:00
Torkel Ödegaard
6d874dd1f1 Improved error handling 2019-02-05 14:42:29 +01:00
Johannes Schill
e103143634 fix: Update snapshot 2019-02-05 14:42:18 +01:00
Johannes Schill
f5431f5210 chore: Explore: Remove inner AutoSizer, spread the size-object to width/height, change height type to number 2019-02-05 14:42:18 +01:00
Johannes Schill
260b6f5de8 chore: Remove react-sizeme 2019-02-05 14:42:18 +01:00
Johannes Schill
d68df9d704 fix: Calculation issue with AutoSizer in explore 2019-02-05 14:42:18 +01:00
Johannes Schill
097396c517 chore: Replace withSize with AutoSizer in explore/Graph.tsx 2019-02-05 14:42:18 +01:00
Johannes Schill
ae0b027d69 chore: Replace sizeMe with AutoSizer in DashboardGrid 2019-02-05 14:42:18 +01:00
Torkel Ödegaard
da53103281 Prevent viewers from going into edit mode 2019-02-05 14:18:35 +01:00
Torkel Ödegaard
aa2bf07c71 Expand rows for panels in collapsed rows 2019-02-05 14:12:32 +01:00
Torkel Ödegaard
08925ffad8 Basic loading state for slow dashboards 2019-02-05 13:53:11 +01:00
Daniel Lee
310ee5674f Merge pull request #15252 from grafana/14940-update-checker-docs
docs: fixes #14940
2019-02-05 13:25:55 +01:00
Hugo Häggmark
9ba98b8703 Fixes #15223 by handling onPaste event because of bug in Slate 2019-02-05 13:13:52 +01:00
Torkel Ödegaard
bbc5dff7bd Fixed add panel should scroll to top 2019-02-05 12:56:03 +01:00
Peter Holmberg
2802569529 minor layout change, simple render test 2019-02-05 12:47:42 +01:00
Alexander Zobnin
181b4f9e80 azuremonitor: improve autocomplete experence 2019-02-05 14:39:24 +03:00
Daniel Lee
139fb65fa9 docs: fixes #14940 2019-02-05 12:36:12 +01:00
Torkel Ödegaard
fd1ef0a2be Added custom scrollbar and remember scroll pos to jump back to same scroll pos when going back to dashboard from edit mode 2019-02-05 12:10:42 +01:00
ijin08
0642c52693 created new color variables, changed primary to blue, changed success-btns to primary-btns. 2019-02-05 12:05:02 +01:00
Alexander Zobnin
4b5bfd3da5 azuremonitor: more autocomplete suggestions for built-in functions 2019-02-05 14:01:06 +03:00
Torkel Ödegaard
04f190c3e3 Updated playlist test 2019-02-05 11:11:17 +01:00
Torkel Ödegaard
6eb69d37de Merge pull request #15243 from ryantxu/better-stackdriver-logo
Better stackdriver logo (bigger + svg)
2019-02-05 10:22:40 +01:00
Torkel Ödegaard
44cef757e5 Merge branch 'hugoh/explore-refactor-initial-modified-queries' 2019-02-05 09:33:28 +01:00
Torkel Ödegaard
bfdfb215f3 added missing typing to explore props 2019-02-05 09:32:42 +01:00
Torkel Ödegaard
e4c92ae124 added comment to initDashboard 2019-02-05 09:24:30 +01:00
Torkel Ödegaard
3d5ca99d53 Merge branch 'dashboard-react-page' of github.com:grafana/grafana into dashboard-react-page 2019-02-05 09:24:14 +01:00
Torkel Ödegaard
28fc27c4ae Merge branch 'master' of github.com:grafana/grafana into dashboard-react-page 2019-02-05 09:23:58 +01:00
ryan
275800cca9 improve the stackdriver logo 2019-02-04 23:19:14 -08:00
ryan
693bb43452 Merge remote-tracking branch 'grafana/master'
* grafana/master: (54 commits)
  now /api/login/ping returns Response
  fix: Explore: Query wrapping on long queries #15222
  fix: Set ace editor min height to avoid problem with scrollbar overlapping ace content #15122
  fix: Data source picker in panel queries options should overlap content below, including ace scrollbar #15122
  fix util for splitting host and port
  changelog: add notes about closing #14231
  fixing logging action
  devenv: switching back using loki master plus various fixes
  Fix save provisioned dashboard modal
  Add AWS/Neptune to metricsMap and dimensionsMap
  did not add file, removing centerered
  Legend toggle should only trigger a re-render, not a refresh
  first stuff
  updated snapshot
  Adding pointer to colorpicker
  Minor post review changes
  More style tweaks to panel option group add button
  Made some style tweaks
  setting margin on label
  Make runQueries action independent from datasource loading
  ...
2019-02-04 23:06:03 -08:00
Hugo Häggmark
3b0a04c627 Fixed so onBlur event trigger an QueryChange and QueryExecute if values differ 2019-02-05 07:03:16 +01:00
Hugo Häggmark
2c255fd85a Renamed initialQueries to queries 2019-02-05 06:19:40 +01:00
SamuelToh
a7a964ec19 Added PATCH verb end point for annotation op
Added new PATCH verb annotation endpoint

Removed unwanted fmt

Added test cases for PATCH verb annotation endpoint

Fixed formatting issue

Check arr len before proceeding

Updated doc to include PATCH verb annotation endpt
2019-02-05 09:43:17 +10:00
Marcus Efraimsson
d53e64a32c move auth token middleware/hooks to middleware package
fix/adds auth token middleware tests
2019-02-05 00:21:05 +01:00
Marcus Efraimsson
7cd3cd6cd4 auth package refactoring
moving middleware/hooks away from package
exposing public struct UserToken accessible from other packages
fix debug log lines so the same order and naming are used
2019-02-05 00:10:56 +01:00
Torkel Ödegaard
d29e1278dc render after leaving fullscreen 2019-02-04 21:39:48 +01:00
Marcus Efraimsson
57457e2aa4 Merge pull request #15170 from bergquist/delete_session_on_logout
deletes auth token on signout
2019-02-04 21:37:58 +01:00
Peter Holmberg
e4dad78045 added flags to vizpicker from query param 2019-02-04 21:26:49 +01:00
Torkel Ödegaard
7d5becceb5 Merge pull request #15206 from grafana/storybook/valuemappingseditor
Adding ValueMappingsEditor to Storybook
2019-02-04 21:09:26 +01:00
Torkel Ödegaard
70974c01f2 Added playlist controls to new react DashNav 2019-02-04 21:08:30 +01:00
Marcus Efraimsson
fb3c510178 Merge branch 'master' into delete_session_on_logout 2019-02-04 20:23:05 +01:00
Torkel Ödegaard
3b1da3758d Merge pull request #15234 from grafana/15222-explore-query-wrapping
Explore: Fix issue with wrapping on long queries
2019-02-04 18:43:42 +01:00
Torkel Ödegaard
60d7d9c691 Merge pull request #15181 from marefr/fix_ping_unauthorized
auth: /api/login/ping fixes
2019-02-04 18:41:35 +01:00
Torkel Ödegaard
d978a66ef6 Fixed lots of loading flow issues and updated solo route page 2019-02-04 18:24:56 +01:00
Torkel Ödegaard
23ac9405c1 Set page title on dashboard load 2019-02-04 17:39:29 +01:00
Marcus Efraimsson
cfd8eb5167 now /api/login/ping returns Response 2019-02-04 17:37:07 +01:00
Torkel Ödegaard
3baaf2c3e4 Added handling of kiosk mode 2019-02-04 17:36:04 +01:00
Dominik Prokop
7626ce9922 WIP Enable js defined theme to be used in SASS 2019-02-04 17:28:57 +01:00
Johannes Schill
9ab5eeb7f3 fix: Explore: Query wrapping on long queries #15222 2019-02-04 17:26:20 +01:00
Alexander Zobnin
99ff8e68ff azuremonitor: fix where suggestions 2019-02-04 19:23:29 +03:00
Peter Holmberg
f6b46f7a34 prepping go to visualization 2019-02-04 17:18:46 +01:00
Alexander Zobnin
dd8ca70151 azuremonitor: use kusto editor for App Insights 2019-02-04 18:51:56 +03:00
Peter Holmberg
b9c58d88dc basic layout 2019-02-04 16:48:27 +01:00
Torkel Ödegaard
d4ecba5235 Merge pull request #15178 from marefr/loki_blocks_fixes
devenv: various fixes for Loki docker block
2019-02-04 16:26:33 +01:00
Torkel Ödegaard
3ceb436462 Merge pull request #15232 from grafana/15122-ace-editor-issues
Ace editor issues
2019-02-04 16:24:47 +01:00
Torkel Ödegaard
fdeea9144c fixed unit test 2019-02-04 15:50:47 +01:00
Torkel Ödegaard
7162583396 Made dashboard view state srv panel view state obsolete 2019-02-04 15:44:08 +01:00
Johannes Schill
648bec1807 fix: Set ace editor min height to avoid problem with scrollbar overlapping ace content #15122 2019-02-04 15:19:23 +01:00
Hugo Häggmark
f74ebdade6 Missed to save 2019-02-04 15:11:19 +01:00
Johannes Schill
bc21c9520f fix: Data source picker in panel queries options should overlap content below, including ace scrollbar #15122 2019-02-04 15:07:11 +01:00
Hugo Häggmark
a0c4837eb5 Merge with master 2019-02-04 15:06:01 +01:00
Torkel Ödegaard
f695975f65 Fixed handling of orgId 2019-02-04 15:02:35 +01:00
Torkel Ödegaard
7634e04231 Fixed template variable value changed handling 2019-02-04 14:45:13 +01:00
Hugo Häggmark
34dd1a22ab Fixed bug with removing a QueryRow thats not part of nextQueries 2019-02-04 14:16:15 +01:00
Torkel Ödegaard
b58a3c939c Merge pull request #15194 from grafana/explore/url
Explore - UI panels state persistance in url
2019-02-04 14:10:50 +01:00
Torkel Ödegaard
ae768193e3 Now handles all dashbord routes 2019-02-04 13:49:14 +01:00
Hugo Häggmark
96aef3bab8 Replaced intialQueris with queryKeys 2019-02-04 13:41:29 +01:00
Torkel Ödegaard
09708dfe20 Merge pull request #15226 from grafana/15189_fix
fix util for splitting host and port
2019-02-04 13:39:00 +01:00
Marcus Efraimsson
d433ca7d40 fix util for splitting host and port
Now you can provide both a default host and a default port
2019-02-04 13:10:32 +01:00
Alexander Zobnin
ad821cf629 azuremonitor: where clause autocomplete 2019-02-04 14:11:49 +03:00
Alexander Zobnin
df9ecc6816 azuremonitor: don't go back to dashboard if escape pressed in the editor 2019-02-04 14:11:49 +03:00
Alexander Zobnin
0c3657da7e azuremonitor: suggest tables initially 2019-02-04 14:11:49 +03:00
Alexander Zobnin
f2d2712a95 azuremonitor: add more builtin functions and operators 2019-02-04 14:11:49 +03:00
Hugo Häggmark
efa48390b7 Reverted redux-logger 2019-02-04 12:09:06 +01:00
Hugo Häggmark
5e2b9e40a2 Added more typings 2019-02-04 11:25:07 +01:00
Torkel Ödegaard
217468074f added submenu, made sure submenu visibility is always up to date 2019-02-04 11:19:45 +01:00
Marcus Efraimsson
eb8dfefb23 changelog: add notes about closing #14231 2019-02-04 11:16:11 +01:00
Marcus Efraimsson
7436723686 Merge pull request #15214 from tcpatterson/cloudwatch-neptune-metrics
Add support for AWS/Neptune in Cloudwatch
2019-02-04 11:12:49 +01:00
Hugo Häggmark
6b98b05976 Removed modifiedQueries from state 2019-02-04 11:07:32 +01:00
Peter Holmberg
c61e905434 fixing logging action 2019-02-04 10:22:45 +01:00
Torkel Ödegaard
c15ab1b2bc Merge pull request #15219 from grafana/fix-provisioned-modal
Fix save provisioned dashboard modal
2019-02-04 10:18:22 +01:00
Marcus Efraimsson
fdd5ac1895 devenv: switching back using loki master plus various fixes 2019-02-04 09:55:23 +01:00
Torkel Ödegaard
f5084045f2 Fix save provisioned dashboard modal 2019-02-04 09:32:39 +01:00
Hugo Häggmark
d9578bc485 Merge with master 2019-02-04 08:17:18 +01:00
Hugo Häggmark
1f5bb76718 Refactor of action, actionTypes and reducer 2019-02-04 07:50:17 +01:00
Hugo Häggmark
2d0fd96621 More types and some refactoring 2019-02-04 07:50:17 +01:00
Hugo Häggmark
acea1d7f00 Alignment of interfaces and components 2019-02-04 07:50:17 +01:00
Hugo Häggmark
43f8098981 Removed the on every key change event 2019-02-04 07:48:25 +01:00
Connor Patterson
48c8ff8899 Add AWS/Neptune to metricsMap and dimensionsMap 2019-02-03 16:29:35 -05:00
Torkel Ödegaard
883f7a164b added time picker 2019-02-03 21:06:07 +01:00
Torkel Ödegaard
0324de37d2 refactorings and cleanup 2019-02-03 20:38:13 +01:00
Paresh
1f3fafb198 mssql: pass timerange for template variable queries 2019-02-03 13:07:33 -06:00
Torkel Ödegaard
d7151e5c88 improving dash nav react comp 2019-02-03 18:25:13 +01:00
Torkel Ödegaard
4a8effddf5 fixed panel removal 2019-02-03 17:15:23 +01:00
Torkel Ödegaard
09efa24f28 Added more buttons in dashboard nav 2019-02-03 15:29:14 +01:00
Torkel Ödegaard
2cb1733c59 wip: progress 2019-02-03 14:53:42 +01:00
Torkel Ödegaard
cba2ca5531 Url state -> dashboard model state sync starting to work 2019-02-03 12:29:47 +01:00
Torkel Ödegaard
8dec74689d Dashboard settings starting to work 2019-02-03 10:55:58 +01:00
Torkel Ödegaard
83937f59c0 wip: dashboard in react starting to work 2019-02-02 23:01:48 +01:00
Torkel Ödegaard
d86e773c75 wip: minor progress 2019-02-02 22:43:19 +01:00
Torkel Ödegaard
60f700a1d2 wip: dashboard react 2019-02-02 19:23:19 +01:00
Peter Holmberg
9ac960a803 did not add file, removing centerered 2019-02-02 00:48:13 +01:00
Peter Holmberg
a4d35e2fbc Merge branch 'master' into storybook/valuemappingseditor 2019-02-02 00:31:48 +01:00
Torkel Ödegaard
9e33f8b7c4 Merge pull request #15203 from jsferrei/optimize_legend
Legend toggle should only trigger a re-render, not a refresh
2019-02-01 20:57:29 +01:00
Jon Ferreira
e2c958eb43 Legend toggle should only trigger a re-render, not a refresh 2019-02-01 14:16:27 -05:00
Torkel Ödegaard
cab6a872cf Merge pull request #15200 from grafana/fix/add-pointer-to-color-picker
Add pointer cursor to colorpicker
2019-02-01 16:05:33 +01:00
Peter Holmberg
bd6fed54de first stuff 2019-02-01 15:45:47 +01:00
Torkel Ödegaard
35693d3b23 Merge pull request #15199 from grafana/fix/label-margin-datasource-list
setting margin on label in datasourcelist
2019-02-01 15:43:28 +01:00
Torkel Ödegaard
16f30664f5 Merge branch 'collapseable-panel-option-groups' 2019-02-01 15:39:47 +01:00
Torkel Ödegaard
59dfe794f5 updated snapshot 2019-02-01 15:39:20 +01:00
Torkel Ödegaard
3a5f03c772 Merge pull request #15191 from bergquist/samesite_for_cookies
introduce samesite setting for login cookie
2019-02-01 15:37:43 +01:00
Torkel Ödegaard
3f1fd6c6f2 Merge pull request #15193 from w4tsn/patch-1
Clearify the Run from master instructions
2019-02-01 15:37:04 +01:00
Torkel Ödegaard
116e70740c Merge pull request #15012 from grafana/loki-query-editor
WIP: Loki query editor for dashboard panels
2019-02-01 15:33:23 +01:00
Peter Holmberg
7d958080c1 Adding pointer to colorpicker 2019-02-01 15:31:42 +01:00
Dominik Prokop
1a0b21b8d1 Minor post review changes 2019-02-01 15:27:02 +01:00
Torkel Ödegaard
24ccfb9ccc More style tweaks to panel option group add button 2019-02-01 15:23:32 +01:00
Torkel Ödegaard
3be1deea44 Made some style tweaks 2019-02-01 15:19:18 +01:00
Peter Holmberg
4b03e7c31f setting margin on label 2019-02-01 15:03:19 +01:00
Dominik Prokop
3c358e406e Make runQueries action independent from datasource loading 2019-02-01 14:56:54 +01:00
Peter Holmberg
609129c039 fixing test 2019-02-01 14:23:03 +01:00
Peter Holmberg
025d37e9a2 add button in header 2019-02-01 14:19:53 +01:00
Torkel Ödegaard
d1d5bbf697 Merge pull request #15188 from grafana/fix/table-data-to-component
React panel table data fix
2019-02-01 13:57:26 +01:00
Torkel Ödegaard
2a4b10a5c8 minor fix 2019-02-01 13:40:46 +01:00
Torkel Ödegaard
740e1a0540 Made really good progress on loki support in dashboards 2019-02-01 13:30:15 +01:00
Dominik Prokop
2ddccb4a21 Temporarily run queries independently from UI state of explore panels 2019-02-01 12:57:09 +01:00
corpglory-dev
6d03766ace Remove extra newline 2019-02-01 14:54:38 +03:00
Alexander Wellbrock
ab1ae1b2a7 Clearify the Run from master instructions
Especially for new developers to the go and yarn ecosystems this readme part is quite confusing.
2019-02-01 12:51:40 +01:00
corpglory-dev
9a3f4def98 Use slate-plugins from app/features/explore 2019-02-01 14:49:04 +03:00
corpglory-dev
bdd59de877 Remove newline && runner plugins 2019-02-01 14:47:33 +03:00
corpglory-dev
cf60ae79c3 Move prism to app/features/explore 2019-02-01 14:47:17 +03:00
Dominik Prokop
6ab9355146 Restoring explore panels state from URL 2019-02-01 12:33:15 +01:00
corpglory-dev
ce2209585c Remove version.ts 2019-02-01 14:32:40 +03:00
bergquist
a6bd2c73a0 introduce samesite setting for login cookie
ref #15067
2019-02-01 11:47:21 +01:00
Torkel Ödegaard
6e0b873739 Merge branch 'master' into loki-query-editor 2019-02-01 11:17:22 +01:00
Peter Holmberg
57596462a4 sending paneldata to component, gauge can handle table data 2019-02-01 10:53:58 +01:00
bergquist
a1b3986532 always delete session cookie even if db delete fails 2019-02-01 09:59:53 +01:00
Torkel Ödegaard
68ae17e4a4 Merge pull request #15155 from grafana/solo-panel-rewrite
New react container route for solo panels
2019-02-01 09:55:00 +01:00
Torkel Ödegaard
3d78cb4f8c Merge pull request #15158 from grafana/hugoh/redux-poc
WIP: Reducing boilerplate code for Redux
2019-02-01 09:41:40 +01:00
Torkel Ödegaard
82f0388af6 Merge pull request #15183 from grafana/dashboard-row-fixes
Fixed dashboard row title not updating when variable changed
2019-02-01 08:19:15 +01:00
Torkel Ödegaard
aeaac7480b New solo panel route working in all scenarios I can test 2019-02-01 08:15:21 +01:00
Torkel Ödegaard
c4f55fecbe Merge branch 'master' into solo-panel-rewrite 2019-02-01 07:09:45 +01:00
Hugo Häggmark
efec897fd4 Removed unused factory and fixed index based mapper lookup 2019-02-01 06:52:25 +01:00
Torkel Ödegaard
ac0140d596 Fixed dashboard row title not updating when variable changed, fixes #15133 2019-02-01 06:38:59 +01:00
Torkel Ödegaard
e54689a964 Removed comment from panel editor 2019-02-01 06:20:54 +01:00
Marcus Efraimsson
bd83078025 signout user if /api/login/ping returns 401 unauthorized 2019-02-01 01:22:56 +01:00
Marcus Efraimsson
dd5a8275f1 must return json response from /api/login/ping
Even though http error 401 was returned, the result was still a http 200
2019-02-01 01:21:23 +01:00
bergquist
91bd908e03 adds more tests signing out session 2019-01-31 22:24:04 +01:00
ryan
5739ad3cfc Merge remote-tracking branch 'grafana/master'
* grafana/master: (835 commits)
  changes some info logging to debug
  add missing ngInject annotation
  typing data
  changelog: adds note about closing #10780
  Do not render time region line or fill if colors not provided
  Fixed row options html template location, fixes #15157
  creating table data type
  build: enterprise release co project.
  Moved dashboard state components to state folder
  Moved time_srv to services folder, this should not belong to dashboard feature but it is too dependant on dashboard to move it out now, needs a bigger refactoring to isolate from dashboard
  Moved a few things around
  Updated what's new article
  Update CHANGELOG.md
  Update CHANGELOG.md
  Replace usages of kbn.valueFormats with ui/getValueFormat
  Fix anchor
  Added download links to docs
  Updated docs
  Updated version again
  Updated version and made some changes to changelog and what's new article
  ...
2019-01-31 12:56:07 -08:00
bergquist
11c4967bdc changes some info logging to debug 2019-01-31 21:51:14 +01:00
Dominik Prokop
f9bab9585a wip 2019-01-31 19:38:49 +01:00
Marcus Efraimsson
0442a86400 tailing grafana logs and temporaily using an older build 2019-01-31 19:22:25 +01:00
Torkel Ödegaard
1e0de188b0 Merge pull request #15172 from grafana/15171_fix
add missing ngInject annotation
2019-01-31 18:38:05 +01:00
Marcus Efraimsson
c7b5fca885 add missing ngInject annotation 2019-01-31 16:45:32 +01:00
bergquist
88ca54eba9 renames signout function 2019-01-31 16:26:36 +01:00
bergquist
43ac79685a delete auth token on signout 2019-01-31 16:13:35 +01:00
Torkel Ödegaard
59dc91ada3 Merge pull request #15163 from grafana/table-data-support
Table data support
2019-01-31 14:45:03 +01:00
Peter Holmberg
ed0f5b71c7 Merge branch 'master' into table-data-support 2019-01-31 14:16:06 +01:00
Peter Holmberg
7def403018 typing data 2019-01-31 13:59:25 +01:00
bergquist
53331772ef changelog: adds note about closing #10780 2019-01-31 13:43:04 +01:00
Torkel Ödegaard
06a9c7bacc Merge pull request #15160 from grafana/fix/time-region-missing-colors
Do not render time region line or fill if colors not provided
2019-01-31 13:23:12 +01:00
Dominik Prokop
a2cba6685c Do not render time region line or fill if colors not provided 2019-01-31 12:20:55 +01:00
Torkel Ödegaard
6c1f8a9cfe Merge pull request #15159 from grafana/row-options-fix
Fixed row options html template location,
2019-01-31 12:13:35 +01:00
Torkel Ödegaard
a43c00ce70 Fixed row options html template location, fixes #15157 2019-01-31 11:37:34 +01:00
Carl Bergquist
e2c2b70a61 Merge pull request #14852 from jpenalbae/pushover-attach
pushover: add support for attaching images (closes #10780)
2019-01-31 11:18:08 +01:00
Peter Holmberg
88cb38adde creating table data type 2019-01-31 09:59:21 +01:00
Torkel Ödegaard
6a4777eafc wip: New react container route for solo panels that supports both angular and react panels 2019-01-31 09:44:37 +01:00
Torkel Ödegaard
7e1b6f59fe Merge pull request #15149 from grafana/dashboard-refactorings
Dashboard organization refactorings (file names / locations)
2019-01-31 09:43:26 +01:00
Leonard Gram
ddfccec2ea build: enterprise release co project. 2019-01-31 09:42:50 +01:00
Hugo Häggmark
7e64ee8225 Fixed another type of fluent reducerFactory 2019-01-31 09:18:40 +01:00
Torkel Ödegaard
aafd4a339a Moved dashboard state components to state folder 2019-01-31 08:56:17 +01:00
Torkel Ödegaard
6663b2fab9 Moved time_srv to services folder, this should not belong to dashboard feature but it is too dependant on dashboard to move it out now, needs a bigger refactoring to isolate from dashboard 2019-01-31 08:44:46 +01:00
Torkel Ödegaard
474185c977 Moved a few things around 2019-01-31 08:37:15 +01:00
Hugo Häggmark
99b500c740 Removed then clauses, no need to test the test within the test 2019-01-31 07:48:33 +01:00
Torkel Ödegaard
ab812e73f6 Updated what's new article 2019-01-31 07:47:55 +01:00
Hugo Häggmark
6a84a85a80 Added reducerTester, reducer tests and tests 2019-01-31 07:37:36 +01:00
Torkel Ödegaard
08cfa5e32a Update CHANGELOG.md 2019-01-31 07:31:12 +01:00
Torkel Ödegaard
ca7afc10a9 Update CHANGELOG.md 2019-01-31 07:30:45 +01:00
Torkel Ödegaard
bcae94f8ca Merge pull request #15135 from grafana/tooling/stortybook-from-root
Add storybook script to run it from root dir
2019-01-31 07:25:52 +01:00
Torkel Ödegaard
36a2b9c015 Merge pull request #15136 from grafana/react-snapshots
React snapshots
2019-01-31 07:24:25 +01:00
Torkel Ödegaard
f643e2abe6 Merge pull request #15144 from Rohlik/patch-1
Fix anchor
2019-01-31 07:23:13 +01:00
Torkel Ödegaard
17ef221c68 Merge pull request #15146 from atjeff/remove-kbn-value-formats
Replace usages of kbn.valueFormats with ui/getValueFormat
2019-01-31 07:22:41 +01:00
Hugo Häggmark
2f47b225a0 Removed ActionTypes and fixed a noPayloadActionCreatorFactory 2019-01-31 06:38:40 +01:00
Jeff Hage
f13018ce05 Replace usages of kbn.valueFormats with ui/getValueFormat 2019-01-30 18:57:33 -05:00
Hugo Häggmark
d3815beb1c Refactored Datasources as POC 2019-01-30 20:07:14 +01:00
Thomas Rohlik
e83831904a Fix anchor 2019-01-30 18:52:40 +01:00
Torkel Ödegaard
e3472f6d81 Added download links to docs 2019-01-30 17:53:41 +01:00
Torkel Ödegaard
b8427ba379 Updated docs 2019-01-30 17:28:43 +01:00
Torkel Ödegaard
6fd60c639f Updated version again 2019-01-30 17:20:43 +01:00
Peter Holmberg
4b47e857f2 adjusting types to match 2019-01-30 13:43:17 +01:00
Dominik Prokop
d784accdec Add storybook script to run it from root dir 2019-01-30 12:54:24 +01:00
Hugo Häggmark
2236a1a36d Minor change to Action interfaces 2019-01-30 12:03:30 +01:00
Hugo Häggmark
94ce065f74 Simplified inteface for reducerFactory 2019-01-30 11:59:00 +01:00
Hugo Häggmark
65fb77ce73 Fixed a small bug and added case sensitivity 2019-01-30 11:28:23 +01:00
Hugo Häggmark
0ddaa95d0e Added reducerFactory and tests 2019-01-30 11:13:36 +01:00
Peter Holmberg
ae0b9692be first implementation 2019-01-30 10:39:42 +01:00
Hugo Häggmark
62341ffe56 Added actionCreatorFactory and tests 2019-01-30 10:31:38 +01:00
Torkel Ödegaard
0de861a3a8 Minor progress on react query editor support, solving updating query persisted state 2019-01-29 09:39:23 +01:00
Torkel Ödegaard
e4244d8bf8 Merge branch 'master' into loki-query-editor 2019-01-29 07:10:06 +01:00
Marcus Efraimsson
f157c19e16 extract parsing of datasource tls config to method 2019-01-28 19:38:56 +01:00
Marcus Efraimsson
7df5e3cebf extract tls auth settings directive from datasource http settings directive 2019-01-28 19:37:19 +01:00
Marcus Efraimsson
6584ca20d2 Merge branch 'master' into 8570-mysql-ssl-datasource 2019-01-28 15:12:33 +01:00
Torkel Ödegaard
02083d71c8 Loki query editor is starting to work, had to make changes to explore query field in order to update query from the outside without unmount between 2019-01-23 17:44:22 +01:00
bugficks
f31fe495e9 fix go fmt 2019-01-15 13:54:25 +01:00
bugficks
7db848f153 [Feature request] MySQL SSL CA in datasource connector
https://github.com/grafana/grafana/issues/8570
2019-01-15 13:29:56 +01:00
NighterMan
ec0fe11939 pushover: add support for attaching images (closes #10780)
Use native pushover native attachment support to deliver images
2019-01-13 04:09:51 +01:00
ryan
6435df0a22 Merge remote-tracking branch 'grafana/master'
* grafana/master: (54 commits)
  s/initialDatasourceId/initialDatasource/
  Fixed issues with panel size in edit mode, fixes #14703
  Tweak datetime picker layout for mobile
  Explore: Remember last use datasource
  Update yarn.lock
  Logs data model: add more log levels
  Review feedback
  Explore: fix loading indicator z-index on panel container
  Loki: change query row to be single field again
  Explore: logging UI style fixes
  Loki: query limit configurable in datasource
  Removed rxjs compat
  ldap: adds extra debug logging
  adds orgId to user dto for provisioned dashboards
  Update rxjs
  closes the body properly on successful webhooks
  makes cache mode configurable
  Fix general tab typos
  added node-sass as dev dependency, needed after I removed grunt-sass
  Husky and sasslint fixes, fixes #14638
  ...
2019-01-02 17:04:54 -08:00
ryan
9c086be591 Merge remote-tracking branch 'grafana/master'
* grafana/master: (41 commits)
  Fixes undefined issue with angular panels and editorTabs
  changelog: adds note about closing #14562
  Update field name
  Add documentation
  Rename the setting and add description
  export init notifier func
  Increase recent and starred limit in search and home dashboard, closes #13950
  changelog: adds note about closing #14486
  Panel help view fixes
  Add min/max height when resizing and replace debounce with throttle
  changelog: adds note about closing #14546
  Adding tests for auth proxy CIDR support
  changelog: adds note about closing #14109
  fix signed in user for orgId=0 result should return active org id
  Another take on resizing the panel, now using react-draggable
  Raise datasources number to 5000
  copy props to state to make it visible in the view
  refactor to not crash when no links
  updating snaps
  renaming component
  ...
2018-12-19 11:35:44 -08:00
ryan
f7170829ce Merge remote-tracking branch 'grafana/master'
* grafana/master: (421 commits)
  Minor update
  Make sure panel id is unique since some datasources (Graphite) will cancel ongoing requests with the same panel id
  changelog: adds note about closing #14548
  Minor cleanup now that angular panel edit is no longer
  Gauge option form markup fixes
  filter out table responses that don't have columns and rows
  enable goto explore from query panel editor for all datasources
  moves migrations to /sqlstore/migrations
  adds integration tests to ci build
  renames main lock function
  clean up integration tests
  change from db_text to nvarchar
  adds server lock package
  initial verison of server lock
  Minor react graph panel refactorings and fixes
  sorting tests for change value
  Fixes issues with user and team picker
  fixing coloring
  remove printed index
  updating test
  ...
2018-12-18 10:56:30 -08:00
ryan
7d6c1dd825 Merge remote-tracking branch 'grafana/master'
* grafana/master: (93 commits)
  updated publish script
  Update CHANGELOG.md
  fix time regions bugs
  fixed issue with colorpicker position above window, fixes #14412
  fixed issue with singlestat and repeated scopedVars, was only working for time series data sources, and only if there was any series, now scoped vars is always set, fixes #14367
  fix search tag issues, fixes #14391
  Clear query models when changing data source type, fixes #14394
  Use correct variable name in fail text
  Fix logs panel meta wrap
  Explore: dont pass all rows to all rows, fixes profiler
  Explore: Logging dedup tooltips
  Explore: Hide scanning again after result was found
  Explore: Fix timepicker inputs for absolute dates
  Switch to global match for full browser support of escaped custom vars
  Allow backslash escaping in custom variables
  Fixed issue with logs graph and stacking
  align yellow collor with graph in logs table
  Add the AWS/SES Cloudwatch metrics of BounceRate and ComplaintRate.  Pull request #14399
  allow sidemenu sections without children still have a hover menu/header
  changelog: adds note about closing #11221
  ...
2018-12-10 16:52:19 -08:00
ryan
b888aeec0d Merge remote-tracking branch 'grafana/master'
* grafana/master: (39 commits)
  update package.json to next version
  changelog: add notes about closing #13815
  changelog: add notes about closing #14246
  changelog: add notes about closing #12653
  Hid "Forgot your password" link from login menu when reset is disabled
  Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled
  update changelog
  update latest.json to latest stable version
  new stable docs version
  dataproxy: Override incoming Authorization header
  changelog: add notes about closing #14228
  Explore: Show logging errors from backend
  change obj order when merging so that correct format is being used
  Explore: Fix logging query parser for regex with quantifiers
  Update README.md
  Fixed typo in function name
  Explore: Fix label and history suggestions
  let each sql datasource handle timeFrom and timeTo macros
  Review feedback
  changelog: add notes about closing #14167
  ...
2018-12-03 08:14:13 -08:00
Ryan
1e94127070 Merge remote-tracking branch 'grafana/master' 2018-11-27 10:00:24 -08:00
ryan
594051b3fb Merge remote-tracking branch 'grafana/master'
* grafana/master: (116 commits)
  Adjust UI depth of query statistics
  Preserve suffix text when applying function suggestion
  changelog: adds note about closing #13993
  Refactored log stream merging, added types, tests, comments
  Fixes #13993 - adds more options for Slack notifications
  add auth.proxy headers to sample.ini
  add auth.proxy headers to default.ini
  fixed issue with reducer sharing url query instance with angular router
  fixed exporter bug missing adding requires for datasources only used via data source variable, fixes #13891
  minor text change in export modal
  build: removes unused.
  Fixed issues introduced by changing to PureComponent
  further refactoring of #13984
  minor fix
  refactorings and some clean-up / removal of things not used
  Update docs/sources/permissions/dashboard_folder_permissions.md
  Fix typo in docs/sources/reference/scripting.md
  move enterprise down in menu
  wip: panel-header: Fix shareModal compatibility with react and angular
  wip: panel-header: Remove custom menu items from panels completely
  ...
2018-11-09 12:28:29 -08:00
ryan
eeb80a43f4 Merge remote-tracking branch 'grafana/master'
* grafana/master: (878 commits)
  changelog: adds note about closing #13945
  removed file I added accidentally
  fixed to template PR issues, #13938
  alerting: increase default duration for queries
  Load hash based styles in error.html, too
  Add [hash] to filename of grafana.{light,dark}.css
  Fix minor JSON typo in HTTP API docs
  Added new backend setting for license file
  changelog: add notes about closing #13925
  fix for responsive rule for footer
  Updated login page logo & wordmark and responsive behavior
  added new workmarks
  fixed react whitespace warning on teams page
  renamed org files to match new naming guide
  moved profile pages to it's own feature folder
  moved new teams page
  reload page after preferences update
  Add delta window function to postgres query builder
  Increase Telegram captions length limit.
  Explore: async starts of language provider
  ...
2018-11-04 15:33:04 -08:00
ryan
16ac02f9c7 Merge remote-tracking branch 'grafana/master'
* grafana/master: (127 commits)
  alerting: move all notification conditions to defaultShouldNotify
  filter NULL values for column value suggestions
  imguploader: Add support for ECS credential provider for S3
  Remove .dropdown-menu-open on body click fixes #13409
  Remove option r from ln command since its not working everywhere
  fix: updated tests
  Fix spelling of your and you're
  Changed setting to be an alerting setting
  Remove non-existing css prop
  fix: Legend to the right, as table, should follow the width prop. Removing css conflicting with baron's width calculation. #13312
  rendering: Added concurrent rendering limits
  devenv: fix uid for bulk alert dashboards
  Explore: moved code to app/features/explore
  target gfdev-prometheus datasource
  devenv: adds script for creating many dashboards with alerts
  Fix goconst issues
  When stacking graphs, always include the y-offset so that tooltips can render proper values for individual points
  provisioning: changed provisioning default update interval from 3 to 10 seconds
  Fix https://github.com/grafana/grafana/issues/13387 metric segment options displays after blur
  docs: improve oauth generic azure ad instructions
  ...
2018-09-26 20:23:33 -07:00
580 changed files with 44247 additions and 8194 deletions

View File

@@ -333,6 +333,7 @@ jobs:
docker:
- image: grafana/grafana-ci-deploy:1.2.0
steps:
- checkout
- attach_workspace:
at: .
- run:

1
.gitignore vendored
View File

@@ -46,6 +46,7 @@ devenv/docker-compose.yaml
/conf/provisioning/**/custom.yaml
/conf/provisioning/**/dev.yaml
/conf/ldap_dev.toml
/conf/ldap_freeipa.toml
profile.cov
/grafana
/local

View File

@@ -1,4 +1,35 @@
# 6.0.0-beta1 (unreleased)
# 6.0.0-beta2 (unreleased)
### New Features
* **AzureMonitor**: Enable alerting by converting Azure Monitor API to Go [#14623](https://github.com/grafana/grafana/issues/14623)
### Minor
* **Alerting**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae)
* **Graphite/InfluxDB/OpenTSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges [#15284](https://github.com/grafana/grafana/issues/15284)
* **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182)
* **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock)
* **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson)
* **Cloudwatch**: Add AWS/EC2/API metrics [#14233](https://github.com/grafana/grafana/issues/14233), thx [@tcpatterson](https://github.com/tcpatterson)
* **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen)
* **MySQL**: Adds datasource SSL CA/client certificates support [#8570](https://github.com/grafana/grafana/issues/8570), thx [@bugficks](https://github.com/bugficks)
* **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh)
* **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh)
* **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda)
* **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288)
* **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131)
* **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432)
### 6.0.0-beta1 fixes
* **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189)
* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295)
* **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219)
* **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122)
* **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222)
* **Explore**: Fix cut & paste adds newline before and after selection [#15223](https://github.com/grafana/grafana/issues/15223)
* **Dataproxy**: Fix global datasource proxy timeout not added to correct http client [#15258](https://github.com/grafana/grafana/issues/15258) [#5699](https://github.com/grafana/grafana/issues/5699)
# 6.0.0-beta1 (2019-01-30)
### New Features
@@ -11,6 +42,7 @@
### Minor
* **Templating**: Built in time range variables `$__from` and `$__to`, [#1909](https://github.com/grafana/grafana/issues/1909)
* **Alerting**: Use separate timeouts for alert evals and notifications [#14701](https://github.com/grafana/grafana/issues/14701), thx [@sharkpc0813](https://github.com/sharkpc0813)
* **Elasticsearch**: Add support for offset in date histogram aggregation [#12653](https://github.com/grafana/grafana/issues/12653), thx [@mattiarossi](https://github.com/mattiarossi)
* **Elasticsearch**: Add support for moving average and derivative using doc count (metric count) [#8843](https://github.com/grafana/grafana/issues/8843) [#11175](https://github.com/grafana/grafana/issues/11175)
@@ -75,7 +107,7 @@
* **Stackdriver**: Fixes issue with data proxy and Authorization header [#14262](https://github.com/grafana/grafana/issues/14262)
* **Units**: fixedUnit for Flow:l/min and mL/min [#14294](https://github.com/grafana/grafana/issues/14294), thx [@flopp999](https://github.com/flopp999).
* **Logging**: Fix for issue where data proxy logged a secret when debug logging was enabled, now redacted. [#14319](https://github.com/grafana/grafana/issues/14319)
* **InfluxDB**: Add support for alerting on InfluxDB queries that use the cumulative_sum function. [#14314](https://github.com/grafana/grafana/pull/14314), thx [@nitti](https://github.com/nitti)
* TSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges**: Add support for alerting on InfluxDB queries that use the cumulative_sum function. [#14314](https://github.com/grafana/grafana/pull/14314), thx [@nitti](https://github.com/nitti)
* **Plugins**: Panel plugins should no receive the panel-initialized event again as usual.
* **Embedded Graphs**: Iframe graph panels should now work as usual. [#14284](https://github.com/grafana/grafana/issues/14284)
* **Postgres**: Improve PostgreSQL Query Editor if using different Schemas, [#14313](
@@ -1010,7 +1042,7 @@ Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472)
* **Docs**: Added some details about Sessions in Postgres [#7694](https://github.com/grafana/grafana/pull/7694) thx [@rickard-von-essen](https://github.com/rickard-von-essen)
* **Influxdb**: Allow commas in template variables [#7681](https://github.com/grafana/grafana/issues/7681) thx [@thuck](https://github.com/thuck)
* **Cloudwatch**: stop using deprecated session.New() [#7736](https://github.com/grafana/grafana/issues/7736) thx [@mtanda](https://github.com/mtanda)
* **OpenTSDB**: Pass dropcounter rate option if no max counter and no reset value or reset value as 0 is specified [#7743](https://github.com/grafana/grafana/pull/7743) thx [@r4um](https://github.com/r4um)
*TSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges**: Pass dropcounter rate option if no max counter and no reset value or reset value as 0 is specified [#7743](https://github.com/grafana/grafana/pull/7743) thx [@r4um](https://github.com/r4um)
* **Templating**: support full resolution for $interval variable [#7696](https://github.com/grafana/grafana/pull/7696) thx [@mtanda](https://github.com/mtanda)
* **Elasticsearch**: Unique Count on string fields in ElasticSearch [#3536](https://github.com/grafana/grafana/issues/3536), thx [@pyro2927](https://github.com/pyro2927)
* **Templating**: Data source template variable that refers to other variable in regex filter [#6365](https://github.com/grafana/grafana/issues/6365) thx [@rlodge](https://github.com/rlodge)

View File

@@ -64,6 +64,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
useradd -r -u $GF_UID -g grafana grafana && \
mkdir -p "$GF_PATHS_PROVISIONING/datasources" \
"$GF_PATHS_PROVISIONING/dashboards" \
"$GF_PATHS_PROVISIONING/notifiers" \
"$GF_PATHS_LOGS" \
"$GF_PATHS_PLUGINS" \
"$GF_PATHS_DATA" && \

12
Gopkg.lock generated
View File

@@ -37,6 +37,7 @@
"aws/credentials",
"aws/credentials/ec2rolecreds",
"aws/credentials/endpointcreds",
"aws/credentials/processcreds",
"aws/credentials/stscreds",
"aws/csm",
"aws/defaults",
@@ -45,13 +46,18 @@
"aws/request",
"aws/session",
"aws/signer/v4",
"internal/ini",
"internal/s3err",
"internal/sdkio",
"internal/sdkrand",
"internal/sdkuri",
"internal/shareddefaults",
"private/protocol",
"private/protocol/ec2query",
"private/protocol/eventstream",
"private/protocol/eventstream/eventstreamapi",
"private/protocol/json/jsonutil",
"private/protocol/jsonrpc",
"private/protocol/query",
"private/protocol/query/queryutil",
"private/protocol/rest",
@@ -60,11 +66,13 @@
"service/cloudwatch",
"service/ec2",
"service/ec2/ec2iface",
"service/resourcegroupstaggingapi",
"service/resourcegroupstaggingapi/resourcegroupstaggingapiiface",
"service/s3",
"service/sts"
]
revision = "fde4ded7becdeae4d26bf1212916aabba79349b4"
version = "v1.14.12"
revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2"
version = "v1.16.15"
[[projects]]
branch = "master"

View File

@@ -7,13 +7,18 @@
Grafana is an open source, feature rich metrics dashboard and graph editor for
Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
![](https://www.grafanacon.org/2019/images/grafanacon_la_nav-logo.png)
Join us Feb 25-26 in Los Angeles, California for GrafanaCon - a two-day event with talks focused on Grafana and the surrounding open source monitoring ecosystem. Get deep dives into Loki, the Explore workflow and all of the new features of Grafana 6, plus participate in hands on workshops to help you get the most out of your data.
Time is running out - grab your ticket now! http://grafanacon.org
<!---
![](http://docs.grafana.org/assets/img/features/dashboard_ex1.png)
-->
## Installation
Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get)
the latest release.
If you have any problems please read the [troubleshooting guide](http://docs.grafana.org/installation/troubleshooting/).
Head to [docs.grafana.org](http://docs.grafana.org/installation/) for documentation or [download](https://grafana.com/get) to get the latest release.
## Documentation & Support
Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides.
@@ -25,49 +30,71 @@ the latest master builds [here](https://grafana.com/grafana/download)
### Dependencies
- Go (Latest Stable)
- bra [`go get github.com/Unknwon/bra`]
- Node.js LTS
- yarn [`npm install -g yarn`]
### Get the project
**The project located in the go-path will be your working directory.**
### Building the backend
```bash
go get github.com/grafana/grafana
cd $GOPATH/src/github.com/grafana/grafana
```
### Building
#### The backend
```bash
go run build.go setup
go run build.go build
```
### Building frontend assets
#### Frontend assets
For this you need Node.js (LTS version).
*For this you need Node.js (LTS version).*
To build the assets, rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000):
```bash
npm install -g yarn
yarn install --pure-lockfile
```
### Run and rebuild on source change
#### Backend
To run the backend and rebuild on source change:
```bash
$GOPATH/bin/bra run
```
#### Frontend
Rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000):
```bash
yarn watch
```
Build the assets, rebuild on file change with Hot Module Replacement (HMR), and serve them by webpack-dev-server (http://localhost:3333):
```bash
yarn start
# OR set a theme
env GRAFANA_THEME=light yarn start
```
Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload.
Run tests
*Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload.*
Run tests and rebuild on source change:
```bash
yarn jest
```
### Recompile backend on source change
To rebuild on source change.
```bash
go get github.com/Unknwon/bra
bra run
```
Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).
**Open grafana in your browser (default: e.g. `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).**
### Building a Docker image

View File

@@ -106,22 +106,6 @@ path = grafana.db
# For "sqlite3" only. cache mode setting used for connecting to the database
cache_mode = private
#################################### Login ###############################
[login]
# Login cookie name
cookie_name = grafana_session
# How many days an session can be unused before we inactivate it
login_remember_days = 7
# How often should the login token be rotated. default to '10m'
rotate_token_minutes = 10
# How long should Grafana keep expired tokens before deleting them
delete_expired_token_after_days = 30
#################################### Session #############################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
@@ -203,8 +187,11 @@ data_source_proxy_whitelist =
# disable protection against brute force login attempts
disable_brute_force_login_protection = false
# set cookies as https only. default is false
https_flag_cookies = false
# set to true if you host Grafana behind HTTPS. default is false.
cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
cookie_samesite = lax
#################################### Snapshots ###########################
[snapshots]
@@ -257,6 +244,18 @@ external_manage_info =
viewers_can_edit = false
[auth]
# Login cookie name
login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
token_rotation_interval_minutes = 10
# Set to true to disable (hide) the login form, useful if you use OAuth
disable_login_form = false

View File

@@ -102,22 +102,6 @@ log_queries =
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
#################################### Login ###############################
[login]
# Login cookie name
;cookie_name = grafana_session
# How many days an session can be unused before we inactivate it
;login_remember_days = 7
# How often should the login token be rotated. default to '10'
;rotate_token_minutes = 10
# How long should Grafana keep expired tokens before deleting them
;delete_expired_token_after_days = 30
#################################### Session ####################################
[session]
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
@@ -190,8 +174,11 @@ log_queries =
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
# set cookies as https only. default is false
;https_flag_cookies = false
# set to true if you host Grafana behind HTTPS. default is false.
;cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
;cookie_samesite = lax
#################################### Snapshots ###########################
[snapshots]
@@ -237,6 +224,18 @@ log_queries =
;viewers_can_edit = false
[auth]
# Login cookie name
;login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
;login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
;login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
;token_rotation_interval_minutes = 10
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false
@@ -250,7 +249,7 @@ log_queries =
# This setting is ignored if multiple OAuth providers are configured.
;oauth_auto_login = false
#################################### Anonymous Auth ##########################
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
;enabled = false

View File

@@ -0,0 +1,54 @@
version: '3'
volumes:
freeipa_data: {}
services:
freeipa:
image: freeipa/freeipa-server:fedora-29
container_name: freeipa
stdin_open: true
tty: true
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
hostname: ipa.example.test
environment:
# - DEBUG_TRACE=1
- IPA_SERVER_IP=172.17.0.2
- DEBUG_NO_EXIT=1
- IPA_SERVER_HOSTNAME=ipa.example.test
- PASSWORD=Secret123
- HOSTNAME=ipa.example.test
command:
- --admin-password=Secret123
- --ds-password=Secret123
- -U
- --realm=EXAMPLE.TEST
ports:
# FreeIPA WebUI
- "80:80"
- "443:443"
# Kerberos
- "88:88/udp"
- "88:88"
- "464:464/udp"
- "464:464"
# LDAP
- "389:389"
- "636:636"
# DNS
# - "53:53/udp"
# - "53:53"
# NTP
- "123:123/udp"
# other
- "7389:7389"
- "9443:9443"
- "9444:9444"
- "9445:9445"
tmpfs:
- /run
- /tmp
volumes:
- freeipa_data:/data:Z
- /sys/fs/cgroup:/sys/fs/cgroup:ro

View File

@@ -0,0 +1,74 @@
# 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 = "172.17.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 = "uid=admin,cn=users,cn=accounts,dc=example,dc=test"
# Search user bind password
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
bind_password = 'Secret123'
# User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"
search_filter = "(uid=%s)"
# An array of base dns to search through
search_base_dns = ["cn=users,cn=accounts,dc=example,dc=test"]
# 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"
username = "uid"
member_of = "memberOf"
# surname = "sn"
# email = "mail"
[[servers.group_mappings]]
# If you want to match all (or no ldap groups) then you can use wildcard
group_dn = "*"
org_role = "Viewer"

View File

@@ -0,0 +1,32 @@
# Notes on FreeIPA LDAP Docker Block
Users have to be created manually. The docker-compose up command takes a few minutes to run.
## Create a user
`docker exec -it freeipa /bin/bash`
To create a user with username: `ldap-viewer` and password: `grafana123`
```bash
kinit admin
```
Log in with password `Secret123`
```bash
ipa user-add ldap-viewer --first ldap --last viewer
ipa passwd ldap-viewer
ldappasswd -D uid=ldap-viewer,cn=users,cn=accounts,dc=example,dc=org -w test -a test -s grafana123
```
## Enabling FreeIPA LDAP in Grafana
Copy the ldap_freeipa.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_freeipa.toml
; allow_sign_up = true
```

View File

@@ -0,0 +1,27 @@
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
client:
url: http://loki:3100/api/prom/push
scrape_configs:
- job_name: system
entry_parser: raw
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*log
- job_name: grafana
entry_parser: raw
static_configs:
- targets:
- localhost
labels:
job: grafana
__path__: /var/log/grafana/*log

View File

@@ -1,22 +1,14 @@
version: "3"
networks:
loki:
services:
loki:
image: grafana/loki:master
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
networks:
- loki
promtail:
image: grafana/promtail:master
volumes:
- ./docker/blocks/loki/config.yaml:/etc/promtail/docker-config.yaml
- /var/log:/var/log
- ../data/log:/var/log/grafana
command:
-config.file=/etc/promtail/docker-config.yaml
networks:
- loki

View File

@@ -15,6 +15,7 @@ services:
MYSQL_DATABASE: grafana
MYSQL_USER: grafana
MYSQL_PASSWORD: password
command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all, --max-connections=1001]
ports:
- 3306
healthcheck:
@@ -22,6 +23,16 @@ services:
timeout: 10s
retries: 10
mysqld-exporter:
image: prom/mysqld-exporter
environment:
- DATA_SOURCE_NAME=root:rootpass@(db:3306)/
ports:
- 9104
depends_on:
db:
condition: service_healthy
# db:
# image: postgres:9.3
# environment:
@@ -47,6 +58,7 @@ services:
- GF_DATABASE_PASSWORD=password
- GF_DATABASE_TYPE=mysql
- GF_DATABASE_HOST=db:3306
- GF_DATABASE_MAX_OPEN_CONN=300
- GF_SESSION_PROVIDER=mysql
- GF_SESSION_PROVIDER_CONFIG=grafana:password@tcp(db:3306)/grafana?allowNativePasswords=true
# - GF_DATABASE_TYPE=postgres
@@ -55,7 +67,7 @@ services:
# - GF_SESSION_PROVIDER=postgres
# - GF_SESSION_PROVIDER_CONFIG=user=grafana password=password host=db port=5432 dbname=grafana sslmode=disable
- GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug,auth:debug
- GF_LOGIN_ROTATE_TOKEN_MINUTES=2
- GF_AUTH_TOKEN_ROTATION_INTERVAL_MINUTES=2
ports:
- 3000
depends_on:
@@ -70,10 +82,3 @@ services:
- VIRTUAL_HOST=prometheus.loc
ports:
- 9090
# mysqld-exporter:
# image: prom/mysqld-exporter
# environment:
# - DATA_SOURCE_NAME=grafana:password@(mysql:3306)/
# ports:
# - 9104

View File

@@ -6,3 +6,9 @@ providers:
type: file
options:
path: /etc/grafana/provisioning/dashboards/alerts
- name: 'MySQL'
folder: 'MySQL'
type: file
options:
path: /etc/grafana/provisioning/dashboards/mysql

File diff suppressed because it is too large Load Diff

View File

@@ -30,10 +30,10 @@ scrape_configs:
port: 3000
refresh_interval: 10s
# - job_name: 'mysql'
# dns_sd_configs:
# - names:
# - 'mysqld-exporter'
# type: 'A'
# port: 9104
# refresh_interval: 10s
- job_name: 'mysql'
dns_sd_configs:
- names:
- 'mysqld-exporter'
type: 'A'
port: 9104
refresh_interval: 10s

View File

@@ -8,7 +8,7 @@ Docker
## Run
Run load test for 15 minutes:
Run load test for 15 minutes using 2 virtual users and targeting http://localhost:3000.
```bash
$ ./run.sh
@@ -20,6 +20,18 @@ Run load test for custom duration:
$ ./run.sh -d 10s
```
Run load test for custom target url:
```bash
$ ./run.sh -u http://grafana.loc
```
Run load test for 10 virtual users:
```bash
$ ./run.sh -v 10
```
Example output:
```bash

View File

@@ -65,7 +65,7 @@ export default (data) => {
}
});
sleep(1)
sleep(5)
}
export const teardown = (data) => {}

View File

@@ -5,8 +5,9 @@ PWD=$(pwd)
run() {
duration='15m'
url='http://localhost:3000'
vus='2'
while getopts ":d:u:" o; do
while getopts ":d:u:v:" o; do
case "${o}" in
d)
duration=${OPTARG}
@@ -14,11 +15,14 @@ run() {
u)
url=${OPTARG}
;;
v)
vus=${OPTARG}
;;
esac
done
shift $((OPTIND-1))
docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus 2 --duration $duration src/auth_token_test.js
docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus $vus --duration $duration src/auth_token_test.js
}
run "$@"

View File

@@ -36,6 +36,35 @@ Grafana of course has a built in user authentication system with password authen
disable authentication by enabling anonymous access. You can also hide login form and only allow login through an auth
provider (listed above). There is also options for allowing self sign up.
### Login and short-lived tokens
> The followung applies when using Grafana's built in user authentication, LDAP (without Auth proxy) or OAuth integration.
Grafana are using short-lived tokens as a mechanism for verifying authenticated users.
These short-lived tokens are rotated each `token_rotation_interval_minutes` for an active authenticated user.
An active authenticated user that gets it token rotated will extend the `login_maximum_inactive_lifetime_days` time from "now" that Grafana will remember the user.
This means that a user can close its browser and come back before `now + login_maximum_inactive_lifetime_days` and still being authenticated.
This is true as long as the time since user login is less than `login_maximum_lifetime_days`.
Example:
```bash
[auth]
# Login cookie name
login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
token_rotation_interval_minutes = 10
```
### Anonymous authentication
You can make Grafana accessible without any login required by enabling anonymous access in the configuration file.

View File

@@ -74,6 +74,12 @@ Here is a minimal policy example:
"ec2:DescribeRegions"
],
"Resource": "*"
},
{
"Sid": "AllowReadingResourcesForTags",
"Effect" : "Allow",
"Action" : "tag:GetResources",
"Resource" : "*"
}
]
}
@@ -128,6 +134,7 @@ Name | Description
*dimension_values(region, namespace, metric, dimension_key, [filters])* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric`, `dimension_key` or you can use dimension `filters` to get more specific result as well.
*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attributes matching the specified `region`, `attribute_name`, `filters`.
*resource_arns(region, resource_type, tags)* | Returns a list of ARNs matching the specified `region`, `resource_type` and `tags`.
For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
@@ -143,6 +150,8 @@ Query | Service
*dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)* | RDS
*dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)* | S3
*dimension_values(us-east-1,CWAgent,disk_used_percent,device,{"InstanceId":"$instance_id"})* | CloudWatch Agent
*resource_arns(eu-west-1,elasticloadbalancing:loadbalancer,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})* | ELB
*resource_arns(eu-west-1,ec2:instance,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})* | EC2
## ec2_instance_attribute examples
@@ -205,6 +214,16 @@ Example `ec2_instance_attribute()` query
ec2_instance_attribute(us-east-1, Tags.Name, { "tag:Team": [ "sysops" ] })
```
## Using json format template variables
Some of query takes JSON format filter. Grafana support to interpolate template variable to JSON format string, it can use as filter string.
If `env = 'production', 'staging'`, following query will return ARNs of EC2 instances which `Environment` tag is `production` or `staging`.
```
resource_arns(us-east-1, ec2:instance, {"Environment":${env:json}})
```
## Cost
Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this,

View File

@@ -99,7 +99,14 @@ The Logs Explorer (the `Log labels` button) next to the query field shows a list
Once the result is returned, the log panel shows a list of log rows and a bar chart where the x-axis shows the time and the y-axis shows the frequency/count.
{{< docs-imagebox img="/img/docs/v60/explore_loki.png" class="docs-image--no-shadow" caption="Explore Loki Log Streams" >}}
<div class="medium-6 columns">
<video width="800" height="500" controls>
<source src="/assets/videos/explore_loki.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<br />
#### Log Stream Selector

View File

@@ -14,15 +14,19 @@ weight = -11
This update to Grafana introduces a new way of exploring your data, support for log data and tons of other features.
Grafana v6.0 is out in **Beta**, [Download Now!](https://grafana.com/grafana/download/beta)
The main highlights are:
- [Explore]({{< relref "#explore" >}}) - A new query focused workflow for ad hoc data exploration and troubleshooting.
- [Explore]({{< relref "#explore" >}}) - A new query focused workflow for ad-hoc data exploration and troubleshooting.
- [Grafana Loki]({{< relref "#explore-and-grafana-loki" >}}) - Integration with the new open source log aggregation system from Grafana Labs.
- [Gauge Panel]({{< relref "#gauge-panel" >}}) - A new standalone panel for gauges.
- [New Panel Editor UX]({{< relref "#easily-switch-visualization-with-panel-edit-ux-update" >}}) improves panel editing
and enables easy switch between different visualizations.
- [New Panel Editor UX]({{< relref "#new-panel-editor" >}}) improves panel editing
and enables easy switching between different visualizations.
- [Google Stackdriver Datasource]({{< relref "#google-stackdriver-datasource" >}}) is out of beta and is officially released.
- [Azure Monitor]({{< relref "#azure-monitor-datasource" >}}) plugin is ported from being an external plugin to being a core datasource
- [React Plugin]({{< relref "#react-panels-query-editors" >}}) support enables an easier way to build plugins.
- [Named Colors]({{< relref "#named-colors" >}}) in our new improved color picker.
## Explore
@@ -36,7 +40,7 @@ For infrastructure monitoring and incident response, you no longer need to switc
of observability - metrics and logs. Explore works with every datasource but for Prometheus we have customized the
query editor and the experience to provide the best possible exploration UX.
#### Explore and Prometheus
### Explore and Prometheus
Explore features a new [Prometheus query editor](/features/explore/#prometheus-specific-features). This new editor has improved autocomplete, metric tree selector,
integrations with the Explore table view for easy label filtering and useful query hints that can automatically apply
@@ -49,6 +53,8 @@ Explore supports splitting the view so you can compare different queries, differ
{{< docs-imagebox img="/img/docs/v60/explore_split.png" max-width="800px" caption="Screenshot of the new Explore option in the panel menu" >}}
<br />
### Explore and Grafana Loki
The log exploration & visualization features in Explore are available to any data source but are currently only implemented by the new open source log
@@ -68,6 +74,8 @@ for other log sources to Explore and the next planned integration is Elasticsear
</video>
</div>
<br />
## New Panel Editor
Grafana v6.0 has a completely redesigned UX around editing panels. You can now resize the visualization area if you want
@@ -103,6 +111,7 @@ source** plugins can be written in React using our published `@grafana/ui` sdk l
will be shared closer to or just after release.
{{< docs-imagebox img="/img/docs/v60/react_panels.png" max-width="600px" caption="React Panel" >}}
<br />
### Google Stackdriver Datasource
@@ -127,15 +136,23 @@ If you are using `Auth proxy` for authentication the session storage will still
This release will force all users to log in again since their previous token is not valid anymore.
### Named Colors
{{< docs-imagebox img="/img/docs/v60/named_colors.png" max-width="400px" class="docs-image--right" caption="Named Colors" >}}
We have updated the color picker to show named colors and primary colors. We hope this will improve accessibility and
helps making colors more consistent across dashboards. We hope to do more in this color picker in the future, like show
colors used in the dashboard.
Named colors also enables Grafana to adapt colors to the current theme.
<div class="clearfix"></div>
### Other features
- The ElasticSearch datasource now supports [bucket script pipeline aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html). This gives the ability to do per bucket computations like the difference or ratio between two metrics.
- Support for Google Hangouts Chat alert notifications
#### Technical Work - moving from Angular to React
The Grafana team is putting a huge amount of work into converting the frontend code in Grafana from Angular to React. Currently, all external plugins for Grafana are written in Angular but we are planning to also support plugins written in React very soon.
- New built in template variables for the current time range in `$__from` and `$__to`
## Changelog

View File

@@ -97,7 +97,7 @@ Creates an annotation in the Grafana database. The `dashboardId` and `panelId` f
## Update Annotation
Content-Type: application/json
`PUT /api/annotations/:id`
Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the [Patch Annotation](#patch-annotation) operation.
@@ -115,7 +115,7 @@ Content-Type: application/json
```http
HTTP/1.1 200
"id": 1
Content-Type: application/json
```
@@ -135,7 +135,7 @@ format (string with multiple tags being separated by a space).
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
Content-Type: application/json
"tags":["tag3","tag4","tag5"]
```
**Example Response**:
@@ -150,7 +150,7 @@ Content-Type: application/json
`DELETE /api/annotations/:id`
Deletes the annotation that matches the specified id.
Content-Type: application/json
**Example Request**:
```http
@@ -164,11 +164,14 @@ Content-Type: application/json
```http
HTTP/1.1 200
Content-Type: application/json
```
Deletes the annotation that matches the specified region id. A region is an annotation that covers a timerange and has a start and end time. In the Grafana database, this is a stored as two annotations connected by a region id.
## Delete Annotation By RegionId
`DELETE /api/annotations/region/:id`
Deletes the annotation that matches the specified region id. A region is an annotation that covers a timerange and has a start and end time. In the Grafana database, this is a stored as two annotations connected by a region id.
**Example Request**:
@@ -180,6 +183,50 @@ Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
"message":"Annotation updated"
}
```
## Patch Annotation
`PATCH /api/annotations/:id`
Updates one or more properties of an annotation that matches the specified id.
This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the [Update Annotation](#update-annotation) operation.
**Example Request**:
```http
PATCH /api/annotations/1145 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
Content-Type: application/json
{
"text":"New Annotation Description",
"tags":["tag6","tag7","tag8"]
}
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"message":"Annotation patched"
}
```
## Delete Annotation By Id
`DELETE /api/annotations/:id`
@@ -201,7 +248,9 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
HTTP/1.1 200
Content-Type: application/json
{"message":"Annotation deleted"}
{
"message":"Annotation deleted"
}
```
## Delete Annotation By RegionId
@@ -225,5 +274,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
HTTP/1.1 200
Content-Type: application/json
{"message":"Annotation region deleted"}
{
"message":"Annotation region deleted"
}
```

View File

@@ -287,6 +287,14 @@ Default is `false`.
Define a white list of allowed ips/domains to use in data sources. Format: `ip_or_domain:port` separated by spaces
### cookie_secure
Set to `true` if you host Grafana behind HTTPS. Default is `false`.
### cookie_samesite
Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is mitigate the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks (CSRF), [read more here](https://www.owasp.org/index.php/SameSite). Valid values are `lax`, `strict` and `none`. Default is `lax`.
<hr />
## [users]
@@ -393,9 +401,7 @@ Analytics ID here. By default this feature is disabled.
### check_for_updates
Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used
in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor
send any sensitive information.
Set to false to disable all checks to https://grafana.com for new versions of installed plugins and to the Grafana GitHub repository to check for a newer version of Grafana. The version information is used in some UI views to notify that a new Grafana update or a plugin update exists. This option does not cause any auto updates, nor send any sensitive information. The check is run every 10 minutes.
<hr />

View File

@@ -50,6 +50,7 @@ Filter Option | Example | Raw | Interpolated | Description
`regex` | ${servers:regex} | `'test.', 'test2'` | <code>(test\.&#124;test2)</code> | Formats multi-value variable into a regex string
`pipe` | ${servers:pipe} | `'test.', 'test2'` | <code>test.&#124;test2</code> | Formats multi-value variable into a pipe-separated string
`csv`| ${servers:csv} | `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string
`json`| ${servers:json} | `'test1', 'test2'` | `["test1","test2"]` | Formats multi-value variable as a JSON string
`distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB.
`lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression.
`percentencode` | ${servers:percentencode} | `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded.

View File

@@ -5,7 +5,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
"version": "6.0.0-beta1",
"version": "6.0.0-beta2",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -27,6 +27,7 @@
"@types/react-dom": "^16.0.9",
"@types/react-grid-layout": "^0.16.6",
"@types/react-select": "^2.0.4",
"@types/react-transition-group": "^2.0.15",
"@types/react-virtualized": "^9.18.12",
"angular-mocks": "1.6.6",
"autoprefixer": "^6.4.0",
@@ -67,7 +68,7 @@
"husky": "^0.14.3",
"jest": "^23.6.0",
"jest-date-mock": "^1.0.6",
"lint-staged": "^6.0.0",
"lint-staged": "^8.1.3",
"load-grunt-tasks": "3.5.2",
"mini-css-extract-plugin": "^0.4.0",
"mocha": "^4.0.1",
@@ -85,6 +86,7 @@
"prettier": "1.9.2",
"react-hot-loader": "^4.3.6",
"react-test-renderer": "^16.5.0",
"redux-mock-store": "^1.5.3",
"regexp-replace-loader": "^1.0.1",
"sass-lint": "^1.10.2",
"sass-loader": "^7.0.1",
@@ -118,7 +120,8 @@
"typecheck": "tsc --noEmit",
"jest": "jest --notify --watch",
"api-tests": "jest --notify --watch --config=tests/api/jest.js",
"precommit": "grunt precommit"
"precommit": "grunt precommit",
"storybook": "cd packages/grafana-ui && yarn storybook"
},
"husky": {
"hooks": {

View File

@@ -8,6 +8,6 @@
"tslint": "echo \"Nothing to do\"",
"typecheck": "echo \"Nothing to do\""
},
"author": "",
"license": "ISC"
"author": "Grafana Labs",
"license": "Apache-2.0"
}

View File

@@ -1,10 +1,15 @@
import { configure } from '@storybook/react';
import { configure, addDecorator } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import { withTheme } from '../src/utils/storybook/withTheme';
import '../../../public/sass/grafana.light.scss';
// automatically import all files ending in *.stories.tsx
const req = require.context('../src/components', true, /.story.tsx$/);
addDecorator(withKnobs);
addDecorator(withTheme);
function loadStories() {
req.keys().forEach(req);
}

View File

@@ -1,7 +1,6 @@
const path = require('path');
module.exports = (baseConfig, env, config) => {
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
@@ -33,7 +32,12 @@ module.exports = (baseConfig, env, config) => {
config: { path: __dirname + '../../../../scripts/webpack/postcss.config.js' },
},
},
{ loader: 'sass-loader', options: { sourceMap: false } },
{
loader: 'sass-loader',
options: {
sourceMap: false
},
},
],
});
@@ -52,5 +56,9 @@ module.exports = (baseConfig, env, config) => {
});
config.resolve.extensions.push('.ts', '.tsx');
// Remove pure js loading rules as Storybook's Babel config is causing problems when mixing ES6 and CJS
// More about the problem we encounter: https://github.com/webpack/webpack/issues/4039
config.module.rules = config.module.rules.filter(rule => rule.test.toString() !== /\.(mjs|jsx?)$/.toString());
return config;
};

View File

@@ -8,8 +8,8 @@
"typecheck": "tsc --noEmit",
"storybook": "start-storybook -p 9001 -c .storybook -s ../../public"
},
"author": "",
"license": "ISC",
"author": "Grafana Labs",
"license": "Apache-2.0",
"dependencies": {
"@torkelo/react-select": "2.1.1",
"@types/react-color": "^2.14.0",

View File

@@ -1,46 +1,43 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, boolean } from '@storybook/addon-knobs';
import { boolean } from '@storybook/addon-knobs';
import { SeriesColorPicker, ColorPicker } from './ColorPicker';
import { action } from '@storybook/addon-actions';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { UseState } from '../../utils/storybook/UseState';
import { getThemeKnob } from '../../utils/storybook/themeKnob';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const getColorPickerKnobs = () => {
return {
selectedTheme: getThemeKnob(),
enableNamedColors: boolean('Enable named colors', false),
};
};
const ColorPickerStories = storiesOf('UI/ColorPicker/Pickers', module);
ColorPickerStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
ColorPickerStories.addDecorator(withCenteredStory);
ColorPickerStories.add('default', () => {
const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
const { enableNamedColors } = getColorPickerKnobs();
return (
<UseState initialState="#00ff00">
{(selectedColor, updateSelectedColor) => {
return (
<ColorPicker
enableNamedColors={enableNamedColors}
color={selectedColor}
onChange={color => {
action('Color changed')(color);
updateSelectedColor(color);
}}
theme={selectedTheme || undefined}
/>
);
return renderComponentWithTheme(ColorPicker, {
enableNamedColors,
color: selectedColor,
onChange: (color: any) => {
action('Color changed')(color);
updateSelectedColor(color);
},
});
}}
</UseState>
);
});
ColorPickerStories.add('Series color picker', () => {
const { selectedTheme, enableNamedColors } = getColorPickerKnobs();
const { enableNamedColors } = getColorPickerKnobs();
return (
<UseState initialState="#00ff00">
@@ -52,7 +49,6 @@ ColorPickerStories.add('Series color picker', () => {
onToggleAxis={() => {}}
color={selectedColor}
onChange={color => updateSelectedColor(color)}
theme={selectedTheme || undefined}
>
<div style={{ color: selectedColor, cursor: 'pointer' }}>Open color picker</div>
</SeriesColorPicker>

View File

@@ -1,12 +1,12 @@
import React, { Component, createRef } from 'react';
import PopperController from '../Tooltip/PopperController';
import Popper, { RenderPopperArrowFn } from '../Tooltip/Popper';
import Popper from '../Tooltip/Popper';
import { ColorPickerPopover } from './ColorPickerPopover';
import { Themeable, GrafanaTheme } from '../../types';
import { Themeable } from '../../types';
import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
import propDeprecationWarning from '../../utils/propDeprecationWarning';
import { withTheme } from '../../themes/ThemeContext';
type ColorPickerChangeHandler = (color: string) => void;
export interface ColorPickerProps extends Themeable {
@@ -18,7 +18,6 @@ export interface ColorPickerProps extends Themeable {
*/
onColorChange?: ColorPickerChangeHandler;
enableNamedColors?: boolean;
withArrow?: boolean;
children?: JSX.Element;
}
@@ -32,7 +31,6 @@ export const warnAboutColorPickerPropsDeprecation = (componentName: string, prop
export const colorPickerFactory = <T extends ColorPickerProps>(
popover: React.ComponentType<T>,
displayName = 'ColorPicker',
renderPopoverArrowFunction?: RenderPopperArrowFn
) => {
return class ColorPicker extends Component<T, any> {
static displayName = displayName;
@@ -50,17 +48,7 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
...this.props,
onChange: this.handleColorChange,
});
const { theme, withArrow, children } = this.props;
const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => {
return (
<div
{...arrowProps}
data-placement={placement}
className={`ColorPicker__arrow ColorPicker__arrow--${theme === GrafanaTheme.Light ? 'light' : 'dark'}`}
/>
);
};
const { theme, children } = this.props;
return (
<PopperController content={popoverElement} hideAfter={300}>
@@ -72,7 +60,6 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
{...popperProps}
referenceElement={this.pickerTriggerRef.current}
wrapperClassName="ColorPicker"
renderArrow={withArrow && (renderPopoverArrowFunction || renderArrow)}
onMouseLeave={hidePopper}
onMouseEnter={showPopper}
/>
@@ -95,7 +82,7 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
<div
className="sp-preview-inner"
style={{
backgroundColor: getColorFromHexRgbOrName(this.props.color || '#000000', theme),
backgroundColor: getColorFromHexRgbOrName(this.props.color || '#000000', theme.type),
}}
/>
</div>
@@ -110,5 +97,5 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
};
};
export const ColorPicker = colorPickerFactory(ColorPickerPopover, 'ColorPicker');
export const SeriesColorPicker = colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker');
export const ColorPicker = withTheme(colorPickerFactory(ColorPickerPopover, 'ColorPicker'));
export const SeriesColorPicker = withTheme(colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker'));

View File

@@ -1,40 +1,27 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { ColorPickerPopover } from './ColorPickerPopover';
import { withKnobs } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { getThemeKnob } from '../../utils/storybook/themeKnob';
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const ColorPickerPopoverStories = storiesOf('UI/ColorPicker/Popovers', module);
ColorPickerPopoverStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
ColorPickerPopoverStories.addDecorator(withCenteredStory);
ColorPickerPopoverStories.add('default', () => {
const selectedTheme = getThemeKnob();
return (
<ColorPickerPopover
color="#BC67E6"
onChange={color => {
console.log(color);
}}
theme={selectedTheme || undefined}
/>
);
return renderComponentWithTheme(ColorPickerPopover, {
color: '#BC67E6',
onChange: (color: any) => {
console.log(color);
},
});
});
ColorPickerPopoverStories.add('SeriesColorPickerPopover', () => {
const selectedTheme = getThemeKnob();
return (
<SeriesColorPickerPopover
color="#BC67E6"
onChange={color => {
console.log(color);
}}
theme={selectedTheme || undefined}
/>
);
return renderComponentWithTheme(SeriesColorPickerPopover, {
color: '#BC67E6',
onChange: (color: any) => {
console.log(color);
},
});
});

View File

@@ -4,7 +4,8 @@ import { ColorPickerPopover } from './ColorPickerPopover';
import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette';
import { ColorSwatch } from './NamedColorsGroup';
import { flatten } from 'lodash';
import { GrafanaTheme } from '../../types';
import { GrafanaThemeType } from '../../types';
import { getTheme } from '../../themes';
const allColors = flatten(Array.from(getNamedColorPalette().values()));
@@ -14,7 +15,7 @@ describe('ColorPickerPopover', () => {
describe('rendering', () => {
it('should render provided color as selected if color provided by name', () => {
const wrapper = mount(<ColorPickerPopover color={BasicGreen.name} onChange={() => {}} />);
const wrapper = mount(<ColorPickerPopover color={BasicGreen.name} onChange={() => {}} theme={getTheme()}/>);
const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false);
@@ -24,7 +25,7 @@ describe('ColorPickerPopover', () => {
});
it('should render provided color as selected if color provided by hex', () => {
const wrapper = mount(<ColorPickerPopover color={BasicGreen.variants.dark} onChange={() => {}} />);
const wrapper = mount(<ColorPickerPopover color={BasicGreen.variants.dark} onChange={() => {}} theme={getTheme()} />);
const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false);
@@ -45,7 +46,7 @@ describe('ColorPickerPopover', () => {
it('should pass hex color value to onChange prop by default', () => {
wrapper = mount(
<ColorPickerPopover color={BasicGreen.variants.dark} onChange={onChangeSpy} theme={GrafanaTheme.Light} />
<ColorPickerPopover color={BasicGreen.variants.dark} onChange={onChangeSpy} theme={getTheme(GrafanaThemeType.Light)} />
);
const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name);
@@ -61,7 +62,7 @@ describe('ColorPickerPopover', () => {
enableNamedColors
color={BasicGreen.variants.dark}
onChange={onChangeSpy}
theme={GrafanaTheme.Light}
theme={getTheme(GrafanaThemeType.Light)}
/>
);
const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name);

View File

@@ -2,9 +2,9 @@ import React from 'react';
import { NamedColorsPalette } from './NamedColorsPalette';
import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker';
import { GrafanaTheme } from '../../types';
import { PopperContentProps } from '../Tooltip/PopperController';
import SpectrumPalette from './SpectrumPalette';
import { GrafanaThemeType } from '@grafana/ui';
export interface Props<T> extends ColorPickerProps, PopperContentProps {
customPickers?: T;
@@ -43,7 +43,7 @@ export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React
if (enableNamedColors) {
return changeHandler(color);
}
changeHandler(getColorFromHexRgbOrName(color, theme));
changeHandler(getColorFromHexRgbOrName(color, theme.type));
};
handleTabChange = (tab: PickerType | keyof T) => {
@@ -58,7 +58,9 @@ export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React
case 'spectrum':
return <SpectrumPalette color={color} onChange={this.handleChange} theme={theme} />;
case 'palette':
return <NamedColorsPalette color={getColorName(color, theme)} onChange={this.handleChange} theme={theme} />;
return (
<NamedColorsPalette color={getColorName(color, theme.type)} onChange={this.handleChange} theme={theme} />
);
default:
return this.renderCustomPicker(activePicker);
}
@@ -88,11 +90,7 @@ export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React
<>
{Object.keys(customPickers).map(key => {
return (
<div
className={this.getTabClassName(key)}
onClick={this.handleTabChange(key)}
key={key}
>
<div className={this.getTabClassName(key)} onClick={this.handleTabChange(key)} key={key}>
{customPickers[key].name}
</div>
);
@@ -103,21 +101,14 @@ export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React
render() {
const { theme } = this.props;
const colorPickerTheme = theme || GrafanaTheme.Dark;
const colorPickerTheme = theme.type || GrafanaThemeType.Dark;
return (
<div className={`ColorPickerPopover ColorPickerPopover--${colorPickerTheme}`}>
<div className="ColorPickerPopover__tabs">
<div
className={this.getTabClassName('palette')}
onClick={this.handleTabChange('palette')}
>
<div className={this.getTabClassName('palette')} onClick={this.handleTabChange('palette')}>
Colors
</div>
<div
className={this.getTabClassName('spectrum')}
onClick={this.handleTabChange('spectrum')}
>
<div className={this.getTabClassName('spectrum')} onClick={this.handleTabChange('spectrum')}>
Custom
</div>
{this.renderCustomPickerTabs()}
@@ -128,3 +119,4 @@ export class ColorPickerPopover<T extends CustomPickersDescriptor> extends React
);
}
}

View File

@@ -1,8 +1,9 @@
import React, { FunctionComponent } from 'react';
import { Themeable, GrafanaTheme } from '../../types';
import { Themeable } from '../../types';
import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette';
import { Color } from 'csstype';
import { find, upperFirst } from 'lodash';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
type ColorChangeHandler = (color: ColorDefinition) => void;
@@ -28,7 +29,15 @@ export const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
}) => {
const isSmall = variant === ColorSwatchVariant.Small;
const swatchSize = isSmall ? '16px' : '32px';
const selectedSwatchBorder = theme === GrafanaTheme.Light ? '#ffffff' : '#1A1B1F';
const selectedSwatchBorder = selectThemeVariant(
{
light: theme.colors.white,
dark: theme.colors.black,
},
theme.type
);
const swatchStyles = {
width: swatchSize,
height: swatchSize,
@@ -76,7 +85,7 @@ const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
key={primaryColor.name}
isSelected={primaryColor.name === selectedColor}
variant={ColorSwatchVariant.Large}
color={getColorForTheme(primaryColor, theme)}
color={getColorForTheme(primaryColor, theme.type)}
label={upperFirst(primaryColor.hue)}
onClick={() => onColorSelect(primaryColor)}
theme={theme}
@@ -95,7 +104,7 @@ const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
<ColorSwatch
key={color.name}
isSelected={color.name === selectedColor}
color={getColorForTheme(color, theme)}
color={getColorForTheme(color, theme.type)}
onClick={() => onColorSelect(color)}
theme={theme}
/>

View File

@@ -2,8 +2,9 @@ import React from 'react';
import { storiesOf } from '@storybook/react';
import { NamedColorsPalette } from './NamedColorsPalette';
import { getColorName, getColorDefinitionByName } from '../../utils/namedColorsPalette';
import { withKnobs, select } from '@storybook/addon-knobs';
import { select } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
import { UseState } from '../../utils/storybook/UseState';
const BasicGreen = getColorDefinitionByName('green');
@@ -12,7 +13,7 @@ const LightBlue = getColorDefinitionByName('light-blue');
const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module);
NamedColorsPaletteStories.addDecorator(withKnobs).addDecorator(withCenteredStory);
NamedColorsPaletteStories.addDecorator(withCenteredStory);
NamedColorsPaletteStories.add('Named colors swatch - support for named colors', () => {
const selectedColor = select(
@@ -28,7 +29,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors',
return (
<UseState initialState={selectedColor}>
{(selectedColor, updateSelectedColor) => {
return <NamedColorsPalette color={selectedColor} onChange={updateSelectedColor} />;
return renderComponentWithTheme(NamedColorsPalette, {
color: selectedColor,
onChange: updateSelectedColor,
});
}}
</UseState>
);
@@ -45,7 +49,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors',
return (
<UseState initialState={selectedColor}>
{(selectedColor, updateSelectedColor) => {
return <NamedColorsPalette color={getColorName(selectedColor)} onChange={updateSelectedColor} />;
return renderComponentWithTheme(NamedColorsPalette, {
color: getColorName(selectedColor),
onChange: updateSelectedColor,
});
}}
</UseState>
);

View File

@@ -3,7 +3,8 @@ import { mount, ReactWrapper } from 'enzyme';
import { NamedColorsPalette } from './NamedColorsPalette';
import { ColorSwatch } from './NamedColorsGroup';
import { getColorDefinitionByName } from '../../utils';
import { GrafanaTheme } from '../../types';
import { getTheme } from '../../themes';
import { GrafanaThemeType } from '../../types';
describe('NamedColorsPalette', () => {
@@ -17,18 +18,18 @@ describe('NamedColorsPalette', () => {
});
it('should render provided color variant specific for theme', () => {
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={GrafanaTheme.Dark} onChange={() => {}} />);
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={getTheme()} onChange={() => {}} />);
selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark);
wrapper.unmount();
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={GrafanaTheme.Light} onChange={() => {}} />);
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} theme={getTheme(GrafanaThemeType.Light)} onChange={() => {}} />);
selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.light);
});
it('should render dar variant of provided color when theme not provided', () => {
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} onChange={() => {}} />);
wrapper = mount(<NamedColorsPalette color={BasicGreen.name} onChange={() => {}} theme={getTheme()}/>);
selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name);
expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark);
});

View File

@@ -4,6 +4,7 @@ import { ColorPickerPopover } from './ColorPickerPopover';
import { ColorPickerProps } from './ColorPicker';
import { PopperContentProps } from '../Tooltip/PopperController';
import { Switch } from '../Switch/Switch';
import { withTheme } from '../../themes/ThemeContext';
export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperContentProps {
yaxis?: number;
@@ -12,7 +13,6 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperC
export const SeriesColorPickerPopover: FunctionComponent<SeriesColorPickerPopoverProps> = props => {
const { yaxis, onToggleAxis, color, ...colorPickerProps } = props;
return (
<ColorPickerPopover
{...colorPickerProps}
@@ -69,8 +69,8 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
}
render() {
const leftButtonClass = this.state.yaxis === 1 ? 'btn-success' : 'btn-inverse';
const rightButtonClass = this.state.yaxis === 2 ? 'btn-success' : 'btn-inverse';
const leftButtonClass = this.state.yaxis === 1 ? 'btn-primary' : 'btn-inverse';
const rightButtonClass = this.state.yaxis === 2 ? 'btn-primary' : 'btn-inverse';
return (
<div className="p-b-1">
@@ -85,3 +85,6 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
);
}
}
// This component is to enable SeriecColorPickerPopover usage via series-color-picker-popover directive
export const SeriesColorPickerPopoverWithTheme = withTheme(SeriesColorPickerPopover);

View File

@@ -1,22 +1,19 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import SpectrumPalette from './SpectrumPalette';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { UseState } from '../../utils/storybook/UseState';
import { getThemeKnob } from '../../utils/storybook/themeKnob';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
const SpectrumPaletteStories = storiesOf('UI/ColorPicker/Palettes/SpectrumPalette', module);
SpectrumPaletteStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
SpectrumPaletteStories.addDecorator(withCenteredStory);
SpectrumPaletteStories.add('Named colors swatch - support for named colors', () => {
const selectedTheme = getThemeKnob();
SpectrumPaletteStories.add('default', () => {
return (
<UseState initialState="red">
{(selectedColor, updateSelectedColor) => {
return <SpectrumPalette theme={selectedTheme} color={selectedColor} onChange={updateSelectedColor} />;
return renderComponentWithTheme(SpectrumPalette, { color: selectedColor, onChange: updateSelectedColor });
}}
</UseState>
);

View File

@@ -13,7 +13,7 @@ export interface SpectrumPaletteProps extends Themeable {
onChange: (color: string) => void;
}
const renderPointer = (theme?: GrafanaTheme) => (props: SpectrumPalettePointerProps) => (
const renderPointer = (theme: GrafanaTheme) => (props: SpectrumPalettePointerProps) => (
<SpectrumPalettePointer {...props} theme={theme} />
);
@@ -92,7 +92,7 @@ const SpectrumPalette: React.FunctionComponent<SpectrumPaletteProps> = ({ color,
}}
theme={theme}
/>
<ColorInput color={color} onChange={onChange} style={{ marginTop: '16px' }} />
<ColorInput theme={theme} color={color} onChange={onChange} style={{ marginTop: '16px' }} />
</div>
);
};

View File

@@ -1,14 +1,12 @@
import React from 'react';
import { GrafanaTheme, Themeable } from '../../types';
import { Themeable } from '../../types';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
export interface SpectrumPalettePointerProps extends Themeable {
direction?: string;
}
const SpectrumPalettePointer: React.FunctionComponent<SpectrumPalettePointerProps> = ({
theme,
direction,
}) => {
const SpectrumPalettePointer: React.FunctionComponent<SpectrumPalettePointerProps> = ({ theme, direction }) => {
const styles = {
picker: {
width: '16px',
@@ -17,7 +15,14 @@ const SpectrumPalettePointer: React.FunctionComponent<SpectrumPalettePointerProp
},
};
const pointerColor = theme === GrafanaTheme.Light ? '#3F444D' : '#8E8E8E';
const pointerColor = selectThemeVariant(
{
light: theme.colors.dark3,
dark: theme.colors.gray2,
},
theme.type
);
let pointerStyles: React.CSSProperties = {
position: 'absolute',

View File

@@ -167,6 +167,7 @@ $arrowSize: 15px;
color: inherit;
padding: 0;
border-radius: 10px;
cursor: pointer;
}
.sp-replacer:hover,

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { shallow } from 'enzyme';
import { Gauge, Props } from './Gauge';
import { TimeSeriesVMs } from '../../types/series';
import { ValueMapping, MappingType } from '../../types';
import { getTheme } from '../../themes';
jest.mock('jquery', () => ({
plot: jest.fn(),
@@ -23,8 +23,9 @@ const setup = (propOverrides?: object) => {
stat: 'avg',
height: 300,
width: 300,
timeSeries: {} as TimeSeriesVMs,
value: 25,
decimals: 0,
theme: getTheme()
};
Object.assign(props, propOverrides);

View File

@@ -1,20 +1,20 @@
import React, { PureComponent } from 'react';
import $ from 'jquery';
import { ValueMapping, Threshold, BasicGaugeColor, TimeSeriesVMs, GrafanaTheme } from '../../types';
import { ValueMapping, Threshold, BasicGaugeColor, GrafanaThemeType } from '../../types';
import { getMappedValue } from '../../utils/valueMappings';
import { getColorFromHexRgbOrName, getValueFormat } from '../../utils';
import { Themeable } from '../../index';
type TimeSeriesValue = string | number | null;
export interface Props {
export interface Props extends Themeable {
decimals: number;
height: number;
valueMappings: ValueMapping[];
maxValue: number;
minValue: number;
prefix: string;
timeSeries: TimeSeriesVMs;
thresholds: Threshold[];
showThresholdMarkers: boolean;
showThresholdLabels: boolean;
@@ -22,7 +22,7 @@ export interface Props {
suffix: string;
unit: string;
width: number;
theme?: GrafanaTheme;
value: number;
}
const FONT_SCALE = 1;
@@ -41,7 +41,7 @@ export class Gauge extends PureComponent<Props> {
thresholds: [],
unit: 'none',
stat: 'avg',
theme: GrafanaTheme.Dark,
theme: GrafanaThemeType.Dark,
};
componentDidMount() {
@@ -77,19 +77,19 @@ export class Gauge extends PureComponent<Props> {
const { thresholds, theme } = this.props;
if (thresholds.length === 1) {
return getColorFromHexRgbOrName(thresholds[0].color, theme);
return getColorFromHexRgbOrName(thresholds[0].color, theme.type);
}
const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
if (atThreshold) {
return getColorFromHexRgbOrName(atThreshold.color, theme);
return getColorFromHexRgbOrName(atThreshold.color, theme.type);
}
const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
if (belowThreshold.length > 0) {
const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
return getColorFromHexRgbOrName(nearestThreshold.color, theme);
return getColorFromHexRgbOrName(nearestThreshold.color, theme.type);
}
return BasicGaugeColor.Red;
@@ -104,13 +104,13 @@ export class Gauge extends PureComponent<Props> {
return [
...thresholdsSortedByIndex.map(threshold => {
if (threshold.index === 0) {
return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme) };
return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme.type) };
}
const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme) };
return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme.type) };
}),
{ value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme) },
{ value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme.type) },
];
}
@@ -122,29 +122,12 @@ export class Gauge extends PureComponent<Props> {
}
draw() {
const {
maxValue,
minValue,
timeSeries,
showThresholdLabels,
showThresholdMarkers,
width,
height,
stat,
theme,
} = this.props;
let value: TimeSeriesValue = '';
if (timeSeries[0]) {
value = timeSeries[0].stats[stat];
} else {
value = null;
}
const { maxValue, minValue, showThresholdLabels, showThresholdMarkers, width, height, theme, value } = this.props;
const formattedValue = this.formatValue(value) as string;
const dimension = Math.min(width, height * 1.3);
const backgroundColor = theme === GrafanaTheme.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
const backgroundColor = theme.type === GrafanaThemeType.Light ? 'rgb(230,230,230)' : theme.colors.dark3;
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
const thresholdMarkersWidth = gaugeWidth / 5;
@@ -194,7 +177,7 @@ export class Gauge extends PureComponent<Props> {
try {
$.plot(this.canvasElement, [plotSeries], options);
} catch (err) {
console.log('Gauge rendering error', err, options, timeSeries);
console.log('Gauge rendering error', err, options, value);
}
}

View File

@@ -1,26 +1,38 @@
// Libraries
import React, { SFC } from 'react';
import React, { FunctionComponent } from 'react';
interface Props {
title?: string;
onClose?: () => void;
children: JSX.Element | JSX.Element[];
children: JSX.Element | JSX.Element[] | boolean;
onAdd?: () => void;
}
export const PanelOptionsGroup: SFC<Props> = props => {
export const PanelOptionsGroup: FunctionComponent<Props> = props => {
return (
<div className="panel-options-group">
{props.title && (
{props.onAdd ? (
<div className="panel-options-group__header">
{props.title}
{props.onClose && (
<button className="btn btn-link" onClick={props.onClose}>
<i className="fa fa-remove" />
</button>
)}
<button className="panel-options-group__add-btn" onClick={props.onAdd}>
<div className="panel-options-group__add-circle">
<i className="fa fa-plus" />
</div>
<span className="panel-options-group__title">{props.title}</span>
</button>
</div>
) : (
props.title && (
<div className="panel-options-group__header">
<span className="panel-options-group__title">{props.title}</span>
{props.onClose && (
<button className="btn btn-link" onClick={props.onClose}>
<i className="fa fa-remove" />
</button>
)}
</div>
)
)}
<div className="panel-options-group__body">{props.children}</div>
{props.children && <div className="panel-options-group__body">{props.children}</div>}
</div>
);
};

View File

@@ -7,18 +7,57 @@
.panel-options-group__header {
padding: 4px 8px;
font-size: 1.1rem;
background: $panel-options-group-header-bg;
position: relative;
border-radius: $border-radius $border-radius 0 0;
display: flex;
align-items: center;
.btn {
position: absolute;
right: 0;
top: 0px;
top: 0;
}
}
.panel-options-group__add-btn {
background: none;
border: none;
display: flex;
align-items: center;
padding: 0;
&:hover {
.panel-options-group__add-circle {
background-color: $btn-primary-bg;
color: $white;
}
}
}
.panel-options-group__add-circle {
@include gradientBar($btn-success-bg, $btn-success-bg-hl, #fff);
border-radius: 50px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 6px;
i {
position: relative;
top: 1px;
}
}
.panel-options-group__title {
font-size: 1.1rem;
position: relative;
top: 1px;
}
.panel-options-group__body {
padding: 20px;

View File

@@ -49,7 +49,7 @@ export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps,
return (
<div className="gf-form-select-box__option-group">
<div className="gf-form-select-box__option-group__header" onClick={this.onToggleChildren}>
<span className="flex-grow">{label}</span>
<span className="flex-grow-1">{label}</span>
<i className={`fa ${expanded ? 'fa-caret-left' : 'fa-caret-down'}`} />{' '}
</div>
{expanded && children}

View File

@@ -1,11 +1,11 @@
import React, { PureComponent } from 'react';
import { Threshold, Themeable } from '../../types';
import { Threshold } from '../../types';
import { ColorPicker } from '../ColorPicker/ColorPicker';
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
import { colors } from '../../utils';
import { getColorFromHexRgbOrName } from '@grafana/ui';
import { getColorFromHexRgbOrName, ThemeContext } from '@grafana/ui';
export interface Props extends Themeable {
export interface Props {
thresholds: Threshold[];
onChange: (thresholds: Threshold[]) => void;
}
@@ -164,7 +164,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
<div className="thresholds-row-input-inner-color">
{threshold.color && (
<div className="thresholds-row-input-inner-color-colorpicker">
<ColorPicker color={threshold.color} onChange={color => this.onChangeThresholdColor(threshold, color)} />
<ColorPicker
color={threshold.color}
onChange={color => this.onChangeThresholdColor(threshold, color)}
/>
</div>
)}
</div>
@@ -188,27 +191,35 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
render() {
const { thresholds } = this.state;
const { theme } = this.props;
return (
<PanelOptionsGroup title="Thresholds">
<div className="thresholds">
{thresholds.map((threshold, index) => {
return (
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
<div className="thresholds-row-add-button" onClick={() => this.onAddThreshold(threshold.index + 1)}>
<i className="fa fa-plus" />
</div>
<div
className="thresholds-row-color-indicator"
style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme) }}
/>
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
<ThemeContext.Consumer>
{theme => {
return (
<PanelOptionsGroup title="Thresholds">
<div className="thresholds">
{thresholds.map((threshold, index) => {
return (
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
<div
className="thresholds-row-add-button"
onClick={() => this.onAddThreshold(threshold.index + 1)}
>
<i className="fa fa-plus" />
</div>
<div
className="thresholds-row-color-indicator"
style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
/>
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
</div>
);
})}
</div>
);
})}
</div>
</PanelOptionsGroup>
</PanelOptionsGroup>
);
}}
</ThemeContext.Consumer>
);
}
}

View File

@@ -1,11 +1,11 @@
.thresholds {
margin-bottom: 10px;
margin-bottom: 20px;
}
.thresholds-row {
display: flex;
flex-direction: row;
height: 70px;
height: 62px;
}
.thresholds-row:first-child > .thresholds-row-color-indicator {
@@ -21,21 +21,21 @@
}
.thresholds-row-add-button {
@include buttonBackground($btn-success-bg, $btn-success-bg-hl, #fff);
align-self: center;
margin-right: 5px;
color: $green;
height: 24px;
width: 24px;
background-color: $green;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.thresholds-row-add-button > i {
color: $white;
&:hover {
color: $white;
}
}
.thresholds-row-color-indicator {

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { ValueMappingsEditor } from './ValueMappingsEditor';
const ValueMappingsEditorStories = storiesOf('UI/ValueMappingsEditor', module);
ValueMappingsEditorStories.add('default', () => {
return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
});

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { ValueMappingsEditor, Props } from './ValueMappingsEditor';
import { MappingType } from '../../types/panel';
import { MappingType } from '../../types';
const setup = (propOverrides?: object) => {
const props: Props = {

View File

@@ -1,8 +1,8 @@
import React, { PureComponent } from 'react';
import MappingRow from './MappingRow';
import { MappingType, ValueMapping } from '../../types/panel';
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
import { MappingType, ValueMapping } from '../../types';
import { PanelOptionsGroup } from '..';
export interface Props {
valueMappings: ValueMapping[];
@@ -81,8 +81,7 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
const { valueMappings } = this.state;
return (
<PanelOptionsGroup title="Value Mappings">
<div>
<PanelOptionsGroup title="Add value mapping" onAdd={this.addMapping}>
{valueMappings.length > 0 &&
valueMappings.map((valueMapping, index) => (
<MappingRow
@@ -92,13 +91,6 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
/>
))}
</div>
<div className="add-mapping-row" onClick={this.addMapping}>
<div className="add-mapping-row-icon">
<i className="fa fa-plus" />
</div>
<div className="add-mapping-row-label">Add mapping</div>
</div>
</PanelOptionsGroup>
);
}

View File

@@ -2,55 +2,37 @@
exports[`Render should render component 1`] = `
<Component
title="Value Mappings"
onAdd={[Function]}
title="Add value mapping"
>
<div>
<MappingRow
key="Ok-0"
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"id": 1,
"operator": "",
"text": "Ok",
"type": 1,
"value": "20",
}
<MappingRow
key="Ok-0"
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"id": 1,
"operator": "",
"text": "Ok",
"type": 1,
"value": "20",
}
/>
<MappingRow
key="Meh-1"
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"from": "21",
"id": 2,
"operator": "",
"text": "Meh",
"to": "30",
"type": 2,
}
}
/>
<MappingRow
key="Meh-1"
removeValueMapping={[Function]}
updateValueMapping={[Function]}
valueMapping={
Object {
"from": "21",
"id": 2,
"operator": "",
"text": "Meh",
"to": "30",
"type": 2,
}
/>
</div>
<div
className="add-mapping-row"
onClick={[Function]}
>
<div
className="add-mapping-row-icon"
>
<i
className="fa fa-plus"
/>
</div>
<div
className="add-mapping-row-label"
>
Add mapping
</div>
</div>
}
/>
</Component>
`;

View File

@@ -14,8 +14,8 @@ export { FormLabel } from './FormLabel/FormLabel';
export { FormField } from './FormField/FormField';
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover';
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
export { Graph } from './Graph/Graph';
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';

View File

@@ -1,3 +1,5 @@
export * from './components';
export * from './types';
export * from './utils';
export * from './themes';
export * from './themes/ThemeContext';

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { GrafanaThemeType, Themeable } from '../types';
import { getTheme } from './index';
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Subtract<T, K> = Omit<T, keyof K>;
// Use Grafana Dark theme by default
export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark));
export const withTheme = <P extends Themeable>(Component: React.ComponentType<P>) => {
const WithTheme: React.FunctionComponent<Subtract<P, Themeable>> = props => {
// @ts-ignore
return <ThemeContext.Consumer>{theme => <Component {...props} theme={theme} />}</ThemeContext.Consumer>;
};
WithTheme.displayName = `WithTheme(${Component.displayName})`;
return WithTheme;
};

View File

@@ -0,0 +1,69 @@
import tinycolor from 'tinycolor2';
import defaultTheme from './default';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
const basicColors = {
black: '#000000',
white: '#ffffff',
dark1: '#141414',
dark2: '#1f1f20',
dark3: '#262628',
dark4: '#333333',
dark5: '#444444',
gray1: '#555555',
gray2: '#8e8e8e',
gray3: '#b3b3b3',
gray4: '#d8d9da',
gray5: '#ececec',
gray6: '#f4f5f8',
gray7: '#fbfbfb',
grayBlue: '#212327',
blue: '#33b5e5',
blueDark: '#005f81',
blueLight: '#00a8e6', // not used in dark theme
green: '#299c46',
red: '#d44a3a',
yellow: '#ecbb13',
pink: '#ff4444',
purple: '#9933cc',
variable: '#32d1df',
orange: '#eb7b18',
};
const darkTheme: GrafanaTheme = {
...defaultTheme,
type: GrafanaThemeType.Dark,
name: 'Grafana Dark',
colors: {
...basicColors,
inputBlack: '#09090b',
queryRed: '#e24d42',
queryGreen: '#74e680',
queryPurple: '#fe85fc',
queryKeyword: '#66d9ef',
queryOrange: 'eb7b18',
online: '#10a345',
warn: '#f79520',
critical: '#ed2e18',
bodyBg: '#171819',
pageBg: '#161719',
bodyColor: basicColors.gray4,
textColor: basicColors.gray4,
textColorStrong: basicColors.white,
textColorWeak: basicColors.gray2,
textColorEmphasis: basicColors.gray5,
textColorFaint: basicColors.dark5,
linkColor: new tinycolor(basicColors.white).darken(11).toString(),
linkColorDisabled: new tinycolor(basicColors.white).darken(11).toString(),
linkColorHover: basicColors.white,
linkColorExternal: basicColors.blue,
headingColor: new tinycolor(basicColors.white).darken(11).toString(),
},
background: {
dropdown: basicColors.dark3,
scrollbar: '#aeb5df',
scrollbar2: '#3a3a3a',
},
};
export default darkTheme;

View File

@@ -0,0 +1,62 @@
const theme = {
name: 'Grafana Default',
typography: {
fontFamily: {
sansSerif: "'Roboto', Helvetica, Arial, sans-serif;",
serif: "Georgia, 'Times New Roman', Times, serif;",
monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace;"
},
size: {
base: '13px',
xs: '10px',
s: '12px',
m: '14px',
l: '18px',
},
heading: {
h1: '2rem',
h2: '1.75rem',
h3: '1.5rem',
h4: '1.3rem',
h5: '1.2rem',
h6: '1rem',
},
weight: {
light: 300,
normal: 400,
semibold: 500,
},
lineHeight: {
xs: 1,
s: 1.1,
m: 4/3,
l: 1.5
}
},
brakpoints: {
xs: '0',
s: '544px',
m: '768px',
l: '992px',
xl: '1200px'
},
spacing: {
xs: '0',
s: '0.2rem',
m: '1rem',
l: '1.5rem',
xl: '3rem',
gutter: '30px',
},
border: {
radius: {
xs: '2px',
s: '3px',
m: '5px',
}
}
};
export default theme;

View File

@@ -0,0 +1,14 @@
import darkTheme from './dark';
import lightTheme from './light';
import { GrafanaTheme } from '../types/theme';
let themeMock: ((name?: string) => GrafanaTheme) | null;
export let getTheme = (name?: string) => (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme);
export const mockTheme = (mock: (name: string) => GrafanaTheme) => {
themeMock = mock;
return () => {
themeMock = null;
};
};

View File

@@ -0,0 +1,70 @@
import tinycolor from 'tinycolor2';
import defaultTheme from './default';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
const basicColors = {
black: '#000000',
white: '#ffffff',
dark1: '#13161d',
dark2: '#1e2028',
dark3: '#303133',
dark4: '#35373f',
dark5: '#41444b',
gray1: '#52545c',
gray2: '#767980',
gray3: '#acb6bf',
gray4: '#c7d0d9',
gray5: '#dde4ed',
gray6: '#e9edf2',
gray7: '#f7f8fa',
grayBlue: '#212327', // not used in light theme
blue: '#0083b3',
blueDark: '#005f81',
blueLight: '#00a8e6',
green: '#3aa655',
red: '#d44939',
yellow: '#ff851b',
pink: '#e671b8',
purple: '#9954bb',
variable: '#0083b3',
orange: '#ff7941',
};
const lightTheme: GrafanaTheme = {
...defaultTheme,
type: GrafanaThemeType.Light,
name: 'Grafana Light',
colors: {
...basicColors,
variable: basicColors.blue,
inputBlack: '#09090b',
queryRed: basicColors.red,
queryGreen: basicColors.green,
queryPurple: basicColors.purple,
queryKeyword: basicColors.blue,
queryOrange: basicColors.orange,
online: '#01a64f',
warn: '#f79520',
critical: '#ec2128',
bodyBg: basicColors.gray7,
pageBg: basicColors.gray7,
bodyColor: basicColors.gray1,
textColor: basicColors.gray1,
textColorStrong: basicColors.dark2,
textColorWeak: basicColors.gray2,
textColorEmphasis: basicColors.gray5,
textColorFaint: basicColors.dark4,
linkColor: basicColors.gray1,
linkColorDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(),
linkColorHover: new tinycolor(basicColors.gray1).darken(20).toString(),
linkColorExternal: basicColors.blueLight,
headingColor: basicColors.gray1,
},
background: {
dropdown: basicColors.white,
scrollbar: basicColors.gray5,
scrollbar2: basicColors.gray5,
},
};
export default lightTheme;

View File

@@ -0,0 +1,52 @@
import { GrafanaThemeType } from '../types/theme';
import { selectThemeVariant } from './selectThemeVariant';
import { mockTheme } from './index';
const lightThemeMock = {
color: {
red: '#ff0000',
green: '#00ff00',
},
};
const darkThemeMock = {
color: {
red: '#ff0000',
green: '#00ff00',
},
};
describe('Theme variable variant selector', () => {
// @ts-ignore
const restoreTheme = mockTheme(name => (name === GrafanaThemeType.Light ? lightThemeMock : darkThemeMock));
afterAll(() => {
restoreTheme();
});
it('return correct variable value for given theme', () => {
const theme = lightThemeMock;
const selectedValue = selectThemeVariant(
{
dark: theme.color.red,
light: theme.color.green,
},
GrafanaThemeType.Light
);
expect(selectedValue).toBe(lightThemeMock.color.green);
});
it('return dark theme variant if no theme given', () => {
const theme = lightThemeMock;
const selectedValue = selectThemeVariant(
{
dark: theme.color.red,
light: theme.color.green,
}
);
expect(selectedValue).toBe(lightThemeMock.color.red);
});
});

View File

@@ -0,0 +1,9 @@
import { GrafanaThemeType } from '../types/theme';
type VariantDescriptor = {
[key in GrafanaThemeType]: string | number;
};
export const selectThemeVariant = (variants: VariantDescriptor, currentTheme?: GrafanaThemeType) => {
return variants[currentTheme || GrafanaThemeType.Dark];
};

View File

@@ -52,3 +52,20 @@ export interface TimeSeriesVMs {
[index: number]: TimeSeriesVM;
length: number;
}
interface Column {
text: string;
title?: string;
type?: string;
sort?: boolean;
desc?: boolean;
filterable?: boolean;
unit?: string;
}
export interface TableData {
columns: Column[];
rows: any[];
type: string;
columnMap: any;
}

View File

@@ -1,9 +1,9 @@
import { TimeRange, RawTimeRange } from './time';
import { TimeSeries } from './series';
import { PluginMeta } from './plugin';
import { TableData, TimeSeries } from './data';
export interface DataQueryResponse {
data: TimeSeries[];
data: TimeSeries[] | [TableData] | any;
}
export interface DataQuery {

View File

@@ -1,14 +1,7 @@
export * from './series';
export * from './data';
export * from './time';
export * from './panel';
export * from './plugin';
export * from './datasource';
export enum GrafanaTheme {
Light = 'light',
Dark = 'dark',
}
export interface Themeable {
theme?: GrafanaTheme;
}
export * from './theme';

View File

@@ -1,10 +1,10 @@
import { TimeSeries, LoadingState } from './series';
import { TimeSeries, LoadingState, TableData } from './data';
import { TimeRange } from './time';
export type InterpolateFunction = (value: string, format?: string | Function) => string;
export interface PanelProps<T = any> {
timeSeries: TimeSeries[];
panelData: PanelData;
timeRange: TimeRange;
loading: LoadingState;
options: T;
@@ -14,6 +14,11 @@ export interface PanelProps<T = any> {
onInterpolate: InterpolateFunction;
}
export interface PanelData {
timeSeries?: TimeSeries[];
tableData?: TableData;
}
export interface PanelOptionsProps<T = any> {
options: T;
onChange: (options: T) => void;

View File

@@ -1,6 +1,6 @@
import { ComponentClass } from 'react';
import { PanelProps, PanelOptionsProps } from './panel';
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource';
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
/**
@@ -41,22 +41,43 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
pluginExports?: PluginExports;
}
export interface ExploreDataSourceApi<TQuery extends DataQuery = DataQuery> extends DataSourceApi {
modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
getHighlighterExpression?(query: TQuery): string;
languageProvider?: any;
}
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
datasource: DSType;
query: TQuery;
onRunQuery: () => void;
onChange: (value: TQuery) => void;
}
export interface ExploreQueryFieldProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
datasource: DSType;
query: TQuery;
error?: string | JSX.Element;
hint?: QueryHint;
history: any[];
onExecuteQuery?: () => void;
onQueryChange?: (value: TQuery) => void;
onExecuteHint?: (action: QueryFixAction) => void;
}
export interface ExploreStartPageProps {
onClickExample: (query: DataQuery) => void;
}
export interface PluginExports {
Datasource?: DataSourceApi;
QueryCtrl?: any;
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi,DataQuery>>;
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi, DataQuery>>;
ConfigCtrl?: any;
AnnotationsQueryCtrl?: any;
VariableQueryEditor?: any;
ExploreQueryField?: any;
ExploreStartPage?: any;
ExploreQueryField?: ComponentClass<ExploreQueryFieldProps<DataSourceApi, DataQuery>>;
ExploreStartPage?: ComponentClass<ExploreStartPageProps>;
// Panel plugin
PanelCtrl?: any;
@@ -114,5 +135,3 @@ export interface PluginMetaInfo {
updated: string;
version: string;
}

View File

@@ -0,0 +1,129 @@
export enum GrafanaThemeType {
Light = 'light',
Dark = 'dark',
}
export interface GrafanaTheme {
type: GrafanaThemeType;
name: string;
// TODO: not sure if should be a part of theme
brakpoints: {
xs: string;
s: string;
m: string;
l: string;
xl: string;
};
typography: {
fontFamily: {
sansSerif: string;
serif: string;
monospace: string;
};
size: {
base: string;
xs: string;
s: string;
m: string;
l: string;
};
weight: {
light: number;
normal: number;
semibold: number;
};
lineHeight: {
xs: number; //1
s: number; //1.1
m: number; // 4/3
l: number; // 1.5
};
// TODO: Refactor to use size instead of custom defs
heading: {
h1: string;
h2: string;
h3: string;
h4: string;
h5: string;
h6: string;
};
};
spacing: {
xs: string;
s: string;
m: string;
l: string;
gutter: string;
};
border: {
radius: {
xs: string;
s: string;
m: string;
};
};
background: {
dropdown: string;
scrollbar: string;
scrollbar2: string;
};
colors: {
black: string;
white: string;
dark1: string;
dark2: string;
dark3: string;
dark4: string;
dark5: string;
gray1: string;
gray2: string;
gray3: string;
gray4: string;
gray5: string;
gray6: string;
gray7: string;
grayBlue: string;
inputBlack: string;
// Accent colors
blue: string;
blueLight: string;
blueDark: string;
green: string;
red: string;
yellow: string;
pink: string;
purple: string;
variable: string;
orange: string;
queryRed: string;
queryGreen: string;
queryPurple: string;
queryKeyword: string;
queryOrange: string;
// Status colors
online: string;
warn: string;
critical: string;
// TODO: move to background section
bodyBg: string;
pageBg: string;
bodyColor: string;
textColor: string;
textColorStrong: string;
textColorWeak: string;
textColorFaint: string;
textColorEmphasis: string;
linkColor: string;
linkColorDisabled: string;
linkColorHover: string;
linkColorExternal: string;
headingColor: string;
};
}
export interface Themeable {
theme: GrafanaTheme;
}

View File

@@ -5,20 +5,20 @@ import {
getColorFromHexRgbOrName,
getColorDefinitionByName,
} from './namedColorsPalette';
import { GrafanaTheme } from '../types/index';
import { GrafanaThemeType } from '../types/index';
describe('colors', () => {
const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue');
describe('getColorDefinition', () => {
it('returns undefined for unknown hex', () => {
expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined();
expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined();
expect(getColorDefinition('#ff0000', GrafanaThemeType.Light)).toBeUndefined();
expect(getColorDefinition('#ff0000', GrafanaThemeType.Dark)).toBeUndefined();
});
it('returns definition for known hex', () => {
expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue);
expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue);
expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue);
expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue);
});
});
@@ -28,8 +28,8 @@ describe('colors', () => {
});
it('returns name for known hex', () => {
expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name);
expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name);
expect(getColorName(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue.name);
expect(getColorName(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue.name);
});
});
@@ -53,12 +53,14 @@ describe('colors', () => {
});
it("returns correct variant's hex for known color if theme specified", () => {
expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light);
expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light);
});
it('returns color if specified as hex or rgb/a', () => {
expect(getColorFromHexRgbOrName('ff0000')).toBe('ff0000');
expect(getColorFromHexRgbOrName('#ff0000')).toBe('#ff0000');
expect(getColorFromHexRgbOrName('#FF0000')).toBe('#FF0000');
expect(getColorFromHexRgbOrName('#CCC')).toBe('#CCC');
expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
});

View File

@@ -1,5 +1,5 @@
import { flatten } from 'lodash';
import { GrafanaTheme } from '../types';
import { GrafanaThemeType } from '../types';
type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
@@ -68,16 +68,16 @@ export const getColorDefinitionByName = (name: Color): ColorDefinition => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0];
};
export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => {
export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorDefinition | undefined => {
return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0];
};
const isHex = (color: string) => {
const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6})$/gi;
const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{3})$/gi;
return hexRegex.test(color);
};
export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => {
export const getColorName = (color?: string, theme?: GrafanaThemeType): Color | undefined => {
if (!color) {
return undefined;
}
@@ -86,7 +86,7 @@ export const getColorName = (color?: string, theme?: GrafanaTheme): Color | unde
return undefined;
}
if (isHex(color)) {
const definition = getColorDefinition(color, theme || GrafanaTheme.Dark);
const definition = getColorDefinition(color, theme || GrafanaThemeType.Dark);
return definition ? definition.name : undefined;
}
@@ -98,7 +98,7 @@ export const getColorByName = (colorName: string) => {
return definition.length > 0 ? definition[0] : undefined;
};
export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => {
export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType): string => {
if (color.indexOf('rgb') > -1 || isHex(color)) {
return color;
}
@@ -112,14 +112,14 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): s
return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;
};
export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaThemeType) => {
return theme ? color.variants[theme] : color.variants.dark;
};
const buildNamedColorsPalette = () => {
const palette = new Map<Hue, ColorDefinition[]>();
const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true);
const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']);
const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']);
const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']);

View File

@@ -1,14 +0,0 @@
import { select } from '@storybook/addon-knobs';
import { GrafanaTheme } from '../../types';
export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => {
return select(
'Theme',
{
Default: defaultTheme,
Light: GrafanaTheme.Light,
Dark: GrafanaTheme.Dark,
},
defaultTheme
);
};

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { RenderFunction } from '@storybook/react';
import { ThemeContext } from '../../themes/ThemeContext';
import { select } from '@storybook/addon-knobs';
import { getTheme } from '../../themes';
import { GrafanaThemeType } from '../../types';
const ThemableStory: React.FunctionComponent<{}> = ({ children }) => {
const themeKnob = select(
'Theme',
{
Light: GrafanaThemeType.Light,
Dark: GrafanaThemeType.Dark,
},
GrafanaThemeType.Dark
);
return (
<ThemeContext.Provider value={getTheme(themeKnob)}>
{children}
</ThemeContext.Provider>
);
};
// Temporary solution. When we update to Storybook V5 we will be able to pass data from decorator to story
// https://github.com/storybooks/storybook/issues/340#issuecomment-456013702
export const renderComponentWithTheme = (component: React.ComponentType<any>, props: any) => {
return (
<ThemeContext.Consumer>
{theme => {
return React.createElement(component, {
...props,
theme,
});
}}
</ThemeContext.Consumer>
);
};
export const withTheme = (story: RenderFunction) => <ThemableStory>{story()}</ThemableStory>;

View File

@@ -31,11 +31,16 @@ case "$1" in
cp /usr/share/grafana/conf/ldap.toml /etc/grafana/ldap.toml
fi
if [ ! -f $PROVISIONING_CFG_DIR ]; then
if [ ! -d $PROVISIONING_CFG_DIR ]; then
mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources
cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml
cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml
fi
fi
if [ ! -d $PROVISIONING_CFG_DIR/notifiers ]; then
mkdir -p $PROVISIONING_CFG_DIR/notifiers
cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml
fi
# configuration files should not be modifiable by grafana user, as this can be a security issue
chown -Rh root:$GRAFANA_GROUP /etc/grafana/*

View File

@@ -39,6 +39,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
useradd -r -u $GF_UID -g grafana grafana && \
mkdir -p "$GF_PATHS_PROVISIONING/datasources" \
"$GF_PATHS_PROVISIONING/dashboards" \
"$GF_PATHS_PROVISIONING/notifiers" \
"$GF_PATHS_LOGS" \
"$GF_PATHS_PLUGINS" \
"$GF_PATHS_DATA" && \

View File

@@ -45,11 +45,16 @@ if [ $1 -eq 1 ] ; then
cp /usr/share/grafana/conf/ldap.toml /etc/grafana/ldap.toml
fi
if [ ! -f $PROVISIONING_CFG_DIR ]; then
if [ ! -d $PROVISIONING_CFG_DIR ]; then
mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources
cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml
cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml
fi
fi
if [ ! -d $PROVISIONING_CFG_DIR/notifiers ]; then
mkdir -p $PROVISIONING_CFG_DIR/notifiers
cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml
fi
# Set user permissions on /var/log/grafana, /var/lib/grafana
mkdir -p /var/log/grafana /var/lib/grafana

View File

@@ -210,6 +210,65 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
return Success("Annotation updated")
}
func PatchAnnotation(c *m.ReqContext, cmd dtos.PatchAnnotationsCmd) Response {
annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository()
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return Error(404, "Could not find annotation to update", err)
}
existing := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
Id: annotationID,
Epoch: items[0].Time,
Text: items[0].Text,
Tags: items[0].Tags,
RegionId: items[0].RegionId,
}
if cmd.Tags != nil {
existing.Tags = cmd.Tags
}
if cmd.Text != "" && cmd.Text != existing.Text {
existing.Text = cmd.Text
}
if cmd.Time > 0 && cmd.Time != existing.Epoch {
existing.Epoch = cmd.Time
}
if err := repo.Update(&existing); err != nil {
return Error(500, "Failed to update annotation", err)
}
// Update region end time if provided
if existing.RegionId != 0 && cmd.TimeEnd > 0 {
itemRight := existing
itemRight.RegionId = existing.Id
itemRight.Epoch = cmd.TimeEnd
// We don't know id of region right event, so set it to 0 and find then using query like
// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
itemRight.Id = 0
if err := repo.Update(&itemRight); err != nil {
return Error(500, "Failed to update annotation for region end time", err)
}
}
return Success("Annotation patched")
}
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
repo := annotations.GetRepository()

View File

@@ -27,6 +27,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
IsRegion: false,
}
patchCmd := dtos.PatchAnnotationsCmd{
Time: 1000,
Text: "annotation text",
Tags: []string{"tag1", "tag2"},
}
Convey("When user is an Org Viewer", func() {
role := m.ROLE_VIEWER
Convey("Should not be allowed to save an annotation", func() {
@@ -40,6 +46,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 403)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -67,6 +78,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 200)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -100,6 +116,13 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
Id: 1,
}
patchCmd := dtos.PatchAnnotationsCmd{
Time: 8000,
Text: "annotation text 50",
Tags: []string{"foo", "bar"},
Id: 1,
}
deleteCmd := dtos.DeleteAnnotationsCmd{
DashboardId: 1,
PanelId: 1,
@@ -136,6 +159,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 403)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -163,6 +191,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 200)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -189,6 +222,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", 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)
@@ -264,6 +303,29 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
})
}
func patchAnnotationScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.PatchAnnotationsCmd, 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 PatchAnnotation(c, cmd)
})
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.m.Patch(routePattern, sc.defaultHandler)
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()

View File

@@ -108,8 +108,8 @@ func (hs *HTTPServer) registerRoutes() {
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"), hs.LoginAPIPing)
// api renew session based on cookie
r.Get("/api/login/ping", quota("session"), Wrap(hs.LoginAPIPing))
// authed api
r.Group("/api", func(apiRoute routing.RouteRegister) {
@@ -354,6 +354,7 @@ func (hs *HTTPServer) registerRoutes() {
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), Wrap(PatchAnnotation))
annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
})

View File

@@ -94,14 +94,13 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
}
type scenarioContext struct {
m *macaron.Macaron
context *m.ReqContext
resp *httptest.ResponseRecorder
handlerFunc handlerFunc
defaultHandler macaron.Handler
req *http.Request
url string
userAuthTokenService *fakeUserAuthTokenService
m *macaron.Macaron
context *m.ReqContext
resp *httptest.ResponseRecorder
handlerFunc handlerFunc
defaultHandler macaron.Handler
req *http.Request
url string
}
func (sc *scenarioContext) exec() {
@@ -123,30 +122,7 @@ func setupScenarioContext(url string) *scenarioContext {
Delims: macaron.Delims{Left: "[[", Right: "]]"},
}))
sc.userAuthTokenService = newFakeUserAuthTokenService()
sc.m.Use(middleware.GetContextHandler(sc.userAuthTokenService))
sc.m.Use(middleware.GetContextHandler(nil))
return sc
}
type fakeUserAuthTokenService struct {
initContextWithTokenProvider func(ctx *m.ReqContext, orgID int64) bool
}
func newFakeUserAuthTokenService() *fakeUserAuthTokenService {
return &fakeUserAuthTokenService{
initContextWithTokenProvider: func(ctx *m.ReqContext, orgID int64) bool {
return false
},
}
}
func (s *fakeUserAuthTokenService) InitContextWithToken(ctx *m.ReqContext, orgID int64) bool {
return s.initContextWithTokenProvider(ctx, orgID)
}
func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqContext) error {
return nil
}
func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) {}

View File

@@ -22,6 +22,14 @@ type UpdateAnnotationsCmd struct {
TimeEnd int64 `json:"timeEnd"`
}
type PatchAnnotationsCmd struct {
Id int64 `json:"id"`
Time int64 `json:"time"`
Text string `json:"text"`
Tags []string `json:"tags"`
TimeEnd int64 `json:"timeEnd"`
}
type DeleteAnnotationsCmd struct {
AlertId int64 `json:"alertId"`
DashboardId int64 `json:"dashboardId"`

View File

@@ -5,6 +5,7 @@ type PlaylistDashboard struct {
Slug string `json:"slug"`
Title string `json:"title"`
Uri string `json:"uri"`
Url string `json:"url"`
Order int `json:"order"`
}

View File

@@ -21,7 +21,6 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/cache"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/hooks"
@@ -48,14 +47,14 @@ type HTTPServer struct {
streamManager *live.StreamManager
httpSrv *http.Server
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *cache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService auth.UserAuthTokenService `inject:""`
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *cache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""`
}
func (hs *HTTPServer) Init() error {

View File

@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -126,17 +127,23 @@ func (hs *HTTPServer) LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response
func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
if user == nil {
hs.log.Error("User login with nil user")
hs.log.Error("user login with nil user")
}
err := hs.AuthTokenService.UserAuthenticatedHook(user, c)
userToken, err := hs.AuthTokenService.CreateToken(user.Id, c.RemoteAddr(), c.Req.UserAgent())
if err != nil {
hs.log.Error("User auth hook failed", "error", err)
hs.log.Error("failed to create auth token", "error", err)
}
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetimeDays)
}
func (hs *HTTPServer) Logout(c *m.ReqContext) {
hs.AuthTokenService.UserSignedOutHook(c)
if err := hs.AuthTokenService.RevokeToken(c.UserToken); err != nil && err != m.ErrUserTokenNotFound {
hs.log.Error("failed to revoke auth token", "error", err)
}
middleware.WriteSessionCookie(c, "", -1)
if setting.SignoutRedirectUrl != "" {
c.Redirect(setting.SignoutRedirectUrl)
@@ -176,7 +183,8 @@ func (hs *HTTPServer) trySetEncryptedCookie(ctx *m.ReqContext, cookieName string
Value: hex.EncodeToString(encryptedError),
HttpOnly: true,
Path: setting.AppSubUrl + "/",
Secure: hs.Cfg.SecurityHTTPSCookies,
Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite,
})
return nil

View File

@@ -214,7 +214,8 @@ func (hs *HTTPServer) writeCookie(w http.ResponseWriter, name string, value stri
Value: value,
HttpOnly: true,
Path: setting.AppSubUrl + "/",
Secure: hs.Cfg.SecurityHTTPSCookies,
Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite,
})
}

View File

@@ -26,6 +26,7 @@ func populateDashboardsByID(dashboardByIDs []int64, dashboardIDOrder map[int64]i
Slug: item.Slug,
Title: item.Title,
Uri: "db/" + item.Slug,
Url: m.GetDashboardUrl(item.Uid, item.Slug),
Order: dashboardIDOrder[item.Id],
})
}

View File

@@ -54,7 +54,7 @@ func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx
func newHTTPClient() httpClient {
return &http.Client{
Timeout: time.Duration(setting.DataProxyTimeout) * time.Second,
Timeout: 30 * time.Second,
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
}
}

View File

@@ -19,6 +19,7 @@ import (
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
_ "github.com/grafana/grafana/pkg/tsdb/azuremonitor"
_ "github.com/grafana/grafana/pkg/tsdb/cloudwatch"
_ "github.com/grafana/grafana/pkg/tsdb/elasticsearch"
_ "github.com/grafana/grafana/pkg/tsdb/graphite"

View File

@@ -32,6 +32,7 @@ import (
_ "github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/plugins"
_ "github.com/grafana/grafana/pkg/services/alerting"
_ "github.com/grafana/grafana/pkg/services/auth"
_ "github.com/grafana/grafana/pkg/services/cleanup"
_ "github.com/grafana/grafana/pkg/services/notifications"
_ "github.com/grafana/grafana/pkg/services/provisioning"

View File

@@ -0,0 +1,54 @@
package usagestats
import (
"context"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/social"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
)
var metricsLogger log.Logger = log.New("metrics")
func init() {
registry.RegisterService(&UsageStatsService{})
}
type UsageStatsService struct {
Cfg *setting.Cfg `inject:""`
Bus bus.Bus `inject:""`
SQLStore *sqlstore.SqlStore `inject:""`
oauthProviders map[string]bool
}
func (uss *UsageStatsService) Init() error {
uss.oauthProviders = social.GetOAuthProviders(uss.Cfg)
return nil
}
func (uss *UsageStatsService) Run(ctx context.Context) error {
uss.updateTotalStats()
onceEveryDayTick := time.NewTicker(time.Hour * 24)
everyMinuteTicker := time.NewTicker(time.Minute)
defer onceEveryDayTick.Stop()
defer everyMinuteTicker.Stop()
for {
select {
case <-onceEveryDayTick.C:
uss.sendUsageStats(uss.oauthProviders)
case <-everyMinuteTicker.C:
uss.updateTotalStats()
case <-ctx.Done():
return ctx.Err()
}
}
}

View File

@@ -0,0 +1,177 @@
package usagestats
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"runtime"
"strings"
"time"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
)
var usageStatsURL = "https://stats.grafana.org/grafana-usage-report"
func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) {
if !setting.ReportingEnabled {
return
}
metricsLogger.Debug(fmt.Sprintf("Sending anonymous usage stats to %s", usageStatsURL))
version := strings.Replace(setting.BuildVersion, ".", "_", -1)
metrics := map[string]interface{}{}
report := map[string]interface{}{
"version": version,
"metrics": metrics,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"edition": getEdition(),
"packaging": setting.Packaging,
}
statsQuery := models.GetSystemStatsQuery{}
if err := uss.Bus.Dispatch(&statsQuery); err != nil {
metricsLogger.Error("Failed to get system stats", "error", err)
return
}
metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards
metrics["stats.users.count"] = statsQuery.Result.Users
metrics["stats.orgs.count"] = statsQuery.Result.Orgs
metrics["stats.playlist.count"] = statsQuery.Result.Playlists
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
metrics["stats.alerts.count"] = statsQuery.Result.Alerts
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
metrics["stats.stars.count"] = statsQuery.Result.Stars
metrics["stats.folders.count"] = statsQuery.Result.Folders
metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions
metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions
metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards
metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots
metrics["stats.teams.count"] = statsQuery.Result.Teams
metrics["stats.total_auth_token.count"] = statsQuery.Result.AuthTokens
userCount := statsQuery.Result.Users
avgAuthTokensPerUser := statsQuery.Result.AuthTokens
if userCount != 0 {
avgAuthTokensPerUser = avgAuthTokensPerUser / userCount
}
metrics["stats.avg_auth_token_per_user.count"] = avgAuthTokensPerUser
dsStats := models.GetDataSourceStatsQuery{}
if err := uss.Bus.Dispatch(&dsStats); err != nil {
metricsLogger.Error("Failed to get datasource stats", "error", err)
return
}
// send counters for each data source
// but ignore any custom data sources
// as sending that name could be sensitive information
dsOtherCount := 0
for _, dsStat := range dsStats.Result {
if models.IsKnownDataSourcePlugin(dsStat.Type) {
metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count
} else {
dsOtherCount += dsStat.Count
}
}
metrics["stats.ds.other.count"] = dsOtherCount
metrics["stats.packaging."+setting.Packaging+".count"] = 1
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
if err := uss.Bus.Dispatch(&dsAccessStats); err != nil {
metricsLogger.Error("Failed to get datasource access stats", "error", err)
return
}
// send access counters for each data source
// but ignore any custom data sources
// as sending that name could be sensitive information
dsAccessOtherCount := make(map[string]int64)
for _, dsAccessStat := range dsAccessStats.Result {
if dsAccessStat.Access == "" {
continue
}
access := strings.ToLower(dsAccessStat.Access)
if models.IsKnownDataSourcePlugin(dsAccessStat.Type) {
metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count
} else {
old := dsAccessOtherCount[access]
dsAccessOtherCount[access] = old + dsAccessStat.Count
}
}
for access, count := range dsAccessOtherCount {
metrics["stats.ds_access.other."+access+".count"] = count
}
anStats := models.GetAlertNotifierUsageStatsQuery{}
if err := uss.Bus.Dispatch(&anStats); err != nil {
metricsLogger.Error("Failed to get alert notification stats", "error", err)
return
}
for _, stats := range anStats.Result {
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
}
authTypes := map[string]bool{}
authTypes["anonymous"] = setting.AnonymousEnabled
authTypes["basic_auth"] = setting.BasicAuthEnabled
authTypes["ldap"] = setting.LdapEnabled
authTypes["auth_proxy"] = setting.AuthProxyEnabled
for provider, enabled := range oauthProviders {
authTypes["oauth_"+provider] = enabled
}
for authType, enabled := range authTypes {
enabledValue := 0
if enabled {
enabledValue = 1
}
metrics["stats.auth_enabled."+authType+".count"] = enabledValue
}
out, _ := json.MarshalIndent(report, "", " ")
data := bytes.NewBuffer(out)
client := http.Client{Timeout: 5 * time.Second}
go client.Post(usageStatsURL, "application/json", data)
}
func (uss *UsageStatsService) updateTotalStats() {
statsQuery := models.GetSystemStatsQuery{}
if err := uss.Bus.Dispatch(&statsQuery); err != nil {
metricsLogger.Error("Failed to get system stats", "error", err)
return
}
metrics.M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards))
metrics.M_StatTotal_Users.Set(float64(statsQuery.Result.Users))
metrics.M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers))
metrics.M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists))
metrics.M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs))
}
func getEdition() string {
if setting.IsEnterprise {
return "enterprise"
} else {
return "oss"
}
}

View File

@@ -1,4 +1,4 @@
package metrics
package usagestats
import (
"bytes"
@@ -15,14 +15,21 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestMetrics(t *testing.T) {
Convey("Test send usage stats", t, func() {
uss := &UsageStatsService{
Bus: bus.New(),
SQLStore: sqlstore.InitTestDB(t),
}
var getSystemStatsQuery *models.GetSystemStatsQuery
bus.AddHandler("test", func(query *models.GetSystemStatsQuery) error {
uss.Bus.AddHandler(func(query *models.GetSystemStatsQuery) error {
query.Result = &models.SystemStats{
Dashboards: 1,
Datasources: 2,
@@ -38,13 +45,14 @@ func TestMetrics(t *testing.T) {
ProvisionedDashboards: 12,
Snapshots: 13,
Teams: 14,
AuthTokens: 15,
}
getSystemStatsQuery = query
return nil
})
var getDataSourceStatsQuery *models.GetDataSourceStatsQuery
bus.AddHandler("test", func(query *models.GetDataSourceStatsQuery) error {
uss.Bus.AddHandler(func(query *models.GetDataSourceStatsQuery) error {
query.Result = []*models.DataSourceStats{
{
Type: models.DS_ES,
@@ -68,7 +76,7 @@ func TestMetrics(t *testing.T) {
})
var getDataSourceAccessStatsQuery *models.GetDataSourceAccessStatsQuery
bus.AddHandler("test", func(query *models.GetDataSourceAccessStatsQuery) error {
uss.Bus.AddHandler(func(query *models.GetDataSourceAccessStatsQuery) error {
query.Result = []*models.DataSourceAccessStats{
{
Type: models.DS_ES,
@@ -116,7 +124,7 @@ func TestMetrics(t *testing.T) {
})
var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery
bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error {
uss.Bus.AddHandler(func(query *models.GetAlertNotifierUsageStatsQuery) error {
query.Result = []*models.NotifierUsageStats{
{
Type: "slack",
@@ -155,11 +163,11 @@ func TestMetrics(t *testing.T) {
"grafana_com": true,
}
sendUsageStats(oauthProviders)
uss.sendUsageStats(oauthProviders)
Convey("Given reporting not enabled and sending usage stats", func() {
setting.ReportingEnabled = false
sendUsageStats(oauthProviders)
uss.sendUsageStats(oauthProviders)
Convey("Should not gather stats or call http endpoint", func() {
So(getSystemStatsQuery, ShouldBeNil)
@@ -179,7 +187,7 @@ func TestMetrics(t *testing.T) {
setting.Packaging = "deb"
wg.Add(1)
sendUsageStats(oauthProviders)
uss.sendUsageStats(oauthProviders)
Convey("Should gather stats and call http endpoint", func() {
if waitTimeout(&wg, 2*time.Second) {
@@ -221,6 +229,8 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards)
So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots)
So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams)
So(metrics.Get("stats.total_auth_token.count").MustInt64(), ShouldEqual, 15)
So(metrics.Get("stats.avg_auth_token_per_user.count").MustInt64(), ShouldEqual, 5)
So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9)
So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10)
@@ -246,6 +256,7 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1)
})
})

View File

@@ -273,23 +273,35 @@ func (a *ldapAuther) initialBind(username, userPassword string) error {
return nil
}
func appendIfNotEmpty(slice []string, values ...string) []string {
for _, v := range values {
if v != "" {
slice = append(slice, v)
}
}
return slice
}
func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
var searchResult *ldap.SearchResult
var err error
for _, searchBase := range a.server.SearchBaseDNs {
attributes := make([]string, 0)
inputs := a.server.Attr
attributes = appendIfNotEmpty(attributes,
inputs.Username,
inputs.Surname,
inputs.Email,
inputs.Name,
inputs.MemberOf)
searchReq := ldap.SearchRequest{
BaseDN: searchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
Attributes: []string{
a.server.Attr.Username,
a.server.Attr.Surname,
a.server.Attr.Email,
a.server.Attr.Name,
a.server.Attr.MemberOf,
},
Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1),
Attributes: attributes,
Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1),
}
a.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq))

View File

@@ -6,6 +6,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ldap.v3"
@@ -322,11 +323,51 @@ func TestLdapAuther(t *testing.T) {
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
})
})
Convey("When searching for a user and not all five attributes are mapped", t, func() {
mockLdapConnection := &mockLdapConn{}
entry := ldap.Entry{
DN: "dn", Attributes: []*ldap.EntryAttribute{
{Name: "username", Values: []string{"roelgerrits"}},
{Name: "surname", Values: []string{"Gerrits"}},
{Name: "email", Values: []string{"roel@test.com"}},
{Name: "name", Values: []string{"Roel"}},
{Name: "memberof", Values: []string{"admins"}},
}}
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
mockLdapConnection.setSearchResult(&result)
// Set up attribute map without surname and email
ldapAuther := &ldapAuther{
server: &LdapServerConf{
Attr: LdapAttributeMap{
Username: "username",
Name: "name",
MemberOf: "memberof",
},
SearchBaseDNs: []string{"BaseDNHere"},
},
conn: mockLdapConnection,
log: log.New("test-logger"),
}
searchResult, err := ldapAuther.searchForUser("roelgerrits")
So(err, ShouldBeNil)
So(searchResult, ShouldNotBeNil)
// User should be searched in ldap
So(mockLdapConnection.searchCalled, ShouldBeTrue)
// No empty attributes should be added to the search request
So(len(mockLdapConnection.searchAttributes), ShouldEqual, 3)
})
}
type mockLdapConn struct {
result *ldap.SearchResult
searchCalled bool
result *ldap.SearchResult
searchCalled bool
searchAttributes []string
}
func (c *mockLdapConn) Bind(username, password string) error {
@@ -339,8 +380,9 @@ func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
c.result = result
}
func (c *mockLdapConn) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
func (c *mockLdapConn) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
c.searchCalled = true
c.searchAttributes = sr.Attributes
return c.result, nil
}

View File

@@ -1,17 +1,10 @@
package metrics
import (
"bytes"
"encoding/json"
"net/http"
"runtime"
"strings"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)
@@ -68,23 +61,6 @@ var (
grafanaBuildVersion *prometheus.GaugeVec
)
func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec {
counter := prometheus.NewCounterVec(opts, labels)
for _, label := range labelValues {
counter.WithLabelValues(label).Add(0)
}
return counter
}
func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter {
counter := prometheus.NewCounter(opts)
counter.Add(0)
return counter
}
func init() {
M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{
Name: "instance_start_total",
@@ -308,7 +284,7 @@ func init() {
Name: "build_info",
Help: "A metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built.",
Namespace: exporterName,
}, []string{"version", "revision", "branch", "goversion"})
}, []string{"version", "revision", "branch", "goversion", "edition"})
}
// SetBuildInformation sets the build information for this binary
@@ -317,8 +293,13 @@ func SetBuildInformation(version, revision, branch string) {
// Once this have been released for some time we should be able to remote `M_Grafana_Version`
// The reason we added a new one is that its common practice in the prometheus community
// to name this metric `*_build_info` so its easy to do aggregation on all programs.
edition := "oss"
if setting.IsEnterprise {
edition = "enterprise"
}
M_Grafana_Version.WithLabelValues(version).Set(1)
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version()).Set(1)
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
}
func initMetricVars() {
@@ -362,154 +343,19 @@ func initMetricVars() {
}
func updateTotalStats() {
statsQuery := models.GetSystemStatsQuery{}
if err := bus.Dispatch(&statsQuery); err != nil {
metricsLogger.Error("Failed to get system stats", "error", err)
return
func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec {
counter := prometheus.NewCounterVec(opts, labels)
for _, label := range labelValues {
counter.WithLabelValues(label).Add(0)
}
M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards))
M_StatTotal_Users.Set(float64(statsQuery.Result.Users))
M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers))
M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists))
M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs))
return counter
}
var usageStatsURL = "https://stats.grafana.org/grafana-usage-report"
func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter {
counter := prometheus.NewCounter(opts)
counter.Add(0)
func getEdition() string {
if setting.IsEnterprise {
return "enterprise"
} else {
return "oss"
}
}
func sendUsageStats(oauthProviders map[string]bool) {
if !setting.ReportingEnabled {
return
}
metricsLogger.Debug("Sending anonymous usage stats to stats.grafana.org")
version := strings.Replace(setting.BuildVersion, ".", "_", -1)
metrics := map[string]interface{}{}
report := map[string]interface{}{
"version": version,
"metrics": metrics,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"edition": getEdition(),
"packaging": setting.Packaging,
}
statsQuery := models.GetSystemStatsQuery{}
if err := bus.Dispatch(&statsQuery); err != nil {
metricsLogger.Error("Failed to get system stats", "error", err)
return
}
metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards
metrics["stats.users.count"] = statsQuery.Result.Users
metrics["stats.orgs.count"] = statsQuery.Result.Orgs
metrics["stats.playlist.count"] = statsQuery.Result.Playlists
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
metrics["stats.alerts.count"] = statsQuery.Result.Alerts
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
metrics["stats.stars.count"] = statsQuery.Result.Stars
metrics["stats.folders.count"] = statsQuery.Result.Folders
metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions
metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions
metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards
metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots
metrics["stats.teams.count"] = statsQuery.Result.Teams
dsStats := models.GetDataSourceStatsQuery{}
if err := bus.Dispatch(&dsStats); err != nil {
metricsLogger.Error("Failed to get datasource stats", "error", err)
return
}
// send counters for each data source
// but ignore any custom data sources
// as sending that name could be sensitive information
dsOtherCount := 0
for _, dsStat := range dsStats.Result {
if models.IsKnownDataSourcePlugin(dsStat.Type) {
metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count
} else {
dsOtherCount += dsStat.Count
}
}
metrics["stats.ds.other.count"] = dsOtherCount
metrics["stats.packaging."+setting.Packaging+".count"] = 1
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
if err := bus.Dispatch(&dsAccessStats); err != nil {
metricsLogger.Error("Failed to get datasource access stats", "error", err)
return
}
// send access counters for each data source
// but ignore any custom data sources
// as sending that name could be sensitive information
dsAccessOtherCount := make(map[string]int64)
for _, dsAccessStat := range dsAccessStats.Result {
if dsAccessStat.Access == "" {
continue
}
access := strings.ToLower(dsAccessStat.Access)
if models.IsKnownDataSourcePlugin(dsAccessStat.Type) {
metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count
} else {
old := dsAccessOtherCount[access]
dsAccessOtherCount[access] = old + dsAccessStat.Count
}
}
for access, count := range dsAccessOtherCount {
metrics["stats.ds_access.other."+access+".count"] = count
}
anStats := models.GetAlertNotifierUsageStatsQuery{}
if err := bus.Dispatch(&anStats); err != nil {
metricsLogger.Error("Failed to get alert notification stats", "error", err)
return
}
for _, stats := range anStats.Result {
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
}
authTypes := map[string]bool{}
authTypes["anonymous"] = setting.AnonymousEnabled
authTypes["basic_auth"] = setting.BasicAuthEnabled
authTypes["ldap"] = setting.LdapEnabled
authTypes["auth_proxy"] = setting.AuthProxyEnabled
for provider, enabled := range oauthProviders {
authTypes["oauth_"+provider] = enabled
}
for authType, enabled := range authTypes {
enabledValue := 0
if enabled {
enabledValue = 1
}
metrics["stats.auth_enabled."+authType+".count"] = enabledValue
}
out, _ := json.MarshalIndent(report, "", " ")
data := bytes.NewBuffer(out)
client := http.Client{Timeout: 5 * time.Second}
go client.Post(usageStatsURL, "application/json", data)
return counter
}

View File

@@ -2,7 +2,6 @@ package metrics
import (
"context"
"time"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
@@ -30,7 +29,6 @@ type InternalMetricsService struct {
intervalSeconds int64
graphiteCfg *graphitebridge.Config
oauthProviders map[string]bool
}
func (im *InternalMetricsService) Init() error {
@@ -50,22 +48,6 @@ func (im *InternalMetricsService) Run(ctx context.Context) error {
M_Instance_Start.Inc()
// set the total stats gauges before we publishing metrics
updateTotalStats()
onceEveryDayTick := time.NewTicker(time.Hour * 24)
everyMinuteTicker := time.NewTicker(time.Minute)
defer onceEveryDayTick.Stop()
defer everyMinuteTicker.Stop()
for {
select {
case <-onceEveryDayTick.C:
sendUsageStats(im.oauthProviders)
case <-everyMinuteTicker.C:
updateTotalStats()
case <-ctx.Done():
return ctx.Err()
}
}
<-ctx.Done()
return ctx.Err()
}

View File

@@ -5,8 +5,6 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/social"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
@@ -24,8 +22,6 @@ func (im *InternalMetricsService) readSettings() error {
return fmt.Errorf("Unable to parse metrics graphite section, %v", err)
}
im.oauthProviders = social.GetOAuthProviders(im.Cfg)
return nil
}

View File

@@ -1,13 +1,15 @@
package middleware
import (
"net/http"
"net/url"
"strconv"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -21,7 +23,7 @@ var (
ReqOrgAdmin = RoleAuth(m.ROLE_ADMIN)
)
func GetContextHandler(ats auth.UserAuthTokenService) macaron.Handler {
func GetContextHandler(ats m.UserTokenService) macaron.Handler {
return func(c *macaron.Context) {
ctx := &m.ReqContext{
Context: c,
@@ -49,7 +51,7 @@ func GetContextHandler(ats auth.UserAuthTokenService) macaron.Handler {
case initContextWithApiKey(ctx):
case initContextWithBasicAuth(ctx, orgId):
case initContextWithAuthProxy(ctx, orgId):
case ats.InitContextWithToken(ctx, orgId):
case initContextWithToken(ats, ctx, orgId):
case initContextWithAnonymousUser(ctx):
}
@@ -166,6 +168,69 @@ func initContextWithBasicAuth(ctx *m.ReqContext, orgId int64) bool {
return true
}
func initContextWithToken(authTokenService m.UserTokenService, ctx *m.ReqContext, orgID int64) bool {
rawToken := ctx.GetCookie(setting.LoginCookieName)
if rawToken == "" {
return false
}
token, err := authTokenService.LookupToken(rawToken)
if err != nil {
ctx.Logger.Error("failed to look up user based on cookie", "error", err)
WriteSessionCookie(ctx, "", -1)
return false
}
query := m.GetSignedInUserQuery{UserId: token.UserId, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
ctx.Logger.Error("failed to get user with id", "userId", token.UserId, "error", err)
return false
}
ctx.SignedInUser = query.Result
ctx.IsSignedIn = true
ctx.UserToken = token
rotated, err := authTokenService.TryRotateToken(token, ctx.RemoteAddr(), ctx.Req.UserAgent())
if err != nil {
ctx.Logger.Error("failed to rotate token", "error", err)
return true
}
if rotated {
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetimeDays)
}
return true
}
func WriteSessionCookie(ctx *m.ReqContext, value string, maxLifetimeDays int) {
if setting.Env == setting.DEV {
ctx.Logger.Info("new token", "unhashed token", value)
}
var maxAge int
if maxLifetimeDays <= 0 {
maxAge = -1
} else {
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour) + time.Hour
maxAge = int(maxAgeHours.Seconds())
}
ctx.Resp.Header().Del("Set-Cookie")
cookie := http.Cookie{
Name: setting.LoginCookieName,
Value: url.QueryEscape(value),
HttpOnly: true,
Path: setting.AppSubUrl + "/",
Secure: setting.CookieSecure,
MaxAge: maxAge,
SameSite: setting.CookieSameSite,
}
http.SetCookie(ctx.Resp, &cookie)
}
func AddDefaultResponseHeaders() macaron.Handler {
return func(ctx *m.ReqContext) {
if ctx.IsApiRequest() && ctx.Req.Method == "GET" {

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