mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 19:44:55 +08:00
Compare commits
15 Commits
docs/add-d
...
v8.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
147704deb9 | ||
|
|
078d716be9 | ||
|
|
04cb471599 | ||
|
|
6564f22772 | ||
|
|
13cd3ea28b | ||
|
|
d5e0665081 | ||
|
|
909141592d | ||
|
|
fda235a862 | ||
|
|
154231a58d | ||
|
|
197e4344da | ||
|
|
64b008e28b | ||
|
|
bc9ac1199b | ||
|
|
2b15e1a962 | ||
|
|
4308a77e27 | ||
|
|
f18749927c |
87
CHANGELOG.md
87
CHANGELOG.md
@@ -1,4 +1,91 @@
|
||||
|
||||
<!-- 8.1.0-beta1 START -->
|
||||
|
||||
# 8.1.0-beta1 (2021-07-22)
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
* **Alerting:** Add Alertmanager notifications tab. [#35759](https://github.com/grafana/grafana/pull/35759), [@nathanrodman](https://github.com/nathanrodman)
|
||||
* **Alerting:** Add button to deactivate current Alertmanager configuration. [#36951](https://github.com/grafana/grafana/pull/36951), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Add toggle in Loki/Prometheus data source configuration to opt out of alerting UI. [#36552](https://github.com/grafana/grafana/pull/36552), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Allow any "evaluate for" value >=0 in the alert rule form. [#35807](https://github.com/grafana/grafana/pull/35807), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Load default configuration from status endpoint, if Cortex Alertmanager returns empty user configuration. [#35769](https://github.com/grafana/grafana/pull/35769), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** view to display alert rule and its underlying data. [#35546](https://github.com/grafana/grafana/pull/35546), [@mckn](https://github.com/mckn)
|
||||
* **Annotation panel:** Release the annotation panel. [#36959](https://github.com/grafana/grafana/pull/36959), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Annotations:** Add typeahead support for tags in built-in annotations. [#36377](https://github.com/grafana/grafana/pull/36377), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **AzureMonitor:** Add curated dashboards for Azure services. [#35356](https://github.com/grafana/grafana/pull/35356), [@avidhanju](https://github.com/avidhanju)
|
||||
* **AzureMonitor:** Add support for deep links to Microsoft Azure portal for Metrics. [#32273](https://github.com/grafana/grafana/pull/32273), [@shuotli](https://github.com/shuotli)
|
||||
* **AzureMonitor:** Remove support for different credentials for Azure Monitor Logs. [#35121](https://github.com/grafana/grafana/pull/35121), [@andresmgot](https://github.com/andresmgot)
|
||||
* **AzureMonitor:** Support querying any Resource for Logs queries. [#33879](https://github.com/grafana/grafana/pull/33879), [@joshhunt](https://github.com/joshhunt)
|
||||
* **Elasticsearch:** Add frozen indices search support. [#36018](https://github.com/grafana/grafana/pull/36018), [@Elfo404](https://github.com/Elfo404)
|
||||
* **Elasticsearch:** Name fields after template variables values instead of their name. [#36035](https://github.com/grafana/grafana/pull/36035), [@Elfo404](https://github.com/Elfo404)
|
||||
* **Elasticsearch:** add rate aggregation. [#33311](https://github.com/grafana/grafana/pull/33311), [@estermv](https://github.com/estermv)
|
||||
* **Email:** Allow configuration of content types for email notifications. [#34530](https://github.com/grafana/grafana/pull/34530), [@djairhogeuens](https://github.com/djairhogeuens)
|
||||
* **Explore:** Add more meta information when line limit is hit. [#33069](https://github.com/grafana/grafana/pull/33069), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore:** UI improvements to trace view. [#34276](https://github.com/grafana/grafana/pull/34276), [@aocenas](https://github.com/aocenas)
|
||||
* **FieldOverrides:** Added support to change display name in an override field and have it be matched by a later rule. [#35893](https://github.com/grafana/grafana/pull/35893), [@torkelo](https://github.com/torkelo)
|
||||
* **HTTP Client:** Introduce `dataproxy_max_idle_connections` config variable. [#35864](https://github.com/grafana/grafana/pull/35864), [@dsotirakis](https://github.com/dsotirakis)
|
||||
* **InfluxDB:** InfluxQL: adds tags to timeseries data. [#36702](https://github.com/grafana/grafana/pull/36702), [@gabor](https://github.com/gabor)
|
||||
* **InfluxDB:** InfluxQL: make measurement search case insensitive. [#34563](https://github.com/grafana/grafana/pull/34563), [@gabor](https://github.com/gabor)
|
||||
* **Legacy Alerting:** Replace simplejson with a struct in webhook notification channel. [#34952](https://github.com/grafana/grafana/pull/34952), [@KEVISONG](https://github.com/KEVISONG)
|
||||
* **Legend:** Updates display name for Last (not null) to just Last*. [#35633](https://github.com/grafana/grafana/pull/35633), [@torkelo](https://github.com/torkelo)
|
||||
* **Logs panel:** Add option to show common labels. [#36166](https://github.com/grafana/grafana/pull/36166), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add $__range variable. [#36175](https://github.com/grafana/grafana/pull/36175), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add support for "label_values(log stream selector, label)" in templating. [#35488](https://github.com/grafana/grafana/pull/35488), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add support for ad-hoc filtering in dashboard. [#36393](https://github.com/grafana/grafana/pull/36393), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **MySQL Datasource:** Add timezone parameter. [#27535](https://github.com/grafana/grafana/pull/27535), [@andipabst](https://github.com/andipabst)
|
||||
* **NodeGraph:** Show gradient fields in legend. [#34078](https://github.com/grafana/grafana/pull/34078), [@aocenas](https://github.com/aocenas)
|
||||
* **PanelOptions:** Don't mutate panel options/field config object when updating. [#36441](https://github.com/grafana/grafana/pull/36441), [@dprokop](https://github.com/dprokop)
|
||||
* **PieChart:** Make pie gradient more subtle to match other charts. [#36961](https://github.com/grafana/grafana/pull/36961), [@nikki-kiga](https://github.com/nikki-kiga)
|
||||
* **Prometheus:** Update PromQL typeahead and highlighting. [#36730](https://github.com/grafana/grafana/pull/36730), [@ekpdt](https://github.com/ekpdt)
|
||||
* **Prometheus:** interpolate variable for step field. [#36437](https://github.com/grafana/grafana/pull/36437), [@zoltanbedi](https://github.com/zoltanbedi)
|
||||
* **Provisioning:** Improve validation by validating across all dashboard providers. [#26742](https://github.com/grafana/grafana/pull/26742), [@nabokihms](https://github.com/nabokihms)
|
||||
* **SQL Datasources:** Allow multiple string/labels columns with time series. [#36485](https://github.com/grafana/grafana/pull/36485), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Select:** Portal select menu to document.body. [#36398](https://github.com/grafana/grafana/pull/36398), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **Team Sync:** Add group mapping to support team sync in the Generic OAuth provider. [#36307](https://github.com/grafana/grafana/pull/36307), [@wardbekker](https://github.com/wardbekker)
|
||||
* **Tooltip:** Make active series more noticeable. [#36824](https://github.com/grafana/grafana/pull/36824), [@nikki-kiga](https://github.com/nikki-kiga)
|
||||
* **Tracing:** Add support to configure trace to logs start and end time. [#34995](https://github.com/grafana/grafana/pull/34995), [@zoltanbedi](https://github.com/zoltanbedi)
|
||||
* **Transformations:** Skip merge when there is only a single data frame. [#36407](https://github.com/grafana/grafana/pull/36407), [@edgarpoce](https://github.com/edgarpoce)
|
||||
* **ValueMapping:** Added support for mapping text to color, boolean values, NaN and Null. Improved UI for value mapping. [#33820](https://github.com/grafana/grafana/pull/33820), [@torkelo](https://github.com/torkelo)
|
||||
* **Visualizations:** Dynamically set any config (min, max, unit, color, thresholds) from query results. [#36548](https://github.com/grafana/grafana/pull/36548), [@torkelo](https://github.com/torkelo)
|
||||
* **live:** Add support to handle origin without a value for the port when matching with root_url. [#36834](https://github.com/grafana/grafana/pull/36834), [@FZambia](https://github.com/FZambia)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* **Alerting:** Handle marshaling Inf values. [#36947](https://github.com/grafana/grafana/pull/36947), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **AzureMonitor:** Fix macro resolution for template variables. [#36944](https://github.com/grafana/grafana/pull/36944), [@andresmgot](https://github.com/andresmgot)
|
||||
* **AzureMonitor:** Fix queries with Microsoft.NetApp/../../volumes resources. [#32661](https://github.com/grafana/grafana/pull/32661), [@pckls](https://github.com/pckls)
|
||||
* **AzureMonitor:** Request and concat subsequent resource pages. [#36958](https://github.com/grafana/grafana/pull/36958), [@andresmgot](https://github.com/andresmgot)
|
||||
* **Bug:** Fix parse duration for day. [#36942](https://github.com/grafana/grafana/pull/36942), [@idafurjes](https://github.com/idafurjes)
|
||||
* **Datasources:** Improve error handling for error messages. [#35120](https://github.com/grafana/grafana/pull/35120), [@ifrost](https://github.com/ifrost)
|
||||
* **Explore:** Correct the functionality of shift-enter shortcut across all uses. [#36600](https://github.com/grafana/grafana/pull/36600), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore:** Show all dataFrames in data tab in Inspector. [#32161](https://github.com/grafana/grafana/pull/32161), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **GraphNG:** Fix Tooltip mode 'All' for XYChart. [#31260](https://github.com/grafana/grafana/pull/31260), [@Posnet](https://github.com/Posnet)
|
||||
* **Loki:** Fix highlight of logs when using filter expressions with backticks. [#36024](https://github.com/grafana/grafana/pull/36024), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Modal:** Force modal content to overflow with scroll. [#36754](https://github.com/grafana/grafana/pull/36754), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **Plugins:** Ignore symlinked folders when verifying plugin signature. [#34434](https://github.com/grafana/grafana/pull/34434), [@wbrowne](https://github.com/wbrowne)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
|
||||
When parsing Elasticsearch query responses using template variables, each field gets named after the variable value instead of the name.
|
||||
For example, executing a `terms` aggregation on a variable named `$groupBy` that has `@hostname` as a value, the resulting column in the table response will be `@hostname` instead of `$groupBy` Issue [#36035](https://github.com/grafana/grafana/issues/36035)
|
||||
|
||||
|
||||
Azure Monitor data source no longer supports different credentials for Metrics and Logs in existing data sources. To use different credentials for Azure Monitor Logs, create another data source. Issue [#35121](https://github.com/grafana/grafana/issues/35121)
|
||||
|
||||
Existing Azure Metrics Logs queries for Log Analytics Workspaces should be backward compatible with this change and should not get impacted. Panels will be migrated to use the new resource-centric backend when you first edit and save them.
|
||||
|
||||
Application Insights and Insights Analytics queries are now read-only and cannot be modified. To update Application Insights queries, users can manually recreate them as Metrics queries, and Insights Analytics are recreated with Logs.
|
||||
|
||||
Issue [#33879](https://github.com/grafana/grafana/issues/33879)
|
||||
|
||||
### Plugin development fixes & changes
|
||||
|
||||
* **Toolkit:** Improve error messages when tasks fail. [#36381](https://github.com/grafana/grafana/pull/36381), [@joshhunt](https://github.com/joshhunt)
|
||||
|
||||
<!-- 8.1.0-beta1 END -->
|
||||
|
||||
<!-- 8.0.6 START -->
|
||||
|
||||
# 8.0.6 (2021-07-14)
|
||||
|
||||
@@ -995,12 +995,12 @@
|
||||
[geomap]
|
||||
# Set the JSON configuration for the default basemap
|
||||
;default_baselayer_config = `{
|
||||
"type": "xyz",
|
||||
"config": {
|
||||
"attribution": "Open street map",
|
||||
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
}
|
||||
}`
|
||||
; "type": "xyz",
|
||||
; "config": {
|
||||
; "attribution": "Open street map",
|
||||
; "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
; }
|
||||
;}`
|
||||
|
||||
# Enable or disable loading other base map layers
|
||||
;enable_custom_baselayers = true
|
||||
|
||||
@@ -109,8 +109,9 @@ The following template variables are available when expanding annotations and la
|
||||
|
||||
| Name | Description |
|
||||
| ------- | --------------- |
|
||||
| $labels | Labels contains the labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. |
|
||||
| $values | Values contains the values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. |
|
||||
| $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. |
|
||||
| $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. |
|
||||
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
|
||||
|
||||
## Preview alerts
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ sync_ttl = 60
|
||||
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
|
||||
whitelist =
|
||||
# Optionally define more headers to sync other user attributes
|
||||
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
|
||||
# Example `headers = Name:X-WEBAUTH-NAME Role:X-WEBAUTH-ROLE Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
|
||||
headers =
|
||||
# Check out docs on this for more details on the below setting
|
||||
enable_login_token = false
|
||||
|
||||
@@ -237,7 +237,9 @@ Content-Length: 97
|
||||
|
||||
`DELETE /api/folders/:uid`
|
||||
|
||||
Deletes an existing folder identified by uid together with all dashboards stored in the folder, if any. This operation cannot be reverted.
|
||||
Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.
|
||||
|
||||
If [Grafana 8 Alerts]({{< relref "../alerting/unified-alerting/_index.md" >}}) are enabled, you can set an optional query parameter `forceDeleteRules=false` so that requests will fail with 400 (Bad Request) error if the folder contains any Grafana 8 Alerts. However, if this parameter is set to `true` then it will delete any Grafana 8 Alerts under this folder.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
@@ -265,6 +267,7 @@ Status Codes:
|
||||
|
||||
- **200** – Deleted
|
||||
- **401** – Unauthorized
|
||||
- **400** – Bad Request
|
||||
- **403** – Access Denied
|
||||
- **404** – Folder not found
|
||||
|
||||
|
||||
91
docs/sources/release-notes/release-notes-8-1-0-beta1.md
Normal file
91
docs/sources/release-notes/release-notes-8-1-0-beta1.md
Normal file
@@ -0,0 +1,91 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 8.1.0-beta1"
|
||||
[_build]
|
||||
list = false
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 8.1.0-beta1
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
* **Alerting:** Add Alertmanager notifications tab. [#35759](https://github.com/grafana/grafana/pull/35759), [@nathanrodman](https://github.com/nathanrodman)
|
||||
* **Alerting:** Add button to deactivate current Alertmanager configuration. [#36951](https://github.com/grafana/grafana/pull/36951), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Add toggle in Loki/Prometheus data source configuration to opt out of alerting UI. [#36552](https://github.com/grafana/grafana/pull/36552), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Allow any "evaluate for" value >=0 in the alert rule form. [#35807](https://github.com/grafana/grafana/pull/35807), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** Load default configuration from status endpoint, if Cortex Alertmanager returns empty user configuration. [#35769](https://github.com/grafana/grafana/pull/35769), [@domasx2](https://github.com/domasx2)
|
||||
* **Alerting:** view to display alert rule and its underlying data. [#35546](https://github.com/grafana/grafana/pull/35546), [@mckn](https://github.com/mckn)
|
||||
* **Annotation panel:** Release the annotation panel. [#36959](https://github.com/grafana/grafana/pull/36959), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Annotations:** Add typeahead support for tags in built-in annotations. [#36377](https://github.com/grafana/grafana/pull/36377), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **AzureMonitor:** Add curated dashboards for Azure services. [#35356](https://github.com/grafana/grafana/pull/35356), [@avidhanju](https://github.com/avidhanju)
|
||||
* **AzureMonitor:** Add support for deep links to Microsoft Azure portal for Metrics. [#32273](https://github.com/grafana/grafana/pull/32273), [@shuotli](https://github.com/shuotli)
|
||||
* **AzureMonitor:** Remove support for different credentials for Azure Monitor Logs. [#35121](https://github.com/grafana/grafana/pull/35121), [@andresmgot](https://github.com/andresmgot)
|
||||
* **AzureMonitor:** Support querying any Resource for Logs queries. [#33879](https://github.com/grafana/grafana/pull/33879), [@joshhunt](https://github.com/joshhunt)
|
||||
* **Elasticsearch:** Add frozen indices search support. [#36018](https://github.com/grafana/grafana/pull/36018), [@Elfo404](https://github.com/Elfo404)
|
||||
* **Elasticsearch:** Name fields after template variables values instead of their name. [#36035](https://github.com/grafana/grafana/pull/36035), [@Elfo404](https://github.com/Elfo404)
|
||||
* **Elasticsearch:** add rate aggregation. [#33311](https://github.com/grafana/grafana/pull/33311), [@estermv](https://github.com/estermv)
|
||||
* **Email:** Allow configuration of content types for email notifications. [#34530](https://github.com/grafana/grafana/pull/34530), [@djairhogeuens](https://github.com/djairhogeuens)
|
||||
* **Explore:** Add more meta information when line limit is hit. [#33069](https://github.com/grafana/grafana/pull/33069), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore:** UI improvements to trace view. [#34276](https://github.com/grafana/grafana/pull/34276), [@aocenas](https://github.com/aocenas)
|
||||
* **FieldOverrides:** Added support to change display name in an override field and have it be matched by a later rule. [#35893](https://github.com/grafana/grafana/pull/35893), [@torkelo](https://github.com/torkelo)
|
||||
* **HTTP Client:** Introduce `dataproxy_max_idle_connections` config variable. [#35864](https://github.com/grafana/grafana/pull/35864), [@dsotirakis](https://github.com/dsotirakis)
|
||||
* **InfluxDB:** InfluxQL: adds tags to timeseries data. [#36702](https://github.com/grafana/grafana/pull/36702), [@gabor](https://github.com/gabor)
|
||||
* **InfluxDB:** InfluxQL: make measurement search case insensitive. [#34563](https://github.com/grafana/grafana/pull/34563), [@gabor](https://github.com/gabor)
|
||||
* **Legacy Alerting:** Replace simplejson with a struct in webhook notification channel. [#34952](https://github.com/grafana/grafana/pull/34952), [@KEVISONG](https://github.com/KEVISONG)
|
||||
* **Legend:** Updates display name for Last (not null) to just Last*. [#35633](https://github.com/grafana/grafana/pull/35633), [@torkelo](https://github.com/torkelo)
|
||||
* **Logs panel:** Add option to show common labels. [#36166](https://github.com/grafana/grafana/pull/36166), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add $__range variable. [#36175](https://github.com/grafana/grafana/pull/36175), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add support for "label_values(log stream selector, label)" in templating. [#35488](https://github.com/grafana/grafana/pull/35488), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Loki:** Add support for ad-hoc filtering in dashboard. [#36393](https://github.com/grafana/grafana/pull/36393), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **MySQL Datasource:** Add timezone parameter. [#27535](https://github.com/grafana/grafana/pull/27535), [@andipabst](https://github.com/andipabst)
|
||||
* **NodeGraph:** Show gradient fields in legend. [#34078](https://github.com/grafana/grafana/pull/34078), [@aocenas](https://github.com/aocenas)
|
||||
* **PanelOptions:** Don't mutate panel options/field config object when updating. [#36441](https://github.com/grafana/grafana/pull/36441), [@dprokop](https://github.com/dprokop)
|
||||
* **PieChart:** Make pie gradient more subtle to match other charts. [#36961](https://github.com/grafana/grafana/pull/36961), [@nikki-kiga](https://github.com/nikki-kiga)
|
||||
* **Prometheus:** Update PromQL typeahead and highlighting. [#36730](https://github.com/grafana/grafana/pull/36730), [@ekpdt](https://github.com/ekpdt)
|
||||
* **Prometheus:** interpolate variable for step field. [#36437](https://github.com/grafana/grafana/pull/36437), [@zoltanbedi](https://github.com/zoltanbedi)
|
||||
* **Provisioning:** Improve validation by validating across all dashboard providers. [#26742](https://github.com/grafana/grafana/pull/26742), [@nabokihms](https://github.com/nabokihms)
|
||||
* **SQL Datasources:** Allow multiple string/labels columns with time series. [#36485](https://github.com/grafana/grafana/pull/36485), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Select:** Portal select menu to document.body. [#36398](https://github.com/grafana/grafana/pull/36398), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **Team Sync:** Add group mapping to support team sync in the Generic OAuth provider. [#36307](https://github.com/grafana/grafana/pull/36307), [@wardbekker](https://github.com/wardbekker)
|
||||
* **Tooltip:** Make active series more noticeable. [#36824](https://github.com/grafana/grafana/pull/36824), [@nikki-kiga](https://github.com/nikki-kiga)
|
||||
* **Tracing:** Add support to configure trace to logs start and end time. [#34995](https://github.com/grafana/grafana/pull/34995), [@zoltanbedi](https://github.com/zoltanbedi)
|
||||
* **Transformations:** Skip merge when there is only a single data frame. [#36407](https://github.com/grafana/grafana/pull/36407), [@edgarpoce](https://github.com/edgarpoce)
|
||||
* **ValueMapping:** Added support for mapping text to color, boolean values, NaN and Null. Improved UI for value mapping. [#33820](https://github.com/grafana/grafana/pull/33820), [@torkelo](https://github.com/torkelo)
|
||||
* **Visualizations:** Dynamically set any config (min, max, unit, color, thresholds) from query results. [#36548](https://github.com/grafana/grafana/pull/36548), [@torkelo](https://github.com/torkelo)
|
||||
* **live:** Add support to handle origin without a value for the port when matching with root_url. [#36834](https://github.com/grafana/grafana/pull/36834), [@FZambia](https://github.com/FZambia)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* **Alerting:** Handle marshaling Inf values. [#36947](https://github.com/grafana/grafana/pull/36947), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **AzureMonitor:** Fix macro resolution for template variables. [#36944](https://github.com/grafana/grafana/pull/36944), [@andresmgot](https://github.com/andresmgot)
|
||||
* **AzureMonitor:** Fix queries with Microsoft.NetApp/../../volumes resources. [#32661](https://github.com/grafana/grafana/pull/32661), [@pckls](https://github.com/pckls)
|
||||
* **AzureMonitor:** Request and concat subsequent resource pages. [#36958](https://github.com/grafana/grafana/pull/36958), [@andresmgot](https://github.com/andresmgot)
|
||||
* **Bug:** Fix parse duration for day. [#36942](https://github.com/grafana/grafana/pull/36942), [@idafurjes](https://github.com/idafurjes)
|
||||
* **Datasources:** Improve error handling for error messages. [#35120](https://github.com/grafana/grafana/pull/35120), [@ifrost](https://github.com/ifrost)
|
||||
* **Explore:** Correct the functionality of shift-enter shortcut across all uses. [#36600](https://github.com/grafana/grafana/pull/36600), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore:** Show all dataFrames in data tab in Inspector. [#32161](https://github.com/grafana/grafana/pull/32161), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **GraphNG:** Fix Tooltip mode 'All' for XYChart. [#31260](https://github.com/grafana/grafana/pull/31260), [@Posnet](https://github.com/Posnet)
|
||||
* **Loki:** Fix highlight of logs when using filter expressions with backticks. [#36024](https://github.com/grafana/grafana/pull/36024), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Modal:** Force modal content to overflow with scroll. [#36754](https://github.com/grafana/grafana/pull/36754), [@ashharrison90](https://github.com/ashharrison90)
|
||||
* **Plugins:** Ignore symlinked folders when verifying plugin signature. [#34434](https://github.com/grafana/grafana/pull/34434), [@wbrowne](https://github.com/wbrowne)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
|
||||
When parsing Elasticsearch query responses using template variables, each field gets named after the variable value instead of the name.
|
||||
For example, executing a `terms` aggregation on a variable named `$groupBy` that has `@hostname` as a value, the resulting column in the table response will be `@hostname` instead of `$groupBy` Issue [#36035](https://github.com/grafana/grafana/issues/36035)
|
||||
|
||||
|
||||
Azure Monitor data source no longer supports different credentials for Metrics and Logs in existing data sources. To use different credentials for Azure Monitor Logs, create another data source. Issue [#35121](https://github.com/grafana/grafana/issues/35121)
|
||||
|
||||
Existing Azure Metrics Logs queries for Log Analytics Workspaces should be backward compatible with this change and should not get impacted. Panels will be migrated to use the new resource-centric backend when you first edit and save them.
|
||||
|
||||
Application Insights and Insights Analytics queries are now read-only and cannot be modified. To update Application Insights queries, users can manually recreate them as Metrics queries, and Insights Analytics are recreated with Logs.
|
||||
|
||||
Issue [#33879](https://github.com/grafana/grafana/issues/33879)
|
||||
|
||||
### Plugin development fixes & changes
|
||||
|
||||
* **Toolkit:** Improve error messages when tasks fail. [#36381](https://github.com/grafana/grafana/pull/36381), [@joshhunt](https://github.com/joshhunt)
|
||||
|
||||
@@ -11,6 +11,7 @@ as info on deprecations, breaking changes and plugin development read the [relea
|
||||
|
||||
## Grafana 8
|
||||
|
||||
- [What's new in 8.1]({{< relref "whats-new-in-v8-1" >}})
|
||||
- [What's new in 8.0]({{< relref "whats-new-in-v8-0" >}})
|
||||
|
||||
## Grafana 7
|
||||
|
||||
79
docs/sources/whatsnew/whats-new-in-v8-1.md
Normal file
79
docs/sources/whatsnew/whats-new-in-v8-1.md
Normal file
@@ -0,0 +1,79 @@
|
||||
+++
|
||||
title = "What's new in Grafana v8.1"
|
||||
description = "Feature and improvement highlights for Grafana v8.1"
|
||||
keywords = ["grafana", "new", "documentation", "8.1", "release notes"]
|
||||
weight = -33
|
||||
aliases = ["/docs/grafana/latest/guides/whats-new-in-v8-1/"]
|
||||
[_build]
|
||||
list = false
|
||||
+++
|
||||
|
||||
# What’s new in Grafana v8.1
|
||||
|
||||
> **Note:** This topic will be updated frequently between now and the final release.
|
||||
|
||||
This topic includes the release notes for Grafana v8.1. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
|
||||
|
||||
## Grafana OSS features
|
||||
|
||||
These features are included in the Grafana open source edition.
|
||||
|
||||
### Time series panel updates
|
||||
|
||||
Time series panels have been updated with the ability to color series and line by thresholds or gradient color scales. This allows users to create time series panels where the line color can change dynamically based on thresholds or using gradient color scales.
|
||||
|
||||
Also, we have added possibility to create annotations directly from the panel. For more information, refer to ...
|
||||
|
||||
|
||||
### Geomap panel
|
||||
|
||||
Grafana 8.1 introduces the foundation for our new map panel. This new panel leverages [OpenLayers](https://openlayers.org/) and gives us a flexible solution for extending the way we use the new Geomap panel moving forward. We expect to ship this new visualization with the ability to use [Circle Overlays](https://github.com/grafana/grafana/pull/36680) and [Heatmaps](https://github.com/open-o11y/grafana/pull/18).
|
||||
|
||||
For more information, refer to [issue 36585](https://github.com/grafana/grafana/issues/36585). For documentation, refer to ...
|
||||
|
||||
### Annotation panel
|
||||
|
||||
This section is for the new panel...
|
||||
|
||||
### Transformations improvements
|
||||
|
||||
Grafana 8.1 includes many transformations enhancements.
|
||||
|
||||
#### Config from query (Beta)
|
||||
|
||||
This transformation enables panel config (Threshold, Min, Max, etc.) to be derived from query results. For more information, refer to [Config from query results transform]({{< relref "../panels/transformations/config-from-query.md" >}}).
|
||||
|
||||
#### Rows to fields (Beta)
|
||||
|
||||
This transformation enables rows in returned data to be converted into separate fields. Prior to this enhancement, you could style and configure fields individually, but not rows. For more information, refer to [Rows to fields transform]({{< relref "../panels/transformations/rows-to-fields.md" >}}).
|
||||
|
||||
|
||||
#### Contextual & Inline Help
|
||||
|
||||
Additional inline help will be available for Transformations. We can now share examples of how to use specific transformations and point users directly to the appropriate place in the docs for more information.
|
||||
|
||||
|
||||
### Data source updates
|
||||
|
||||
The following data source updates are included with this Grafana release.
|
||||
|
||||
#### MySQL Data Source
|
||||
|
||||
We have added timezone support. As a result, you can now specify the time zone used in the database session, such as `Europe/Berlin` or `+02:00`.
|
||||
|
||||
### Trace to logs improvements
|
||||
|
||||
We have updated the default behavior from creating a one (1) hour span Loki query to only query at the exact time the trace span started for the duration of it. For more fine grained control over this you can shift this time in the tracing data source settings. It is now possible to to shift the start time and end time of the Loki query by the set amount. For more information, refer to [Trace to logs]({{< relref "../datasources/tempo.md#trace-to-logs" >}}).
|
||||
|
||||
#### Documentation updates
|
||||
|
||||
New panel summaries and preview on the top level [Visualizations]({{< relref "../panels/visualizations/_index.md" >}}) page to help users pick or learn about specific visualizations more easily.
|
||||
|
||||
## Enterprise features
|
||||
|
||||
These features are included in the Grafana Enterprise edition.
|
||||
|
||||
### Oauth2 - Team Sync to Group Mapping
|
||||
|
||||
With Team Sync you can map your Generic OAuth groups to teams in Grafana so that the users are automatically added to the correct teams.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "8.1.0-pre"
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "8.1.0-beta.2"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -644,7 +644,7 @@ describe('getLinksSupplier', () => {
|
||||
expect(links[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'testDS',
|
||||
href: '/explore?left={"datasource":"testDS","queries":["12345"]}',
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"testDS","queries":["12345"]}')}`,
|
||||
onClick: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('mapInternalLinkToExplore', () => {
|
||||
expect(link).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'dsName',
|
||||
href: '/explore?left={"datasource":"dsName","queries":[{"query":"12344"}]}',
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"dsName","queries":[{"query":"12344"}]}')}`,
|
||||
onClick: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -67,11 +67,13 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
|
||||
*/
|
||||
function generateInternalHref<T extends DataQuery = any>(datasourceName: string, query: T, range: TimeRange): string {
|
||||
return locationUtil.assureBaseUrl(
|
||||
`/explore?left=${serializeStateToUrlParam({
|
||||
range: range.raw,
|
||||
datasource: datasourceName,
|
||||
queries: [query],
|
||||
})}`
|
||||
`/explore?left=${encodeURIComponent(
|
||||
serializeStateToUrlParam({
|
||||
range: range.raw,
|
||||
datasource: datasourceName,
|
||||
queries: [query],
|
||||
})
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -45,7 +45,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "5.9.0",
|
||||
"@grafana/e2e-selectors": "8.1.0-pre",
|
||||
"@grafana/e2e-selectors": "8.1.0-beta.2",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"blink-diff": "1.0.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,9 +22,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/e2e-selectors": "8.1.0-pre",
|
||||
"@grafana/ui": "8.1.0-pre",
|
||||
"@grafana/data": "8.1.0-beta.2",
|
||||
"@grafana/e2e-selectors": "8.1.0-beta.2",
|
||||
"@grafana/ui": "8.1.0-beta.2",
|
||||
"history": "4.10.1",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -28,10 +28,10 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "7.13.14",
|
||||
"@babel/preset-env": "7.13.12",
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/data": "8.1.0-beta.2",
|
||||
"@grafana/eslint-config": "2.5.0",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@grafana/ui": "8.1.0-pre",
|
||||
"@grafana/ui": "8.1.0-beta.2",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/expect-puppeteer": "3.3.1",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
|
||||
@@ -18,6 +18,7 @@ module.exports = {
|
||||
backgrounds: false,
|
||||
},
|
||||
},
|
||||
'@storybook/addon-a11y',
|
||||
'@storybook/addon-knobs',
|
||||
'@storybook/addon-storysource',
|
||||
'storybook-dark-mode',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -29,8 +29,8 @@
|
||||
"@emotion/css": "11.1.3",
|
||||
"@emotion/react": "11.1.5",
|
||||
"@grafana/aws-sdk": "0.0.3",
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/e2e-selectors": "8.1.0-pre",
|
||||
"@grafana/data": "8.1.0-beta.2",
|
||||
"@grafana/e2e-selectors": "8.1.0-beta.2",
|
||||
"@grafana/slate-react": "0.22.10-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@monaco-editor/react": "4.1.1",
|
||||
@@ -79,11 +79,12 @@
|
||||
"@rollup/plugin-commonjs": "16.0.0",
|
||||
"@rollup/plugin-image": "2.0.5",
|
||||
"@rollup/plugin-node-resolve": "10.0.0",
|
||||
"@storybook/addon-essentials": "6.3.0",
|
||||
"@storybook/addon-a11y": "6.3.5",
|
||||
"@storybook/addon-essentials": "6.3.5",
|
||||
"@storybook/addon-knobs": "6.3.0",
|
||||
"@storybook/addon-storysource": "6.3.0",
|
||||
"@storybook/react": "6.3.0",
|
||||
"@storybook/theming": "6.3.0",
|
||||
"@storybook/addon-storysource": "6.3.5",
|
||||
"@storybook/react": "6.3.5",
|
||||
"@storybook/theming": "6.3.5",
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@types/classnames": "2.2.7",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
|
||||
@@ -56,7 +56,14 @@ const HttpAccessHelp = () => (
|
||||
);
|
||||
|
||||
export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
const { defaultUrl, dataSourceConfig, onChange, showAccessOptions, sigV4AuthToggleEnabled } = props;
|
||||
const {
|
||||
defaultUrl,
|
||||
dataSourceConfig,
|
||||
onChange,
|
||||
showAccessOptions,
|
||||
sigV4AuthToggleEnabled,
|
||||
azureAuthSettings,
|
||||
} = props;
|
||||
let urlTooltip;
|
||||
const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false);
|
||||
const theme = useTheme();
|
||||
@@ -207,6 +214,22 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{azureAuthSettings?.azureAuthEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
label="Azure Authentication"
|
||||
labelClass="width-13"
|
||||
checked={dataSourceConfig.jsonData.azureAuth || false}
|
||||
onChange={(event) => {
|
||||
onSettingsChange({
|
||||
jsonData: { ...dataSourceConfig.jsonData, azureAuth: event!.currentTarget.checked },
|
||||
});
|
||||
}}
|
||||
tooltip="Use Azure authentication for Azure endpoint."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sigV4AuthToggleEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
@@ -238,6 +261,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{azureAuthSettings?.azureAuthEnabled &&
|
||||
azureAuthSettings?.azureSettingsUI &&
|
||||
dataSourceConfig.jsonData.azureAuth && (
|
||||
<azureAuthSettings.azureSettingsUI dataSourceConfig={dataSourceConfig} onChange={onChange} />
|
||||
)}
|
||||
|
||||
{dataSourceConfig.jsonData.sigV4Auth && <SigV4AuthSettings {...props} />}
|
||||
|
||||
{(dataSourceConfig.jsonData.tlsAuth || dataSourceConfig.jsonData.tlsAuthWithCACert) && (
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
|
||||
export interface AzureAuthSettings {
|
||||
azureAuthEnabled: boolean;
|
||||
azureSettingsUI?: React.ComponentType<HttpSettingsBaseProps>;
|
||||
}
|
||||
|
||||
export interface HttpSettingsBaseProps {
|
||||
/** The configuration object of the data source */
|
||||
dataSourceConfig: DataSourceSettings<any, any>;
|
||||
@@ -14,4 +20,6 @@ export interface HttpSettingsProps extends HttpSettingsBaseProps {
|
||||
showAccessOptions?: boolean;
|
||||
/** Show the SigV4 auth toggle option */
|
||||
sigV4AuthToggleEnabled?: boolean;
|
||||
/** Azure authentication settings **/
|
||||
azureAuthSettings?: AzureAuthSettings;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTheme } from '../../themes';
|
||||
import mdx from './Icon.mdx';
|
||||
|
||||
export default {
|
||||
title: 'General/Icon',
|
||||
title: 'Docs overview/Icon',
|
||||
component: Icon,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
|
||||
@@ -26,17 +26,32 @@ export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
/** Variant to change the color of the Icon */
|
||||
variant?: IconButtonVariant;
|
||||
/** Text avilable ony for screenscreen readers. Will use tooltip text as fallback. */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
type SurfaceType = 'dashboard' | 'panel' | 'header';
|
||||
|
||||
export const IconButton = React.forwardRef<HTMLButtonElement, Props>(
|
||||
({ name, size = 'md', iconType, tooltip, tooltipPlacement, className, variant = 'secondary', ...restProps }, ref) => {
|
||||
(
|
||||
{
|
||||
name,
|
||||
size = 'md',
|
||||
iconType,
|
||||
tooltip,
|
||||
tooltipPlacement,
|
||||
ariaLabel,
|
||||
className,
|
||||
variant = 'secondary',
|
||||
...restProps
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, size, variant);
|
||||
|
||||
const button = (
|
||||
<button ref={ref} {...restProps} className={cx(styles.button, className)}>
|
||||
<button ref={ref} aria-label={ariaLabel || tooltip || ''} {...restProps} className={cx(styles.button, className)}>
|
||||
<Icon name={name} size={size} className={styles.icon} type={iconType} />
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -93,7 +93,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.setCursor({
|
||||
show: true,
|
||||
show: false,
|
||||
x: false, // no crosshairs
|
||||
y: false,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -16,8 +16,8 @@
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.1.3",
|
||||
"@emotion/react": "11.1.5",
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/ui": "8.1.0-pre",
|
||||
"@grafana/data": "8.1.0-beta.2",
|
||||
"@grafana/ui": "8.1.0-beta.2",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
|
||||
@@ -96,7 +96,7 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { //
|
||||
return ToFolderErrorResponse(err)
|
||||
}
|
||||
|
||||
f, err := s.DeleteFolder(c.Params(":uid"))
|
||||
f, err := s.DeleteFolder(c.Params(":uid"), c.QueryBool("forceDeleteRules"))
|
||||
if err != nil {
|
||||
return ToFolderErrorResponse(err)
|
||||
}
|
||||
@@ -149,7 +149,8 @@ func ToFolderErrorResponse(err error) response.Response {
|
||||
if errors.Is(err, models.ErrFolderTitleEmpty) ||
|
||||
errors.Is(err, models.ErrDashboardTypeMismatch) ||
|
||||
errors.Is(err, models.ErrDashboardInvalidUid) ||
|
||||
errors.Is(err, models.ErrDashboardUidTooLong) {
|
||||
errors.Is(err, models.ErrDashboardUidTooLong) ||
|
||||
errors.Is(err, models.ErrFolderContainsAlertRules) {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *models.UpdateF
|
||||
return s.UpdateFolderError
|
||||
}
|
||||
|
||||
func (s *fakeFolderService) DeleteFolder(uid string) (*models.Folder, error) {
|
||||
func (s *fakeFolderService) DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error) {
|
||||
s.DeletedFolderUids = append(s.DeletedFolderUids, uid)
|
||||
return s.DeleteFolderResult, s.DeleteFolderError
|
||||
}
|
||||
|
||||
113
pkg/infra/httpclient/httpclientprovider/azure_middleware.go
Normal file
113
pkg/infra/httpclient/httpclientprovider/azure_middleware.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package httpclientprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
|
||||
)
|
||||
|
||||
const azureMiddlewareName = "AzureAuthentication.Provider"
|
||||
|
||||
func AzureMiddleware(cfg *setting.Cfg) httpclient.Middleware {
|
||||
return httpclient.NamedMiddlewareFunc(azureMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
|
||||
if enabled, err := isAzureAuthenticationEnabled(opts.CustomOptions); err != nil {
|
||||
return errorResponse(err)
|
||||
} else if !enabled {
|
||||
return next
|
||||
}
|
||||
|
||||
credentials, err := getAzureCredentials(opts.CustomOptions)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
} else if credentials == nil {
|
||||
credentials = getDefaultAzureCredentials(cfg)
|
||||
}
|
||||
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
scopes, err := getAzureEndpointScopes(opts.CustomOptions)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
return aztokenprovider.ApplyAuth(tokenProvider, scopes, next)
|
||||
})
|
||||
}
|
||||
|
||||
func errorResponse(err error) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return nil, fmt.Errorf("invalid Azure configuration: %s", err)
|
||||
})
|
||||
}
|
||||
|
||||
func isAzureAuthenticationEnabled(customOptions map[string]interface{}) (bool, error) {
|
||||
if untypedValue, ok := customOptions["_azureAuth"]; !ok {
|
||||
return false, nil
|
||||
} else if value, ok := untypedValue.(bool); !ok {
|
||||
err := fmt.Errorf("the field 'azureAuth' should be a bool")
|
||||
return false, err
|
||||
} else {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAzureCredentials(customOptions map[string]interface{}) (azcredentials.AzureCredentials, error) {
|
||||
if untypedValue, ok := customOptions["_azureCredentials"]; !ok {
|
||||
return nil, nil
|
||||
} else if value, ok := untypedValue.(azcredentials.AzureCredentials); !ok {
|
||||
err := fmt.Errorf("the field 'azureCredentials' should be a valid credentials object")
|
||||
return nil, err
|
||||
} else {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultAzureCredentials(cfg *setting.Cfg) azcredentials.AzureCredentials {
|
||||
if cfg.Azure.ManagedIdentityEnabled {
|
||||
return &azcredentials.AzureManagedIdentityCredentials{}
|
||||
} else {
|
||||
return &azcredentials.AzureClientSecretCredentials{
|
||||
AzureCloud: cfg.Azure.Cloud,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAzureEndpointResourceId(customOptions map[string]interface{}) (*url.URL, error) {
|
||||
var value string
|
||||
if untypedValue, ok := customOptions["azureEndpointResourceId"]; !ok {
|
||||
err := fmt.Errorf("the field 'azureEndpointResourceId' should be set")
|
||||
return nil, err
|
||||
} else if value, ok = untypedValue.(string); !ok {
|
||||
err := fmt.Errorf("the field 'azureEndpointResourceId' should be a string")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceId, err := url.Parse(value)
|
||||
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
|
||||
err := fmt.Errorf("invalid endpoint Resource ID URL '%s'", value)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resourceId, nil
|
||||
}
|
||||
|
||||
func getAzureEndpointScopes(customOptions map[string]interface{}) ([]string, error) {
|
||||
resourceId, err := getAzureEndpointResourceId(customOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceId.Path = path.Join(resourceId.Path, ".default")
|
||||
scopes := []string{resourceId.String()}
|
||||
|
||||
return scopes, nil
|
||||
}
|
||||
@@ -34,6 +34,10 @@ func New(cfg *setting.Cfg) httpclient.Provider {
|
||||
|
||||
setDefaultTimeoutOptions(cfg)
|
||||
|
||||
if cfg.FeatureToggles["httpclientprovider_azure_auth"] {
|
||||
middlewares = append(middlewares, AzureMiddleware(cfg))
|
||||
}
|
||||
|
||||
return newProviderFunc(sdkhttpclient.ProviderOptions{
|
||||
Middlewares: middlewares,
|
||||
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -349,6 +350,8 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
t.Run("auth_proxy", func(t *testing.T) {
|
||||
const userID int64 = 33
|
||||
const orgID int64 = 4
|
||||
const defaultOrgId int64 = 1
|
||||
const orgRole = "Admin"
|
||||
|
||||
configure := func(cfg *setting.Cfg) {
|
||||
cfg.AuthProxyEnabled = true
|
||||
@@ -356,7 +359,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
cfg.LDAPEnabled = true
|
||||
cfg.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||
cfg.AuthProxyHeaderProperty = "username"
|
||||
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"}
|
||||
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS", "Role": "X-WEBAUTH-ROLE"}
|
||||
}
|
||||
|
||||
const hdrName = "markelog"
|
||||
@@ -432,6 +435,71 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
cfg.AuthProxyAutoSignUp = true
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Should assign role from header to default org", func(t *testing.T, sc *scenarioContext) {
|
||||
var storedRoleInfo map[int64]models.RoleType = nil
|
||||
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetSignedInUserQuery) error {
|
||||
if query.UserId > 0 {
|
||||
query.Result = &models.SignedInUser{OrgId: defaultOrgId, UserId: userID, OrgRole: storedRoleInfo[defaultOrgId]}
|
||||
return nil
|
||||
}
|
||||
return models.ErrUserNotFound
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
|
||||
cmd.Result = &models.User{Id: userID}
|
||||
storedRoleInfo = cmd.ExternalUser.OrgRoles
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReq("GET", "/")
|
||||
sc.req.Header.Set(sc.cfg.AuthProxyHeaderName, hdrName)
|
||||
sc.req.Header.Set("X-WEBAUTH-ROLE", orgRole)
|
||||
sc.exec()
|
||||
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, userID, sc.context.UserId)
|
||||
assert.Equal(t, defaultOrgId, sc.context.OrgId)
|
||||
assert.Equal(t, orgRole, string(sc.context.OrgRole))
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
cfg.LDAPEnabled = false
|
||||
cfg.AuthProxyAutoSignUp = true
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Should NOT assign role from header to non-default org", func(t *testing.T, sc *scenarioContext) {
|
||||
var storedRoleInfo map[int64]models.RoleType = nil
|
||||
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetSignedInUserQuery) error {
|
||||
if query.UserId > 0 {
|
||||
query.Result = &models.SignedInUser{OrgId: orgID, UserId: userID, OrgRole: storedRoleInfo[orgID]}
|
||||
return nil
|
||||
}
|
||||
return models.ErrUserNotFound
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
|
||||
cmd.Result = &models.User{Id: userID}
|
||||
storedRoleInfo = cmd.ExternalUser.OrgRoles
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReq("GET", "/")
|
||||
sc.req.Header.Set(sc.cfg.AuthProxyHeaderName, hdrName)
|
||||
sc.req.Header.Set("X-WEBAUTH-ROLE", "Admin")
|
||||
sc.req.Header.Set("X-Grafana-Org-Id", strconv.FormatInt(orgID, 10))
|
||||
sc.exec()
|
||||
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, userID, sc.context.UserId)
|
||||
assert.Equal(t, orgID, sc.context.OrgId)
|
||||
|
||||
// For non-default org, the user role should be empty
|
||||
assert.Equal(t, "", string(sc.context.OrgRole))
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
cfg.LDAPEnabled = false
|
||||
cfg.AuthProxyAutoSignUp = true
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Should get an existing user from header", func(t *testing.T, sc *scenarioContext) {
|
||||
const userID int64 = 12
|
||||
const orgID int64 = 2
|
||||
|
||||
@@ -363,8 +363,9 @@ type DashboardProvisioning struct {
|
||||
}
|
||||
|
||||
type DeleteDashboardCommand struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
Id int64
|
||||
OrgId int64
|
||||
ForceDeleteFolderRules bool
|
||||
}
|
||||
|
||||
type DeleteOrphanedProvisionedDashboardsCommand struct {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
)
|
||||
|
||||
func (ds *DataSource) getTimeout() time.Duration {
|
||||
@@ -66,10 +67,14 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
|
||||
return t.roundTripper, nil
|
||||
}
|
||||
|
||||
opts := ds.HTTPClientOptions()
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts.Middlewares = customMiddlewares
|
||||
|
||||
rt, err := provider.GetTransport(opts)
|
||||
rt, err := provider.GetTransport(*opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,7 +87,7 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
|
||||
func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
|
||||
tlsOptions := ds.TLSOptions()
|
||||
timeouts := &sdkhttpclient.TimeoutOptions{
|
||||
Timeout: ds.getTimeout(),
|
||||
@@ -95,7 +100,7 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
|
||||
MaxIdleConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConnsPerHost,
|
||||
IdleConnTimeout: sdkhttpclient.DefaultTimeoutOptions.IdleConnTimeout,
|
||||
}
|
||||
opts := sdkhttpclient.Options{
|
||||
opts := &sdkhttpclient.Options{
|
||||
Timeouts: timeouts,
|
||||
Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()),
|
||||
Labels: map[string]string{
|
||||
@@ -121,6 +126,19 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
|
||||
}
|
||||
}
|
||||
|
||||
if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() {
|
||||
credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), ds.DecryptedValues())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid Azure credentials: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts.CustomOptions["_azureAuth"] = true
|
||||
if credentials != nil {
|
||||
opts.CustomOptions["_azureCredentials"] = credentials
|
||||
}
|
||||
}
|
||||
|
||||
if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool(false) {
|
||||
opts.SigV4 = &sdkhttpclient.SigV4Config{
|
||||
Service: awsServiceNamespace(ds.Type),
|
||||
@@ -140,7 +158,7 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
|
||||
@@ -180,7 +198,11 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
|
||||
}
|
||||
|
||||
func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) {
|
||||
return httpClientProvider.GetTLSConfig(ds.HTTPClientOptions())
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return httpClientProvider.GetTLSConfig(*opts)
|
||||
}
|
||||
|
||||
// getCustomHeaders returns a map with all the to be set headers
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -394,6 +395,109 @@ func TestDataSource_DecryptedValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDataSource_HTTPClientOptions(t *testing.T) {
|
||||
emptyJsonData := simplejson.New()
|
||||
emptySecureJsonData := map[string][]byte{}
|
||||
|
||||
ds := DataSource{
|
||||
Id: 1,
|
||||
Url: "https://api.example.com",
|
||||
Type: "prometheus",
|
||||
}
|
||||
|
||||
t.Run("Azure authentication", func(t *testing.T) {
|
||||
t.Run("should be disabled if not enabled in JsonData", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
|
||||
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
|
||||
})
|
||||
|
||||
t.Run("should be enabled if enabled in JsonData without credentials configured", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
|
||||
"azureAuth": true,
|
||||
})
|
||||
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
|
||||
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
|
||||
})
|
||||
|
||||
t.Run("should be enabled if enabled in JsonData with credentials configured", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
|
||||
"azureAuth": true,
|
||||
"azureCredentials": map[string]interface{}{
|
||||
"authType": "msi",
|
||||
},
|
||||
})
|
||||
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
|
||||
|
||||
require.Contains(t, opts.CustomOptions, "_azureCredentials")
|
||||
credentials := opts.CustomOptions["_azureCredentials"]
|
||||
|
||||
assert.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
|
||||
})
|
||||
|
||||
t.Run("should be disabled if disabled in JsonData even with credentials configured", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
|
||||
"azureAuth": false,
|
||||
"azureCredentials": map[string]interface{}{
|
||||
"authType": "msi",
|
||||
},
|
||||
})
|
||||
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
|
||||
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
|
||||
})
|
||||
|
||||
t.Run("should fail if credentials are invalid", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
|
||||
"azureAuth": true,
|
||||
"azureCredentials": "invalid",
|
||||
})
|
||||
|
||||
_, err := ds.HTTPClientOptions()
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should pass resourceId from JsonData", func(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
|
||||
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
|
||||
})
|
||||
|
||||
opts, err := ds.HTTPClientOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, opts.CustomOptions, "azureEndpointResourceId")
|
||||
azureEndpointResourceId := opts.CustomOptions["azureEndpointResourceId"]
|
||||
|
||||
assert.Equal(t, "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", azureEndpointResourceId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func clearDSProxyCache(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ var (
|
||||
ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists")
|
||||
ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID")
|
||||
ErrFolderAccessDenied = errors.New("access denied to folder")
|
||||
ErrFolderContainsAlertRules = errors.New("folder contains alert rules")
|
||||
)
|
||||
|
||||
type Folder struct {
|
||||
|
||||
@@ -45,7 +45,7 @@ var isLDAPEnabled = func(cfg *setting.Cfg) bool {
|
||||
var newLDAP = multildap.New
|
||||
|
||||
// supportedHeaders states the supported headers configuration fields
|
||||
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups"}
|
||||
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups", "Role"}
|
||||
|
||||
// AuthProxy struct
|
||||
type AuthProxy struct {
|
||||
@@ -152,7 +152,7 @@ func HashCacheKey(key string) (string, error) {
|
||||
|
||||
// getKey forms a key for the cache based on the headers received as part of the authentication flow.
|
||||
// Our configuration supports multiple headers. The main header contains the email or username.
|
||||
// And the additional ones that allow us to specify extra attributes: Name, Email or Groups.
|
||||
// And the additional ones that allow us to specify extra attributes: Name, Email, Role, or Groups.
|
||||
func (auth *AuthProxy) getKey() (string, error) {
|
||||
key := strings.TrimSpace(auth.header) // start the key with the main header
|
||||
|
||||
@@ -278,9 +278,23 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) {
|
||||
}
|
||||
|
||||
auth.headersIterator(func(field string, header string) {
|
||||
if field == "Groups" {
|
||||
switch field {
|
||||
case "Groups":
|
||||
extUser.Groups = util.SplitString(header)
|
||||
} else {
|
||||
case "Role":
|
||||
// If Role header is specified, we update the user role of the default org
|
||||
if header != "" {
|
||||
rt := models.RoleType(header)
|
||||
if rt.IsValid() {
|
||||
extUser.OrgRoles = map[int64]models.RoleType{}
|
||||
orgID := int64(1)
|
||||
if setting.AutoAssignOrg && setting.AutoAssignOrgId > 0 {
|
||||
orgID = int64(setting.AutoAssignOrgId)
|
||||
}
|
||||
extUser.OrgRoles[orgID] = rt
|
||||
}
|
||||
}
|
||||
default:
|
||||
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(header)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -107,8 +107,9 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
t.Run("When the cache key contains additional headers", func(t *testing.T) {
|
||||
const id int64 = 33
|
||||
const group = "grafana-core-team"
|
||||
const role = "Admin"
|
||||
|
||||
h, err := HashCacheKey(hdrName + "-" + group)
|
||||
h, err := HashCacheKey(hdrName + "-" + group + "-" + role)
|
||||
require.NoError(t, err)
|
||||
key := fmt.Sprintf(CachePrefix, h)
|
||||
err = cache.Set(key, id, 0)
|
||||
@@ -116,9 +117,10 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
|
||||
auth := prepareMiddleware(t, cache, func(req *http.Request, cfg *setting.Cfg) {
|
||||
req.Header.Set("X-WEBAUTH-GROUPS", group)
|
||||
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"}
|
||||
req.Header.Set("X-WEBAUTH-ROLE", role)
|
||||
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS", "Role": "X-WEBAUTH-ROLE"}
|
||||
})
|
||||
assert.Equal(t, "auth-proxy-sync-ttl:14f69b7023baa0ac98c96b31cec07bc0", key)
|
||||
assert.Equal(t, "auth-proxy-sync-ttl:f5acfffd56daac98d502ef8c8b8c5d56", key)
|
||||
|
||||
gotID, err := auth.Login(logger, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -19,7 +19,7 @@ type FolderService interface {
|
||||
GetFolderByTitle(title string) (*models.Folder, error)
|
||||
CreateFolder(title, uid string) (*models.Folder, error)
|
||||
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
|
||||
DeleteFolder(uid string) (*models.Folder, error)
|
||||
DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error)
|
||||
MakeUserAdmin(orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.Upd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) DeleteFolder(uid string) (*models.Folder, error) {
|
||||
func (dr *dashboardServiceImpl) DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error) {
|
||||
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
|
||||
dashFolder, err := getFolder(query)
|
||||
if err != nil {
|
||||
@@ -207,7 +207,7 @@ func (dr *dashboardServiceImpl) DeleteFolder(uid string) (*models.Folder, error)
|
||||
return nil, models.ErrFolderAccessDenied
|
||||
}
|
||||
|
||||
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id}
|
||||
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
|
||||
if err := bus.Dispatch(&deleteCmd); err != nil {
|
||||
return nil, toFolderError(err)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestFolderService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) {
|
||||
_, err := service.DeleteFolder("uid")
|
||||
_, err := service.DeleteFolder("uid", false)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, err, models.ErrFolderAccessDenied)
|
||||
})
|
||||
@@ -121,7 +121,7 @@ func TestFolderService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When deleting folder by uid should not return access denied error", func(t *testing.T) {
|
||||
_, err := service.DeleteFolder("uid")
|
||||
_, err := service.DeleteFolder("uid", false)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func (c *cache) getOrCreate(alertRule *ngModels.AlertRule, result eval.Result) *
|
||||
// clone the labels so we don't change eval.Result
|
||||
labels := result.Instance.Copy()
|
||||
attachRuleLabels(labels, alertRule)
|
||||
ruleLabels, annotations := c.expandRuleLabelsAndAnnotations(alertRule, labels, result.Values)
|
||||
ruleLabels, annotations := c.expandRuleLabelsAndAnnotations(alertRule, labels, result)
|
||||
|
||||
// if duplicate labels exist, alertRule label will take precedence
|
||||
lbs := mergeLabels(ruleLabels, result.Instance)
|
||||
@@ -88,11 +88,11 @@ func attachRuleLabels(m map[string]string, alertRule *ngModels.AlertRule) {
|
||||
m[prometheusModel.AlertNameLabel] = alertRule.Title
|
||||
}
|
||||
|
||||
func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, labels map[string]string, values map[string]eval.NumberValueCapture) (map[string]string, map[string]string) {
|
||||
func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, labels map[string]string, alertInstance eval.Result) (map[string]string, map[string]string) {
|
||||
expand := func(original map[string]string) map[string]string {
|
||||
expanded := make(map[string]string, len(original))
|
||||
for k, v := range original {
|
||||
ev, err := expandTemplate(alertRule.Title, v, labels, values)
|
||||
ev, err := expandTemplate(alertRule.Title, v, labels, alertInstance)
|
||||
expanded[k] = ev
|
||||
if err != nil {
|
||||
c.log.Error("error in expanding template", "name", k, "value", v, "err", err.Error())
|
||||
@@ -122,9 +122,9 @@ func (v templateCaptureValue) String() string {
|
||||
return "null"
|
||||
}
|
||||
|
||||
func expandTemplate(name, text string, labels map[string]string, values map[string]eval.NumberValueCapture) (result string, resultErr error) {
|
||||
func expandTemplate(name, text string, labels map[string]string, alertInstance eval.Result) (result string, resultErr error) {
|
||||
name = "__alert_" + name
|
||||
text = "{{- $labels := .Labels -}}{{- $values := .Values -}}" + text
|
||||
text = "{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}" + text
|
||||
// It'd better to have no alert description than to kill the whole process
|
||||
// if there's a bug in the template.
|
||||
defer func() {
|
||||
@@ -145,11 +145,12 @@ func expandTemplate(name, text string, labels map[string]string, values map[stri
|
||||
if err := tmpl.Execute(&buffer, struct {
|
||||
Labels map[string]string
|
||||
Values map[string]templateCaptureValue
|
||||
Value string
|
||||
}{
|
||||
Labels: labels,
|
||||
Values: func() map[string]templateCaptureValue {
|
||||
m := make(map[string]templateCaptureValue)
|
||||
for k, v := range values {
|
||||
for k, v := range alertInstance.Values {
|
||||
m[k] = templateCaptureValue{
|
||||
Labels: v.Labels,
|
||||
Value: v.Value,
|
||||
@@ -157,6 +158,7 @@ func expandTemplate(name, text string, labels map[string]string, values map[stri
|
||||
}
|
||||
return m
|
||||
}(),
|
||||
Value: alertInstance.EvaluationString,
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("error executing template %v: %s", name, err.Error())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptr "github.com/xorcare/pointer"
|
||||
)
|
||||
|
||||
@@ -32,3 +36,65 @@ func TestTemplateCaptureValueStringer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandTemplate(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
text string
|
||||
alertInstance eval.Result
|
||||
labels data.Labels
|
||||
expected string
|
||||
expectedError error
|
||||
}{{
|
||||
name: "instance labels are expanded into $labels",
|
||||
text: "{{ $labels.instance }} is down",
|
||||
labels: data.Labels{"instance": "foo"},
|
||||
expected: "foo is down",
|
||||
}, {
|
||||
name: "missing instance label returns error",
|
||||
text: "{{ $labels.instance }} is down",
|
||||
labels: data.Labels{},
|
||||
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$labels.instance>: map has no entry for key \"instance\""),
|
||||
}, {
|
||||
name: "values are expanded into $values",
|
||||
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
|
||||
alertInstance: eval.Result{
|
||||
Values: map[string]eval.NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"instance": "foo"},
|
||||
Value: ptr.Float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "foo has value 10",
|
||||
}, {
|
||||
name: "missing label in $values returns error",
|
||||
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
|
||||
alertInstance: eval.Result{
|
||||
Values: map[string]eval.NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{},
|
||||
Value: ptr.Float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$values.A.Labels.instance>: map has no entry for key \"instance\""),
|
||||
}, {
|
||||
name: "value string is expanded into $value",
|
||||
text: "{{ $value }}",
|
||||
alertInstance: eval.Result{
|
||||
EvaluationString: "[ var='A' labels={instance=foo} value=10 ]",
|
||||
},
|
||||
expected: "[ var='A' labels={instance=foo} value=10 ]",
|
||||
}}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
v, err := expandTemplate("test", c.text, c.labels, c.alertInstance)
|
||||
require.Equal(t, c.expectedError, err)
|
||||
require.Equal(t, c.expected, v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,16 +476,27 @@ func deleteDashboard(cmd *models.DeleteDashboardCommand, sess *DBSession) error
|
||||
}
|
||||
}
|
||||
|
||||
// clean ngalert tables
|
||||
ngalertDeletes := []string{
|
||||
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
|
||||
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
|
||||
var existingRuleID int64
|
||||
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", dashboard.Id).Cols("id").Get(&existingRuleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
if !cmd.ForceDeleteFolderRules {
|
||||
return fmt.Errorf("folder cannot be deleted: %w", models.ErrFolderContainsAlertRules)
|
||||
}
|
||||
|
||||
for _, sql := range ngalertDeletes {
|
||||
_, err := sess.Exec(sql, dashboard.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
// Delete all rules under this folder.
|
||||
deleteNGAlertsByFolder := []string{
|
||||
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
|
||||
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
|
||||
}
|
||||
|
||||
for _, sql := range deleteNGAlertsByFolder {
|
||||
_, err := sess.Exec(sql, dashboard.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -29,6 +31,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
savedDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard(t, sqlStore, "test dash 45", 1, savedFolder.Id, false, "prod")
|
||||
savedDash2 := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
|
||||
insertTestRule(t, sqlStore, savedFolder.OrgId, savedFolder.Uid)
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedDash.Title, ShouldEqual, "test dash 23")
|
||||
@@ -204,8 +207,14 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete a dashboard folder and its children", func() {
|
||||
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id}
|
||||
Convey("Should be not able to delete a dashboard if force delete rules is disabled", func() {
|
||||
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false}
|
||||
err := DeleteDashboard(deleteCmd)
|
||||
So(errors.Is(err, models.ErrFolderContainsAlertRules), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func() {
|
||||
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: true}
|
||||
err := DeleteDashboard(deleteCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -219,6 +228,20 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
|
||||
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
|
||||
var existingRuleID int64
|
||||
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", savedFolder.Id).Cols("id").Get(&existingRuleID)
|
||||
require.NoError(t, err)
|
||||
So(exists, ShouldBeFalse)
|
||||
|
||||
var existingRuleVersionID int64
|
||||
exists, err = sess.Table("alert_rule_version").Where("rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", savedFolder.Id).Cols("id").Get(&existingRuleVersionID)
|
||||
require.NoError(t, err)
|
||||
So(exists, ShouldBeFalse)
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should return error if no dashboard is found for update when dashboard id is greater than zero", func() {
|
||||
@@ -460,6 +483,84 @@ func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId i
|
||||
return dash
|
||||
}
|
||||
|
||||
func insertTestRule(t *testing.T, sqlStore *SQLStore, foderOrgID int64, folderUID string) {
|
||||
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
|
||||
|
||||
type alertQuery struct {
|
||||
RefID string
|
||||
DatasourceUID string
|
||||
Model json.RawMessage
|
||||
}
|
||||
|
||||
type alertRule struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
Title string
|
||||
Updated time.Time
|
||||
UID string `xorm:"uid"`
|
||||
NamespaceUID string `xorm:"namespace_uid"`
|
||||
RuleGroup string
|
||||
Condition string
|
||||
Data []alertQuery
|
||||
}
|
||||
|
||||
rule := alertRule{
|
||||
OrgID: foderOrgID,
|
||||
NamespaceUID: folderUID,
|
||||
UID: "rule",
|
||||
RuleGroup: "rulegroup",
|
||||
Updated: time.Now(),
|
||||
Condition: "A",
|
||||
Data: []alertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := sess.Insert(&rule)
|
||||
require.NoError(t, err)
|
||||
|
||||
type alertRuleVersion struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
RuleOrgID int64 `xorm:"rule_org_id"`
|
||||
RuleUID string `xorm:"rule_uid"`
|
||||
RuleNamespaceUID string `xorm:"rule_namespace_uid"`
|
||||
RuleGroup string
|
||||
ParentVersion int64
|
||||
RestoredFrom int64
|
||||
Version int64
|
||||
Created time.Time
|
||||
Title string
|
||||
Condition string
|
||||
Data []alertQuery
|
||||
IntervalSeconds int64
|
||||
}
|
||||
|
||||
ruleVersion := alertRuleVersion{
|
||||
RuleOrgID: rule.OrgID,
|
||||
RuleUID: rule.UID,
|
||||
RuleNamespaceUID: rule.NamespaceUID,
|
||||
RuleGroup: rule.RuleGroup,
|
||||
Created: rule.Updated,
|
||||
Condition: rule.Condition,
|
||||
Data: rule.Data,
|
||||
ParentVersion: 0,
|
||||
RestoredFrom: 0,
|
||||
Version: 1,
|
||||
IntervalSeconds: 60,
|
||||
}
|
||||
_, err = sess.Insert(&ruleVersion)
|
||||
require.NoError(t, err)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func insertTestDashboardForPlugin(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
|
||||
folderId int64, isFolder bool, pluginId string) *models.Dashboard {
|
||||
t.Helper()
|
||||
|
||||
@@ -54,6 +54,16 @@ func TestLoadingSettings(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
Convey("sample.ini should load successfully", func() {
|
||||
customInitPath := CustomInitPath
|
||||
CustomInitPath = "conf/sample.ini"
|
||||
cfg := NewCfg()
|
||||
err := cfg.Load(&CommandLineArgs{HomePath: "../../"})
|
||||
So(err, ShouldBeNil)
|
||||
// Restore CustomInitPath to avoid side effects.
|
||||
CustomInitPath = customInitPath
|
||||
})
|
||||
|
||||
Convey("Should be able to override via environment variables", func() {
|
||||
err := os.Setenv("GF_SECURITY_ADMIN_USER", "superduper")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -748,7 +748,7 @@ func TestDeleteFolderWithRules(t *testing.T) {
|
||||
assert.JSONEq(t, expectedGetRulesResponseBody, string(b))
|
||||
}
|
||||
|
||||
// Next, the editor can delete the folder.
|
||||
// Next, the editor can not delete the folder because it contains Grafana 8 alerts.
|
||||
{
|
||||
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s", grafanaListedAddr, namespaceUID)
|
||||
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||
@@ -762,6 +762,24 @@ func TestDeleteFolderWithRules(t *testing.T) {
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
require.JSONEq(t, `{"message":"folder cannot be deleted: folder contains alert rules"}`, string(b))
|
||||
}
|
||||
|
||||
// Next, the editor can delete the folder if forceDeleteRules is true.
|
||||
{
|
||||
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s?forceDeleteRules=true", grafanaListedAddr, namespaceUID)
|
||||
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||
require.NoError(t, err)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
require.JSONEq(t, `{"id":1,"message":"Folder default deleted","title":"default"}`, string(b))
|
||||
}
|
||||
|
||||
83
pkg/tsdb/azuremonitor/azcredentials/builder.go
Normal file
83
pkg/tsdb/azuremonitor/azcredentials/builder.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package azcredentials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func FromDatasourceData(data map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
|
||||
if credentialsObj, err := getMapOptional(data, "azureCredentials"); err != nil {
|
||||
return nil, err
|
||||
} else if credentialsObj == nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
return getFromCredentialsObject(credentialsObj, secureData)
|
||||
}
|
||||
}
|
||||
|
||||
func getFromCredentialsObject(credentialsObj map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
|
||||
authType, err := getStringValue(credentialsObj, "authType")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch authType {
|
||||
case AzureAuthManagedIdentity:
|
||||
credentials := &AzureManagedIdentityCredentials{}
|
||||
return credentials, nil
|
||||
|
||||
case AzureAuthClientSecret:
|
||||
cloud, err := getStringValue(credentialsObj, "azureCloud")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantId, err := getStringValue(credentialsObj, "tenantId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientId, err := getStringValue(credentialsObj, "clientId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientSecret := secureData["azureClientSecret"]
|
||||
|
||||
credentials := &AzureClientSecretCredentials{
|
||||
AzureCloud: cloud,
|
||||
TenantId: tenantId,
|
||||
ClientId: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
}
|
||||
return credentials, nil
|
||||
|
||||
default:
|
||||
err := fmt.Errorf("the authentication type '%s' not supported", authType)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func getMapOptional(obj map[string]interface{}, key string) (map[string]interface{}, error) {
|
||||
if untypedValue, ok := obj[key]; ok {
|
||||
if value, ok := untypedValue.(map[string]interface{}); ok {
|
||||
return value, nil
|
||||
} else {
|
||||
err := fmt.Errorf("the field '%s' should be an object", key)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Value optional, not error
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getStringValue(obj map[string]interface{}, key string) (string, error) {
|
||||
if untypedValue, ok := obj[key]; ok {
|
||||
if value, ok := untypedValue.(string); ok {
|
||||
return value, nil
|
||||
} else {
|
||||
err := fmt.Errorf("the field '%s' should be a string", key)
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
err := fmt.Errorf("the field '%s' should be set", key)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,17 @@ const authenticationMiddlewareName = "AzureAuthentication"
|
||||
|
||||
func AuthMiddleware(tokenProvider AzureTokenProvider, scopes []string) httpclient.Middleware {
|
||||
return httpclient.NamedMiddlewareFunc(authenticationMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return next.RoundTrip(req)
|
||||
})
|
||||
return ApplyAuth(tokenProvider, scopes, next)
|
||||
})
|
||||
}
|
||||
|
||||
func ApplyAuth(tokenProvider AzureTokenProvider, scopes []string, next http.RoundTripper) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return next.RoundTrip(req)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "8.1.0-pre",
|
||||
"version": "8.1.0-beta.2",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -15,9 +15,9 @@
|
||||
},
|
||||
"author": "Grafana Labs",
|
||||
"devDependencies": {
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/toolkit": "8.1.0-pre",
|
||||
"@grafana/ui": "8.1.0-pre"
|
||||
"@grafana/data": "8.1.0-beta.2",
|
||||
"@grafana/toolkit": "8.1.0-beta.2",
|
||||
"@grafana/ui": "8.1.0-beta.2"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../../package.json"
|
||||
|
||||
@@ -59,7 +59,9 @@ describe('createSpanLinkFactory', () => {
|
||||
} as any);
|
||||
|
||||
expect(linkDef.href).toBe(
|
||||
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}`
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -91,7 +93,9 @@ describe('createSpanLinkFactory', () => {
|
||||
} as any);
|
||||
|
||||
expect(linkDef.href).toBe(
|
||||
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}`
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -126,7 +130,9 @@ describe('createSpanLinkFactory', () => {
|
||||
} as any);
|
||||
|
||||
expect(linkDef.href).toBe(
|
||||
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}`
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -163,7 +169,9 @@ describe('createSpanLinkFactory', () => {
|
||||
} as any);
|
||||
|
||||
expect(linkDef.href).toBe(
|
||||
`/explore?left={"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}`
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,9 @@ describe('getFieldLinksForExplore', () => {
|
||||
const links = getFieldLinksForExplore({ field, rowIndex: 0, splitOpenFn: splitfn, range });
|
||||
|
||||
expect(links[0].href).toBe(
|
||||
'/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}]}'
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}]}'
|
||||
)}`
|
||||
);
|
||||
expect(links[0].title).toBe('test_ds');
|
||||
|
||||
|
||||
@@ -66,10 +66,11 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
const confirmationText = `Do you want to delete this folder and all its dashboards and alerts?`;
|
||||
appEvents.publish(
|
||||
new ShowConfirmModalEvent({
|
||||
title: 'Delete',
|
||||
text: `Do you want to delete this folder and all its dashboards?`,
|
||||
text: confirmationText,
|
||||
icon: 'trash-alt',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
|
||||
@@ -31,7 +31,7 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
|
||||
|
||||
export function deleteFolder(uid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await backendSrv.delete(`/api/folders/${uid}`);
|
||||
await backendSrv.delete(`/api/folders/${uid}?forceDeleteRules=true`);
|
||||
locationService.push('/dashboards');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ export function saveDashboard(options: SaveDashboardOptions) {
|
||||
function deleteFolder(uid: string, showSuccessAlert: boolean) {
|
||||
return getBackendSrv().request({
|
||||
method: 'DELETE',
|
||||
url: `/api/folders/${uid}`,
|
||||
url: `/api/folders/${uid}?forceDeleteRules=true`,
|
||||
showSuccessAlert: showSuccessAlert === true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ export const ConfirmDeleteModal: FC<Props> = ({ results, onDeleteItems, isOpen,
|
||||
|
||||
if (folderCount > 0 && dashCount > 0) {
|
||||
text += `selected folder${folderEnding} and dashboard${dashEnding}?\n`;
|
||||
subtitle = `All dashboards of the selected folder${folderEnding} will also be deleted`;
|
||||
subtitle = `All dashboards and alerts of the selected folder${folderEnding} will also be deleted`;
|
||||
} else if (folderCount > 0) {
|
||||
text += `selected folder${folderEnding} and all its dashboards?`;
|
||||
text += `selected folder${folderEnding} and all their dashboards and alerts?`;
|
||||
} else {
|
||||
text += `selected dashboard${dashEnding}?`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
import { InlineFormLabel, Input } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { KnownAzureClouds, AzureCredentials } from './AzureCredentials';
|
||||
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
|
||||
import { AzureCredentialsForm } from './AzureCredentialsForm';
|
||||
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
|
||||
|
||||
export const AzureAuthSettings: FunctionComponent<HttpSettingsBaseProps> = (props: HttpSettingsBaseProps) => {
|
||||
const { dataSourceConfig, onChange } = props;
|
||||
|
||||
const credentials = useMemo(() => getCredentials(dataSourceConfig), [dataSourceConfig]);
|
||||
|
||||
const onCredentialsChange = (credentials: AzureCredentials): void => {
|
||||
onChange(updateCredentials(dataSourceConfig, credentials));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h6>Azure Authentication</h6>
|
||||
<AzureCredentialsForm
|
||||
managedIdentityEnabled={config.azure.managedIdentityEnabled}
|
||||
credentials={credentials}
|
||||
azureCloudOptions={KnownAzureClouds}
|
||||
onCredentialsChange={onCredentialsChange}
|
||||
/>
|
||||
<h6>Azure Configuration</h6>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">AAD resource ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
value={dataSourceConfig.jsonData.azureEndpointResourceId || ''}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...dataSourceConfig,
|
||||
jsonData: { ...dataSourceConfig.jsonData, azureEndpointResourceId: event.currentTarget.value },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AzureAuthSettings;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
export enum AzureCloud {
|
||||
Public = 'AzureCloud',
|
||||
China = 'AzureChinaCloud',
|
||||
USGovernment = 'AzureUSGovernment',
|
||||
Germany = 'AzureGermanCloud',
|
||||
None = '',
|
||||
}
|
||||
|
||||
export const KnownAzureClouds = [
|
||||
{ value: AzureCloud.Public, label: 'Azure' },
|
||||
{ value: AzureCloud.China, label: 'Azure China' },
|
||||
{ value: AzureCloud.USGovernment, label: 'Azure US Government' },
|
||||
{ value: AzureCloud.Germany, label: 'Azure Germany' },
|
||||
] as SelectableValue[];
|
||||
|
||||
export type AzureAuthType = 'msi' | 'clientsecret';
|
||||
|
||||
export type ConcealedSecret = symbol;
|
||||
|
||||
interface AzureCredentialsBase {
|
||||
authType: AzureAuthType;
|
||||
defaultSubscriptionId?: string;
|
||||
}
|
||||
|
||||
export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'msi';
|
||||
}
|
||||
|
||||
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
authType: 'clientsecret';
|
||||
azureCloud?: string;
|
||||
tenantId?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string | ConcealedSecret;
|
||||
}
|
||||
|
||||
export type AzureCredentials = AzureManagedIdentityCredentials | AzureClientSecretCredentials;
|
||||
|
||||
export function isCredentialsComplete(credentials: AzureCredentials): boolean {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
return true;
|
||||
case 'clientsecret':
|
||||
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { AzureCloud, AzureCredentials, ConcealedSecret } from './AzureCredentials';
|
||||
|
||||
const concealed: ConcealedSecret = Symbol('Concealed client secret');
|
||||
|
||||
function getDefaultAzureCloud(): string {
|
||||
return config.azure.cloud || AzureCloud.Public;
|
||||
}
|
||||
|
||||
function getSecret(options: DataSourceSettings<any, any>): undefined | string | ConcealedSecret {
|
||||
if (options.secureJsonFields.azureClientSecret) {
|
||||
// The secret is concealed on server
|
||||
return concealed;
|
||||
} else {
|
||||
const secret = options.secureJsonData?.azureClientSecret;
|
||||
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCredentials(options: DataSourceSettings<any, any>): AzureCredentials {
|
||||
const credentials = options.jsonData.azureCredentials as AzureCredentials | undefined;
|
||||
|
||||
// If no credentials saved, then return empty credentials
|
||||
// of type based on whether the managed identity enabled
|
||||
if (!credentials) {
|
||||
return {
|
||||
authType: config.azure.managedIdentityEnabled ? 'msi' : 'clientsecret',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
}
|
||||
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (config.azure.managedIdentityEnabled) {
|
||||
return {
|
||||
authType: 'msi',
|
||||
};
|
||||
} else {
|
||||
// If authentication type is managed identity but managed identities were disabled in Grafana config,
|
||||
// then we should fallback to an empty app registration (client secret) configuration
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
}
|
||||
case 'clientsecret':
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
clientSecret: getSecret(options),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCredentials(
|
||||
options: DataSourceSettings<any, any>,
|
||||
credentials: AzureCredentials
|
||||
): DataSourceSettings<any, any> {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (!config.azure.managedIdentityEnabled) {
|
||||
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureCredentials: {
|
||||
authType: 'msi',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
|
||||
case 'clientsecret':
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureCredentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
},
|
||||
},
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
azureClientSecret:
|
||||
typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0
|
||||
? credentials.clientSecret
|
||||
: undefined,
|
||||
},
|
||||
secureJsonFields: {
|
||||
...options.secureJsonFields,
|
||||
azureClientSecret: typeof credentials.clientSecret === 'symbol',
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
|
||||
|
||||
const setup = (propsFunc?: (props: Props) => Props) => {
|
||||
let props: Props = {
|
||||
managedIdentityEnabled: false,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: undefined,
|
||||
defaultSubscriptionId: '44987801-6nn6-49he-9b2d-9106972f9789',
|
||||
},
|
||||
azureCloudOptions: [
|
||||
{ value: 'azuremonitor', label: 'Azure' },
|
||||
{ value: 'govazuremonitor', label: 'Azure US Government' },
|
||||
{ value: 'germanyazuremonitor', label: 'Azure Germany' },
|
||||
{ value: 'chinaazuremonitor', label: 'Azure China' },
|
||||
],
|
||||
onCredentialsChange: jest.fn(),
|
||||
getSubscriptions: jest.fn(),
|
||||
};
|
||||
|
||||
if (propsFunc) {
|
||||
props = propsFunc(props);
|
||||
}
|
||||
|
||||
return shallow(<AzureCredentialsForm {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disable azure monitor secret input', () => {
|
||||
const wrapper = setup((props) => ({
|
||||
...props,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: Symbol(),
|
||||
},
|
||||
}));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should enable azure monitor load subscriptions button', () => {
|
||||
const wrapper = setup((props) => ({
|
||||
...props,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
|
||||
},
|
||||
}));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,279 @@
|
||||
import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineFormLabel, Button } from '@grafana/ui/src/components';
|
||||
import { Select } from '@grafana/ui/src/components/Forms/Legacy/Select/Select';
|
||||
import { Input } from '@grafana/ui/src/components/Forms/Legacy/Input/Input';
|
||||
import { AzureAuthType, AzureCredentials, isCredentialsComplete } from './AzureCredentials';
|
||||
|
||||
export interface Props {
|
||||
managedIdentityEnabled: boolean;
|
||||
credentials: AzureCredentials;
|
||||
azureCloudOptions?: SelectableValue[];
|
||||
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
|
||||
getSubscriptions?: () => Promise<SelectableValue[]>;
|
||||
}
|
||||
|
||||
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
},
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) => {
|
||||
const { credentials, azureCloudOptions, onCredentialsChange, getSubscriptions } = props;
|
||||
const hasRequiredFields = isCredentialsComplete(credentials);
|
||||
|
||||
const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
|
||||
useEffect(() => {
|
||||
if (!getSubscriptions || !hasRequiredFields) {
|
||||
updateSubscriptions([]);
|
||||
return;
|
||||
}
|
||||
let canceled = false;
|
||||
getSubscriptions().then((result) => {
|
||||
if (!canceled) {
|
||||
updateSubscriptions(result, loadSubscriptionsClicked);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
// This effect is intended to be called only once initially and on Load Subscriptions click
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [loadSubscriptionsClicked]);
|
||||
|
||||
const updateSubscriptions = (received: Array<SelectableValue<string>>, autoSelect = false) => {
|
||||
setSubscriptions(received);
|
||||
if (getSubscriptions) {
|
||||
if (autoSelect && !credentials.defaultSubscriptionId && received.length > 0) {
|
||||
// Selecting the default subscription if subscriptions received but no default subscription selected
|
||||
onSubscriptionChange(received[0]);
|
||||
} else if (credentials.defaultSubscriptionId) {
|
||||
const found = received.find((opt) => opt.value === credentials.defaultSubscriptionId);
|
||||
if (!found) {
|
||||
// Unselecting the default subscription if it isn't found among the received subscriptions
|
||||
onSubscriptionChange(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
|
||||
if (onCredentialsChange) {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || 'msi',
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onAzureCloudChange = (selected: SelectableValue<string>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
azureCloud: selected.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
tenantId: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientId: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientSecretReset = () => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: '',
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubscriptionChange = (selected: SelectableValue<string> | undefined) => {
|
||||
if (onCredentialsChange) {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
defaultSubscriptionId: selected?.value,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
{props.managedIdentityEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12" tooltip="Choose the type of authentication to Azure services">
|
||||
Authentication
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
className="width-15"
|
||||
value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
|
||||
options={authTypeOptions}
|
||||
onChange={onAuthTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{credentials.authType === 'clientsecret' && (
|
||||
<>
|
||||
{azureCloudOptions && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12" tooltip="Choose an Azure Cloud">
|
||||
Azure Cloud
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
className="width-15"
|
||||
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
|
||||
options={azureCloudOptions}
|
||||
onChange={onAzureCloudChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Directory (tenant) ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.tenantId || ''}
|
||||
onChange={onTenantIdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Application (client) ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientId || ''}
|
||||
onChange={onClientIdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{typeof credentials.clientSecret === 'symbol' ? (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
|
||||
<Input className="width-25" placeholder="configured" disabled={true} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button variant="secondary" type="button" onClick={onClientSecretReset}>
|
||||
reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientSecret || ''}
|
||||
onChange={onClientSecretChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{getSubscriptions && (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
|
||||
<div className="width-25">
|
||||
<Select
|
||||
value={
|
||||
credentials.defaultSubscriptionId
|
||||
? subscriptions.find((opt) => opt.value === credentials.defaultSubscriptionId)
|
||||
: undefined
|
||||
}
|
||||
options={subscriptions}
|
||||
onChange={onSubscriptionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onLoadSubscriptions}
|
||||
disabled={!hasRequiredFields}
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AzureCredentialsForm;
|
||||
@@ -1,13 +1,20 @@
|
||||
import React from 'react';
|
||||
import { AlertingSettings, DataSourceHttpSettings } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { PromSettings } from './PromSettings';
|
||||
import { PromOptions } from '../types';
|
||||
import { config } from 'app/core/config';
|
||||
import { PromOptions } from '../types';
|
||||
import { AzureAuthSettings } from './AzureAuthSettings';
|
||||
import { PromSettings } from './PromSettings';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<PromOptions>;
|
||||
export const ConfigEditor = (props: Props) => {
|
||||
const { options, onOptionsChange } = props;
|
||||
|
||||
const azureAuthSettings = {
|
||||
azureAuthEnabled: config.featureToggles['prometheus_azure_auth'] ?? false,
|
||||
azureSettingsUI: AzureAuthSettings,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataSourceHttpSettings
|
||||
@@ -16,6 +23,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
showAccessOptions={true}
|
||||
onChange={onOptionsChange}
|
||||
sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
|
||||
azureAuthSettings={azureAuthSettings}
|
||||
/>
|
||||
|
||||
<AlertingSettings<PromOptions> options={options} onOptionsChange={onOptionsChange} />
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should disable azure monitor secret input 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<Input
|
||||
className="width-25"
|
||||
disabled={true}
|
||||
placeholder="configured"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should enable azure monitor load subscriptions button 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
565
yarn.lock
565
yarn.lock
@@ -3898,17 +3898,39 @@
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@storybook/addon-actions@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.3.0.tgz#e5a24c69d70da9aa98560f19d10c06a50495ca2e"
|
||||
integrity sha512-7Ls1OIAdtAa4a27/bTuAlejQee4j7bFBkRzAeaHzcaZT1VVXoF6yBfMGuEGJI8brQ+KuSaIhIU2b0Iuzq47dDQ==
|
||||
"@storybook/addon-a11y@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.3.5.tgz#f1819b2c756bef3d69c9978e7ccac908490890fe"
|
||||
integrity sha512-iczDyensBVAUkXEuHcMDe14N797EHc85IQkG894jhCTOorKde4RhTbdExk0wVcCM/psk346CECk8HHMgO9CBGQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
axe-core "^4.2.0"
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
lodash "^4.17.20"
|
||||
react-sizeme "^3.0.1"
|
||||
regenerator-runtime "^0.13.7"
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addon-actions@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.3.5.tgz#3fe0e4f76e1d88c16f0c68977b081c624137c15f"
|
||||
integrity sha512-FdGT7Y6XSXqbmxY/SRDWKqEdYJyxshmA5xiNmJonSWiZ3e7cL3Awfqju4/7Y+kgR/gTaBylb5j8vUESWEwFwEQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
global "^4.4.0"
|
||||
@@ -3921,17 +3943,17 @@
|
||||
util-deprecate "^1.0.2"
|
||||
uuid-browser "^3.1.0"
|
||||
|
||||
"@storybook/addon-backgrounds@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.0.tgz#0562ec41ffff479803bd4b8a9d17abea2d6d6cdd"
|
||||
integrity sha512-MzqD94IDfJ9oipFKMLoJhf3zTxqQ0DVfsWXGV1o2nslg8gZFFH04yXex2kVuTiHYCuaLxfk/wnlpSyzqX2+CZQ==
|
||||
"@storybook/addon-backgrounds@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.5.tgz#8f09cddb6a1e05b2e2d0456be3881c715da6efa7"
|
||||
integrity sha512-Wx62sihR5022DzD98jc8EsoMLOwBe33LpCluMVfIuNSgrpn2rzRgdYTuJqsQFOTKAb+8zQvAS0qeHdBfocmLHQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
memoizerific "^1.11.3"
|
||||
@@ -3939,24 +3961,24 @@
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addon-controls@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.3.0.tgz#30275b9508a4d1acd1f3fa8f7dd432be629c3fec"
|
||||
integrity sha512-caiWFJ/iCdZPHI5rwk26fAQsf8QI7WXIoB850SYVDhkIirzJVZjugvwgrqgTfVf2Z5dWOe9aceroC9rBClHAlQ==
|
||||
"@storybook/addon-controls@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.3.5.tgz#63ae24c996cf73c4ad45c836bf1b3c05dcce10ea"
|
||||
integrity sha512-5wrCm5JPFvXemCptf7UAuHMJe0A31aWaSDQpLB8aglLQhi9Xwlff9Js3MgDwmMyEjkjblqNmQfe0sckH35bHag==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-docs@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.3.0.tgz#b8b7f3b8a38d78b7c63ba2aa9b87bf078e8e942b"
|
||||
integrity sha512-FpANy+7J3jpoxUoMfqwAetMatwbxQctOwN+Eh95uwQWYRZwsNHqdTv72/rtHiWR9wMaYThok5vqYHFvCpQTVPw==
|
||||
"@storybook/addon-docs@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.3.5.tgz#e0febd3dcfbbc21c54dfda3c43fe9ef5ee13f2b7"
|
||||
integrity sha512-jR9HcWDHxMMaOpPPmaNpcr4TEglrAy8AHRNqD+P8DhPc3VQOHcxnSfn30k3vGP02WELvEDreTOjsQBhVMweT7A==
|
||||
dependencies:
|
||||
"@babel/core" "^7.12.10"
|
||||
"@babel/generator" "^7.12.11"
|
||||
@@ -3967,20 +3989,20 @@
|
||||
"@mdx-js/loader" "^1.6.22"
|
||||
"@mdx-js/mdx" "^1.6.22"
|
||||
"@mdx-js/react" "^1.6.22"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/builder-webpack4" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/builder-webpack4" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/csf-tools" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/postinstall" "6.3.0"
|
||||
"@storybook/source-loader" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/csf-tools" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/postinstall" "6.3.5"
|
||||
"@storybook/source-loader" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
acorn "^7.4.1"
|
||||
acorn-jsx "^5.3.1"
|
||||
acorn-walk "^7.2.0"
|
||||
@@ -4003,27 +4025,27 @@
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/addon-essentials@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.3.0.tgz#8b0329042e0f25192c04c78eac5c38d4d8259a62"
|
||||
integrity sha512-8ejOP3l4UC2utDbcq8QUQ2nOqAOzL9ri20So5qrlTuBPtMmSNUea7p5yAGB0GOJ9j96k3pS2nU1/dlEqepo5nA==
|
||||
"@storybook/addon-essentials@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.3.5.tgz#b2856f604a3db5eb76217400105c0726aedcbef9"
|
||||
integrity sha512-jKxvpBB9bb3mBehAXABWtbLAB9b7DHT4WY6yMvnUFlmUaVn4vOAU+Jwxu7kwzU5/240scKgaZm1OIlBWIQF9Xw==
|
||||
dependencies:
|
||||
"@storybook/addon-actions" "6.3.0"
|
||||
"@storybook/addon-backgrounds" "6.3.0"
|
||||
"@storybook/addon-controls" "6.3.0"
|
||||
"@storybook/addon-docs" "6.3.0"
|
||||
"@storybook/addon-measure" "^1.2.3"
|
||||
"@storybook/addon-toolbars" "6.3.0"
|
||||
"@storybook/addon-viewport" "6.3.0"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/addon-actions" "6.3.5"
|
||||
"@storybook/addon-backgrounds" "6.3.5"
|
||||
"@storybook/addon-controls" "6.3.5"
|
||||
"@storybook/addon-docs" "6.3.5"
|
||||
"@storybook/addon-measure" "^2.0.0"
|
||||
"@storybook/addon-toolbars" "6.3.5"
|
||||
"@storybook/addon-viewport" "6.3.5"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
regenerator-runtime "^0.13.7"
|
||||
storybook-addon-outline "^1.3.3"
|
||||
storybook-addon-outline "^1.4.1"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-knobs@6.3.0":
|
||||
"@storybook/addon-knobs@6.3.5":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-6.3.0.tgz#f289c072729651150a27a163371df20922c24f93"
|
||||
integrity sha512-wsZZ1t38KHdaxzrc9oPyiIJDihJnjHRRabrENQbylktJwETEjb2z3eX0iBRJGiz/YCHO+tGd0ItyZArOdijT6g==
|
||||
@@ -4039,23 +4061,23 @@
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
react-select "^3.2.0"
|
||||
|
||||
"@storybook/addon-measure@^1.2.3":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-1.2.4.tgz#149705ef9de5e9251c012deb84406b3bc9307452"
|
||||
integrity sha512-pxAo7QcETdiienZYMjAhX/3BqPnYTuH0ZSjmJzsr+yCBvZmZUciq1h3WvyUN59rT0ewFwLTKsmZG/wVZj+aN+Q==
|
||||
"@storybook/addon-measure@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-2.0.0.tgz#c40bbe91bacd3f795963dc1ee6ff86be87deeda9"
|
||||
integrity sha512-ZhdT++cX+L9LwjhGYggvYUUVQH/MGn2rwbrAwCMzA/f2QTFvkjxzX8nDgMxIhaLCDC+gHIxfJG2wrWN0jkBr3g==
|
||||
|
||||
"@storybook/addon-storysource@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.3.0.tgz#b523abd4592ce494366109eedb2c04501029bbf2"
|
||||
integrity sha512-Vk0oswJ0hWYqjypHox+HRj/iovyc42w6B88ZL1yjdH45+bQYjMnxE2yqY4kGVSZKQMkZidjWGKtgD8Y8xP4TXA==
|
||||
"@storybook/addon-storysource@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.3.5.tgz#fe988e2bec5004e1aa58aa926081cca8a8b62dc9"
|
||||
integrity sha512-N7lmiPdMU12lKJqnckAQKXHKroxSyD9UKI5SZLdCaDxmOUwH6D82x4CWB/yUQYFrhhlhxYFFVSmbq/XkokmuIw==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/router" "6.3.0"
|
||||
"@storybook/source-loader" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/router" "6.3.5"
|
||||
"@storybook/source-loader" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
estraverse "^5.2.0"
|
||||
loader-utils "^2.0.0"
|
||||
@@ -4064,64 +4086,64 @@
|
||||
react-syntax-highlighter "^13.5.3"
|
||||
regenerator-runtime "^0.13.7"
|
||||
|
||||
"@storybook/addon-toolbars@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.3.0.tgz#5e5837812c7ba94e4d5be3b02b0f915a33b4f98b"
|
||||
integrity sha512-E0LwAaoWNtmPfMq9GbySsK2ZdXlPf9gJQD1uI3KXbcaGBhtY136QmZS+VpUmPfilplrYJ2G6GAQoPHrIPUf1VQ==
|
||||
"@storybook/addon-toolbars@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.3.5.tgz#a88b476795d1ecf32ad08e69d5188ec10868d24e"
|
||||
integrity sha512-167CMhcevkepJStbvjMfFzU+/MdMU5hhr69N99m2M2ZeJnp5q6rXHMVvcjgBmKJsuAkLd4BPJ1U5GAPOS9oNHg==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
regenerator-runtime "^0.13.7"
|
||||
|
||||
"@storybook/addon-viewport@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.3.0.tgz#a30660fe1873f16e955798718e3f14e26f4bff09"
|
||||
integrity sha512-aOENuKIfmeQOhm++p2ezwIV9gET05s5/QQ1TTZrrPixQ3FxmCwAb/OqsmD4m/8e075C5gLXQEV47vGAkYyTm0Q==
|
||||
"@storybook/addon-viewport@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.3.5.tgz#df3a12ff36b46eb302d386f37d35bd05c290b733"
|
||||
integrity sha512-XklcnqbbtZgVMyoAuF8LO45t/M/zjqq51qmQviizVFlbcL3MKAGGnSPQjqYvDv34kkkkfFXIshfv/+diWqkpNQ==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
memoizerific "^1.11.3"
|
||||
prop-types "^15.7.2"
|
||||
regenerator-runtime "^0.13.7"
|
||||
|
||||
"@storybook/addons@6.3.0", "@storybook/addons@^6.3.0-beta.6":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.0.tgz#a86849f46a654d2d78b91fad0088264a32d4e58e"
|
||||
integrity sha512-/dcq20HtdSw5+cG8znR59Y/uv2zCR2VjRK3N52IkGWk162b/UbSjjL0PhNnnQFGpH9Fruft6mqvjTAKT41kmJw==
|
||||
"@storybook/addons@6.3.5", "@storybook/addons@^6.3.0":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.5.tgz#af7fec11cd780d64d0927840748d18e4df43e5d7"
|
||||
integrity sha512-7IKTTzISRZdmdZrV/+CmcOfH3O1y/vWn+apkooWLwFhFkEKJXdmAzJ3A1uRmze4mF0ahdMTGVPm44DGlbGqv4g==
|
||||
dependencies:
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/router" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/router" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
regenerator-runtime "^0.13.7"
|
||||
|
||||
"@storybook/api@6.3.0", "@storybook/api@^6.3.0-beta.6":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.0.tgz#5ecb646e7c3c4c7c494bb15f4c94554f7f4ee09e"
|
||||
integrity sha512-swPMcQadLDRTnMjL9dwY6K1zXHn3KcAa3euvSHd1R4OKXTSBBj1zHvIaOrq6yHz7RIYOACmZlEV3CUru9FlvEA==
|
||||
"@storybook/api@6.3.5", "@storybook/api@^6.3.0":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.5.tgz#3edaf32029fba018917ba25a307f471ddd165cc0"
|
||||
integrity sha512-H5DfvreRbiN7O+VBCbHraM+xNvdzs60ehC7P2jGRAkbzurPjQAmVCMKkPVKzOF9JA5cGOyv/ctkDn/9PFfEl9Q==
|
||||
dependencies:
|
||||
"@reach/router" "^1.3.4"
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/router" "6.3.0"
|
||||
"@storybook/router" "6.3.5"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/theming" "6.3.5"
|
||||
"@types/reach__router" "^1.3.7"
|
||||
core-js "^3.8.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
@@ -4135,10 +4157,10 @@
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/builder-webpack4@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.0.tgz#ce6d832c4e6f7ed25d4362aed5be4ab1db67b85a"
|
||||
integrity sha512-s2s9uVNIvj/CFQOwA9B8nbOKHNtVj5wIIeeR8cNAGWKxoDNA1YFAqSrmLGWDtxpZpADmJXzmVKMQts7MjkKdKg==
|
||||
"@storybook/builder-webpack4@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.5.tgz#71fbbd849a6d1e987ffa6291057bc4a74b1c58e6"
|
||||
integrity sha512-jQZGHYiwZbB8ByfRQSJk6uYraUGFmQ/qAX9K5B0Yd2OE6AVdN6hxwSjjXjjKwm3HQTzvJfG75sByU76+xUjRLw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.12.10"
|
||||
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||
@@ -4161,20 +4183,20 @@
|
||||
"@babel/preset-env" "^7.12.11"
|
||||
"@babel/preset-react" "^7.12.10"
|
||||
"@babel/preset-typescript" "^7.12.7"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/channel-postmessage" "6.3.0"
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core-common" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/router" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/channel-postmessage" "6.3.5"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-common" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/router" "6.3.5"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/ui" "6.3.0"
|
||||
"@storybook/theming" "6.3.5"
|
||||
"@storybook/ui" "6.3.5"
|
||||
"@types/node" "^14.0.10"
|
||||
"@types/webpack" "^4.41.26"
|
||||
autoprefixer "^9.8.6"
|
||||
@@ -4211,38 +4233,38 @@
|
||||
webpack-hot-middleware "^2.25.0"
|
||||
webpack-virtual-modules "^0.2.2"
|
||||
|
||||
"@storybook/channel-postmessage@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.3.0.tgz#96e7aea034ec1c4f397323ab7923eaa80d017324"
|
||||
integrity sha512-q7FeNWIIrvZxycIMBscqahFLygxAa2L4eJ9oxZFF9zJpSV80bxDalMou3Uo7RvDJFrAeHCanF1Y7bnEDMus4yg==
|
||||
"@storybook/channel-postmessage@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.3.5.tgz#3f3c1b2dc58babbdfb3eb2a7bece4fbcc7d8383e"
|
||||
integrity sha512-KQfPfoFV4VfzcFKT3xINywWkGdJPWdwiOUycz5/N9uNgl6KXurrGp+GHolUPiYnf4Z7AGVC+CAR7/WjxIIm85g==
|
||||
dependencies:
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
qs "^6.10.0"
|
||||
telejson "^5.3.2"
|
||||
|
||||
"@storybook/channels@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.0.tgz#f378c6ee03e0c72a2ee9263c8dfdfa4a7a1bcf51"
|
||||
integrity sha512-E+SCQLSIlCaOGKEkZ8rFKNyH24/N4IA6h+EDF+9mhw3yT4iv7NCoswCgqX7JhyhSjWkM01onhuMVUVKVvi7CSw==
|
||||
"@storybook/channels@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.5.tgz#8c660057b2cc48859dbda269e9bc4f71be260a46"
|
||||
integrity sha512-kYR822+NLSokY0JkOBSDJCC8woXx9T4m6IaHS3/R4BhurSS80Jt09YWc8nQPKTAkkTrSSnzKiS1D/yVGsGbMjg==
|
||||
dependencies:
|
||||
core-js "^3.8.2"
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/client-api@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.0.tgz#a285c4b64ec318f360ade31d0c87c22e6e1db2a6"
|
||||
integrity sha512-5HLtYPBOHif9AdzwLCrVbMQdOJ2dne9zv7oTo6Yl0wvLhbr6V/VypoXE0CgFF3hAI2iUquG5z00KlrE8UErC5Q==
|
||||
"@storybook/client-api@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.5.tgz#243ab8a9616298db1451ee7a134d18136aefeb50"
|
||||
integrity sha512-GU5jOS3i9oLTl74Xd5GUV+l06dx64BkDlPgaeGpKcoQlIwReFNVZxobZazaroYij8mCNUnQpgM/ArRAG8Uz8Cw==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/channel-postmessage" "6.3.0"
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/channel-postmessage" "6.3.5"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@types/qs" "^6.9.5"
|
||||
"@types/webpack-env" "^1.16.0"
|
||||
@@ -4257,23 +4279,23 @@
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/client-logger@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.0.tgz#3188f84dd10353d225efadee9f24928395d38aab"
|
||||
integrity sha512-x/y820f/2Jm6RW5TxRv7IlbF6zWpTkHoajfwYuTpK/OXvK5gx6dwXGdgjNoaAGofGRz5SVjDjTDPOcd5X5AUPw==
|
||||
"@storybook/client-logger@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.5.tgz#428b69c6433433372cc8265f285da9707fed63ab"
|
||||
integrity sha512-vPD8MYf/847lsYGhq9eFmcAi+fQc2p6fue9McEreHviaT3S3Sk5/u8L2f8D8sdpym0yPJrHBmla+fK45MnxQLg==
|
||||
dependencies:
|
||||
core-js "^3.8.2"
|
||||
global "^4.4.0"
|
||||
|
||||
"@storybook/components@6.3.0", "@storybook/components@^6.3.0-beta.6":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.0.tgz#5ad372abd60ee0cb02516f960f514659e3fbf865"
|
||||
integrity sha512-TDcazQAtNgE1E33jKKABx51XpvWyXMcJZFWA0d5wu8XrElrL1PuZqz7dPePoWKGMfTaPYWP6rRyDg4Svv36j+A==
|
||||
"@storybook/components@6.3.5", "@storybook/components@^6.3.0":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.5.tgz#7e2f2d15c3552de6507727bfa4a13a20facd81e5"
|
||||
integrity sha512-EYyUhxuuted3fmPC8055cxG4kMf8tp/sjI6l2wAQ5myRZ3zBpV0mjOxxgOeiDphtBnyDKmOF+4x8EkSUJmu/5w==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.6.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/theming" "6.3.5"
|
||||
"@types/color-convert" "^2.0.0"
|
||||
"@types/overlayscrollbars" "^1.12.0"
|
||||
"@types/react-syntax-highlighter" "11.0.5"
|
||||
@@ -4295,18 +4317,18 @@
|
||||
ts-dedent "^2.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/core-client@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.3.0.tgz#d7acf4a6071cbac76a7d38e232640fc37b6a2e72"
|
||||
integrity sha512-S2MZmHGjkZdGYgkWNXzn3Z/AS2NeiYVyO503mF7d+4zfgAoasKBkc7Y/1Ry3RuaGRwOq5bNQtJUZsF0kX1a8iQ==
|
||||
"@storybook/core-client@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.3.5.tgz#8187547c052521859a6f8f0a4047ec53655582a8"
|
||||
integrity sha512-hOvTzeiCDnOK7eD40wfI10JZ7BI7IRyzCyIKxRaROq8x53TpBHbuOKGhJFddUVJLT+w/BVA5y0Hf9AOMgn68Iw==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/channel-postmessage" "6.3.0"
|
||||
"@storybook/client-api" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/channel-postmessage" "6.3.5"
|
||||
"@storybook/client-api" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
"@storybook/ui" "6.3.0"
|
||||
"@storybook/ui" "6.3.5"
|
||||
airbnb-js-shims "^2.2.1"
|
||||
ansi-to-html "^0.6.11"
|
||||
core-js "^3.8.2"
|
||||
@@ -4318,10 +4340,10 @@
|
||||
unfetch "^4.2.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/core-common@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.3.0.tgz#1310a0480bfd84d3399c3ba13e28b873b037f108"
|
||||
integrity sha512-AYoN1g8g4FI2K2UcGfLAm7EUPgesiClLT/zqy2q6dWQrIUayWzJqI+gqDyYukv5s+KHRanGBZNCTBww/VhcPlg==
|
||||
"@storybook/core-common@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.3.5.tgz#fff39c65e8872b475afd8a102e6e56ec8e914557"
|
||||
integrity sha512-edLGDDbUPKZunxIL4g9TfHCVUB9owmcPxg7y3XWuM3murHS98t6uGfcSFOg5fTYIdYq7ZuwPQM08KGqAAoiRsg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.12.10"
|
||||
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||
@@ -4344,7 +4366,7 @@
|
||||
"@babel/preset-react" "^7.12.10"
|
||||
"@babel/preset-typescript" "^7.12.7"
|
||||
"@babel/register" "^7.12.1"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@types/glob-base" "^0.3.0"
|
||||
"@types/micromatch" "^4.0.1"
|
||||
@@ -4372,24 +4394,24 @@
|
||||
util-deprecate "^1.0.2"
|
||||
webpack "4"
|
||||
|
||||
"@storybook/core-events@6.3.0", "@storybook/core-events@^6.3.0-beta.6":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.0.tgz#5e220a866db5b93550b5c3464774a7b10ad036a6"
|
||||
integrity sha512-ZGTm5nQvFLlc2LVgoDyxo99MbQcFqQzkxIQReFkO7hPwwkcjcwmdBtnlmkn9/p5QQ5/8aU0k+ceCkrBNu1M83w==
|
||||
"@storybook/core-events@6.3.5", "@storybook/core-events@^6.3.0":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.5.tgz#40d51b39e086192dc0af11e22da011e5483ee400"
|
||||
integrity sha512-1RUaOwD1STqf1EG727fLuy18dS67Nkdf/8pIGGATxAohfwHkHOhOI9tOP+GPx+aE7UioDmVWvwbUxRQ1b4YitA==
|
||||
dependencies:
|
||||
core-js "^3.8.2"
|
||||
|
||||
"@storybook/core-server@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.0.tgz#06025c0f920d827649465ddb76b3766cf4bd313e"
|
||||
integrity sha512-6Lckos2bleYb0Qg0JXhLSyqASKMquueefIjde5ySelyJzZLyW8ZYt+sfKu7+rdi/RqOvUCyfLcPHAxJSub2bRg==
|
||||
"@storybook/core-server@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.5.tgz#3323f54f865258f70955ebd7ff9d23282a02cea5"
|
||||
integrity sha512-bxWKXq9us1PYrFtCq5mMCQcVgPs4lqRXEWIYzfbnXTBGX+5lD0JH1lsTnm99mVqz/x/WjSrzSLO9+A6UX9/cYA==
|
||||
dependencies:
|
||||
"@storybook/builder-webpack4" "6.3.0"
|
||||
"@storybook/core-client" "6.3.0"
|
||||
"@storybook/core-common" "6.3.0"
|
||||
"@storybook/csf-tools" "6.3.0"
|
||||
"@storybook/manager-webpack4" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/builder-webpack4" "6.3.5"
|
||||
"@storybook/core-client" "6.3.5"
|
||||
"@storybook/core-common" "6.3.5"
|
||||
"@storybook/csf-tools" "6.3.5"
|
||||
"@storybook/manager-webpack4" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@types/node" "^14.0.10"
|
||||
"@types/node-fetch" "^2.5.7"
|
||||
@@ -4418,18 +4440,18 @@
|
||||
util-deprecate "^1.0.2"
|
||||
webpack "4"
|
||||
|
||||
"@storybook/core@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.3.0.tgz#9e2dd83629be411898fa578c9f3a883ff45a81b4"
|
||||
integrity sha512-8sEhlzD0f3ewnnXutt+aBTaVJ1EuW6yR8pSSLVSSwdBRQE2UVy1YOA+0kAspq+lNrF1IrvX6WvPqJq/ZmPWcOw==
|
||||
"@storybook/core@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.3.5.tgz#45579b741a5836023572e9721d3390a5226bc348"
|
||||
integrity sha512-ZzzqxtsxjkO1RmJVSUgI7o87cQtFmhlca6sSWCInlwZYeOOgRQVukZ3KkFaPFzVbDeJtLQAST4ONNaCtUMVgwg==
|
||||
dependencies:
|
||||
"@storybook/core-client" "6.3.0"
|
||||
"@storybook/core-server" "6.3.0"
|
||||
"@storybook/core-client" "6.3.5"
|
||||
"@storybook/core-server" "6.3.5"
|
||||
|
||||
"@storybook/csf-tools@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.3.0.tgz#6ec59ebcf8739209b7871956e3be426018a18583"
|
||||
integrity sha512-7bG83511Hj6Hb1J+NrHtmzew/ib5dlgl2HjIQYWvL1xyUqBIDJNgaSixO624Yu36Yrcyv3+018hPdnn8E1nNuQ==
|
||||
"@storybook/csf-tools@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.3.5.tgz#a396257323f32ea5968bfcfbccc0a6214f107694"
|
||||
integrity sha512-P6bD//gG9lEJi58gE8e8zpbWFDz4L4yPFzjL7VPQBp11ZQxCj4HgFcwRnyq2jctyFjQlJNM+CjHhiuts1mqD/A==
|
||||
dependencies:
|
||||
"@babel/generator" "^7.12.11"
|
||||
"@babel/parser" "^7.12.11"
|
||||
@@ -4453,20 +4475,20 @@
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
"@storybook/manager-webpack4@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.3.0.tgz#6dca60ba152c53f766daae79cdb12e969460cd48"
|
||||
integrity sha512-M4HjxKQeNaMTg7PlxVp06jmdpVHu1H4cdgXbHZcD977nJ6R7bpZ4YTlTez3TjshJLoze75FRyubOlNu0l5CdKQ==
|
||||
"@storybook/manager-webpack4@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.3.5.tgz#fb33cb6cf2ac23f6755a98e4943de56aedf0ee33"
|
||||
integrity sha512-MNeRv8GkJkIgr4wbUUdBkMYNSORpywEkh0Ya1aYKI2FalXu8FjUCTTPzpvkaHk01hGFlvwf6xy59WJgzvljP4g==
|
||||
dependencies:
|
||||
"@babel/core" "^7.12.10"
|
||||
"@babel/plugin-transform-template-literals" "^7.12.1"
|
||||
"@babel/preset-react" "^7.12.10"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/core-client" "6.3.0"
|
||||
"@storybook/core-common" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/ui" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/core-client" "6.3.5"
|
||||
"@storybook/core-common" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/theming" "6.3.5"
|
||||
"@storybook/ui" "6.3.5"
|
||||
"@types/node" "^14.0.10"
|
||||
"@types/webpack" "^4.41.26"
|
||||
babel-loader "^8.2.2"
|
||||
@@ -4496,10 +4518,10 @@
|
||||
webpack-dev-middleware "^3.7.3"
|
||||
webpack-virtual-modules "^0.2.2"
|
||||
|
||||
"@storybook/node-logger@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.3.0.tgz#ad64b23361beeb1864eb89337b7fe127a5d32929"
|
||||
integrity sha512-gxvYOwDzHSYDTvnrwsyonCk88lRQ9gHrEvu3J8sM/0G/0br8g7G8+jSakKR8miE7urcwxd0uoYK+Y4KwJHkJpg==
|
||||
"@storybook/node-logger@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.3.5.tgz#730e1ec384c30dbc9c5ab1896a558af3215b0635"
|
||||
integrity sha512-mV6hXcMLdFPO7l1CFI+ycaoRDrbBxLBBSv8ivCX6acAhoheF0sLqJftpzwqlW3UYFEznVqD3PasqmO8I3f8dag==
|
||||
dependencies:
|
||||
"@types/npmlog" "^4.1.2"
|
||||
chalk "^4.1.0"
|
||||
@@ -4507,17 +4529,17 @@
|
||||
npmlog "^4.1.2"
|
||||
pretty-hrtime "^1.0.3"
|
||||
|
||||
"@storybook/postinstall@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.3.0.tgz#616999e96bc2f30e5deefd3c75415ce1dbc35cb3"
|
||||
integrity sha512-QhhrhnB4yRdn5DGzygitccoKOYV+nKXWtQQm1TvEjMGrbZu57kI4X3TAsU4f3+wU8Xbdlfc8vhXpgCSzofRzGA==
|
||||
"@storybook/postinstall@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.3.5.tgz#8fe88ffc4274ab447984fbbd435247d79e0d6f5d"
|
||||
integrity sha512-ZR7xMZe1sVpwRudN67mLkjXeOYxSxvJ2MQJ+TnQcv0L0skTmF4GHLcuOs0aFpNa4rvo8VH9bqXNFVMDHj8R9Vw==
|
||||
dependencies:
|
||||
core-js "^3.8.2"
|
||||
|
||||
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.3c70e01.0":
|
||||
version "1.0.2-canary.3c70e01.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.3c70e01.0.tgz#de49451523b86640463acc6028985ca11d8a63d1"
|
||||
integrity sha512-go1LO+iM6qLGhgqvEoEpw339/kf2YBX86aG2JewWwlHCO0YyyYdlsdZd3KkB5MVtudyK7mtrcNDq0k/EIaB2JA==
|
||||
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0":
|
||||
version "1.0.2-canary.253f8c1.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa"
|
||||
integrity sha512-mmoRG/rNzAiTbh+vGP8d57dfcR2aP+5/Ll03KKFyfy5FqWFm/Gh7u27ikx1I3LmVMI8n6jh5SdWMkMKon7/tDw==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
endent "^2.0.1"
|
||||
@@ -4527,19 +4549,19 @@
|
||||
react-docgen-typescript "^2.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@storybook/react@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.3.0.tgz#e86ef1976fba2f0f80925278f196fb709df55eb4"
|
||||
integrity sha512-GxK88Si9WQa16uUsUBhe6kRhSEZUrR/1ljP6QvLY+C5MyYJZ89DZPAbWnVo47SJCXhAlvJW83nSTSxnobn8RWA==
|
||||
"@storybook/react@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.3.5.tgz#9fb3cdfda5f333084cb4e9b3ef952855d9483e0c"
|
||||
integrity sha512-6k9rnDZ8/L1rwvlJMiLTQY6vcIHOnysFIYTzxBO32Ud0+RUh7UTyjp4JYoHu1LPLz3z+q77y7YgAhCvVNuMGQg==
|
||||
dependencies:
|
||||
"@babel/preset-flow" "^7.12.1"
|
||||
"@babel/preset-react" "^7.12.10"
|
||||
"@pmmmwh/react-refresh-webpack-plugin" "^0.4.3"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/core" "6.3.0"
|
||||
"@storybook/core-common" "6.3.0"
|
||||
"@storybook/node-logger" "6.3.0"
|
||||
"@storybook/react-docgen-typescript-plugin" "1.0.2-canary.3c70e01.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/core" "6.3.5"
|
||||
"@storybook/core-common" "6.3.5"
|
||||
"@storybook/node-logger" "6.3.5"
|
||||
"@storybook/react-docgen-typescript-plugin" "1.0.2-canary.253f8c1.0"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@types/webpack-env" "^1.16.0"
|
||||
babel-plugin-add-react-displayname "^0.0.5"
|
||||
@@ -4556,13 +4578,13 @@
|
||||
ts-dedent "^2.0.0"
|
||||
webpack "4"
|
||||
|
||||
"@storybook/router@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.0.tgz#8b63773f11fe4c6749ccfda5d725c94275f0a459"
|
||||
integrity sha512-RJcRVI6IqffLOU6k9GrlB3cXLLK5TRmFSIjwW3lEHVhj313e56uLRYTylT11aBf8bPEQ+MeQVe2sqQUBG3Ugng==
|
||||
"@storybook/router@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.5.tgz#ab24a8e1416323413ee434f887b872a515be5da1"
|
||||
integrity sha512-cLGYdmKhb3awE/Un4bNgTXY5tpPppYmoxuFNrOrWSgS3z4eed1W1YcMbL0EWbCTubz9zDAInD2gd0nhOS+HYeQ==
|
||||
dependencies:
|
||||
"@reach/router" "^1.3.4"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@types/reach__router" "^1.3.7"
|
||||
core-js "^3.8.2"
|
||||
fast-deep-equal "^3.1.3"
|
||||
@@ -4580,13 +4602,13 @@
|
||||
core-js "^3.6.5"
|
||||
find-up "^4.1.0"
|
||||
|
||||
"@storybook/source-loader@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.3.0.tgz#d1bbbb9c350c89f1207233d209694dcac5350e76"
|
||||
integrity sha512-5LpqY5uu35Fg01D7Zu0xAT7ow6tcuHz+fkIxsGAJhzWovbV5NYl/BP8WSPv7TH+WjYve+RI2Xp6a9JFrgi9gpQ==
|
||||
"@storybook/source-loader@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.3.5.tgz#66a57903e621fdd16ec07de5daf0f9e0bc7cfcf2"
|
||||
integrity sha512-cIUVSItfaAot7MhiRkYoSM2/sYmRVGDTpx6ttUG1BVKwrb1/IcS5L6iMEyGTCyL70s3yLjxrhrLBW4QqiqKDXg==
|
||||
dependencies:
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/csf" "0.0.1"
|
||||
core-js "^3.8.2"
|
||||
estraverse "^5.2.0"
|
||||
@@ -4596,15 +4618,15 @@
|
||||
prettier "~2.2.1"
|
||||
regenerator-runtime "^0.13.7"
|
||||
|
||||
"@storybook/theming@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.0.tgz#4b6ef023631663d8e50f1348469b9a9641244cd0"
|
||||
integrity sha512-Mtnq8qFv/TTtnl1sB6DGBCg/kJq7sR2e2uh/Uy2sHyksnhVITVJxEIFHSBo2L+IE6y0S2Oh6F9WdddWAO4Ao2g==
|
||||
"@storybook/theming@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.5.tgz#673fa63e7723b651d0a400bf916169bfd3da8c81"
|
||||
integrity sha512-TvgzzUvjJK1jJzqCiB4FUjvK9JZjPUsgYgAdbDuZX//N9zwbjR1cSR8eJWfuCDk0ECvQ6V+vnwnGQ5XmFqSJ1Q==
|
||||
dependencies:
|
||||
"@emotion/core" "^10.1.1"
|
||||
"@emotion/is-prop-valid" "^0.8.6"
|
||||
"@emotion/styled" "^10.0.27"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
core-js "^3.8.2"
|
||||
deep-object-diff "^1.1.0"
|
||||
emotion-theming "^10.0.27"
|
||||
@@ -4614,21 +4636,21 @@
|
||||
resolve-from "^5.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/ui@6.3.0":
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.0.tgz#3a17689c76d2ba2c3afadad2b5d0cf41abb74933"
|
||||
integrity sha512-1f2+IB+WRrBSht538jNFSUDUxA8PpEOnDkJsE9PyOJEvskV/8POuCTjbpcvdBYFQ9hSq1oi3535mdbNiTivYTg==
|
||||
"@storybook/ui@6.3.5":
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.5.tgz#b237c4ba69e8688a570f5ac967e9081c1260d59c"
|
||||
integrity sha512-OCFnMCIhHgl9OwcX0WvPRgjIsoVUrhRxYLAsav67FI33qcjSCPDfN2aX+gp3VEJNGL+vmXRqLzMmhsfYaCQNpA==
|
||||
dependencies:
|
||||
"@emotion/core" "^10.1.1"
|
||||
"@storybook/addons" "6.3.0"
|
||||
"@storybook/api" "6.3.0"
|
||||
"@storybook/channels" "6.3.0"
|
||||
"@storybook/client-logger" "6.3.0"
|
||||
"@storybook/components" "6.3.0"
|
||||
"@storybook/core-events" "6.3.0"
|
||||
"@storybook/router" "6.3.0"
|
||||
"@storybook/addons" "6.3.5"
|
||||
"@storybook/api" "6.3.5"
|
||||
"@storybook/channels" "6.3.5"
|
||||
"@storybook/client-logger" "6.3.5"
|
||||
"@storybook/components" "6.3.5"
|
||||
"@storybook/core-events" "6.3.5"
|
||||
"@storybook/router" "6.3.5"
|
||||
"@storybook/semver" "^7.3.2"
|
||||
"@storybook/theming" "6.3.0"
|
||||
"@storybook/theming" "6.3.5"
|
||||
"@types/markdown-to-jsx" "^6.11.3"
|
||||
copy-to-clipboard "^3.3.1"
|
||||
core-js "^3.8.2"
|
||||
@@ -7236,6 +7258,11 @@ aws4@^1.8.0:
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
axe-core@^4.2.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.1.tgz#0c6a076e4a1c3e0544ba6a9479158f9be7a7928e"
|
||||
integrity sha512-3WVgVPs/7OnKU3s+lqMtkv3wQlg3WxK1YifmpJSDO0E1aPBrZWlrrTO6cxRqCXLuX2aYgCljqXIQd0VnRidV0g==
|
||||
|
||||
axios@0.21.1:
|
||||
version "0.21.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
||||
@@ -21878,15 +21905,15 @@ store2@^2.12.0:
|
||||
resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf"
|
||||
integrity sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==
|
||||
|
||||
storybook-addon-outline@^1.3.3:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/storybook-addon-outline/-/storybook-addon-outline-1.3.4.tgz#4d90c262781db995312ca8bb6d4ba26028ff55b6"
|
||||
integrity sha512-UNFansfJq1j5Z+GdB8/eoSck9A27VPm5HPG4LBnPKwvAmvjccVgY9mcbcG/ezF83RlrtCOKkfQ1NgOqz2NlGGg==
|
||||
storybook-addon-outline@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/storybook-addon-outline/-/storybook-addon-outline-1.4.1.tgz#0a1b262b9c65df43fc63308a1fdbd4283c3d9458"
|
||||
integrity sha512-Qvv9X86CoONbi+kYY78zQcTGmCgFaewYnOVR6WL7aOFJoW7TrLiIc/O4hH5X9PsEPZFqjfXEPUPENWVUQim6yw==
|
||||
dependencies:
|
||||
"@storybook/addons" "^6.3.0-beta.6"
|
||||
"@storybook/api" "^6.3.0-beta.6"
|
||||
"@storybook/components" "^6.3.0-beta.6"
|
||||
"@storybook/core-events" "^6.3.0-beta.6"
|
||||
"@storybook/addons" "^6.3.0"
|
||||
"@storybook/api" "^6.3.0"
|
||||
"@storybook/components" "^6.3.0"
|
||||
"@storybook/core-events" "^6.3.0"
|
||||
ts-dedent "^2.1.1"
|
||||
|
||||
storybook-dark-mode@1.0.8:
|
||||
|
||||
Reference in New Issue
Block a user