diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9bd1631214e..2deae5a196e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -602,6 +602,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /pkg/services/anonymous/ @grafana/identity-access-team /pkg/services/auth/ @grafana/identity-access-team /pkg/services/authn/ @grafana/identity-access-team +/pkg/services/authz/ @grafana/identity-access-team /pkg/services/signingkeys/ @grafana/identity-access-team /pkg/services/dashboards/accesscontrol.go @grafana/identity-access-team /pkg/services/datasources/guardian/ @grafana/identity-access-team diff --git a/Makefile b/Makefile index 420c0c187fe..38d2b70e3c7 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ swagger-oss-gen: $(SWAGGER) ## Generate API Swagger specification rm -f $(SPEC_TARGET) SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(SPEC_TARGET) \ -x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \ + -x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \ -x "github.com/prometheus/alertmanager" \ -i pkg/api/swagger_tags.json \ --exclude-tag=alpha \ @@ -78,6 +79,7 @@ swagger-enterprise-gen: $(SWAGGER) ## Generate API Swagger specification rm -f $(ENTERPRISE_SPEC_TARGET) SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \ -x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \ + -x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \ -x "github.com/prometheus/alertmanager" \ -i pkg/api/swagger_tags.json \ --exclude-tag=alpha \ diff --git a/docs/sources/_index.md b/docs/sources/_index.md index 24ddc32ac79..c7a04de0e8c 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -79,7 +79,7 @@ cards: ## Overview -_Grafana Open Source Software (OSS)_ enables you to query, visualize, alert on, and explore your metrics, logs, and traces wherever they're stored. Grafana OSS provides you with tools to turn your time-series database (TSDB) data into insightful graphs and visualizations. The Grafana OSS plugin framework also enables you to connect other data sources like NoSQL/SQL databases, ticketing tools like Jira or ServiceNow, and CI/CD tooling like GitLab. +_Grafana Open Source Software (OSS)_ enables you to query, visualize, alert on, and explore your metrics, logs, and traces wherever they're stored. Grafana data source plugins enable you to query data sources including time series databases like Prometheus and CloudWatch, logging tools like Loki and Elasticsearch, NoSQL/SQL databases like Postgres, CI/CD tooling like GitHub, and many more. Grafana OSS provides you with tools to display that data on live dashboards with insightful graphs and visualizations. _Grafana Enterprise_ is a commercial edition of Grafana that includes exclusive data source plugins and additional features not found in the open source version. You also get 24x7x365 support and training from the core Grafana team. To learn more about these features, refer to [Enterprise features](https://grafana.com/docs/grafana//introduction/grafana-enterprise/#enterprise-features-in-grafana-cloud). diff --git a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md index 749b23c209c..eecbad7f530 100644 --- a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md +++ b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md @@ -33,7 +33,7 @@ refs: # Group alert notifications -Grouping is an important feature of Grafana Alerting as it allows you to batch relevant alerts together into a smaller number of notifications. This is particularly important if notifications are delivered to first-responders, such as engineers on-call, where receiving lots of notifications in a short period of time can be overwhelming and in some cases can negatively impact a first-responders ability to respond to an incident. For example, consider a large outage where many of your systems are down. In this case, grouping can be the difference between receiving 1 phone call and 100 phone calls. +Grouping in Grafana Alerting allows you to batch relevant alerts together into a smaller number of notifications. This is particularly important if notifications are delivered to first-responders, such as engineers on-call, where receiving lots of notifications in a short period of time can be overwhelming. In some cases, it can negatively impact a first-responders ability to respond to an incident. For example, consider a large outage where many of your systems are down. In this case, grouping can be the difference between receiving 1 phone call and 100 phone calls. ## Group notifications @@ -42,7 +42,7 @@ Grouping combines similar alert instances within a specific period into a single In the notification policy, you can configure how to group multiple alerts into a single notification: - The `Group by` option specifies the criteria for grouping incoming alerts within the policy. The default is by alert rule. -- [Timing options](#timing-options) determine when to sent the notification. +- [Timing options](#timing-options) determine when and how often to send the notification. {{< figure src="/media/docs/alerting/alerting-notification-policy-diagram-with-labels-v3.png" max-width="750px" alt="A diagram about the components of a notification policy, including labels and groups" >}} @@ -65,7 +65,7 @@ If you want to group all alerts handled by the notification policy in a single g ### Disable grouping -If you want to receive every alert as a separate notification, you can do so by grouping by a special label called `...`. +If you want to receive every alert as a separate notification, you can do so by grouping by a special label called `...`, ensuring that other labels are not present. ## Timing options @@ -101,7 +101,7 @@ The longer the group wait, the more time other alerts have to be included in the Consider a notification policy that: -- Matches all alert instances with the `team` label—matching labels equals to `team=~".*"`. +- Matches all alert instances with the `team` label—matching labels equals to `team=~.+`. - Groups notifications by the `team` label—one group for each distinct `team`. - Sets the Group wait timer to `30s`. diff --git a/docs/sources/alerting/fundamentals/notifications/notification-policies.md b/docs/sources/alerting/fundamentals/notifications/notification-policies.md index 80eae663429..ab1200a2f68 100644 --- a/docs/sources/alerting/fundamentals/notifications/notification-policies.md +++ b/docs/sources/alerting/fundamentals/notifications/notification-policies.md @@ -88,7 +88,7 @@ The `team=security` policy is not a match and **Continue matching siblings** was {{< /collapse >}} -This routing and tree structure make it easy to organize and handle alerts for dedicated teams, while also narrowing down specific cases within the team by applying additional labels. +This routing and tree structure makes it convenient to organize and handle alerts for dedicated teams, while also narrowing down specific cases within the team by applying additional labels. ## Inheritance diff --git a/go.mod b/go.mod index d0b6dcd4aa2..7ab60b40d94 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240606211712-071c8609797a // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20240515154731-fe4779055ef4 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics @@ -320,7 +320,7 @@ require ( github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect @@ -437,8 +437,8 @@ require ( golang.org/x/term v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 032239d2534..3ddf346674f 100644 --- a/go.sum +++ b/go.sum @@ -2307,8 +2307,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20240606211712-071c8609797a h1:MvrEgxNxmUqaFWkBtRlbgxStbdD8FcmtjlRp98bmMJU= github.com/grafana/alerting v0.0.0-20240606211712-071c8609797a/go.mod h1:U7Ta3K4T7jVgqGSYuPsfuPKHFiL2GbCZSHa3nHjmCos= -github.com/grafana/authlib v0.0.0-20240515154731-fe4779055ef4 h1:Bfa397TXkM0X97MhbCC+fNSwVtg21c0Mg5STes1dRug= -github.com/grafana/authlib v0.0.0-20240515154731-fe4779055ef4/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= +github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f h1:hvRCAv+TgcHu3i/Sd7lFJx84iEtgzDCYuk7OWeXatD0= +github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f/go.mod h1:+MjD5sxxgLOIvw0ox18wJmjBzz8tOECo7quiiZAmgJY= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= @@ -2380,8 +2380,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= @@ -4381,8 +4381,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go. google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= -google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 h1:DTJM0R8LECCgFeUwApvcEJHz85HLagW8uRENYxHh1ww= -google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4427,8 +4427,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/go.work.sum b/go.work.sum index ef8b817603a..07147a44027 100644 --- a/go.work.sum +++ b/go.work.sum @@ -555,6 +555,7 @@ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240612101930-58f7032b3983/g github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -959,12 +960,17 @@ google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= diff --git a/package.json b/package.json index 31fc060a9c4..bbcda8d2c52 100644 --- a/package.json +++ b/package.json @@ -247,7 +247,7 @@ "@grafana/azure-sdk": "0.0.3", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/faro-core": "^1.3.6", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/flamegraph": "workspace:*", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index dbc3da4c8f5..fb7399368ca 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -193,4 +193,5 @@ export interface FeatureToggles { pluginProxyPreserveTrailingSlash?: boolean; azureMonitorPrometheusExemplars?: boolean; pinNavItems?: boolean; + authZGRPCServer?: boolean; } diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 79400eaa759..2e87d3a65d4 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -20,7 +20,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", "@grafana/e2e-selectors": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/schema": "11.0.0", "@grafana/ui": "11.0.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 8014bdce7c8..12263fb3980 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -39,7 +39,7 @@ "@emotion/css": "11.11.2", "@floating-ui/react": "0.26.16", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/faro-web-sdk": "1.7.3", "@grafana/runtime": "11.0.0", "@grafana/schema": "11.0.0", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 974ea27003d..f0971de38a4 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -17,7 +17,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", "@grafana/e2e-selectors": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/ui": "11.0.0", "@react-awesome-query-builder/ui": "6.5.2", diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 267f839233e..8c106bf0921 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -64,7 +64,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -139,8 +139,8 @@ require ( golang.org/x/tools v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index fad14b4c9fb..e5cb088403c 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -128,7 +128,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a534 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= @@ -401,8 +401,8 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 h1:DTJM0R8LECCgFeUwApvcEJHz85HLagW8uRENYxHh1ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 6a38a288347..247c0be48d1 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -55,7 +55,7 @@ require ( github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -111,8 +111,8 @@ require ( golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 4addec1214b..4254a7d3099 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -85,7 +85,7 @@ github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzed github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= @@ -277,8 +277,8 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 h1:DTJM0R8LECCgFeUwApvcEJHz85HLagW8uRENYxHh1ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index 4c5cc6b9164..7f53a8298b0 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -13,6 +13,7 @@ import ( grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn/authnimpl" + "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cloudmigration" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" @@ -67,8 +68,8 @@ func ProvideBackgroundServiceRegistry( _ dashboardsnapshots.Service, _ serviceaccounts.Service, _ *guardian.Provider, _ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider, - _ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService, _ *ldapapi.Service, - _ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI, _ ssosettings.Service, + _ *grpcserver.HealthService, _ entity.EntityStoreServer, _ authz.Client, _ *grpcserver.ReflectionService, + _ *ldapapi.Service, _ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI, _ ssosettings.Service, _ cloudmigration.Service, _ authnimpl.Registration, ) *BackgroundServiceRegistry { return NewBackgroundServiceRegistry( diff --git a/pkg/server/wire.go b/pkg/server/wire.go index e049c960f2f..ccf410105ab 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -50,6 +50,7 @@ import ( "github.com/grafana/grafana/pkg/services/auth/idimpl" "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/authn/authnimpl" + "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cloudmigration/cloudmigrationimpl" "github.com/grafana/grafana/pkg/services/contexthandler" @@ -379,6 +380,7 @@ var wireBasicSet = wire.NewSet( userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), + authz.WireSet, // Kubernetes API server grafanaapiserver.WireSet, apiregistry.WireSet, diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 6711144e803..aa0bae28b41 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -707,7 +707,7 @@ func (s *store) createPermissions(sess *db.Session, roleID int64, cmd SetResourc } func (s *store) shouldStoreActionSet(permission string) bool { - return (s.features.IsEnabled(context.TODO(), featuremgmt.FlagAccessActionSets) && permission != "") + return (s.features.IsEnabled(context.TODO(), featuremgmt.FlagAccessActionSets) && permission != "" && isFolderOrDashboardAction(permission)) } func deletePermissions(sess *db.Session, ids []int64) error { diff --git a/pkg/services/authz/README.md b/pkg/services/authz/README.md new file mode 100644 index 00000000000..aee78e4c399 --- /dev/null +++ b/pkg/services/authz/README.md @@ -0,0 +1,3 @@ +# Authorization + +This package contains the authorization server implementation. diff --git a/pkg/services/authz/client.go b/pkg/services/authz/client.go new file mode 100644 index 00000000000..f85afbdb56b --- /dev/null +++ b/pkg/services/authz/client.go @@ -0,0 +1,33 @@ +package authz + +import ( + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/grpcserver" + "github.com/grafana/grafana/pkg/setting" +) + +type Client interface { + // TODO +} + +type LegacyClient struct { +} + +func ProvideAuthZClient( + cfg *setting.Cfg, features featuremgmt.FeatureToggles, acSvc accesscontrol.Service, + grpcServer grpcserver.Provider, tracer tracing.Tracer, +) (Client, error) { + if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) { + return nil, nil + } + + _, err := newLegacyServer(acSvc, features, grpcServer, tracer) + if err != nil { + return nil, err + } + + // TODO differentiate run local from run remote grpc + return &LegacyClient{}, nil +} diff --git a/pkg/services/authz/server.go b/pkg/services/authz/server.go new file mode 100644 index 00000000000..71b0ceb0140 --- /dev/null +++ b/pkg/services/authz/server.go @@ -0,0 +1,66 @@ +package authz + +import ( + "context" + + authzv1 "github.com/grafana/authlib/authz/proto/v1" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/grpcserver" +) + +var _ authzv1.AuthzServiceServer = (*legacyServer)(nil) + +type legacyServer struct { + authzv1.UnimplementedAuthzServiceServer + + acSvc accesscontrol.Service + logger log.Logger + tracer tracing.Tracer +} + +func newLegacyServer( + acSvc accesscontrol.Service, features featuremgmt.FeatureToggles, + grpcServer grpcserver.Provider, tracer tracing.Tracer, +) (*legacyServer, error) { + if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) { + return nil, nil + } + + s := &legacyServer{ + acSvc: acSvc, + logger: log.New("authz-grpc-server"), + tracer: tracer, + } + + grpcServer.GetServer().RegisterService(&authzv1.AuthzService_ServiceDesc, s) + + return s, nil +} + +func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*authzv1.ReadResponse, error) { + ctx, span := s.tracer.Start(ctx, "authz.grpc.Read") + defer span.End() + + action := req.GetAction() + subject := req.GetSubject() + stackID := req.GetStackId() // TODO can we consider the stackID as the orgID? + + ctxLogger := s.logger.FromContext(ctx) + ctxLogger.Debug("Read", "action", action, "subject", subject, "stackID", stackID) + + permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, accesscontrol.SearchOptions{NamespacedID: subject, Action: action}) + if err != nil { + ctxLogger.Error("failed to search user permissions", "error", err) + return nil, tracing.Errorf(span, "failed to search user permissions: %w", err) + } + + data := make([]*authzv1.ReadResponse_Data, 0, len(permissions)) + for _, perm := range permissions { + data = append(data, &authzv1.ReadResponse_Data{Object: perm.Scope}) + } + return &authzv1.ReadResponse{Data: data}, nil +} diff --git a/pkg/services/authz/wireset.go b/pkg/services/authz/wireset.go new file mode 100644 index 00000000000..f16235fa4b4 --- /dev/null +++ b/pkg/services/authz/wireset.go @@ -0,0 +1,9 @@ +package authz + +import ( + "github.com/google/wire" +) + +var WireSet = wire.NewSet( + ProvideAuthZClient, +) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index c44823fa0c8..f06d92c15ca 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1308,6 +1308,14 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaFrontendPlatformSquad, }, + { + Name: "authZGRPCServer", + Description: "Enables the gRPC server for authorization", + Stage: FeatureStageExperimental, + Owner: identityAccessTeam, + HideFromAdminPage: true, + HideFromDocs: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index f76fb3ae227..992701dd5c1 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -174,3 +174,4 @@ alertingCentralAlertHistory,experimental,@grafana/alerting-squad,false,false,tru pluginProxyPreserveTrailingSlash,GA,@grafana/plugins-platform-backend,false,false,false azureMonitorPrometheusExemplars,experimental,@grafana/partner-datasources,false,false,false pinNavItems,experimental,@grafana/grafana-frontend-platform,false,false,false +authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 495ad2d3923..9afc6b29e29 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -706,4 +706,8 @@ const ( // FlagPinNavItems // Enables pinning of nav items FlagPinNavItems = "pinNavItems" + + // FlagAuthZGRPCServer + // Enables the gRPC server for authorization + FlagAuthZGRPCServer = "authZGRPCServer" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 922a10279ed..9103c3b0fc8 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2264,6 +2264,20 @@ "stage": "experimental", "codeowner": "@grafana/grafana-frontend-platform" } + }, + { + "metadata": { + "name": "authZGRPCServer", + "resourceVersion": "1718093439898", + "creationTimestamp": "2024-06-11T08:10:39Z" + }, + "spec": { + "description": "Enables the gRPC server for authorization", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } } ] } \ No newline at end of file diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 422eab15d93..9be53c2b10b 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -53,6 +53,10 @@ interface ImportOptions { folderUid: string; } +interface RestoreDashboardArgs { + dashboardUID: string; +} + function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryFn { async function backendSrvBaseQuery(requestOptions: RequestOptions) { try { @@ -148,6 +152,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + // move an *individual* folder. used in the folder actions menu. moveFolder: builder.mutation({ invalidatesTags: ['getFolder'], @@ -174,6 +179,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + // delete an *individual* folder. used in the folder actions menu. deleteFolder: builder.mutation({ query: ({ uid }) => ({ @@ -196,6 +202,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + // gets the descendant counts for a folder. used in the move/delete modals. getAffectedItems: builder.query({ queryFn: async (selectedItems) => { @@ -225,6 +232,7 @@ export const browseDashboardsAPI = createApi({ return { data: totalCounts }; }, }), + // move *multiple* items (folders and dashboards). used in the move modal. moveItems: builder.mutation({ invalidatesTags: ['getFolder'], @@ -276,6 +284,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + // delete *multiple* items (folders and dashboards). used in the delete modal. deleteItems: builder.mutation({ queryFn: async ({ selectedItems }, _api, _extraOptions, baseQuery) => { @@ -313,6 +322,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + // save an existing dashboard saveDashboard: builder.mutation({ query: ({ dashboard, folderUid, message, overwrite, showErrorAlert }) => ({ @@ -339,6 +349,7 @@ export const browseDashboardsAPI = createApi({ }); }, }), + importDashboard: builder.mutation({ query: ({ dashboard, overwrite, inputs, folderUid }) => ({ method: 'POST', @@ -363,6 +374,19 @@ export const browseDashboardsAPI = createApi({ }); }, }), + + // restore a dashboard that got soft deleted + restoreDashboard: builder.mutation({ + query: ({ dashboardUID }) => ({ + url: `/dashboards/uid/${dashboardUID}/trash`, + method: 'PATCH', + }), + onQueryStarted: ({ dashboardUID }, { queryFulfilled, dispatch }) => { + queryFulfilled.then(() => { + dispatch(refreshParents([dashboardUID])); + }); + }, + }), }), }); @@ -377,6 +401,7 @@ export const { useNewFolderMutation, useSaveDashboardMutation, useSaveFolderMutation, + useRestoreDashboardMutation, } = browseDashboardsAPI; export { skipToken } from '@reduxjs/toolkit/query/react'; diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index d2138cc8b57..bb4488f705f 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -753,6 +753,20 @@ describe('DashboardScene', () => { expect(gridItem.state.body!.state.key).toBe('panel-7'); }); + it('Should maintain size of duplicated panel', () => { + const gItem = (scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem; + gItem.setState({ height: 1 }); + const vizPanel = gItem.state.body; + scene.duplicatePanel(vizPanel as VizPanel); + + const body = scene.state.body as SceneGridLayout; + const newGridItem = body.state.children[5] as DashboardGridItem; + + expect(body.state.children.length).toBe(6); + expect(newGridItem.state.body!.state.key).toBe('panel-7'); + expect(newGridItem.state.height).toBe(1); + }); + it('Should duplicate a library panel', () => { const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state.body; const vizPanel = (libraryPanel as LibraryVizPanel).state.panel; diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index bf14cc0c422..c050e66ec37 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -594,8 +594,8 @@ export class DashboardScene extends SceneObjectBase { newGridItem = new DashboardGridItem({ x: gridItem.state.x, y: gridItem.state.y, - height: NEW_PANEL_HEIGHT, - width: NEW_PANEL_WIDTH, + height: gridItem.state.height, + width: gridItem.state.width, body: new VizPanel({ ...panelState, $data: panelData, key: getVizPanelKeyForPanelId(newPanelId) }), }); } diff --git a/public/app/features/inspector/utils/download.test.ts b/public/app/features/inspector/utils/download.test.ts index 7294ded12fe..6735731dabb 100644 --- a/public/app/features/inspector/utils/download.test.ts +++ b/public/app/features/inspector/utils/download.test.ts @@ -38,10 +38,25 @@ describe('inspector download', () => { const filename = call[1]; const text = await blob.text(); + // By default the BOM character should not be included + expect(await hasBOM(blob)).toBe(false); expect(text).toEqual(expected); expect(filename).toEqual(`${title}-data-${dateTimeFormat(1400000000000)}.csv`); } ); + + it('should include the BOM character when useExcelHeader is true', async () => { + downloadDataFrameAsCsv(dataFrameFromJSON(json), 'test', { useExcelHeader: true }); + + const call = (saveAs as unknown as jest.Mock).mock.calls[0]; + const blob = call[0]; + const filename = call[1]; + const text = await blob.text(); + + expect(await hasBOM(blob)).toBe(true); + expect(text).toEqual('sep=,\r\n"time","name","value"\r\n100,a,1'); + expect(filename).toEqual(`test-data-${dateTimeFormat(1400000000000)}.csv`); + }); }); describe('downloadAsJson', () => { @@ -104,3 +119,19 @@ describe('inspector download', () => { }); }); }); + +async function hasBOM(blob: Blob) { + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = (event: ProgressEvent) => { + if (event.target?.result instanceof ArrayBuffer) { + const arr = new Uint8Array(event.target.result); + resolve(arr[0] === 0xef && arr[1] === 0xbb && arr[2] === 0xbf); // Check for UTF-8 BOM + } else { + reject(new Error('Unexpected FileReader result type')); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(blob.slice(0, 3)); // Read only the first 3 bytes + }); +} diff --git a/public/app/features/inspector/utils/download.ts b/public/app/features/inspector/utils/download.ts index 47c52a47f3a..2a7c203677b 100644 --- a/public/app/features/inspector/utils/download.ts +++ b/public/app/features/inspector/utils/download.ts @@ -57,8 +57,9 @@ export function downloadDataFrameAsCsv( transformId: DataTransformerID = DataTransformerID.noop ) { const dataFrameCsv = toCSV([dataFrame], csvConfig); + const bomChar = csvConfig?.useExcelHeader ? String.fromCharCode(0xfeff) : ''; - const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], { + const blob = new Blob([bomChar, dataFrameCsv], { type: 'text/csv;charset=utf-8', }); diff --git a/public/app/features/manage-dashboards/RecentlyDeletedPage.tsx b/public/app/features/manage-dashboards/RecentlyDeletedPage.tsx index 12f08f4bd6d..c54bb7b6688 100644 --- a/public/app/features/manage-dashboards/RecentlyDeletedPage.tsx +++ b/public/app/features/manage-dashboards/RecentlyDeletedPage.tsx @@ -10,6 +10,7 @@ import { SearchView } from '../browse-dashboards/components/SearchView'; import { getFolderPermissions } from '../browse-dashboards/permissions'; import { setAllSelection } from '../browse-dashboards/state'; +import { RecentlyDeletedActions } from './state/RecentlyDeletedActions'; import { useRecentlyDeletedStateManager } from './utils/useRecentlyDeletedStateManager'; const RecentlyDeletedPage = memo(() => { @@ -47,6 +48,7 @@ const RecentlyDeletedPage = memo(() => { onPanelTypeChange={stateManager.onPanelTypeChange} onSetIncludePanels={stateManager.onSetIncludePanels} /> + {({ width, height }) => ( Promise; + onDismiss: () => void; + selectedItems: DashboardTreeSelection; + isLoading: boolean; +} + +export const RestoreModal = ({ onConfirm, onDismiss, selectedItems, isLoading, ...props }: Props) => { + const numberOfDashboards = selectedItems ? Object.keys(selectedItems.dashboard).length : 0; + const onRestore = async () => { + await onConfirm(); + onDismiss(); + }; + return ( + + + This action will restore {{ numberOfDashboards }} dashboards. + + + // TODO: replace by list of dashboards (list up to 5 dashboards) or number (from 6 dashboards)? + } + confirmText={ + isLoading + ? t('recently-deleted.restore-modal.restore-loading', 'Restoring...') + : t('recently-deleted.restore-modal.restore-button', 'Restore') + } + confirmButtonVariant="primary" + onDismiss={onDismiss} + onConfirm={onRestore} + title={t('recently-deleted.restore-modal.title', 'Restore Dashboards')} + {...props} + /> + ); +}; diff --git a/public/app/features/manage-dashboards/state/RecentlyDeletedActions.tsx b/public/app/features/manage-dashboards/state/RecentlyDeletedActions.tsx new file mode 100644 index 00000000000..a46efec3911 --- /dev/null +++ b/public/app/features/manage-dashboards/state/RecentlyDeletedActions.tsx @@ -0,0 +1,65 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data/'; +import { Button, useStyles2 } from '@grafana/ui'; + +import appEvents from '../../../core/app_events'; +import { Trans } from '../../../core/internationalization'; +import { useDispatch } from '../../../types'; +import { ShowModalReactEvent } from '../../../types/events'; +import { useRestoreDashboardMutation } from '../../browse-dashboards/api/browseDashboardsAPI'; +import { setAllSelection, useActionSelectionState } from '../../browse-dashboards/state'; +import { RestoreModal } from '../components/RestoreModal'; +import { useRecentlyDeletedStateManager } from '../utils/useRecentlyDeletedStateManager'; + +export function RecentlyDeletedActions() { + const styles = useStyles2(getStyles); + + const dispatch = useDispatch(); + const selectedItems = useActionSelectionState(); + const [, stateManager] = useRecentlyDeletedStateManager(); + + const [restoreDashboard, { isLoading: isRestoreLoading }] = useRestoreDashboardMutation(); + + const onActionComplete = () => { + dispatch(setAllSelection({ isSelected: false, folderUID: undefined })); + + stateManager.doSearchWithDebounce(); + }; + + const onRestore = async () => { + const promises = Object.entries(selectedItems.dashboard) + .filter(([_, selected]) => selected) + .map(([uid]) => restoreDashboard({ dashboardUID: uid })); + await Promise.all(promises); + onActionComplete(); + }; + + const showRestoreModal = () => { + appEvents.publish( + new ShowModalReactEvent({ + component: RestoreModal, + props: { + selectedItems, + onConfirm: onRestore, + isLoading: isRestoreLoading, + }, + }) + ); + }; + + return ( +
+ +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + row: css({ + marginBottom: theme.spacing(2), + }), +}); diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index f898b01edea..fb37fa00e45 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/schema": "11.0.0", "@grafana/ui": "11.0.0", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 02dede38600..f1a271bf79d 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/google-sdk": "0.1.2", "@grafana/runtime": "11.0.0", "@grafana/schema": "11.0.0", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index d32849ca69a..2086155cd44 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/sql": "11.0.0", "@grafana/ui": "11.0.0", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index b658a86279f..32cd809309f 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/schema": "11.0.0", "@grafana/ui": "11.0.0", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index b0639be1eff..bb1df958d75 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 19f87ada27c..0b785605838 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "11.0.0", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/runtime": "11.0.0", "@grafana/sql": "11.0.0", "@grafana/ui": "11.0.0", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index be1195533f0..ba687ff8104 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/lezer-logql": "0.2.4", "@grafana/lezer-traceql": "0.0.17", "@grafana/monaco-logql": "^0.0.7", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index c65ab03052b..34554cdd784 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "1.7.11", + "@grafana/experimental": "1.7.12", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index e9192406205..fd946b729a6 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1384,6 +1384,16 @@ "revoke-public-URL-button-title": "Öffentliche URL widerrufen", "settings-title": "Einstellungen" }, + "configuration": { + "display-annotations-label": "", + "enable-time-range-label": "", + "settings-label": "", + "success-pause": "", + "success-resume": "", + "success-update": "", + "success-update-old": "", + "time-range-tooltip": "" + }, "create-page": { "generate-public-url-button": "Öffentliche URL generieren", "unsupported-features-desc": "Momentan unterstützen wir keine Template-Variablen oder Frontend-Datenquellen", @@ -1395,15 +1405,32 @@ "revoke-title": "Öffentliche URL widerrufen" }, "email-sharing": { + "accept-button": "", + "alert-text": "", + "bill-ack": "", + "cancel-button": "", "input-invalid-email-text": "Ungültige E-Mail", "input-required-email-text": "E-Mail-Adresse ist erforderlich", "invite-button": "Einladen", "invite-field-desc": "Personen per E-Mail einladen", "invite-field-label": "Einladen", + "learn-more-button": "", + "recipient-email-placeholder": "", + "recipient-invalid-email-text": "", + "recipient-invitation-button": "", + "recipient-invitation-description": "", + "recipient-invitation-tooltip": "", + "recipient-list-description": "", + "recipient-list-title": "", + "recipient-required-email-text": "", "resend-button": "Erneut senden", "resend-button-title": "Erneut senden", + "resend-invite-label": "", + "revoke-access-label": "", "revoke-button": "Widerrufen", - "revoke-button-title": "Widerrufen" + "revoke-button-title": "Widerrufen", + "success-creation": "", + "success-share-type-change": "" }, "modal-alerts": { "no-upsert-perm-alert-desc": "Kontaktieren Sie Ihren Administrator, um die Berechtigung zu {{mode}} öffentlichen Dashboards zu erhalten", @@ -1415,6 +1442,15 @@ "unsupported-template-variable-alert-desc": "Dieses öffentliche Dashboard funktioniert möglicherweise nicht, da es Template-Variablen verwendet", "unsupported-template-variable-alert-title": "Template-Variablen werden nicht unterstützt" }, + "public-sharing": { + "accept-button": "", + "alert-text": "", + "cancel-button": "", + "learn-more-button": "", + "public-ack": "", + "success-creation": "", + "success-share-type-change": "" + }, "settings-bar-header": { "collapse-settings-tooltip": "Einstellungen einklappen", "expand-settings-tooltip": "Einstellungen aufklappen" @@ -1433,6 +1469,28 @@ "time-range-picker-disabled-text": "Zeitraumauswahl = deaktiviert", "time-range-picker-enabled-text": "Zeitraumauswahl = aktiviert", "time-range-text": "Zeitraum = " + }, + "share": { + "success-delete": "", + "success-delete-old": "" + }, + "share-configuration": { + "access-label": "", + "share-type-label": "" + }, + "share-externally": { + "copy-link-button": "", + "email-share-type-option-description": "", + "email-share-type-option-label": "", + "pause-access-button": "", + "pause-access-tooltip": "", + "public-share-type-option-description": "", + "public-share-type-option-label": "", + "resume-access-button": "", + "revoke-access-button": "" + }, + "sharing": { + "success-creation": "" } }, "public-dashboard-list": { @@ -1495,6 +1553,18 @@ }, "query-editor-not-exported": "Datenquellen-Plugin exportiert keine Komponente des Abfrageeditors" }, + "recently-deleted": { + "buttons": { + "restore": "" + }, + "restore-modal": { + "restore-button": "", + "restore-loading": "", + "title": "", + "text_one": "", + "text_other": "" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Automatische Aktualisierung ausgeschaltet. Aktualisierungszeitintervall auswählen", diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 331632b831a..6d766c64cdf 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1553,6 +1553,18 @@ }, "query-editor-not-exported": "Data source plugin does not export any Query Editor component" }, + "recently-deleted": { + "buttons": { + "restore": "Restore" + }, + "restore-modal": { + "restore-button": "Restore", + "restore-loading": "Restoring...", + "text_one": "This action will restore {{numberOfDashboards}} dashboard.", + "text_other": "This action will restore {{numberOfDashboards}} dashboards.", + "title": "Restore Dashboards" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Auto refresh turned off. Choose refresh time interval", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index d7595475012..b36bdb76687 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1384,6 +1384,16 @@ "revoke-public-URL-button-title": "Revocar URL pública", "settings-title": "Configuración" }, + "configuration": { + "display-annotations-label": "", + "enable-time-range-label": "", + "settings-label": "", + "success-pause": "", + "success-resume": "", + "success-update": "", + "success-update-old": "", + "time-range-tooltip": "" + }, "create-page": { "generate-public-url-button": "Generar URL pública", "unsupported-features-desc": "Actualmente, no son compatibles las variables de plantillas o las fuentes de datos de la interfaz", @@ -1395,15 +1405,32 @@ "revoke-title": "Revocar URL pública" }, "email-sharing": { + "accept-button": "", + "alert-text": "", + "bill-ack": "", + "cancel-button": "", "input-invalid-email-text": "Correo no válido", "input-required-email-text": "El correo electrónico es obligatorio", "invite-button": "Invitar", "invite-field-desc": "Invitar a otras personas por correo electrónico", "invite-field-label": "Invitar", + "learn-more-button": "", + "recipient-email-placeholder": "", + "recipient-invalid-email-text": "", + "recipient-invitation-button": "", + "recipient-invitation-description": "", + "recipient-invitation-tooltip": "", + "recipient-list-description": "", + "recipient-list-title": "", + "recipient-required-email-text": "", "resend-button": "Reenviar", "resend-button-title": "Reenviar", + "resend-invite-label": "", + "revoke-access-label": "", "revoke-button": "Revocar", - "revoke-button-title": "Revocar" + "revoke-button-title": "Revocar", + "success-creation": "", + "success-share-type-change": "" }, "modal-alerts": { "no-upsert-perm-alert-desc": "Ponte en contacto con tu administrador para obtener permiso para {{mode}} los tableros públicos", @@ -1415,6 +1442,15 @@ "unsupported-template-variable-alert-desc": "Este tablero público puede que no funcione, ya que utiliza variables de plantillas", "unsupported-template-variable-alert-title": "Las variables de plantillas no son compatibles" }, + "public-sharing": { + "accept-button": "", + "alert-text": "", + "cancel-button": "", + "learn-more-button": "", + "public-ack": "", + "success-creation": "", + "success-share-type-change": "" + }, "settings-bar-header": { "collapse-settings-tooltip": "Contraer ajustes", "expand-settings-tooltip": "Expandir ajustes" @@ -1433,6 +1469,28 @@ "time-range-picker-disabled-text": "Selector de rango de tiempo = desactivado", "time-range-picker-enabled-text": "Selector de rango de tiempo = activado", "time-range-text": "Rango de tiempo = " + }, + "share": { + "success-delete": "", + "success-delete-old": "" + }, + "share-configuration": { + "access-label": "", + "share-type-label": "" + }, + "share-externally": { + "copy-link-button": "", + "email-share-type-option-description": "", + "email-share-type-option-label": "", + "pause-access-button": "", + "pause-access-tooltip": "", + "public-share-type-option-description": "", + "public-share-type-option-label": "", + "resume-access-button": "", + "revoke-access-button": "" + }, + "sharing": { + "success-creation": "" } }, "public-dashboard-list": { @@ -1495,6 +1553,18 @@ }, "query-editor-not-exported": "El complemento de la fuente de datos no exporta ningún componente del editor de consultas" }, + "recently-deleted": { + "buttons": { + "restore": "" + }, + "restore-modal": { + "restore-button": "", + "restore-loading": "", + "title": "", + "text_one": "", + "text_other": "" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Actualización automática desactivada. Elija un intervalo de tiempo de actualización", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index a82c54f7606..786dff71b71 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1384,6 +1384,16 @@ "revoke-public-URL-button-title": "Désactiver l'URL publique", "settings-title": "Paramètres" }, + "configuration": { + "display-annotations-label": "", + "enable-time-range-label": "", + "settings-label": "", + "success-pause": "", + "success-resume": "", + "success-update": "", + "success-update-old": "", + "time-range-tooltip": "" + }, "create-page": { "generate-public-url-button": "Générer une URL publique", "unsupported-features-desc": "Actuellement, nous ne prenons pas en charge les variables de modèle ou les données provenant des utilisateurs", @@ -1395,15 +1405,32 @@ "revoke-title": "Désactiver l'URL publique" }, "email-sharing": { + "accept-button": "", + "alert-text": "", + "bill-ack": "", + "cancel-button": "", "input-invalid-email-text": "E-mail non valide", "input-required-email-text": "Une adresse e-mail est obligatoire", "invite-button": "Inviter", "invite-field-desc": "Invitez des personnes par e-mail", "invite-field-label": "Inviter", + "learn-more-button": "", + "recipient-email-placeholder": "", + "recipient-invalid-email-text": "", + "recipient-invitation-button": "", + "recipient-invitation-description": "", + "recipient-invitation-tooltip": "", + "recipient-list-description": "", + "recipient-list-title": "", + "recipient-required-email-text": "", "resend-button": "Renvoyer", "resend-button-title": "Renvoyer", + "resend-invite-label": "", + "revoke-access-label": "", "revoke-button": "Retirer", - "revoke-button-title": "Retirer" + "revoke-button-title": "Retirer", + "success-creation": "", + "success-share-type-change": "" }, "modal-alerts": { "no-upsert-perm-alert-desc": "Contactez votre administrateur pour obtenir la permission d'accéder aux tableaux de bord publics {{mode}}", @@ -1415,6 +1442,15 @@ "unsupported-template-variable-alert-desc": "Ce tableau de bord public peut ne pas fonctionner car il utilise des variables de modèle", "unsupported-template-variable-alert-title": "Les variables de modèle ne sont pas prises en charge" }, + "public-sharing": { + "accept-button": "", + "alert-text": "", + "cancel-button": "", + "learn-more-button": "", + "public-ack": "", + "success-creation": "", + "success-share-type-change": "" + }, "settings-bar-header": { "collapse-settings-tooltip": "Réduire les paramètres", "expand-settings-tooltip": "Développer les paramètres" @@ -1433,6 +1469,28 @@ "time-range-picker-disabled-text": "Sélecteur de plage de temps = désactivé", "time-range-picker-enabled-text": "Sélecteur de plage de temps = activé", "time-range-text": "Plage de temps = " + }, + "share": { + "success-delete": "", + "success-delete-old": "" + }, + "share-configuration": { + "access-label": "", + "share-type-label": "" + }, + "share-externally": { + "copy-link-button": "", + "email-share-type-option-description": "", + "email-share-type-option-label": "", + "pause-access-button": "", + "pause-access-tooltip": "", + "public-share-type-option-description": "", + "public-share-type-option-label": "", + "resume-access-button": "", + "revoke-access-button": "" + }, + "sharing": { + "success-creation": "" } }, "public-dashboard-list": { @@ -1495,6 +1553,18 @@ }, "query-editor-not-exported": "Le plugin source de données n'exporte aucun composant de l'éditeur de requête" }, + "recently-deleted": { + "buttons": { + "restore": "" + }, + "restore-modal": { + "restore-button": "", + "restore-loading": "", + "title": "", + "text_one": "", + "text_other": "" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Actualisation automatique désactivée. Choisir un intervalle de temps d'actualisation", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 59286371b22..71af32ec586 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1553,6 +1553,18 @@ }, "query-editor-not-exported": "Đäŧä şőūřčę pľūģįʼn đőęş ʼnőŧ ęχpőřŧ äʼny Qūęřy Ēđįŧőř čőmpőʼnęʼnŧ" }, + "recently-deleted": { + "buttons": { + "restore": "Ŗęşŧőřę" + }, + "restore-modal": { + "restore-button": "Ŗęşŧőřę", + "restore-loading": "Ŗęşŧőřįʼnģ...", + "text_one": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđ.", + "text_other": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđş.", + "title": "Ŗęşŧőřę Đäşĥþőäřđş" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Åūŧő řęƒřęşĥ ŧūřʼnęđ őƒƒ. Cĥőőşę řęƒřęşĥ ŧįmę įʼnŧęřväľ", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 538030fec77..564843dda42 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1384,6 +1384,16 @@ "revoke-public-URL-button-title": "Revogar URL público", "settings-title": "Configurações" }, + "configuration": { + "display-annotations-label": "", + "enable-time-range-label": "", + "settings-label": "", + "success-pause": "", + "success-resume": "", + "success-update": "", + "success-update-old": "", + "time-range-tooltip": "" + }, "create-page": { "generate-public-url-button": "Gerar URL público", "unsupported-features-desc": "Atualmente, não oferecemos suporte a variáveis de modelo ou fontes de dados frontend", @@ -1395,15 +1405,32 @@ "revoke-title": "Revogar URL público" }, "email-sharing": { + "accept-button": "", + "alert-text": "", + "bill-ack": "", + "cancel-button": "", "input-invalid-email-text": "E-mail inválido", "input-required-email-text": "O e-mail é obrigatório", "invite-button": "Convidar", "invite-field-desc": "Convidar pessoas por e-mail", "invite-field-label": "Convidar", + "learn-more-button": "", + "recipient-email-placeholder": "", + "recipient-invalid-email-text": "", + "recipient-invitation-button": "", + "recipient-invitation-description": "", + "recipient-invitation-tooltip": "", + "recipient-list-description": "", + "recipient-list-title": "", + "recipient-required-email-text": "", "resend-button": "Reenviar", "resend-button-title": "Reenviar", + "resend-invite-label": "", + "revoke-access-label": "", "revoke-button": "Revogar", - "revoke-button-title": "Revogar" + "revoke-button-title": "Revogar", + "success-creation": "", + "success-share-type-change": "" }, "modal-alerts": { "no-upsert-perm-alert-desc": "Entre em contato com seu administrador para obter permissão para painéis de controle {{mode}} públicos", @@ -1415,6 +1442,15 @@ "unsupported-template-variable-alert-desc": "Este painel de controle público pode não funcionar, pois ele usa variáveis de modelo", "unsupported-template-variable-alert-title": "As variáveis de modelo não são suportadas" }, + "public-sharing": { + "accept-button": "", + "alert-text": "", + "cancel-button": "", + "learn-more-button": "", + "public-ack": "", + "success-creation": "", + "success-share-type-change": "" + }, "settings-bar-header": { "collapse-settings-tooltip": "Recolher configurações", "expand-settings-tooltip": "Expandir configurações" @@ -1433,6 +1469,28 @@ "time-range-picker-disabled-text": "Seletor de intervalo de tempo = desativado", "time-range-picker-enabled-text": "Seletor de intervalo de tempo = ativado", "time-range-text": "Intervalo de tempo = " + }, + "share": { + "success-delete": "", + "success-delete-old": "" + }, + "share-configuration": { + "access-label": "", + "share-type-label": "" + }, + "share-externally": { + "copy-link-button": "", + "email-share-type-option-description": "", + "email-share-type-option-label": "", + "pause-access-button": "", + "pause-access-tooltip": "", + "public-share-type-option-description": "", + "public-share-type-option-label": "", + "resume-access-button": "", + "revoke-access-button": "" + }, + "sharing": { + "success-creation": "" } }, "public-dashboard-list": { @@ -1495,6 +1553,18 @@ }, "query-editor-not-exported": "O plug-in de origem de dados não exporta nenhum componente de editor de consulta" }, + "recently-deleted": { + "buttons": { + "restore": "" + }, + "restore-modal": { + "restore-button": "", + "restore-loading": "", + "title": "", + "text_one": "", + "text_other": "" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "Atualização automática desativada. Escolha o intervalo de tempo de atualização", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index ed98538b140..2f4f5116ca5 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1378,6 +1378,16 @@ "revoke-public-URL-button-title": "注销公共网址", "settings-title": "设置" }, + "configuration": { + "display-annotations-label": "", + "enable-time-range-label": "", + "settings-label": "", + "success-pause": "", + "success-resume": "", + "success-update": "", + "success-update-old": "", + "time-range-tooltip": "" + }, "create-page": { "generate-public-url-button": "生成公共网址", "unsupported-features-desc": "目前,我们不支持模板变量或前端数据源", @@ -1389,15 +1399,32 @@ "revoke-title": "注销公共网址" }, "email-sharing": { + "accept-button": "", + "alert-text": "", + "bill-ack": "", + "cancel-button": "", "input-invalid-email-text": "无效电子邮箱", "input-required-email-text": "电子邮箱是必填项", "invite-button": "邀请", "invite-field-desc": "通过电子邮件邀请人员", "invite-field-label": "邀请", + "learn-more-button": "", + "recipient-email-placeholder": "", + "recipient-invalid-email-text": "", + "recipient-invitation-button": "", + "recipient-invitation-description": "", + "recipient-invitation-tooltip": "", + "recipient-list-description": "", + "recipient-list-title": "", + "recipient-required-email-text": "", "resend-button": "重新发送", "resend-button-title": "重新发送", + "resend-invite-label": "", + "revoke-access-label": "", "revoke-button": "注销", - "revoke-button-title": "注销" + "revoke-button-title": "注销", + "success-creation": "", + "success-share-type-change": "" }, "modal-alerts": { "no-upsert-perm-alert-desc": "请联系您的管理员以获得{{mode}}公共仪表板的权限", @@ -1409,6 +1436,15 @@ "unsupported-template-variable-alert-desc": "此公共仪表板可能无法工作,因为其使用了模板变量", "unsupported-template-variable-alert-title": "模板变量不受支持" }, + "public-sharing": { + "accept-button": "", + "alert-text": "", + "cancel-button": "", + "learn-more-button": "", + "public-ack": "", + "success-creation": "", + "success-share-type-change": "" + }, "settings-bar-header": { "collapse-settings-tooltip": "折叠设置", "expand-settings-tooltip": "展开设置" @@ -1427,6 +1463,28 @@ "time-range-picker-disabled-text": "时间范围选择器 = 已禁用", "time-range-picker-enabled-text": "时间范围选择器 = 已启用", "time-range-text": "时间范围 = " + }, + "share": { + "success-delete": "", + "success-delete-old": "" + }, + "share-configuration": { + "access-label": "", + "share-type-label": "" + }, + "share-externally": { + "copy-link-button": "", + "email-share-type-option-description": "", + "email-share-type-option-label": "", + "pause-access-button": "", + "pause-access-tooltip": "", + "public-share-type-option-description": "", + "public-share-type-option-label": "", + "resume-access-button": "", + "revoke-access-button": "" + }, + "sharing": { + "success-creation": "" } }, "public-dashboard-list": { @@ -1489,6 +1547,17 @@ }, "query-editor-not-exported": "数据源插件不导出任何查询编辑器组件" }, + "recently-deleted": { + "buttons": { + "restore": "" + }, + "restore-modal": { + "restore-button": "", + "restore-loading": "", + "title": "", + "text_other": "" + } + }, "refresh-picker": { "aria-label": { "choose-interval": "自动刷新已关闭。选择刷新时间间隔", diff --git a/yarn.lock b/yarn.lock index f857837a013..a83d5997e87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2573,7 +2573,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/plugin-configs": "npm:11.0.0" "@grafana/runtime": "npm:11.0.0" "@grafana/schema": "npm:11.0.0" @@ -2617,7 +2617,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/plugin-configs": "npm:11.0.0" "@grafana/runtime": "npm:11.0.0" "@grafana/sql": "npm:11.0.0" @@ -2689,7 +2689,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/plugin-configs": "npm:11.0.0" "@grafana/runtime": "npm:11.0.0" "@grafana/schema": "npm:11.0.0" @@ -2730,7 +2730,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -2772,7 +2772,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/plugin-configs": "npm:11.0.0" "@grafana/runtime": "npm:11.0.0" "@grafana/sql": "npm:11.0.0" @@ -2835,7 +2835,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/google-sdk": "npm:0.1.2" "@grafana/plugin-configs": "npm:11.0.0" "@grafana/runtime": "npm:11.0.0" @@ -2883,7 +2883,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/lezer-logql": "npm:0.2.4" "@grafana/lezer-traceql": "npm:0.0.17" "@grafana/monaco-logql": "npm:^0.0.7" @@ -2943,7 +2943,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -3121,9 +3121,9 @@ __metadata: languageName: node linkType: hard -"@grafana/experimental@npm:1.7.11": - version: 1.7.11 - resolution: "@grafana/experimental@npm:1.7.11" +"@grafana/experimental@npm:1.7.12": + version: 1.7.12 + resolution: "@grafana/experimental@npm:1.7.12" dependencies: "@types/uuid": "npm:^8.3.3" lodash: "npm:^4.17.21" @@ -3143,7 +3143,7 @@ __metadata: react-dom: 17.0.2 react-select: ^5.2.1 rxjs: 7.8.0 - checksum: 10/c0950b943f10b490f768046a081cc29a3dc8103d0154574a36beaa2af6fab4e429bbfb0767c5c89b5fd8ac3f3fc5936997b62e9db4e75ceaeb40837b234bb84e + checksum: 10/fd70c6925ce31fca8508c17c1722481f9649ce65383be166a05825e66a7ff47401fef84b55e74db5714be63e5c1cdf0f571fddec186669b74d0e2b005f4bf829 languageName: node linkType: hard @@ -3262,7 +3262,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/runtime": "npm:11.0.0" "@grafana/schema": "npm:11.0.0" "@grafana/tsconfig": "npm:^1.3.0-rc1" @@ -3333,7 +3333,7 @@ __metadata: "@floating-ui/react": "npm:0.26.16" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/faro-web-sdk": "npm:1.7.3" "@grafana/runtime": "npm:11.0.0" "@grafana/schema": "npm:11.0.0" @@ -3559,7 +3559,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.0.0" "@grafana/e2e-selectors": "npm:11.0.0" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/runtime": "npm:11.0.0" "@grafana/tsconfig": "npm:^1.3.0-rc1" "@grafana/ui": "npm:11.0.0" @@ -16969,7 +16969,7 @@ __metadata: "@grafana/e2e-selectors": "workspace:*" "@grafana/eslint-config": "npm:7.0.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" - "@grafana/experimental": "npm:1.7.11" + "@grafana/experimental": "npm:1.7.12" "@grafana/faro-core": "npm:^1.3.6" "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/flamegraph": "workspace:*"