Compare commits
22 Commits
remove-upd
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db1607179a | ||
|
|
b57b8d4359 | ||
|
|
5219ccddb6 | ||
|
|
c95e3da2d5 | ||
|
|
43d9fbc056 | ||
|
|
7b80c44ac7 | ||
|
|
98f271f345 | ||
|
|
60c4fab063 | ||
|
|
ce8663ac24 | ||
|
|
5dd9a14903 | ||
|
|
68bf19d840 | ||
|
|
220c29de89 | ||
|
|
91ab753368 | ||
|
|
250ca7985f | ||
|
|
b57ed32484 | ||
|
|
d0217588a3 | ||
|
|
ce9ab6a89a | ||
|
|
8c8efd2494 | ||
|
|
69ccfd6bfc | ||
|
|
53aa5e8f7f | ||
|
|
69bf3068b3 | ||
|
|
1263a3d364 |
7
Makefile
7
Makefile
@@ -135,7 +135,7 @@ i18n-extract-enterprise:
|
||||
@echo "Skipping i18n extract for Enterprise: not enabled"
|
||||
else
|
||||
i18n-extract-enterprise:
|
||||
@echo "Extracting i18n strings for Enterprise"
|
||||
@echo "Extracting i18n strings for Enterprise"
|
||||
cd public/locales/enterprise && yarn run i18next-cli extract --sync-primary
|
||||
endif
|
||||
|
||||
@@ -227,6 +227,10 @@ fix-cue:
|
||||
gen-jsonnet:
|
||||
go generate ./devenv/jsonnet
|
||||
|
||||
.PHONY: gen-themes
|
||||
gen-themes:
|
||||
go generate ./pkg/services/preference
|
||||
|
||||
.PHONY: update-workspace
|
||||
update-workspace: gen-go
|
||||
@echo "updating workspace"
|
||||
@@ -244,6 +248,7 @@ build-go-fast: ## Build all Go binaries without updating workspace.
|
||||
.PHONY: build-backend
|
||||
build-backend: ## Build Grafana backend.
|
||||
@echo "build backend"
|
||||
$(MAKE) gen-themes
|
||||
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
|
||||
|
||||
.PHONY: build-air
|
||||
|
||||
@@ -336,7 +336,7 @@ rudderstack_data_plane_url =
|
||||
rudderstack_sdk_url =
|
||||
|
||||
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
|
||||
rudderstack_v3_sdk_url =
|
||||
rudderstack_v3_sdk_url =
|
||||
|
||||
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
|
||||
rudderstack_config_url =
|
||||
@@ -2079,8 +2079,14 @@ enable =
|
||||
# To enable features by default, set `Expression: "true"` in:
|
||||
# https://github.com/grafana/grafana/blob/main/pkg/services/featuremgmt/registry.go
|
||||
|
||||
# The feature_toggles section supports feature flags of a number of types,
|
||||
# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
|
||||
#
|
||||
# feature1 = true
|
||||
# feature2 = false
|
||||
# feature3 = "foobar"
|
||||
# feature4 = 1.5
|
||||
# feature5 = { "foo": "bar" }
|
||||
|
||||
[feature_toggles.openfeature]
|
||||
# This is EXPERIMENTAL. Please, do not use this section
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
;rudderstack_sdk_url =
|
||||
|
||||
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
|
||||
;rudderstack_v3_sdk_url =
|
||||
;rudderstack_v3_sdk_url =
|
||||
|
||||
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
|
||||
;rudderstack_config_url =
|
||||
@@ -1913,7 +1913,7 @@ default_datasource_uid =
|
||||
|
||||
# client_queue_max_size is the maximum size in bytes of the client queue
|
||||
# for Live connections. Defaults to 4MB.
|
||||
;client_queue_max_size =
|
||||
;client_queue_max_size =
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
@@ -1996,9 +1996,14 @@ default_datasource_uid =
|
||||
|
||||
;enable = feature1,feature2
|
||||
|
||||
# The feature_toggles section supports feature flags of a number of types,
|
||||
# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
|
||||
|
||||
;feature1 = true
|
||||
;feature2 = false
|
||||
|
||||
;feature3 = "foobar"
|
||||
;feature4 = 1.5
|
||||
;feature5 = { "foo": "bar" }
|
||||
[date_formats]
|
||||
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||
|
||||
|
||||
@@ -66,17 +66,18 @@ Please refer to plugin documentation to see what RBAC permissions the plugin has
|
||||
|
||||
The following list contains app plugins that have fine-grained RBAC support.
|
||||
|
||||
| App plugin | App plugin ID | App plugin permission documentation |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
|
||||
| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
|
||||
| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
|
||||
| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
|
||||
| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
|
||||
| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
|
||||
| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
|
||||
| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
|
||||
| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
|
||||
| App plugin | App plugin ID | App plugin permission documentation |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
|
||||
| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
|
||||
| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
|
||||
| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
|
||||
| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
|
||||
| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
|
||||
| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
|
||||
| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
|
||||
| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
|
||||
| [Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/) | `grafana-synthetic-monitoring-app` | [Configure RBAC for Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/user-and-team-management/) |
|
||||
|
||||
### Revoke fine-grained access from app plugins
|
||||
|
||||
|
||||
@@ -2836,9 +2836,11 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise](../
|
||||
|
||||
Keys of features to enable, separated by space.
|
||||
|
||||
#### `FEATURE_TOGGLE_NAME = false`
|
||||
#### `FEATURE_NAME = <value>`
|
||||
|
||||
Some feature toggles for stable features are on by default. Use this setting to disable an on-by-default feature toggle with the name FEATURE_TOGGLE_NAME, for example, `exploreMixedDatasource = false`.
|
||||
Use a key-value pair to set feature flag values explicitly, overriding any default values. A few different types are supported, following the OpenFeature specification. See the defaults.ini file for more details.
|
||||
|
||||
For example, to disable an on-by-default feature toggle named `exploreMixedDatasource`, specify `exploreMixedDatasource = false`.
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = false
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[feature_toggles]
|
||||
unifiedStorageSearchUI = true
|
||||
grafanaAPIServerWithExperimentalAPIs = true
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
|
||||
@@ -1156,11 +1156,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/core/config.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/core/navigation/types.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
|
||||
44
go.mod
44
go.mod
@@ -31,16 +31,16 @@ require (
|
||||
github.com/apache/arrow-go/v18 v18.4.1 // @grafana/plugins-platform-backend
|
||||
github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // @grafana/grafana-operator-experience-squad
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect; @grafana/grafana-operator-experience-squad
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 // @grafana/grafana-operator-experience-squad
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // @grafana/grafana-operator-experience-squad
|
||||
github.com/aws/smithy-go v1.23.2 // @grafana/aws-datasources
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect; @grafana/grafana-operator-experience-squad
|
||||
github.com/aws/smithy-go v1.24.0 // @grafana/aws-datasources
|
||||
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
||||
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group
|
||||
github.com/golang/snappy v1.0.0 // @grafana/alerting-backend
|
||||
github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-github/v70 v70.0.0 // indirect; @grafana/grafana-git-ui-sync-team
|
||||
github.com/google/go-github/v70 v70.0.0 // @grafana/grafana-git-ui-sync-team
|
||||
github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent
|
||||
github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/wire v0.7.0 // @grafana/grafana-backend-group
|
||||
@@ -113,6 +113,7 @@ require (
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // @grafana/observability-traces-and-profiling
|
||||
github.com/grafana/pyroscope/api v1.2.1-0.20251118081820-ace37f973a0f // @grafana/observability-traces-and-profiling
|
||||
github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/grafana-search-and-storage
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // @grafana/plugins-platform-backend
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // @grafana/grafana-backend-group
|
||||
@@ -160,9 +161,9 @@ require (
|
||||
github.com/prometheus/alertmanager v0.28.2 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_golang v1.23.2 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_model v0.6.2 // @grafana/grafana-backend-group
|
||||
github.com/prometheus/common v0.67.4 // @grafana/alerting-backend
|
||||
github.com/prometheus/common v0.67.5 // @grafana/alerting-backend
|
||||
github.com/prometheus/prometheus v0.303.1 // @grafana/alerting-backend
|
||||
github.com/prometheus/sigv4 v0.1.2 // @grafana/alerting-backend
|
||||
github.com/prometheus/sigv4 v0.4.0 // @grafana/alerting-backend
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 // @grafana/grafana-backend-group
|
||||
github.com/redis/go-redis/v9 v9.14.0 // @grafana/alerting-backend
|
||||
github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group
|
||||
@@ -260,12 +261,13 @@ require (
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/plugins v0.0.0 // @grafana/plugins-platform-backend
|
||||
|
||||
// This needs to be here for other projects that import grafana/grafana
|
||||
// For local development grafana/grafana will always use the local files
|
||||
// Check go.work file for details
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8 // @grafana/oss-big-tent
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0 // @grafana/grafana-app-platform-squad
|
||||
)
|
||||
|
||||
// Replace the workspace versions
|
||||
@@ -294,6 +296,8 @@ replace (
|
||||
github.com/grafana/grafana/pkg/aggregator => ./pkg/aggregator
|
||||
github.com/grafana/grafana/pkg/apimachinery => ./pkg/apimachinery
|
||||
github.com/grafana/grafana/pkg/apiserver => ./pkg/apiserver
|
||||
github.com/grafana/grafana/pkg/plugins => ./pkg/plugins
|
||||
github.com/grafana/grafana/pkg/semconv => ./pkg/semconv
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -340,21 +344,21 @@ require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/at-wat/mqtt-go v0.19.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
|
||||
@@ -652,11 +656,13 @@ require (
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
require github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/IBM/pgxpoolprometheus v1.1.2 // indirect
|
||||
github.com/Machiel/slugify v1.0.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
@@ -676,6 +682,8 @@ require (
|
||||
github.com/google/gnostic v0.7.1 // indirect
|
||||
github.com/gophercloud/gophercloud/v2 v2.9.0 // indirect
|
||||
github.com/grafana/sqlds/v5 v5.0.3 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 // indirect
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
|
||||
73
go.sum
73
go.sum
@@ -680,6 +680,7 @@ github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7Og
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
@@ -737,6 +738,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY
|
||||
github.com/IBM/pgxpoolprometheus v1.1.2 h1:sHJwxoL5Lw4R79Zt+H4Uj1zZ4iqXJLdk7XDE7TPs97U=
|
||||
github.com/IBM/pgxpoolprometheus v1.1.2/go.mod h1:+vWzISN6S9ssgurhUNmm6AlXL9XLah3TdWJktquKTR8=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
github.com/Machiel/slugify v1.0.1 h1:EfWSlRWstMadsgzmiV7d0yVd2IFlagWH68Q+DcYCm4E=
|
||||
github.com/Machiel/slugify v1.0.1/go.mod h1:fTFGn5uWEynW4CUMG7sWkYXOf1UgDxyTM3DbR6Qfg3k=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
@@ -759,6 +762,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
@@ -845,22 +850,22 @@ github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
|
||||
@@ -871,12 +876,12 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFie
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo=
|
||||
@@ -889,14 +894,16 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZ
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 h1:w6a0H79HrHf3lr+zrw+pSzR5B+caiQFAKiNHlrUcnoc=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1/go.mod h1:c6Vg0BRiU7v0MVhHupw90RyL120QBwAMLbDCzptGeMk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk=
|
||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
|
||||
@@ -1026,6 +1033,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -1664,8 +1673,6 @@ github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 h1:rDP
|
||||
github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo=
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao=
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s=
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:2HRzUK/xQEYc+8d5If/XSusMcaYq9IptnBSHACiQcOQ=
|
||||
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 h1:NznuPwItog+rwdVg8hAuGKP29ndRSzJAwhxKldkP8oQ=
|
||||
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32/go.mod h1:796sq+UcONnSlzA3RtlBZ+b/hrerkZXiEmO8oMjyRwY=
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0=
|
||||
@@ -1753,6 +1760,8 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||
github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 h1:gCNiM4T5xEc4IpT8vM50CIO+AtElr5kO9l2Rxbq+Sz8=
|
||||
github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2/go.mod h1:6ZM4ZdwClyAsiU2uDBmRHCvq0If/03BMbF9U+U7G5pA=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
@@ -1877,6 +1886,10 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4=
|
||||
github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY=
|
||||
github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
@@ -2300,8 +2313,8 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
|
||||
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
|
||||
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
|
||||
@@ -2327,8 +2340,8 @@ github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05Zp
|
||||
github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0=
|
||||
github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY=
|
||||
github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck=
|
||||
github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8=
|
||||
github.com/prometheus/sigv4 v0.1.2/go.mod h1:GF9fwrvLgkQwDdQ5BXeV9XUSCH/IPNqzvAoaohfjqMU=
|
||||
github.com/prometheus/sigv4 v0.4.0 h1:s8oiq+S4ORkpjftnBvzObLrz5Hw49YwEhumNGBdfg4M=
|
||||
github.com/prometheus/sigv4 v0.4.0/go.mod h1:D6dQeKEsDyUWzoNGjby5HgXshiOAbsz7vuApHTCmOxA=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b h1:fPVI9E6QNFYI0Ph3XpKUDrcAvbCifHvqYJcntFLPog8=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
||||
|
||||
@@ -280,7 +280,6 @@ github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fw
|
||||
github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4=
|
||||
github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=
|
||||
@@ -906,6 +905,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20250729175202-b4b881b7b263/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
|
||||
github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM=
|
||||
github.com/grafana/alerting v0.0.0-20260112110054-6c6f13659ad3 h1:KVncUdAc5YwY/OQmw6HgzJmbRKn6IwrhvtcBAd1yDHo=
|
||||
github.com/grafana/alerting v0.0.0-20260112110054-6c6f13659ad3/go.mod h1:Oy4MthJqfErlieO14ryZXdukDrUACy8Lg56P3zP7S1k=
|
||||
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
|
||||
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
@@ -1911,6 +1912,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
|
||||
|
||||
@@ -62,8 +62,7 @@
|
||||
"stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json",
|
||||
"storybook": "yarn workspace @grafana/ui storybook --ci",
|
||||
"storybook:build": "yarn workspace @grafana/ui storybook:build",
|
||||
"themes-schema": "typescript-json-schema ./tsconfig.json NewThemeOptions --include 'packages/grafana-data/src/themes/createTheme.ts' --out public/app/features/theme-playground/schema.generated.json",
|
||||
"themes-generate": "yarn themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --conditions=@grafana-app/source --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
|
||||
"themes-generate": "yarn workspace @grafana/data themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --conditions=@grafana-app/source --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
|
||||
"themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'",
|
||||
"typecheck": "tsc --noEmit && yarn run packages:typecheck",
|
||||
"plugins:build-bundled": "echo 'bundled plugins are no longer supported'",
|
||||
@@ -254,7 +253,6 @@
|
||||
"ts-jest": "29.4.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.2",
|
||||
"typescript-json-schema": "^0.65.1",
|
||||
"webpack": "5.101.0",
|
||||
"webpack-assets-manifest": "^5.1.0",
|
||||
"webpack-cli": "6.0.1",
|
||||
@@ -265,7 +263,7 @@
|
||||
"webpackbar": "^7.0.0",
|
||||
"yaml": "^2.0.0",
|
||||
"yargs": "^18.0.0",
|
||||
"zod": "^4.0.0"
|
||||
"zod": "^4.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bsull/augurs": "^0.10.0",
|
||||
|
||||
@@ -47,11 +47,12 @@
|
||||
"LICENSE_APACHE2"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
|
||||
"build": "yarn themes-schema && tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
|
||||
"clean": "rimraf ./dist ./compiled ./unstable ./package.tgz",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"postpack": "mv package.json.bak package.json",
|
||||
"themes-schema": "tsx ./src/themes/scripts/generateSchema.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
@@ -81,10 +82,12 @@
|
||||
"tinycolor2": "1.6.0",
|
||||
"tslib": "2.8.1",
|
||||
"uplot": "1.6.32",
|
||||
"xss": "^1.0.14"
|
||||
"xss": "^1.0.14",
|
||||
"zod": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/scenes": "6.38.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-node-resolve": "16.0.1",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@types/history": "4.7.11",
|
||||
@@ -101,6 +104,7 @@
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-esbuild": "6.2.1",
|
||||
"rollup-plugin-node-externals": "^8.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json from '@rollup/plugin-json';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts';
|
||||
@@ -8,13 +9,13 @@ const pkg = rq('./package.json');
|
||||
export default [
|
||||
{
|
||||
input: entryPoint,
|
||||
plugins,
|
||||
plugins: [...plugins, json()],
|
||||
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
|
||||
treeshake: false,
|
||||
},
|
||||
{
|
||||
input: 'src/unstable.ts',
|
||||
plugins,
|
||||
plugins: [...plugins, json()],
|
||||
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
|
||||
treeshake: false,
|
||||
},
|
||||
|
||||
@@ -106,3 +106,4 @@ export { findNumericFieldMinMax } from '../field/fieldOverrides';
|
||||
export { type PanelOptionsSupplier } from '../panel/PanelPlugin';
|
||||
export { sanitize, sanitizeUrl } from '../text/sanitize';
|
||||
export { type NestedValueAccess, type NestedPanelOptions, isNestedPanelOptions } from '../utils/OptionsUIBuilders';
|
||||
export { NewThemeOptionsSchema } from '../themes/createTheme';
|
||||
|
||||
@@ -1,83 +1,103 @@
|
||||
import { merge } from 'lodash';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { alpha, darken, emphasize, getContrastRatio, lighten } from './colorManipulator';
|
||||
import { palette } from './palette';
|
||||
import { DeepPartial, ThemeRichColor } from './types';
|
||||
import { DeepRequired, ThemeRichColor, ThemeRichColorInputSchema } from './types';
|
||||
|
||||
const ThemeColorsModeSchema = z.enum(['light', 'dark']);
|
||||
/** @internal */
|
||||
export type ThemeColorsMode = 'light' | 'dark';
|
||||
export type ThemeColorsMode = z.infer<typeof ThemeColorsModeSchema>;
|
||||
|
||||
const createThemeColorsBaseSchema = <TColor>(color: TColor) =>
|
||||
z
|
||||
.object({
|
||||
mode: ThemeColorsModeSchema,
|
||||
|
||||
primary: color,
|
||||
secondary: color,
|
||||
info: color,
|
||||
error: color,
|
||||
success: color,
|
||||
warning: color,
|
||||
|
||||
text: z.object({
|
||||
primary: z.string().optional(),
|
||||
secondary: z.string().optional(),
|
||||
disabled: z.string().optional(),
|
||||
link: z.string().optional(),
|
||||
/** Used for auto white or dark text on colored backgrounds */
|
||||
maxContrast: z.string().optional(),
|
||||
}),
|
||||
|
||||
background: z.object({
|
||||
/** Dashboard and body background */
|
||||
canvas: z.string().optional(),
|
||||
/** Primary content pane background (panels etc) */
|
||||
primary: z.string().optional(),
|
||||
/** Cards and elements that need to stand out on the primary background */
|
||||
secondary: z.string().optional(),
|
||||
/**
|
||||
* For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark
|
||||
* themes it has a brighter shade to help give it contrast against the primary background.
|
||||
**/
|
||||
elevated: z.string().optional(),
|
||||
}),
|
||||
|
||||
border: z.object({
|
||||
weak: z.string().optional(),
|
||||
medium: z.string().optional(),
|
||||
strong: z.string().optional(),
|
||||
}),
|
||||
|
||||
gradients: z.object({
|
||||
brandVertical: z.string().optional(),
|
||||
brandHorizontal: z.string().optional(),
|
||||
}),
|
||||
|
||||
action: z.object({
|
||||
/** Used for selected menu item / select option */
|
||||
selected: z.string().optional(),
|
||||
/**
|
||||
* @alpha (Do not use from plugins)
|
||||
* Used for selected items when background only change is not enough (Currently only used for FilterPill)
|
||||
**/
|
||||
selectedBorder: z.string().optional(),
|
||||
/** Used for hovered menu item / select option */
|
||||
hover: z.string().optional(),
|
||||
/** Used for button/colored background hover opacity */
|
||||
hoverOpacity: z.number().optional(),
|
||||
/** Used focused menu item / select option */
|
||||
focus: z.string().optional(),
|
||||
/** Used for disabled buttons and inputs */
|
||||
disabledBackground: z.string().optional(),
|
||||
/** Disabled text */
|
||||
disabledText: z.string().optional(),
|
||||
/** Disablerd opacity */
|
||||
disabledOpacity: z.number().optional(),
|
||||
}),
|
||||
|
||||
hoverFactor: z.number(),
|
||||
contrastThreshold: z.number(),
|
||||
tonalOffset: z.number(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
// Need to override the zod type to include the generic properly
|
||||
/** @internal */
|
||||
export interface ThemeColorsBase<TColor> {
|
||||
mode: ThemeColorsMode;
|
||||
|
||||
export type ThemeColorsBase<TColor> = DeepRequired<
|
||||
Omit<
|
||||
z.infer<ReturnType<typeof createThemeColorsBaseSchema>>,
|
||||
'primary' | 'secondary' | 'info' | 'error' | 'success' | 'warning'
|
||||
>
|
||||
> & {
|
||||
primary: TColor;
|
||||
secondary: TColor;
|
||||
info: TColor;
|
||||
error: TColor;
|
||||
success: TColor;
|
||||
warning: TColor;
|
||||
|
||||
text: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
disabled: string;
|
||||
link: string;
|
||||
/** Used for auto white or dark text on colored backgrounds */
|
||||
maxContrast: string;
|
||||
};
|
||||
|
||||
background: {
|
||||
/** Dashboard and body background */
|
||||
canvas: string;
|
||||
/** Primary content pane background (panels etc) */
|
||||
primary: string;
|
||||
/** Cards and elements that need to stand out on the primary background */
|
||||
secondary: string;
|
||||
/**
|
||||
* For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark
|
||||
* themes it has a brighter shade to help give it contrast against the primary background.
|
||||
**/
|
||||
elevated: string;
|
||||
};
|
||||
|
||||
border: {
|
||||
weak: string;
|
||||
medium: string;
|
||||
strong: string;
|
||||
};
|
||||
|
||||
gradients: {
|
||||
brandVertical: string;
|
||||
brandHorizontal: string;
|
||||
};
|
||||
|
||||
action: {
|
||||
/** Used for selected menu item / select option */
|
||||
selected: string;
|
||||
/**
|
||||
* @alpha (Do not use from plugins)
|
||||
* Used for selected items when background only change is not enough (Currently only used for FilterPill)
|
||||
**/
|
||||
selectedBorder: string;
|
||||
/** Used for hovered menu item / select option */
|
||||
hover: string;
|
||||
/** Used for button/colored background hover opacity */
|
||||
hoverOpacity: number;
|
||||
/** Used focused menu item / select option */
|
||||
focus: string;
|
||||
/** Used for disabled buttons and inputs */
|
||||
disabledBackground: string;
|
||||
/** Disabled text */
|
||||
disabledText: string;
|
||||
/** Disablerd opacity */
|
||||
disabledOpacity: number;
|
||||
};
|
||||
|
||||
hoverFactor: number;
|
||||
contrastThreshold: number;
|
||||
tonalOffset: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface ThemeHoverStrengh {}
|
||||
|
||||
@@ -89,8 +109,10 @@ export interface ThemeColors extends ThemeColorsBase<ThemeRichColor> {
|
||||
emphasize(color: string, amount?: number): string;
|
||||
}
|
||||
|
||||
export const ThemeColorsInputSchema = createThemeColorsBaseSchema(ThemeRichColorInputSchema);
|
||||
|
||||
/** @internal */
|
||||
export type ThemeColorsInput = DeepPartial<ThemeColorsBase<ThemeRichColor>>;
|
||||
export type ThemeColorsInput = z.infer<typeof ThemeColorsInputSchema>;
|
||||
|
||||
class DarkColors implements ThemeColorsBase<Partial<ThemeRichColor>> {
|
||||
mode: ThemeColorsMode = 'dark';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** @beta */
|
||||
export interface ThemeShape {
|
||||
/**
|
||||
@@ -34,9 +36,12 @@ export interface Radii {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface ThemeShapeInput {
|
||||
borderRadius?: number;
|
||||
}
|
||||
export const ThemeShapeInputSchema = z.object({
|
||||
borderRadius: z.int().nonnegative().optional(),
|
||||
});
|
||||
|
||||
/** @internal */
|
||||
export type ThemeShapeInput = z.infer<typeof ThemeShapeInputSchema>;
|
||||
|
||||
export function createShape(options: ThemeShapeInput): ThemeShape {
|
||||
const baseBorderRadius = options.borderRadius ?? 6;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Code based on Material UI
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Call-Em-All
|
||||
import { z } from 'zod';
|
||||
|
||||
/** @internal */
|
||||
export type ThemeSpacingOptions = {
|
||||
gridSize?: number;
|
||||
};
|
||||
export const ThemeSpacingOptionsSchema = z.object({
|
||||
gridSize: z.int().positive().optional(),
|
||||
});
|
||||
|
||||
/** @internal */
|
||||
export type ThemeSpacingOptions = z.infer<typeof ThemeSpacingOptionsSchema>;
|
||||
|
||||
/** @internal */
|
||||
export type ThemeSpacingArgument = number | string;
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
import * as z from 'zod';
|
||||
|
||||
import { createBreakpoints } from './breakpoints';
|
||||
import { createColors, ThemeColorsInput } from './createColors';
|
||||
import { createColors, ThemeColorsInputSchema } from './createColors';
|
||||
import { createComponents } from './createComponents';
|
||||
import { createShadows } from './createShadows';
|
||||
import { createShape, ThemeShapeInput } from './createShape';
|
||||
import { createSpacing, ThemeSpacingOptions } from './createSpacing';
|
||||
import { createShape, ThemeShapeInputSchema } from './createShape';
|
||||
import { createSpacing, ThemeSpacingOptionsSchema } from './createSpacing';
|
||||
import { createTransitions } from './createTransitions';
|
||||
import { createTypography, ThemeTypographyInput } from './createTypography';
|
||||
import { createTypography, ThemeTypographyInputSchema } from './createTypography';
|
||||
import { createV1Theme } from './createV1Theme';
|
||||
import { createVisualizationColors, ThemeVisualizationColorsInput } from './createVisualizationColors';
|
||||
import { createVisualizationColors, ThemeVisualizationColorsInputSchema } from './createVisualizationColors';
|
||||
import { GrafanaTheme2 } from './types';
|
||||
import { zIndex } from './zIndex';
|
||||
|
||||
/** @internal */
|
||||
export interface NewThemeOptions {
|
||||
name?: string;
|
||||
colors?: ThemeColorsInput;
|
||||
spacing?: ThemeSpacingOptions;
|
||||
shape?: ThemeShapeInput;
|
||||
typography?: ThemeTypographyInput;
|
||||
visualization?: ThemeVisualizationColorsInput;
|
||||
}
|
||||
export const NewThemeOptionsSchema = z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
colors: ThemeColorsInputSchema.optional(),
|
||||
spacing: ThemeSpacingOptionsSchema.optional(),
|
||||
shape: ThemeShapeInputSchema.optional(),
|
||||
typography: ThemeTypographyInputSchema.optional(),
|
||||
visualization: ThemeVisualizationColorsInputSchema.optional(),
|
||||
});
|
||||
|
||||
/** @internal */
|
||||
export function createTheme(options: NewThemeOptions = {}): GrafanaTheme2 {
|
||||
export type NewThemeOptions = z.infer<typeof NewThemeOptionsSchema>;
|
||||
|
||||
/** @internal */
|
||||
export function createTheme(
|
||||
options: Omit<NewThemeOptions, 'id' | 'name'> & {
|
||||
name?: NewThemeOptions['name'];
|
||||
} = {}
|
||||
): GrafanaTheme2 {
|
||||
const {
|
||||
name,
|
||||
colors: colorsInput = {},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Code based on Material UI
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2014 Call-Em-All
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ThemeColors } from './createColors';
|
||||
|
||||
@@ -40,18 +41,20 @@ export interface ThemeTypographyVariant {
|
||||
letterSpacing?: string;
|
||||
}
|
||||
|
||||
export interface ThemeTypographyInput {
|
||||
fontFamily?: string;
|
||||
fontFamilyMonospace?: string;
|
||||
fontSize?: number;
|
||||
fontWeightLight?: number;
|
||||
fontWeightRegular?: number;
|
||||
fontWeightMedium?: number;
|
||||
fontWeightBold?: number;
|
||||
// hat's the font-size on the html element.
|
||||
export const ThemeTypographyInputSchema = z.object({
|
||||
fontFamily: z.string().optional(),
|
||||
fontFamilyMonospace: z.string().optional(),
|
||||
fontSize: z.number().positive().optional(),
|
||||
fontWeightLight: z.number().positive().optional(),
|
||||
fontWeightRegular: z.number().positive().optional(),
|
||||
fontWeightMedium: z.number().positive().optional(),
|
||||
fontWeightBold: z.number().positive().optional(),
|
||||
// what's the font-size on the html element.
|
||||
// 16px is the default font-size used by browsers.
|
||||
htmlFontSize?: number;
|
||||
}
|
||||
htmlFontSize: z.number().positive().optional(),
|
||||
});
|
||||
|
||||
export type ThemeTypographyInput = z.infer<typeof ThemeTypographyInputSchema>;
|
||||
|
||||
const defaultFontFamily = "'Inter', 'Helvetica', 'Arial', sans-serif";
|
||||
const defaultFontFamilyMonospace = "'Roboto Mono', monospace";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FALLBACK_COLOR } from '../types/fieldColor';
|
||||
|
||||
import { ThemeColors } from './createColors';
|
||||
@@ -26,29 +28,44 @@ export interface ThemeVizColor<T extends ThemeVizColorName> {
|
||||
|
||||
type ThemeVizColorName = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple';
|
||||
|
||||
type ThemeVizColorShadeName<T extends ThemeVizColorName> =
|
||||
| `super-light-${T}`
|
||||
| `light-${T}`
|
||||
| T
|
||||
| `semi-dark-${T}`
|
||||
| `dark-${T}`;
|
||||
const createShadeSchema = <T>(color: T extends ThemeVizColorName ? T : never) =>
|
||||
z.enum([`super-light-${color}`, `light-${color}`, color, `semi-dark-${color}`, `dark-${color}`]);
|
||||
|
||||
type ThemeVizHueGeneric<T> = T extends ThemeVizColorName
|
||||
? {
|
||||
name: T;
|
||||
shades: Array<ThemeVizColor<T>>;
|
||||
}
|
||||
: never;
|
||||
type ThemeVizColorShadeName<T extends ThemeVizColorName> = z.infer<ReturnType<typeof createShadeSchema<T>>>;
|
||||
|
||||
const createHueSchema = <T>(color: T extends ThemeVizColorName ? T : never) =>
|
||||
z.object({
|
||||
name: z.literal(color),
|
||||
shades: z.array(
|
||||
z.object({
|
||||
color: z.string(),
|
||||
name: createShadeSchema(color),
|
||||
aliases: z.array(z.string()).optional(),
|
||||
primary: z.boolean().optional(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const ThemeVizHueSchema = z.union([
|
||||
createHueSchema('red'),
|
||||
createHueSchema('orange'),
|
||||
createHueSchema('yellow'),
|
||||
createHueSchema('green'),
|
||||
createHueSchema('blue'),
|
||||
createHueSchema('purple'),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export type ThemeVizHue = ThemeVizHueGeneric<ThemeVizColorName>;
|
||||
export type ThemeVizHue = z.infer<typeof ThemeVizHueSchema>;
|
||||
|
||||
export type ThemeVisualizationColorsInput = {
|
||||
hues?: ThemeVizHue[];
|
||||
palette?: string[];
|
||||
};
|
||||
export const ThemeVisualizationColorsInputSchema = z.object({
|
||||
hues: z.array(ThemeVizHueSchema).optional(),
|
||||
palette: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export type ThemeVisualizationColorsInput = z.infer<typeof ThemeVisualizationColorsInputSchema>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Registry, RegistryItem } from '../utils/Registry';
|
||||
|
||||
import { createTheme } from './createTheme';
|
||||
import { createTheme, NewThemeOptionsSchema } from './createTheme';
|
||||
import * as extraThemes from './themeDefinitions';
|
||||
import { GrafanaTheme2 } from './types';
|
||||
|
||||
@@ -42,9 +42,6 @@ export function getBuiltInThemes(allowedExtras: string[]) {
|
||||
return sortedThemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* There is also a backend list at pkg/services/preference/themes.go
|
||||
*/
|
||||
const themeRegistry = new Registry<ThemeRegistryItem>(() => {
|
||||
return [
|
||||
{ id: 'system', name: 'System preference', build: getSystemPreferenceTheme },
|
||||
@@ -53,13 +50,19 @@ const themeRegistry = new Registry<ThemeRegistryItem>(() => {
|
||||
];
|
||||
});
|
||||
|
||||
for (const [id, theme] of Object.entries(extraThemes)) {
|
||||
themeRegistry.register({
|
||||
id,
|
||||
name: theme.name ?? '',
|
||||
build: () => createTheme(theme),
|
||||
isExtra: true,
|
||||
});
|
||||
for (const [name, json] of Object.entries(extraThemes)) {
|
||||
const result = NewThemeOptionsSchema.safeParse(json);
|
||||
if (!result.success) {
|
||||
console.error(`Invalid theme definition for theme ${name}: ${result.error.message}`);
|
||||
} else {
|
||||
const theme = result.data;
|
||||
themeRegistry.register({
|
||||
id: theme.id,
|
||||
name: theme.name,
|
||||
build: () => createTheme(theme),
|
||||
isExtra: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemPreferenceTheme() {
|
||||
|
||||
608
packages/grafana-data/src/themes/schema.generated.json
Normal file
608
packages/grafana-data/src/themes/schema.generated.json
Normal file
@@ -0,0 +1,608 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"colors": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["light", "dark"]
|
||||
},
|
||||
"primary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"secondary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"success": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"warning": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"main": {
|
||||
"type": "string"
|
||||
},
|
||||
"shade": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"border": {
|
||||
"type": "string"
|
||||
},
|
||||
"transparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"borderTransparent": {
|
||||
"type": "string"
|
||||
},
|
||||
"contrastText": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"text": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary": {
|
||||
"type": "string"
|
||||
},
|
||||
"secondary": {
|
||||
"type": "string"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxContrast": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"background": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"canvas": {
|
||||
"type": "string"
|
||||
},
|
||||
"primary": {
|
||||
"type": "string"
|
||||
},
|
||||
"secondary": {
|
||||
"type": "string"
|
||||
},
|
||||
"elevated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"weak": {
|
||||
"type": "string"
|
||||
},
|
||||
"medium": {
|
||||
"type": "string"
|
||||
},
|
||||
"strong": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"gradients": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"brandVertical": {
|
||||
"type": "string"
|
||||
},
|
||||
"brandHorizontal": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"action": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"selected": {
|
||||
"type": "string"
|
||||
},
|
||||
"selectedBorder": {
|
||||
"type": "string"
|
||||
},
|
||||
"hover": {
|
||||
"type": "string"
|
||||
},
|
||||
"hoverOpacity": {
|
||||
"type": "number"
|
||||
},
|
||||
"focus": {
|
||||
"type": "string"
|
||||
},
|
||||
"disabledBackground": {
|
||||
"type": "string"
|
||||
},
|
||||
"disabledText": {
|
||||
"type": "string"
|
||||
},
|
||||
"disabledOpacity": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"hoverFactor": {
|
||||
"type": "number"
|
||||
},
|
||||
"contrastThreshold": {
|
||||
"type": "number"
|
||||
},
|
||||
"tonalOffset": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"spacing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gridSize": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"shape": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"borderRadius": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"typography": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fontFamily": {
|
||||
"type": "string"
|
||||
},
|
||||
"fontFamilyMonospace": {
|
||||
"type": "string"
|
||||
},
|
||||
"fontSize": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"fontWeightLight": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"fontWeightRegular": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"fontWeightMedium": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"fontWeightBold": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"htmlFontSize": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"visualization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hues": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "red"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-red", "light-red", "red", "semi-dark-red", "dark-red"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "orange"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-orange", "light-orange", "orange", "semi-dark-orange", "dark-orange"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "yellow"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-yellow", "light-yellow", "yellow", "semi-dark-yellow", "dark-yellow"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "green"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-green", "light-green", "green", "semi-dark-green", "dark-green"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "blue"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-blue", "light-blue", "blue", "semi-dark-blue", "dark-blue"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"const": "purple"
|
||||
},
|
||||
"shades": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["super-light-purple", "light-purple", "purple", "semi-dark-purple", "dark-purple"]
|
||||
},
|
||||
"aliases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["color", "name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "shades"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"palette": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "id"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
19
packages/grafana-data/src/themes/scripts/generateSchema.ts
Normal file
19
packages/grafana-data/src/themes/scripts/generateSchema.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { NewThemeOptionsSchema } from '../createTheme';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '../schema.generated.json'),
|
||||
JSON.stringify(
|
||||
NewThemeOptionsSchema.toJSONSchema({
|
||||
target: 'draft-07',
|
||||
}),
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Aubergine",
|
||||
"id": "aubergine",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "#4F2A3D",
|
||||
"medium": "#6A3C4B",
|
||||
"strong": "#8C5A69"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#E5D0D6",
|
||||
"secondary": "#D1A8C4",
|
||||
"disabled": "#B7A0A6",
|
||||
"link": "#A56BB6",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#8C5A69"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#6A3C4B",
|
||||
"text": "#D1A8C4",
|
||||
"border": "#8C5A69"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#2E1F2D",
|
||||
"primary": "#3C2136",
|
||||
"secondary": "#4A2D47",
|
||||
"elevated": "#4A2D47"
|
||||
},
|
||||
"action": {
|
||||
"hover": "#6A3C4B",
|
||||
"selected": "#8C5A69",
|
||||
"selectedBorder": "#FFB300",
|
||||
"focus": "#A56BB6",
|
||||
"hoverOpacity": 0.1,
|
||||
"disabledText": "#B7A0A6",
|
||||
"disabledBackground": "#4A2D47",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #6A3C4B 0%, #A56BB6 100%)",
|
||||
"brandVertical": "linear-gradient(0deg, #6A3C4B 0%, #A56BB6 100%)"
|
||||
},
|
||||
"contrastThreshold": 4,
|
||||
"hoverFactor": 0.07,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const aubergineTheme: NewThemeOptions = {
|
||||
name: 'Aubergine',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: '#4F2A3D',
|
||||
medium: '#6A3C4B',
|
||||
strong: '#8C5A69',
|
||||
},
|
||||
text: {
|
||||
primary: '#E5D0D6',
|
||||
secondary: '#D1A8C4',
|
||||
disabled: '#B7A0A6',
|
||||
link: '#A56BB6',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#8C5A69',
|
||||
},
|
||||
secondary: {
|
||||
main: '#6A3C4B',
|
||||
text: '#D1A8C4',
|
||||
border: '#8C5A69',
|
||||
},
|
||||
background: {
|
||||
canvas: '#2E1F2D',
|
||||
primary: '#3C2136',
|
||||
secondary: '#4A2D47',
|
||||
elevated: '#4A2D47',
|
||||
},
|
||||
action: {
|
||||
hover: '#6A3C4B',
|
||||
selected: '#8C5A69',
|
||||
selectedBorder: '#FFB300',
|
||||
focus: '#A56BB6',
|
||||
hoverOpacity: 0.1,
|
||||
disabledText: '#B7A0A6',
|
||||
disabledBackground: '#4A2D47',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #6A3C4B 0%, #A56BB6 100%)',
|
||||
brandVertical: 'linear-gradient(0deg, #6A3C4B 0%, #A56BB6 100%)',
|
||||
},
|
||||
contrastThreshold: 4,
|
||||
hoverFactor: 0.07,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default aubergineTheme;
|
||||
60
packages/grafana-data/src/themes/themeDefinitions/debug.json
Normal file
60
packages/grafana-data/src/themes/themeDefinitions/debug.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "Debug",
|
||||
"id": "debug",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"background": {
|
||||
"canvas": "#000033",
|
||||
"primary": "#000044",
|
||||
"secondary": "#000055",
|
||||
"elevated": "#000055"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#bbbb00",
|
||||
"secondary": "#888800",
|
||||
"disabled": "#444400",
|
||||
"link": "#dddd00",
|
||||
"maxContrast": "#ffff00"
|
||||
},
|
||||
"border": {
|
||||
"weak": "#ff000044",
|
||||
"medium": "#ff000088",
|
||||
"strong": "#ff0000ff"
|
||||
},
|
||||
"primary": {
|
||||
"border": "#ff000088",
|
||||
"text": "#cccc00",
|
||||
"contrastText": "#ffff00",
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"secondary": {
|
||||
"border": "#ff000088",
|
||||
"text": "#cccc00",
|
||||
"contrastText": "#ffff00",
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"info": {
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"warning": {
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"success": {
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"error": {
|
||||
"shade": "#9900dd"
|
||||
},
|
||||
"action": {
|
||||
"hover": "#9900dd",
|
||||
"focus": "#6600aa",
|
||||
"selected": "#440088"
|
||||
}
|
||||
},
|
||||
"shape": {
|
||||
"borderRadius": 8
|
||||
},
|
||||
"spacing": {
|
||||
"gridSize": 10
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
/**
|
||||
* a very ugly theme that is useful for debugging and checking if the theme is applied correctly
|
||||
* borders are red,
|
||||
* backgrounds are blue,
|
||||
* text is yellow,
|
||||
* and grafana loves you <3
|
||||
* (also corners are rounded, action states (hover, focus, selected) are purple)
|
||||
*/
|
||||
const debugTheme: NewThemeOptions = {
|
||||
name: 'Debug',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
canvas: '#000033',
|
||||
primary: '#000044',
|
||||
secondary: '#000055',
|
||||
elevated: '#000055',
|
||||
},
|
||||
text: {
|
||||
primary: '#bbbb00',
|
||||
secondary: '#888800',
|
||||
disabled: '#444400',
|
||||
link: '#dddd00',
|
||||
maxContrast: '#ffff00',
|
||||
},
|
||||
border: {
|
||||
weak: '#ff000044',
|
||||
medium: '#ff000088',
|
||||
strong: '#ff0000ff',
|
||||
},
|
||||
primary: {
|
||||
border: '#ff000088',
|
||||
text: '#cccc00',
|
||||
contrastText: '#ffff00',
|
||||
shade: '#9900dd',
|
||||
},
|
||||
secondary: {
|
||||
border: '#ff000088',
|
||||
text: '#cccc00',
|
||||
contrastText: '#ffff00',
|
||||
shade: '#9900dd',
|
||||
},
|
||||
info: {
|
||||
shade: '#9900dd',
|
||||
},
|
||||
warning: {
|
||||
shade: '#9900dd',
|
||||
},
|
||||
success: {
|
||||
shade: '#9900dd',
|
||||
},
|
||||
error: {
|
||||
shade: '#9900dd',
|
||||
},
|
||||
action: {
|
||||
hover: '#9900dd',
|
||||
focus: '#6600aa',
|
||||
selected: '#440088',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
spacing: {
|
||||
gridSize: 10,
|
||||
},
|
||||
};
|
||||
|
||||
export default debugTheme;
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "Desert bloom",
|
||||
"id": "desertbloom",
|
||||
"colors": {
|
||||
"mode": "light",
|
||||
"border": {
|
||||
"weak": "rgba(0, 0, 0, 0.12)",
|
||||
"medium": "rgba(0, 0, 0, 0.20)",
|
||||
"strong": "rgba(0, 0, 0, 0.30)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#333333",
|
||||
"secondary": "#555555",
|
||||
"disabled": "rgba(0, 0, 0, 0.5)",
|
||||
"link": "#1A82E2",
|
||||
"maxContrast": "#000000"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#FF6F61",
|
||||
"text": "#FE6F61",
|
||||
"border": "#E55B4D",
|
||||
"name": "primary",
|
||||
"shade": "#E55B4D",
|
||||
"transparent": "#FF6F6126",
|
||||
"contrastText": "#FFFFFF",
|
||||
"borderTransparent": "#FF6F6140"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#FFFFFF",
|
||||
"text": "#695f53",
|
||||
"border": "#d9cec0",
|
||||
"name": "secondary",
|
||||
"shade": "#d9cec0",
|
||||
"transparent": "#FFFFFF26",
|
||||
"contrastText": "#4c4339",
|
||||
"borderTransparent": "#FFFFFF40"
|
||||
},
|
||||
"info": {
|
||||
"main": "#1A82E2"
|
||||
},
|
||||
"success": {
|
||||
"main": "#4CAF50"
|
||||
},
|
||||
"warning": {
|
||||
"main": "#FFC107"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#FFF8F0",
|
||||
"primary": "#FFFFFF",
|
||||
"secondary": "#f9f3e8",
|
||||
"elevated": "#FFFFFF"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(168, 156, 134, 0.12)",
|
||||
"selected": "rgba(168, 156, 134, 0.36)",
|
||||
"selectedBorder": "#FF6F61",
|
||||
"focus": "rgba(168, 156, 134, 0.50)",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "rgba(168, 156, 134, 0.5)",
|
||||
"disabledBackground": "rgba(168, 156, 134, 0.06)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg,rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)",
|
||||
"brandVertical": "linear-gradient(0deg, rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const desertBloomTheme: NewThemeOptions = {
|
||||
name: 'Desert bloom',
|
||||
colors: {
|
||||
mode: 'light',
|
||||
border: {
|
||||
weak: 'rgba(0, 0, 0, 0.12)',
|
||||
medium: 'rgba(0, 0, 0, 0.20)',
|
||||
strong: 'rgba(0, 0, 0, 0.30)',
|
||||
},
|
||||
text: {
|
||||
primary: '#333333',
|
||||
secondary: '#555555',
|
||||
disabled: 'rgba(0, 0, 0, 0.5)',
|
||||
link: '#1A82E2',
|
||||
maxContrast: '#000000',
|
||||
},
|
||||
primary: {
|
||||
main: '#FF6F61',
|
||||
text: '#FE6F61',
|
||||
border: '#E55B4D',
|
||||
name: 'primary',
|
||||
shade: '#E55B4D',
|
||||
transparent: '#FF6F6126',
|
||||
contrastText: '#FFFFFF',
|
||||
borderTransparent: '#FF6F6140',
|
||||
},
|
||||
secondary: {
|
||||
main: '#FFFFFF',
|
||||
text: '#695f53',
|
||||
border: '#d9cec0',
|
||||
name: 'secondary',
|
||||
shade: '#d9cec0',
|
||||
transparent: '#FFFFFF26',
|
||||
contrastText: '#4c4339',
|
||||
borderTransparent: '#FFFFFF40',
|
||||
},
|
||||
info: {
|
||||
main: '#1A82E2',
|
||||
},
|
||||
success: {
|
||||
main: '#4CAF50',
|
||||
},
|
||||
warning: {
|
||||
main: '#FFC107',
|
||||
},
|
||||
background: {
|
||||
canvas: '#FFF8F0',
|
||||
primary: '#FFFFFF',
|
||||
secondary: '#f9f3e8',
|
||||
elevated: '#FFFFFF',
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(168, 156, 134, 0.12)',
|
||||
selected: 'rgba(168, 156, 134, 0.36)',
|
||||
selectedBorder: '#FF6F61',
|
||||
focus: 'rgba(168, 156, 134, 0.50)',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: 'rgba(168, 156, 134, 0.5)',
|
||||
disabledBackground: 'rgba(168, 156, 134, 0.06)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg,rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)',
|
||||
brandVertical: 'linear-gradient(0deg, rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default desertBloomTheme;
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "Gilded grove",
|
||||
"id": "gildedgrove",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "rgba(200, 200, 180, 0.12)",
|
||||
"medium": "rgba(200, 200, 180, 0.20)",
|
||||
"strong": "rgba(200, 200, 180, 0.30)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "rgb(250, 250, 239)",
|
||||
"secondary": "rgba(200, 200, 180, 0.85)",
|
||||
"disabled": "rgba(200, 200, 180, 0.6)",
|
||||
"link": "#FEAC34",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#FEAC34",
|
||||
"text": "#FFD783",
|
||||
"border": "#FFD783",
|
||||
"name": "primary",
|
||||
"shade": "rgb(255, 173, 80)",
|
||||
"transparent": "#FEAC3426",
|
||||
"contrastText": "#111614",
|
||||
"borderTransparent": "#FFD78340"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "rgba(200, 200, 180, 0.10)",
|
||||
"shade": "rgba(200, 200, 180, 0.14)",
|
||||
"transparent": "rgba(200, 200, 180, 0.08)",
|
||||
"text": "rgb(200, 200, 180)",
|
||||
"contrastText": "rgb(200, 200, 180)",
|
||||
"border": "rgba(200, 200, 180, 0.08)",
|
||||
"name": "secondary",
|
||||
"borderTransparent": "rgba(200, 200, 180, 0.25)"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#111614",
|
||||
"primary": "#1d2220",
|
||||
"secondary": "#27312E",
|
||||
"elevated": "#27312E"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(200, 200, 180, 0.16)",
|
||||
"selected": "rgba(200, 200, 180, 0.12)",
|
||||
"selectedBorder": "#FEAC34",
|
||||
"focus": "rgba(200, 200, 180, 0.16)",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "rgba(200, 200, 180, 0.6)",
|
||||
"disabledBackground": "rgba(200, 200, 180, 0.04)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #FEAC34 0%, #FFD783 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #FEAC34 0.01%, #FFD783 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const gildedGroveTheme: NewThemeOptions = {
|
||||
name: 'Gilded grove',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: 'rgba(200, 200, 180, 0.12)',
|
||||
medium: 'rgba(200, 200, 180, 0.20)',
|
||||
strong: 'rgba(200, 200, 180, 0.30)',
|
||||
},
|
||||
text: {
|
||||
primary: 'rgb(250, 250, 239)',
|
||||
secondary: 'rgba(200, 200, 180, 0.85)',
|
||||
disabled: 'rgba(200, 200, 180, 0.6)',
|
||||
link: '#FEAC34',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#FEAC34',
|
||||
text: '#FFD783',
|
||||
border: '#FFD783',
|
||||
name: 'primary',
|
||||
shade: 'rgb(255, 173, 80)',
|
||||
transparent: '#FEAC3426',
|
||||
contrastText: '#111614',
|
||||
borderTransparent: '#FFD78340',
|
||||
},
|
||||
secondary: {
|
||||
main: 'rgba(200, 200, 180, 0.10)',
|
||||
shade: 'rgba(200, 200, 180, 0.14)',
|
||||
transparent: 'rgba(200, 200, 180, 0.08)',
|
||||
text: 'rgb(200, 200, 180)',
|
||||
contrastText: 'rgb(200, 200, 180)',
|
||||
border: 'rgba(200, 200, 180, 0.08)',
|
||||
name: 'secondary',
|
||||
borderTransparent: 'rgba(200, 200, 180, 0.25)',
|
||||
},
|
||||
background: {
|
||||
canvas: '#111614',
|
||||
primary: '#1d2220',
|
||||
secondary: '#27312E',
|
||||
elevated: '#27312E',
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(200, 200, 180, 0.16)',
|
||||
selected: 'rgba(200, 200, 180, 0.12)',
|
||||
selectedBorder: '#FEAC34',
|
||||
focus: 'rgba(200, 200, 180, 0.16)',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: 'rgba(200, 200, 180, 0.6)',
|
||||
disabledBackground: 'rgba(200, 200, 180, 0.04)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #FEAC34 0%, #FFD783 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #FEAC34 0.01%, #FFD783 99.99%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default gildedGroveTheme;
|
||||
52
packages/grafana-data/src/themes/themeDefinitions/gloom.json
Normal file
52
packages/grafana-data/src/themes/themeDefinitions/gloom.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "Gloom",
|
||||
"id": "gloom",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "rgba(210, 210, 220, 0.12)",
|
||||
"medium": "rgba(210, 210, 220, 0.20)",
|
||||
"strong": "rgba(210, 210, 220, 0.30)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "rgb(210, 210, 220)",
|
||||
"secondary": "rgba(210, 210, 220, 0.65)",
|
||||
"disabled": "rgba(210, 210, 220, 0.48)",
|
||||
"link": "#f99a5c",
|
||||
"maxContrast": "#FFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#ff934d",
|
||||
"text": "#f99a5c",
|
||||
"border": "#ff934d",
|
||||
"name": "primary"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "rgba(195, 195, 245, 0.10)",
|
||||
"shade": "rgba(195, 195, 245, 0.14)",
|
||||
"transparent": "rgba(195, 195, 245, 0.08)",
|
||||
"text": "rgba(195, 195, 245)",
|
||||
"contrastText": "rgb(195, 195, 245)",
|
||||
"border": "rgba(195, 195, 245, 0.08)"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#000",
|
||||
"primary": "#121118",
|
||||
"secondary": "#211e28",
|
||||
"elevated": "#211e28"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(195, 195, 245, 0.07)",
|
||||
"selected": "rgba(195, 195, 245, 0.11)",
|
||||
"selectedBorder": "#ff934d",
|
||||
"focus": "rgba(195, 195, 245, 0.07)",
|
||||
"hoverOpacity": 0.05,
|
||||
"disabledText": "rgba(210, 210, 220, 0.48)",
|
||||
"disabledBackground": "rgba(210, 210, 220, 0.04)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
/**
|
||||
* Torkel's GrafanaCon theme
|
||||
* very WIP state
|
||||
*/
|
||||
|
||||
const whiteBase = `210, 210, 220`;
|
||||
const secondaryBase = `195, 195, 245`;
|
||||
|
||||
//const brandMain = '#3d71d9';
|
||||
//const brandText = '#6e9fff';
|
||||
const brandMain = '#ff934d';
|
||||
const brandText = '#f99a5c';
|
||||
const disabledText = `rgba(${whiteBase}, 0.48)`;
|
||||
|
||||
const gloomTheme: NewThemeOptions = {
|
||||
name: 'Gloom',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: `rgba(${whiteBase}, 0.12)`,
|
||||
medium: `rgba(${whiteBase}, 0.20)`,
|
||||
strong: `rgba(${whiteBase}, 0.30)`,
|
||||
},
|
||||
|
||||
text: {
|
||||
primary: `rgb(${whiteBase})`,
|
||||
secondary: `rgba(${whiteBase}, 0.65)`,
|
||||
disabled: disabledText,
|
||||
link: brandText,
|
||||
maxContrast: '#FFF',
|
||||
},
|
||||
|
||||
primary: {
|
||||
main: brandMain,
|
||||
text: brandText,
|
||||
border: brandMain,
|
||||
name: 'primary',
|
||||
},
|
||||
|
||||
secondary: {
|
||||
main: `rgba(${secondaryBase}, 0.10)`,
|
||||
shade: `rgba(${secondaryBase}, 0.14)`,
|
||||
transparent: `rgba(${secondaryBase}, 0.08)`,
|
||||
text: `rgba(${secondaryBase})`,
|
||||
contrastText: `rgb(${secondaryBase})`,
|
||||
border: `rgba(${secondaryBase}, 0.08)`,
|
||||
},
|
||||
|
||||
background: {
|
||||
canvas: '#000',
|
||||
primary: '#121118',
|
||||
secondary: '#211e28',
|
||||
elevated: '#211e28',
|
||||
},
|
||||
|
||||
action: {
|
||||
hover: `rgba(${secondaryBase}, 0.07)`,
|
||||
selected: `rgba(${secondaryBase}, 0.11)`,
|
||||
selectedBorder: brandMain,
|
||||
focus: `rgba(${secondaryBase}, 0.07)`,
|
||||
hoverOpacity: 0.05,
|
||||
disabledText: disabledText,
|
||||
disabledBackground: `rgba(${whiteBase}, 0.04)`,
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
|
||||
// gradients: {
|
||||
// brandHorizontal: 'linear-gradient(270deg, #ff934d 0%, #FEAC34 100%)',
|
||||
// brandVertical: 'linear-gradient(0.01deg, #ff934d 0.01%, #FEAC34 99.99%)',
|
||||
// },
|
||||
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default gloomTheme;
|
||||
@@ -1,12 +1,12 @@
|
||||
export { default as aubergine } from './aubergine';
|
||||
export { default as debug } from './debug';
|
||||
export { default as desertbloom } from './desertbloom';
|
||||
export { default as gildedgrove } from './gildedgrove';
|
||||
export { default as mars } from './mars';
|
||||
export { default as matrix } from './matrix';
|
||||
export { default as sapphiredusk } from './sapphiredusk';
|
||||
export { default as synthwave } from './synthwave';
|
||||
export { default as tron } from './tron';
|
||||
export { default as victorian } from './victorian';
|
||||
export { default as zen } from './zen';
|
||||
export { default as gloom } from './gloom';
|
||||
export { default as aubergine } from './aubergine.json';
|
||||
export { default as debug } from './debug.json';
|
||||
export { default as desertbloom } from './desertbloom.json';
|
||||
export { default as gildedgrove } from './gildedgrove.json';
|
||||
export { default as mars } from './mars.json';
|
||||
export { default as matrix } from './matrix.json';
|
||||
export { default as sapphiredusk } from './sapphiredusk.json';
|
||||
export { default as synthwave } from './synthwave.json';
|
||||
export { default as tron } from './tron.json';
|
||||
export { default as victorian } from './victorian.json';
|
||||
export { default as zen } from './zen.json';
|
||||
export { default as gloom } from './gloom.json';
|
||||
|
||||
50
packages/grafana-data/src/themes/themeDefinitions/mars.json
Normal file
50
packages/grafana-data/src/themes/themeDefinitions/mars.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Mars",
|
||||
"id": "mars",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "rgba(210, 90, 60, 0.2)",
|
||||
"medium": "rgba(210, 90, 60, 0.35)",
|
||||
"strong": "rgba(210, 90, 60, 0.5)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#DDDDDD",
|
||||
"secondary": "#BBBBBB",
|
||||
"disabled": "rgba(221, 221, 221, 0.5)",
|
||||
"link": "#FF6F61",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#FF6F61"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#6a2f2f",
|
||||
"text": "#BBBBBB",
|
||||
"border": "rgba(210, 90, 60, 0.2)"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#3C1E1E",
|
||||
"primary": "#522626",
|
||||
"secondary": "#6A2F2F",
|
||||
"elevated": "#6A2F2F"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(210, 90, 60, 0.16)",
|
||||
"selected": "rgba(210, 90, 60, 0.12)",
|
||||
"selectedBorder": "#FF6F61",
|
||||
"focus": "rgba(210, 90, 60, 0.16)",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "rgba(221, 221, 221, 0.5)",
|
||||
"disabledBackground": "rgba(210, 90, 60, 0.08)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #FF6F61 0%, #D25A3C 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #FF6F61 0.01%, #D25A3C 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.05,
|
||||
"tonalOffset": 0.2
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const marsTheme: NewThemeOptions = {
|
||||
name: 'Mars',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: 'rgba(210, 90, 60, 0.2)',
|
||||
medium: 'rgba(210, 90, 60, 0.35)',
|
||||
strong: 'rgba(210, 90, 60, 0.5)',
|
||||
},
|
||||
text: {
|
||||
primary: '#DDDDDD',
|
||||
secondary: '#BBBBBB',
|
||||
disabled: 'rgba(221, 221, 221, 0.5)',
|
||||
link: '#FF6F61',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#FF6F61',
|
||||
},
|
||||
secondary: {
|
||||
main: '#6a2f2f',
|
||||
text: '#BBBBBB',
|
||||
border: 'rgba(210, 90, 60, 0.2)',
|
||||
},
|
||||
background: {
|
||||
canvas: '#3C1E1E',
|
||||
primary: '#522626',
|
||||
secondary: '#6A2F2F',
|
||||
elevated: '#6A2F2F',
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(210, 90, 60, 0.16)',
|
||||
selected: 'rgba(210, 90, 60, 0.12)',
|
||||
selectedBorder: '#FF6F61',
|
||||
focus: 'rgba(210, 90, 60, 0.16)',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: 'rgba(221, 221, 221, 0.5)',
|
||||
disabledBackground: 'rgba(210, 90, 60, 0.08)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #FF6F61 0%, #D25A3C 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #FF6F61 0.01%, #D25A3C 99.99%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.05,
|
||||
tonalOffset: 0.2,
|
||||
},
|
||||
};
|
||||
|
||||
export default marsTheme;
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "Matrix",
|
||||
"id": "matrix",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"background": {
|
||||
"canvas": "#000000",
|
||||
"primary": "#020202",
|
||||
"secondary": "#080808",
|
||||
"elevated": "#080808"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#00c017",
|
||||
"secondary": "#008910",
|
||||
"disabled": "#006a0c",
|
||||
"link": "#00ff41",
|
||||
"maxContrast": "#00ff41"
|
||||
},
|
||||
"border": {
|
||||
"weak": "#008f1144",
|
||||
"medium": "#008f1188",
|
||||
"strong": "#008910"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#008910"
|
||||
},
|
||||
"secondary": {
|
||||
"text": "#008910"
|
||||
},
|
||||
"gradients": {
|
||||
"brandVertical": "linear-gradient(0deg, #008910 0%, #00ff41 100%)",
|
||||
"brandHorizontal": "linear-gradient(90deg, #008910 0%, #00ff41 100%)"
|
||||
}
|
||||
},
|
||||
"shape": {
|
||||
"borderRadius": 0
|
||||
},
|
||||
"typography": {
|
||||
"fontFamily": "monospace"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const matrixTheme: NewThemeOptions = {
|
||||
name: 'Matrix',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
canvas: '#000000',
|
||||
primary: '#020202',
|
||||
secondary: '#080808',
|
||||
elevated: '#080808',
|
||||
},
|
||||
text: {
|
||||
primary: '#00c017',
|
||||
secondary: '#008910',
|
||||
disabled: '#006a0c',
|
||||
link: '#00ff41',
|
||||
maxContrast: '#00ff41',
|
||||
},
|
||||
border: {
|
||||
weak: '#008f1144',
|
||||
medium: '#008f1188',
|
||||
strong: '#008910',
|
||||
},
|
||||
primary: {
|
||||
main: '#008910',
|
||||
},
|
||||
secondary: {
|
||||
text: '#008910',
|
||||
},
|
||||
gradients: {
|
||||
brandVertical: 'linear-gradient(0deg, #008910 0%, #00ff41 100%)',
|
||||
brandHorizontal: 'linear-gradient(90deg, #008910 0%, #00ff41 100%)',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 0,
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
};
|
||||
|
||||
export default matrixTheme;
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "Sapphire dusk",
|
||||
"id": "sapphiredusk",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "#232e47",
|
||||
"medium": "#2c3853",
|
||||
"strong": "#404d6b"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#FFFFFF",
|
||||
"secondary": "#bcccdd",
|
||||
"disabled": "#838da5",
|
||||
"link": "#93EBF0",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#93EBF0",
|
||||
"text": "#a8e9ed",
|
||||
"border": "#93ebf0",
|
||||
"name": "primary",
|
||||
"shade": "#c0f5d9",
|
||||
"transparent": "#93EBF029",
|
||||
"contrastText": "#111614",
|
||||
"borderTransparent": "#93ebf040"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#2c364f",
|
||||
"shade": "#36415e",
|
||||
"transparent": "rgba(200, 200, 180, 0.08)",
|
||||
"text": "#d1dfff",
|
||||
"contrastText": "#acfeff",
|
||||
"border": "rgba(200, 200, 180, 0.08)",
|
||||
"name": "secondary",
|
||||
"borderTransparent": "rgba(200, 200, 180, 0.25)"
|
||||
},
|
||||
"info": {
|
||||
"main": "#4d4593",
|
||||
"text": "#a8e9ed",
|
||||
"border": "#5d54a7"
|
||||
},
|
||||
"error": {
|
||||
"main": "#c63370"
|
||||
},
|
||||
"success": {
|
||||
"main": "#1A7F4B"
|
||||
},
|
||||
"warning": {
|
||||
"main": "#D448EA"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#1e273d",
|
||||
"primary": "#12192e",
|
||||
"secondary": "#212c47",
|
||||
"elevated": "#212c47"
|
||||
},
|
||||
"action": {
|
||||
"hover": "#364057",
|
||||
"selected": "#364260",
|
||||
"selectedBorder": "#D448EA",
|
||||
"focus": "#364057",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "#838da5",
|
||||
"disabledBackground": "rgba(54, 64, 87, 0.2)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #D346EF 0%, #2C83FE 100%)",
|
||||
"brandVertical": "linear-gradient(0deg, #D346EF 0%, #2C83FE 100%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const sapphireDuskTheme: NewThemeOptions = {
|
||||
name: 'Sapphire dusk',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: '#232e47',
|
||||
medium: '#2c3853',
|
||||
strong: '#404d6b',
|
||||
},
|
||||
text: {
|
||||
primary: '#FFFFFF',
|
||||
secondary: '#bcccdd',
|
||||
disabled: '#838da5',
|
||||
link: '#93EBF0',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#93EBF0',
|
||||
text: '#a8e9ed',
|
||||
border: '#93ebf0',
|
||||
name: 'primary',
|
||||
shade: '#c0f5d9',
|
||||
transparent: '#93EBF029',
|
||||
contrastText: '#111614',
|
||||
borderTransparent: '#93ebf040',
|
||||
},
|
||||
secondary: {
|
||||
main: '#2c364f',
|
||||
shade: '#36415e',
|
||||
transparent: 'rgba(200, 200, 180, 0.08)',
|
||||
text: '#d1dfff',
|
||||
contrastText: '#acfeff',
|
||||
border: 'rgba(200, 200, 180, 0.08)',
|
||||
name: 'secondary',
|
||||
borderTransparent: 'rgba(200, 200, 180, 0.25)',
|
||||
},
|
||||
info: {
|
||||
main: '#4d4593',
|
||||
text: '#a8e9ed',
|
||||
border: '#5d54a7',
|
||||
},
|
||||
error: {
|
||||
main: '#c63370',
|
||||
},
|
||||
success: {
|
||||
main: '#1A7F4B',
|
||||
},
|
||||
warning: {
|
||||
main: '#D448EA',
|
||||
},
|
||||
background: {
|
||||
canvas: '#1e273d',
|
||||
primary: '#12192e',
|
||||
secondary: '#212c47',
|
||||
elevated: '#212c47',
|
||||
},
|
||||
action: {
|
||||
hover: '#364057',
|
||||
selected: '#364260',
|
||||
selectedBorder: '#D448EA',
|
||||
focus: '#364057',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: '#838da5',
|
||||
disabledBackground: 'rgba(54, 64, 87, 0.2)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #D346EF 0%, #2C83FE 100%)',
|
||||
brandVertical: 'linear-gradient(0deg, #D346EF 0%, #2C83FE 100%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default sapphireDuskTheme;
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Synthwave",
|
||||
"id": "synthwave",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "rgba(255, 20, 147, 0.12)",
|
||||
"medium": "rgba(255, 20, 147, 0.20)",
|
||||
"strong": "rgba(255, 20, 147, 0.30)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#E0E0E0",
|
||||
"secondary": "rgba(224, 224, 224, 0.75)",
|
||||
"disabled": "rgba(224, 224, 224, 0.5)",
|
||||
"link": "#FF69B4",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#FF1493"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#37183a",
|
||||
"text": "rgba(224, 224, 224, 0.75)",
|
||||
"border": "rgba(255, 20, 147, 0.10)"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#1A1A2E",
|
||||
"primary": "#16213E",
|
||||
"secondary": "#0F3460",
|
||||
"elevated": "#0F3460"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(255, 20, 147, 0.16)",
|
||||
"selected": "rgba(255, 20, 147, 0.12)",
|
||||
"selectedBorder": "#FF1493",
|
||||
"focus": "rgba(255, 20, 147, 0.16)",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "rgba(224, 224, 224, 0.5)",
|
||||
"disabledBackground": "rgba(255, 20, 147, 0.08)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #FF1493 0%, #1E90FF 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #FF1493 0.01%, #1E90FF 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.15
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const synthwaveTheme: NewThemeOptions = {
|
||||
name: 'Synthwave',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: 'rgba(255, 20, 147, 0.12)',
|
||||
medium: 'rgba(255, 20, 147, 0.20)',
|
||||
strong: 'rgba(255, 20, 147, 0.30)',
|
||||
},
|
||||
text: {
|
||||
primary: '#E0E0E0',
|
||||
secondary: 'rgba(224, 224, 224, 0.75)',
|
||||
disabled: 'rgba(224, 224, 224, 0.5)',
|
||||
link: '#FF69B4',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#FF1493',
|
||||
},
|
||||
secondary: {
|
||||
main: '#37183a',
|
||||
text: 'rgba(224, 224, 224, 0.75)',
|
||||
border: 'rgba(255, 20, 147, 0.10)',
|
||||
},
|
||||
background: {
|
||||
canvas: '#1A1A2E',
|
||||
primary: '#16213E',
|
||||
secondary: '#0F3460',
|
||||
elevated: '#0F3460',
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(255, 20, 147, 0.16)',
|
||||
selected: 'rgba(255, 20, 147, 0.12)',
|
||||
selectedBorder: '#FF1493',
|
||||
focus: 'rgba(255, 20, 147, 0.16)',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: 'rgba(224, 224, 224, 0.5)',
|
||||
disabledBackground: 'rgba(255, 20, 147, 0.08)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #FF1493 0%, #1E90FF 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #FF1493 0.01%, #1E90FF 99.99%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
};
|
||||
|
||||
export default synthwaveTheme;
|
||||
50
packages/grafana-data/src/themes/themeDefinitions/tron.json
Normal file
50
packages/grafana-data/src/themes/themeDefinitions/tron.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Tron",
|
||||
"id": "tron",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "rgba(0, 255, 255, 0.12)",
|
||||
"medium": "rgba(0, 255, 255, 0.20)",
|
||||
"strong": "rgba(0, 255, 255, 0.30)"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#E0E0E0",
|
||||
"secondary": "rgba(224, 224, 224, 0.75)",
|
||||
"disabled": "rgba(224, 224, 224, 0.5)",
|
||||
"link": "#00FFFF",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#00FFFF"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#0b2e36",
|
||||
"text": "rgba(224, 224, 224, 0.75)",
|
||||
"border": "rgba(0, 255, 255, 0.10)"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#0A0F18",
|
||||
"primary": "#0F1B2A",
|
||||
"secondary": "#152234",
|
||||
"elevated": "#152234"
|
||||
},
|
||||
"action": {
|
||||
"hover": "rgba(0, 255, 255, 0.16)",
|
||||
"selected": "rgba(0, 255, 255, 0.12)",
|
||||
"selectedBorder": "#00FFFF",
|
||||
"focus": "rgba(0, 255, 255, 0.16)",
|
||||
"hoverOpacity": 0.08,
|
||||
"disabledText": "rgba(224, 224, 224, 0.5)",
|
||||
"disabledBackground": "rgba(0, 255, 255, 0.08)",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #00FFFF 0%, #29ABE2 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #00FFFF 0.01%, #29ABE2 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.05,
|
||||
"tonalOffset": 0.2
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const tronTheme: NewThemeOptions = {
|
||||
name: 'Tron',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: 'rgba(0, 255, 255, 0.12)',
|
||||
medium: 'rgba(0, 255, 255, 0.20)',
|
||||
strong: 'rgba(0, 255, 255, 0.30)',
|
||||
},
|
||||
text: {
|
||||
primary: '#E0E0E0',
|
||||
secondary: 'rgba(224, 224, 224, 0.75)',
|
||||
disabled: 'rgba(224, 224, 224, 0.5)',
|
||||
link: '#00FFFF',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#00FFFF',
|
||||
},
|
||||
secondary: {
|
||||
main: '#0b2e36',
|
||||
text: 'rgba(224, 224, 224, 0.75)',
|
||||
border: 'rgba(0, 255, 255, 0.10)',
|
||||
},
|
||||
background: {
|
||||
canvas: '#0A0F18',
|
||||
primary: '#0F1B2A',
|
||||
secondary: '#152234',
|
||||
elevated: '#152234',
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(0, 255, 255, 0.16)',
|
||||
selected: 'rgba(0, 255, 255, 0.12)',
|
||||
selectedBorder: '#00FFFF',
|
||||
focus: 'rgba(0, 255, 255, 0.16)',
|
||||
hoverOpacity: 0.08,
|
||||
disabledText: 'rgba(224, 224, 224, 0.5)',
|
||||
disabledBackground: 'rgba(0, 255, 255, 0.08)',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #00FFFF 0%, #29ABE2 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #00FFFF 0.01%, #29ABE2 99.99%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.05,
|
||||
tonalOffset: 0.2,
|
||||
},
|
||||
};
|
||||
|
||||
export default tronTheme;
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "Victorian",
|
||||
"id": "victorian",
|
||||
"colors": {
|
||||
"mode": "dark",
|
||||
"border": {
|
||||
"weak": "#3A2C22",
|
||||
"medium": "#3A2C22",
|
||||
"strong": "#4B3D32"
|
||||
},
|
||||
"text": {
|
||||
"primary": "#D9D0A2",
|
||||
"secondary": "#C4B89B",
|
||||
"disabled": "#A89F91",
|
||||
"link": "#C28A4D",
|
||||
"maxContrast": "#FFFFFF"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#C28A4D"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#3A2C22",
|
||||
"text": "#C4B89B",
|
||||
"border": "#4B3D32"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#1F1510",
|
||||
"primary": "#2C1A13",
|
||||
"secondary": "#402A21",
|
||||
"elevated": "#402A21"
|
||||
},
|
||||
"action": {
|
||||
"hover": "#3A2C22",
|
||||
"selected": "#4B3D32",
|
||||
"selectedBorder": "#C28A4D",
|
||||
"focus": "#C28A4D",
|
||||
"hoverOpacity": 0.1,
|
||||
"disabledText": "#A89F91",
|
||||
"disabledBackground": "#402A21",
|
||||
"disabledOpacity": 0.38
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #D9D0a1 0%, #C28A4D 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #D9D0a1 0.01%, #C28A4D 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 4,
|
||||
"hoverFactor": 0.07,
|
||||
"tonalOffset": 0.15
|
||||
},
|
||||
"typography": {
|
||||
"fontFamily": "\"Georgia\", \"Times New Roman\", serif",
|
||||
"fontFamilyMonospace": "'Courier New', monospace"
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const victorianTheme: NewThemeOptions = {
|
||||
name: 'Victorian',
|
||||
colors: {
|
||||
mode: 'dark',
|
||||
border: {
|
||||
weak: '#3A2C22',
|
||||
medium: '#3A2C22',
|
||||
strong: '#4B3D32',
|
||||
},
|
||||
text: {
|
||||
primary: '#D9D0A2',
|
||||
secondary: '#C4B89B',
|
||||
disabled: '#A89F91',
|
||||
link: '#C28A4D',
|
||||
maxContrast: '#FFFFFF',
|
||||
},
|
||||
primary: {
|
||||
main: '#C28A4D',
|
||||
},
|
||||
secondary: {
|
||||
main: '#3A2C22',
|
||||
text: '#C4B89B',
|
||||
border: '#4B3D32',
|
||||
},
|
||||
background: {
|
||||
canvas: '#1F1510',
|
||||
primary: '#2C1A13',
|
||||
secondary: '#402A21',
|
||||
elevated: '#402A21',
|
||||
},
|
||||
action: {
|
||||
hover: '#3A2C22',
|
||||
selected: '#4B3D32',
|
||||
selectedBorder: '#C28A4D',
|
||||
focus: '#C28A4D',
|
||||
hoverOpacity: 0.1,
|
||||
disabledText: '#A89F91',
|
||||
disabledBackground: '#402A21',
|
||||
disabledOpacity: 0.38,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #D9D0a1 0%, #C28A4D 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #D9D0a1 0.01%, #C28A4D 99.99%)',
|
||||
},
|
||||
contrastThreshold: 4,
|
||||
hoverFactor: 0.07,
|
||||
tonalOffset: 0.15,
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Georgia", "Times New Roman", serif',
|
||||
fontFamilyMonospace: "'Courier New', monospace",
|
||||
},
|
||||
};
|
||||
|
||||
export default victorianTheme;
|
||||
50
packages/grafana-data/src/themes/themeDefinitions/zen.json
Normal file
50
packages/grafana-data/src/themes/themeDefinitions/zen.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "Zen",
|
||||
"id": "zen",
|
||||
"colors": {
|
||||
"mode": "light",
|
||||
"text": {
|
||||
"primary": "#333333",
|
||||
"secondary": "#666666",
|
||||
"disabled": "#B8B8B8",
|
||||
"link": "#4F9F6E",
|
||||
"maxContrast": "#000000"
|
||||
},
|
||||
"border": {
|
||||
"weak": "#B1B7B3",
|
||||
"medium": "#A2A8A2",
|
||||
"strong": "#7C7F7A"
|
||||
},
|
||||
"primary": {
|
||||
"main": "#6D8E6D"
|
||||
},
|
||||
"secondary": {
|
||||
"main": "#E0E0E0",
|
||||
"text": "#666666",
|
||||
"border": "#A2A8A2"
|
||||
},
|
||||
"background": {
|
||||
"canvas": "#F4F4F4",
|
||||
"primary": "#E9E9E9",
|
||||
"secondary": "#D8D8D8",
|
||||
"elevated": "#E9E9E9"
|
||||
},
|
||||
"action": {
|
||||
"hover": "#D1D1D1",
|
||||
"selected": "#B8B8B8",
|
||||
"selectedBorder": "#88B88B",
|
||||
"hoverOpacity": 0.1,
|
||||
"focus": "#D1D1D1",
|
||||
"disabledBackground": "#E0E0E0",
|
||||
"disabledText": "#B8B8B8",
|
||||
"disabledOpacity": 0.5
|
||||
},
|
||||
"gradients": {
|
||||
"brandHorizontal": "linear-gradient(270deg, #88B88B 0%, #6D8E6D 100%)",
|
||||
"brandVertical": "linear-gradient(0.01deg, #88B88B 0.01%, #6D8E6D 99.99%)"
|
||||
},
|
||||
"contrastThreshold": 3,
|
||||
"hoverFactor": 0.03,
|
||||
"tonalOffset": 0.2
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NewThemeOptions } from '../createTheme';
|
||||
|
||||
const zenTheme: NewThemeOptions = {
|
||||
name: 'Zen',
|
||||
colors: {
|
||||
mode: 'light',
|
||||
text: {
|
||||
primary: '#333333',
|
||||
secondary: '#666666',
|
||||
disabled: '#B8B8B8',
|
||||
link: '#4F9F6E',
|
||||
maxContrast: '#000000',
|
||||
},
|
||||
border: {
|
||||
weak: '#B1B7B3',
|
||||
medium: '#A2A8A2',
|
||||
strong: '#7C7F7A',
|
||||
},
|
||||
primary: {
|
||||
main: '#6D8E6D',
|
||||
},
|
||||
secondary: {
|
||||
main: '#E0E0E0',
|
||||
text: '#666666',
|
||||
border: '#A2A8A2',
|
||||
},
|
||||
background: {
|
||||
canvas: '#F4F4F4',
|
||||
primary: '#E9E9E9',
|
||||
secondary: '#D8D8D8',
|
||||
elevated: '#E9E9E9',
|
||||
},
|
||||
action: {
|
||||
hover: '#D1D1D1',
|
||||
selected: '#B8B8B8',
|
||||
selectedBorder: '#88B88B',
|
||||
hoverOpacity: 0.1,
|
||||
focus: '#D1D1D1',
|
||||
disabledBackground: '#E0E0E0',
|
||||
disabledText: '#B8B8B8',
|
||||
disabledOpacity: 0.5,
|
||||
},
|
||||
gradients: {
|
||||
brandHorizontal: 'linear-gradient(270deg, #88B88B 0%, #6D8E6D 100%)',
|
||||
brandVertical: 'linear-gradient(0.01deg, #88B88B 0.01%, #6D8E6D 99.99%)',
|
||||
},
|
||||
contrastThreshold: 3,
|
||||
hoverFactor: 0.03,
|
||||
tonalOffset: 0.2,
|
||||
},
|
||||
};
|
||||
|
||||
export default zenTheme;
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
|
||||
import { ThemeBreakpoints } from './breakpoints';
|
||||
@@ -35,27 +37,36 @@ export interface GrafanaTheme2 {
|
||||
flags: {};
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
export interface ThemeRichColor {
|
||||
export const ThemeRichColorInputSchema = z.object({
|
||||
/** color intent (primary, secondary, info, error, etc) */
|
||||
name: string;
|
||||
name: z.string().optional(),
|
||||
/** Main color */
|
||||
main: string;
|
||||
main: z.string().optional(),
|
||||
/** Used for hover */
|
||||
shade: string;
|
||||
shade: z.string().optional(),
|
||||
/** Used for text */
|
||||
text: string;
|
||||
text: z.string().optional(),
|
||||
/** Used for borders */
|
||||
border: string;
|
||||
border: z.string().optional(),
|
||||
/** Used subtly colored backgrounds */
|
||||
transparent: string;
|
||||
transparent: z.string().optional(),
|
||||
/** Used for weak colored borders like larger alert/banner boxes and smaller badges and tags */
|
||||
borderTransparent: string;
|
||||
borderTransparent: z.string().optional(),
|
||||
/** Text color for text ontop of main */
|
||||
contrastText: string;
|
||||
}
|
||||
contrastText: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ThemeRichColorSchema = ThemeRichColorInputSchema.required();
|
||||
|
||||
/** @alpha */
|
||||
export type ThemeRichColor = z.infer<typeof ThemeRichColorSchema>;
|
||||
|
||||
/** @internal */
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export type DeepRequired<T> = Required<{
|
||||
[P in keyof T]: T[P] extends Required<T[P]> ? T[P] : DeepRequired<T[P]>;
|
||||
}>;
|
||||
|
||||
@@ -622,10 +622,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
exploreLogsAggregatedMetrics?: boolean;
|
||||
/**
|
||||
* Used in Logs Drilldown to limit the time range
|
||||
*/
|
||||
exploreLogsLimitedTimeRange?: boolean;
|
||||
/**
|
||||
* Enables the gRPC client to authenticate with the App Platform by using ID & access tokens
|
||||
*/
|
||||
appPlatformGrpcClientAuth?: boolean;
|
||||
@@ -653,10 +649,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
rolePickerDrawer?: boolean;
|
||||
/**
|
||||
* Enable sprinkles on unified storage search
|
||||
*/
|
||||
unifiedStorageSearchSprinkles?: boolean;
|
||||
/**
|
||||
* Pick the dual write mode from database configs
|
||||
*/
|
||||
managedDualWriter?: boolean;
|
||||
@@ -695,10 +687,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
passwordlessMagicLinkAuthentication?: boolean;
|
||||
/**
|
||||
* Display Related Logs in Grafana Metrics Drilldown
|
||||
*/
|
||||
exploreMetricsRelatedLogs?: boolean;
|
||||
/**
|
||||
* Adds support for quotes and special characters in label values for Prometheus queries
|
||||
*/
|
||||
prometheusSpecialCharsInLabelValues?: boolean;
|
||||
|
||||
@@ -9,5 +9,4 @@
|
||||
* and be subject to the standard policies
|
||||
*/
|
||||
|
||||
// This is a dummy export so typescript doesn't error importing an "empty module"
|
||||
export const unstable = {};
|
||||
export { default as themeJsonSchema } from './themes/schema.generated.json';
|
||||
|
||||
@@ -14,6 +14,8 @@ export type Props = React.ComponentProps<typeof TextArea> & {
|
||||
isConfigured: boolean;
|
||||
/** Called when the user clicks on the "Reset" button in order to clear the secret */
|
||||
onReset: () => void;
|
||||
/** If true, the text area will grow to fill available width. */
|
||||
grow?: boolean;
|
||||
};
|
||||
|
||||
export const CONFIGURED_TEXT = 'configured';
|
||||
@@ -35,11 +37,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
*
|
||||
* https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secrettextarea--docs
|
||||
*/
|
||||
export const SecretTextArea = ({ isConfigured, onReset, ...props }: Props) => {
|
||||
export const SecretTextArea = ({ isConfigured, onReset, grow, ...props }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<Stack>
|
||||
<Box>
|
||||
<Box grow={grow ? 1 : undefined}>
|
||||
{!isConfigured && <TextArea {...props} />}
|
||||
{isConfigured && (
|
||||
<TextArea
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
_ "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||
_ "github.com/Azure/go-autorest/autorest"
|
||||
_ "github.com/Azure/go-autorest/autorest/adal"
|
||||
_ "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
|
||||
_ "github.com/beevik/etree"
|
||||
_ "github.com/blugelabs/bluge"
|
||||
_ "github.com/blugelabs/bluge_segment_api"
|
||||
|
||||
@@ -552,6 +552,7 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
||||
// gets dashboards that the user was granted read access to
|
||||
permissions := user.GetPermissions()
|
||||
dashboardPermissions := permissions[dashboards.ActionDashboardsRead]
|
||||
folderPermissions := permissions[dashboards.ActionFoldersRead]
|
||||
dashboardUids := make([]string, 0)
|
||||
sharedDashboards := make([]string, 0)
|
||||
|
||||
@@ -562,6 +563,13 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, folderPermission := range folderPermissions {
|
||||
if folderUid, found := strings.CutPrefix(folderPermission, dashboards.ScopeFoldersPrefix); found {
|
||||
if !slices.Contains(dashboardUids, folderUid) && folderUid != foldermodel.SharedWithMeFolderUID && folderUid != foldermodel.GeneralFolderUID {
|
||||
dashboardUids = append(dashboardUids, folderUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(dashboardUids) == 0 {
|
||||
return sharedDashboards, nil
|
||||
@@ -572,9 +580,15 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
folderKey, err := asResourceKey(user.GetNamespace(), folders.RESOURCE)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
dashboardSearchRequest := &resourcepb.ResourceSearchRequest{
|
||||
Fields: []string{"folder"},
|
||||
Limit: int64(len(dashboardUids)),
|
||||
Federated: []*resourcepb.ResourceKey{folderKey},
|
||||
Fields: []string{"folder"},
|
||||
Limit: int64(len(dashboardUids)),
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: key,
|
||||
Fields: []*resourcepb.Requirement{{
|
||||
@@ -610,12 +624,6 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
||||
}
|
||||
}
|
||||
|
||||
// only folders the user has access to will be returned here
|
||||
folderKey, err := asResourceKey(user.GetNamespace(), folders.RESOURCE)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
folderSearchRequest := &resourcepb.ResourceSearchRequest{
|
||||
Fields: []string{"folder"},
|
||||
Limit: int64(len(allFolders)),
|
||||
@@ -628,6 +636,7 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
||||
}},
|
||||
},
|
||||
}
|
||||
// only folders the user has access to will be returned here
|
||||
foldersResult, err := s.client.Search(ctx, folderSearchRequest)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
|
||||
@@ -507,6 +507,15 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
|
||||
[]byte("publicfolder"), // folder uid
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resourcepb.ResourceKey{
|
||||
Name: "sharedfolder",
|
||||
Resource: "folder",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("privatefolder"), // folder uid
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -550,6 +559,15 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
|
||||
[]byte("privatefolder"), // folder uid
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resourcepb.ResourceKey{
|
||||
Name: "sharedfolder",
|
||||
Resource: "folder",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("privatefolder"), // folder uid
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -571,6 +589,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
|
||||
allPermissions := make(map[int64]map[string][]string)
|
||||
permissions := make(map[string][]string)
|
||||
permissions[dashboards.ActionDashboardsRead] = []string{"dashboards:uid:dashboardinroot", "dashboards:uid:dashboardinprivatefolder", "dashboards:uid:dashboardinpublicfolder"}
|
||||
permissions[dashboards.ActionFoldersRead] = []string{"folders:uid:sharedfolder"}
|
||||
allPermissions[1] = permissions
|
||||
// "Permissions" is where we store the uid of dashboards shared with the user
|
||||
req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test", OrgID: 1, Permissions: allPermissions}))
|
||||
@@ -581,14 +600,19 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
|
||||
|
||||
// first call gets all dashboards user has permission for
|
||||
firstCall := mockClient.MockCalls[0]
|
||||
assert.Equal(t, firstCall.Options.Fields[0].Values, []string{"dashboardinroot", "dashboardinprivatefolder", "dashboardinpublicfolder"})
|
||||
assert.Equal(t, firstCall.Options.Fields[0].Values, []string{"dashboardinroot", "dashboardinprivatefolder", "dashboardinpublicfolder", "sharedfolder"})
|
||||
// verify federated field is set to include folders
|
||||
assert.NotNil(t, firstCall.Federated)
|
||||
assert.Equal(t, 1, len(firstCall.Federated))
|
||||
assert.Equal(t, "folder.grafana.app", firstCall.Federated[0].Group)
|
||||
assert.Equal(t, "folders", firstCall.Federated[0].Resource)
|
||||
// second call gets folders associated with the previous dashboards
|
||||
secondCall := mockClient.MockCalls[1]
|
||||
assert.Equal(t, secondCall.Options.Fields[0].Values, []string{"privatefolder", "publicfolder"})
|
||||
// lastly, search ONLY for dashboards user has permission to read that are within folders the user does NOT have
|
||||
// lastly, search ONLY for dashboards and folders user has permission to read that are within folders the user does NOT have
|
||||
// permission to read
|
||||
thirdCall := mockClient.MockCalls[2]
|
||||
assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder"})
|
||||
assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder", "sharedfolder"})
|
||||
|
||||
resp := rr.Result()
|
||||
defer func() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/dskit/flagext"
|
||||
@@ -15,11 +16,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
@@ -111,14 +116,25 @@ func newClientPool(clientCfg grpcclient.Config, log log.Logger, reg prometheus.R
|
||||
Help: "Time spent executing requests to resource server.",
|
||||
Buckets: prometheus.ExponentialBuckets(0.008, 4, 7),
|
||||
}, []string{"operation", "status_code"})
|
||||
factoryRequestRetries := promauto.With(reg).NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "resource_server_client_request_retries_total",
|
||||
Help: "Total number of retries for requests to the resource server.",
|
||||
}, []string{"operation"})
|
||||
|
||||
factory := ringclient.PoolInstFunc(func(inst ring.InstanceDesc) (ringclient.PoolClient, error) {
|
||||
unaryInterceptors, streamInterceptors := grpcclient.Instrument(factoryRequestDuration)
|
||||
|
||||
// Add retry interceptors for transient connection issues
|
||||
unaryInterceptors = append(unaryInterceptors, ringClientRetryInterceptor())
|
||||
unaryInterceptors = append(unaryInterceptors, ringClientRetryInstrument(factoryRequestRetries))
|
||||
|
||||
opts, err := clientCfg.DialOption(unaryInterceptors, streamInterceptors, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts = append(opts, connectionBackoffOptions())
|
||||
|
||||
conn, err := grpc.NewClient(inst.Addr, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial resource server %s %s: %s", inst.Id, inst.Addr, err)
|
||||
@@ -135,3 +151,40 @@ func newClientPool(clientCfg grpcclient.Config, log log.Logger, reg prometheus.R
|
||||
|
||||
return ringclient.NewPool(resource.RingName, poolCfg, nil, factory, clientsCount, log)
|
||||
}
|
||||
|
||||
// ringClientRetryInterceptor creates an interceptor to perform retries for unary methods.
|
||||
// It retries on ResourceExhausted and Unavailable codes, which are typical for
|
||||
// transient connection issues and rate limiting.
|
||||
func ringClientRetryInterceptor() grpc.UnaryClientInterceptor {
|
||||
return grpc_retry.UnaryClientInterceptor(
|
||||
grpc_retry.WithMax(3),
|
||||
grpc_retry.WithBackoff(grpc_retry.BackoffExponentialWithJitter(time.Second, 0.1)),
|
||||
grpc_retry.WithCodes(codes.ResourceExhausted, codes.Unavailable),
|
||||
)
|
||||
}
|
||||
|
||||
// ringClientRetryInstrument creates an interceptor to count retry attempts for metrics.
|
||||
func ringClientRetryInstrument(metric *prometheus.CounterVec) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
// We can tell if a call is a retry by checking the retry attempt metadata.
|
||||
attempt, err := strconv.Atoi(metautils.ExtractOutgoing(ctx).Get(grpc_retry.AttemptMetadataKey))
|
||||
if err == nil && attempt > 0 {
|
||||
metric.WithLabelValues(method).Inc()
|
||||
}
|
||||
return invoker(ctx, method, req, resp, cc, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// connectionBackoffOptions configures connection backoff parameters for faster recovery from
|
||||
// transient connection failures (e.g., during pod restarts).
|
||||
func connectionBackoffOptions() grpc.DialOption {
|
||||
return grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
BaseDelay: 100 * time.Millisecond,
|
||||
Multiplier: 1.6,
|
||||
Jitter: 0.2,
|
||||
MaxDelay: 10 * time.Second,
|
||||
},
|
||||
MinConnectTimeout: 5 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,11 +11,16 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
|
||||
apiserverrest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
secret "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
@@ -232,19 +237,16 @@ func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfi
|
||||
if o.StorageType != StorageTypeUnifiedGrpc {
|
||||
return nil
|
||||
}
|
||||
conn, err := grpc.NewClient(o.Address,
|
||||
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
|
||||
grpcOpts := o.buildGrpcDialOptions()
|
||||
|
||||
conn, err := grpc.NewClient(o.Address, grpcOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var indexConn *grpc.ClientConn
|
||||
if o.SearchServerAddress != "" {
|
||||
indexConn, err = grpc.NewClient(o.SearchServerAddress,
|
||||
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
indexConn, err = grpc.NewClient(o.SearchServerAddress, grpcOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -293,3 +295,42 @@ func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfi
|
||||
serverConfig.RESTOptionsGetter = getter
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildGrpcDialOptions creates gRPC dial options with resilience mechanisms:
|
||||
// - Round-robin load balancing with client-side health checking
|
||||
// - Retry interceptor for transient connection issues
|
||||
// - Keepalive for long-lived connections
|
||||
func (o *StorageOptions) buildGrpcDialOptions() []grpc.DialOption {
|
||||
// Retry interceptor for transient connection issues (codes.Unavailable includes connection refused)
|
||||
retryInterceptor := grpc_retry.UnaryClientInterceptor(
|
||||
grpc_retry.WithMax(3),
|
||||
grpc_retry.WithBackoff(grpc_retry.BackoffExponentialWithJitter(time.Second, 0.5)),
|
||||
grpc_retry.WithCodes(codes.ResourceExhausted, codes.Unavailable),
|
||||
)
|
||||
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithChainUnaryInterceptor(retryInterceptor),
|
||||
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
BaseDelay: 100 * time.Millisecond,
|
||||
Multiplier: 1.6,
|
||||
Jitter: 0.2,
|
||||
MaxDelay: 10 * time.Second,
|
||||
},
|
||||
MinConnectTimeout: 5 * time.Second,
|
||||
}),
|
||||
}
|
||||
|
||||
if o.GrpcClientKeepaliveTime > 0 {
|
||||
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: o.GrpcClientKeepaliveTime,
|
||||
Timeout: 10 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
}))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
@@ -133,7 +133,11 @@ type FeatureFlag struct {
|
||||
Stage FeatureFlagStage `json:"stage,omitempty"`
|
||||
Owner codeowner `json:"-"` // Owner person or team that owns this feature flag
|
||||
|
||||
// CEL-GO expression. Using the value "true" will mean this is on by default
|
||||
// Expression defined by the feature_toggles configuration.
|
||||
// Supports multiple types including boolean, string, integer, float,
|
||||
// and structured values following the OpenFeature specification.
|
||||
// Using the value "true" means the feature flag is enabled by default,
|
||||
// Using the value "1.0" means the default value of the feature flag is 1.0
|
||||
Expression string `json:"expression,omitempty"`
|
||||
|
||||
// Special behavior properties
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
clientauthmiddleware "github.com/grafana/grafana/pkg/clientauth/middleware"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
@@ -26,7 +27,7 @@ type OpenFeatureConfig struct {
|
||||
// HTTPClient is a pre-configured HTTP client (optional, used by features-service + OFREP providers)
|
||||
HTTPClient *http.Client
|
||||
// StaticFlags are the feature flags to use with static provider
|
||||
StaticFlags map[string]bool
|
||||
StaticFlags map[string]memprovider.InMemoryFlag
|
||||
// TargetingKey is used for evaluation context
|
||||
TargetingKey string
|
||||
// ContextAttrs are additional attributes for evaluation context
|
||||
@@ -100,7 +101,7 @@ func InitOpenFeatureWithCfg(cfg *setting.Cfg) error {
|
||||
func createProvider(
|
||||
providerType string,
|
||||
u *url.URL,
|
||||
staticFlags map[string]bool,
|
||||
staticFlags map[string]memprovider.InMemoryFlag,
|
||||
httpClient *http.Client,
|
||||
) (openfeature.FeatureProvider, error) {
|
||||
if providerType == setting.FeaturesServiceProviderType || providerType == setting.OFREPProviderType {
|
||||
@@ -117,7 +118,7 @@ func createProvider(
|
||||
}
|
||||
}
|
||||
|
||||
return newStaticProvider(staticFlags)
|
||||
return newStaticProvider(staticFlags, standardFeatureFlags)
|
||||
}
|
||||
|
||||
func createHTTPClient(m *clientauthmiddleware.TokenExchangeMiddleware) (*http.Client, error) {
|
||||
|
||||
@@ -1031,13 +1031,6 @@ var (
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaObservabilityLogsSquad,
|
||||
},
|
||||
{
|
||||
Name: "exploreLogsLimitedTimeRange",
|
||||
Description: "Used in Logs Drilldown to limit the time range",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaObservabilityLogsSquad,
|
||||
},
|
||||
{
|
||||
Name: "appPlatformGrpcClientAuth",
|
||||
Description: "Enables the gRPC client to authenticate with the App Platform by using ID & access tokens",
|
||||
@@ -1080,13 +1073,6 @@ var (
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: identityAccessTeam,
|
||||
},
|
||||
{
|
||||
Name: "unifiedStorageSearchSprinkles",
|
||||
Description: "Enable sprinkles on unified storage search",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaSearchAndStorageSquad,
|
||||
HideFromDocs: true,
|
||||
},
|
||||
{
|
||||
Name: "managedDualWriter",
|
||||
Description: "Pick the dual write mode from database configs",
|
||||
@@ -1148,14 +1134,6 @@ var (
|
||||
Owner: identityAccessTeam,
|
||||
HideFromDocs: true,
|
||||
},
|
||||
{
|
||||
Name: "exploreMetricsRelatedLogs",
|
||||
Description: "Display Related Logs in Grafana Metrics Drilldown",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaObservabilityMetricsSquad,
|
||||
FrontendOnly: true,
|
||||
HideFromDocs: false,
|
||||
},
|
||||
{
|
||||
Name: "prometheusSpecialCharsInLabelValues",
|
||||
Description: "Adds support for quotes and special characters in label values for Prometheus queries",
|
||||
|
||||
@@ -47,7 +47,8 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) {
|
||||
}
|
||||
mgmt.warnings[key] = "unknown flag in config"
|
||||
}
|
||||
mgmt.startup[key] = val
|
||||
|
||||
mgmt.startup[key] = val.Variants[val.DefaultVariant] == true
|
||||
}
|
||||
|
||||
// update the values
|
||||
|
||||
@@ -29,7 +29,7 @@ func CreateStaticEvaluator(cfg *setting.Cfg) (StaticFlagEvaluator, error) {
|
||||
return nil, fmt.Errorf("failed to read feature flags from config: %w", err)
|
||||
}
|
||||
|
||||
staticProvider, err := newStaticProvider(staticFlags)
|
||||
staticProvider, err := newStaticProvider(staticFlags, standardFeatureFlags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create static provider: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package featuremgmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// inMemoryBulkProvider is a wrapper around memprovider.InMemoryProvider that
|
||||
@@ -28,37 +33,21 @@ func (p *inMemoryBulkProvider) ListFlags() ([]string, error) {
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func newStaticProvider(confFlags map[string]bool) (openfeature.FeatureProvider, error) {
|
||||
flags := make(map[string]memprovider.InMemoryFlag, len(standardFeatureFlags))
|
||||
func newStaticProvider(confFlags map[string]memprovider.InMemoryFlag, standardFlags []FeatureFlag) (openfeature.FeatureProvider, error) {
|
||||
flags := make(map[string]memprovider.InMemoryFlag, len(standardFlags))
|
||||
|
||||
// Parse and add standard flags
|
||||
for _, flag := range standardFlags {
|
||||
inMemFlag, err := setting.ParseFlag(flag.Name, flag.Expression)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse flag %s: %w", flag.Name, err)
|
||||
}
|
||||
|
||||
flags[flag.Name] = inMemFlag
|
||||
}
|
||||
|
||||
// Add flags from config.ini file
|
||||
for name, value := range confFlags {
|
||||
flags[name] = createInMemoryFlag(name, value)
|
||||
}
|
||||
|
||||
// Add standard flags
|
||||
for _, flag := range standardFeatureFlags {
|
||||
if _, exists := flags[flag.Name]; !exists {
|
||||
enabled := flag.Expression == "true"
|
||||
flags[flag.Name] = createInMemoryFlag(flag.Name, enabled)
|
||||
}
|
||||
}
|
||||
maps.Copy(flags, confFlags)
|
||||
|
||||
return newInMemoryBulkProvider(flags), nil
|
||||
}
|
||||
|
||||
func createInMemoryFlag(name string, enabled bool) memprovider.InMemoryFlag {
|
||||
variant := "disabled"
|
||||
if enabled {
|
||||
variant = "enabled"
|
||||
}
|
||||
|
||||
return memprovider.InMemoryFlag{
|
||||
Key: name,
|
||||
DefaultVariant: variant,
|
||||
Variants: map[string]interface{}{
|
||||
"enabled": true,
|
||||
"disabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -93,3 +94,144 @@ ABCD = true
|
||||
enabledFeatureManager := mgr.GetEnabled(ctx)
|
||||
assert.Equal(t, openFeatureEnabledFlags, enabledFeatureManager)
|
||||
}
|
||||
|
||||
func Test_StaticProvider_TypedFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
flags FeatureFlag
|
||||
defaultValue any
|
||||
expectedValue any
|
||||
}{
|
||||
{
|
||||
flags: FeatureFlag{
|
||||
Name: "Flag",
|
||||
Expression: "true",
|
||||
},
|
||||
defaultValue: false,
|
||||
expectedValue: true,
|
||||
},
|
||||
{
|
||||
flags: FeatureFlag{
|
||||
Name: "Flag",
|
||||
Expression: "1.0",
|
||||
},
|
||||
defaultValue: 0.0,
|
||||
expectedValue: 1.0,
|
||||
},
|
||||
{
|
||||
flags: FeatureFlag{
|
||||
Name: "Flag",
|
||||
Expression: "blue",
|
||||
},
|
||||
defaultValue: "red",
|
||||
expectedValue: "blue",
|
||||
},
|
||||
{
|
||||
flags: FeatureFlag{
|
||||
Name: "Flag",
|
||||
Expression: "1",
|
||||
},
|
||||
defaultValue: int64(0),
|
||||
expectedValue: int64(1),
|
||||
},
|
||||
{
|
||||
flags: FeatureFlag{
|
||||
Name: "Flag",
|
||||
Expression: `{ "foo": "bar" }`,
|
||||
},
|
||||
expectedValue: map[string]any{"foo": "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
provider, err := newStaticProvider(nil, []FeatureFlag{tt.flags})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var result any
|
||||
switch tt.expectedValue.(type) {
|
||||
case bool:
|
||||
result = provider.BooleanEvaluation(t.Context(), tt.flags.Name, tt.defaultValue.(bool), openfeature.FlattenedContext{}).Value
|
||||
case float64:
|
||||
result = provider.FloatEvaluation(t.Context(), tt.flags.Name, tt.defaultValue.(float64), openfeature.FlattenedContext{}).Value
|
||||
case string:
|
||||
result = provider.StringEvaluation(t.Context(), tt.flags.Name, tt.defaultValue.(string), openfeature.FlattenedContext{}).Value
|
||||
case int64:
|
||||
result = provider.IntEvaluation(t.Context(), tt.flags.Name, tt.defaultValue.(int64), openfeature.FlattenedContext{}).Value
|
||||
case map[string]any:
|
||||
result = provider.ObjectEvaluation(t.Context(), tt.flags.Name, tt.defaultValue, openfeature.FlattenedContext{}).Value
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedValue, result)
|
||||
}
|
||||
}
|
||||
func Test_StaticProvider_ConfigOverride(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
originalValue string
|
||||
configValue any
|
||||
}{
|
||||
{
|
||||
name: "bool",
|
||||
originalValue: "false",
|
||||
configValue: true,
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
originalValue: "0",
|
||||
configValue: int64(1),
|
||||
},
|
||||
{
|
||||
name: "float",
|
||||
originalValue: "0.0",
|
||||
configValue: 1.0,
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
originalValue: "foo",
|
||||
configValue: "bar",
|
||||
},
|
||||
{
|
||||
name: "structure",
|
||||
originalValue: "{}",
|
||||
configValue: make(map[string]any),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
configFlags, standardFlags := makeFlags(tt)
|
||||
provider, err := newStaticProvider(configFlags, standardFlags)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var result any
|
||||
switch tt.configValue.(type) {
|
||||
case bool:
|
||||
result = provider.BooleanEvaluation(t.Context(), tt.name, false, openfeature.FlattenedContext{}).Value
|
||||
case float64:
|
||||
result = provider.FloatEvaluation(t.Context(), tt.name, 0.0, openfeature.FlattenedContext{}).Value
|
||||
case string:
|
||||
result = provider.StringEvaluation(t.Context(), tt.name, "foo", openfeature.FlattenedContext{}).Value
|
||||
case int64:
|
||||
result = provider.IntEvaluation(t.Context(), tt.name, 1, openfeature.FlattenedContext{}).Value
|
||||
case map[string]any:
|
||||
result = provider.ObjectEvaluation(t.Context(), tt.name, make(map[string]any), openfeature.FlattenedContext{}).Value
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.configValue, result)
|
||||
}
|
||||
}
|
||||
|
||||
func makeFlags(tt struct {
|
||||
name string
|
||||
originalValue string
|
||||
configValue any
|
||||
}) (map[string]memprovider.InMemoryFlag, []FeatureFlag) {
|
||||
orig := FeatureFlag{
|
||||
Name: tt.name,
|
||||
Expression: tt.originalValue,
|
||||
}
|
||||
|
||||
config := map[string]memprovider.InMemoryFlag{
|
||||
tt.name: setting.NewInMemoryFlag(tt.name, tt.configValue),
|
||||
}
|
||||
|
||||
return config, []FeatureFlag{orig}
|
||||
}
|
||||
|
||||
@@ -409,7 +409,6 @@ lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5
|
||||
kubernetesCliDashboards,2024-12-13T22:55:43Z,2025-02-18T23:11:26Z,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen
|
||||
useV2DashboardsAPI,2024-12-17T21:17:09Z,2025-03-12T17:43:32Z,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop
|
||||
investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho
|
||||
unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood
|
||||
prometheusSpecialCharsInLabelValues,2024-12-18T21:31:08Z,,721c50a304588ebd7cea76e301ec0f68a5a55d68,Nick Richmond
|
||||
unifiedStorageSearchUI,2024-12-19T18:21:48Z,,a8f347144ddc16f2033fdeb4f3474e49239ba7ab,Scott Lepper
|
||||
playlistsReconciler,2024-12-20T03:09:31Z,,24bf337c562dc9b9d8684cc9acb7ea171ea83414,Charandas
|
||||
|
||||
|
3
pkg/services/featuremgmt/toggles_gen.csv
generated
3
pkg/services/featuremgmt/toggles_gen.csv
generated
@@ -142,14 +142,12 @@ vizActionsAuth,preview,@grafana/dataviz-squad,false,false,true
|
||||
alertingPrometheusRulesPrimary,experimental,@grafana/alerting-squad,false,false,true
|
||||
exploreLogsShardSplitting,experimental,@grafana/observability-logs,false,false,true
|
||||
exploreLogsAggregatedMetrics,experimental,@grafana/observability-logs,false,false,true
|
||||
exploreLogsLimitedTimeRange,experimental,@grafana/observability-logs,false,false,true
|
||||
appPlatformGrpcClientAuth,experimental,@grafana/identity-access-team,false,false,false
|
||||
groupAttributeSync,privatePreview,@grafana/identity-access-team,false,false,false
|
||||
alertingQueryAndExpressionsStepMode,GA,@grafana/alerting-squad,false,false,true
|
||||
improvedExternalSessionHandling,GA,@grafana/identity-access-team,false,false,false
|
||||
useSessionStorageForRedirection,GA,@grafana/identity-access-team,false,false,false
|
||||
rolePickerDrawer,experimental,@grafana/identity-access-team,false,false,false
|
||||
unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false
|
||||
managedDualWriter,experimental,@grafana/search-and-storage,false,false,false
|
||||
pluginsSriChecks,GA,@grafana/plugins-platform-backend,false,false,false
|
||||
unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false
|
||||
@@ -159,7 +157,6 @@ newTimeRangeZoomShortcuts,experimental,@grafana/dataviz-squad,false,false,true
|
||||
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
|
||||
playlistsReconciler,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||
passwordlessMagicLinkAuthentication,experimental,@grafana/identity-access-team,false,false,false
|
||||
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
|
||||
prometheusSpecialCharsInLabelValues,experimental,@grafana/oss-big-tent,false,false,true
|
||||
enableExtensionsAdminPage,experimental,@grafana/plugins-platform-backend,false,true,false
|
||||
enableSCIM,preview,@grafana/identity-access-team,false,false,false
|
||||
|
||||
|
4
pkg/services/featuremgmt/toggles_gen.go
generated
4
pkg/services/featuremgmt/toggles_gen.go
generated
@@ -455,10 +455,6 @@ const (
|
||||
// Enables the new role picker drawer design
|
||||
FlagRolePickerDrawer = "rolePickerDrawer"
|
||||
|
||||
// FlagUnifiedStorageSearchSprinkles
|
||||
// Enable sprinkles on unified storage search
|
||||
FlagUnifiedStorageSearchSprinkles = "unifiedStorageSearchSprinkles"
|
||||
|
||||
// FlagManagedDualWriter
|
||||
// Pick the dual write mode from database configs
|
||||
FlagManagedDualWriter = "managedDualWriter"
|
||||
|
||||
19
pkg/services/featuremgmt/toggles_gen.json
generated
19
pkg/services/featuremgmt/toggles_gen.json
generated
@@ -1382,7 +1382,8 @@
|
||||
"metadata": {
|
||||
"name": "exploreLogsLimitedTimeRange",
|
||||
"resourceVersion": "1764664939750",
|
||||
"creationTimestamp": "2024-08-29T13:55:59Z"
|
||||
"creationTimestamp": "2024-08-29T13:55:59Z",
|
||||
"deletionTimestamp": "2026-01-12T22:18:14Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Used in Logs Drilldown to limit the time range",
|
||||
@@ -1408,7 +1409,8 @@
|
||||
"metadata": {
|
||||
"name": "exploreMetricsRelatedLogs",
|
||||
"resourceVersion": "1764664939750",
|
||||
"creationTimestamp": "2024-11-05T16:28:43Z"
|
||||
"creationTimestamp": "2024-11-05T16:28:43Z",
|
||||
"deletionTimestamp": "2026-01-09T22:14:53Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Display Related Logs in Grafana Metrics Drilldown",
|
||||
@@ -3721,19 +3723,6 @@
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "unifiedStorageSearchSprinkles",
|
||||
"resourceVersion": "1764664939750",
|
||||
"creationTimestamp": "2024-12-18T17:00:54Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable sprinkles on unified storage search",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/search-and-storage",
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "unifiedStorageSearchUI",
|
||||
|
||||
@@ -190,9 +190,6 @@ func verifyFlagsConfiguration(t *testing.T) {
|
||||
if flag.Stage == FeatureStageGeneralAvailability && flag.Expression == "" {
|
||||
t.Errorf("GA features must be explicitly enabled or disabled, please add the `Expression` property for %s", flag.Name)
|
||||
}
|
||||
if flag.Expression != "" && flag.Expression != "true" && flag.Expression != "false" {
|
||||
t.Errorf("the `Expression` property for %s is incorrect. valid values are: `true`, `false` or empty string for default", flag.Name)
|
||||
}
|
||||
// Check camel case names
|
||||
if flag.Name != strcase.ToLowerCamel(flag.Name) && !legacyNames[flag.Name] {
|
||||
invalidNames = append(invalidNames, flag.Name)
|
||||
|
||||
90
pkg/services/preference/generate_themes.go
Normal file
90
pkg/services/preference/generate_themes.go
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Colors struct {
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
type ThemeDefinition struct {
|
||||
Colors Colors `json:"colors"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
themesPath := filepath.Join("..", "..", "..", "packages", "grafana-data", "src", "themes", "themeDefinitions")
|
||||
|
||||
// Check if the themes directory exists
|
||||
if _, err := os.Stat(themesPath); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "Themes directory not found: %s\n", themesPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
output := `// Code generated by go generate; DO NOT EDIT.
|
||||
|
||||
package pref
|
||||
|
||||
var themes = []ThemeDTO{
|
||||
{ID: "light", Type: "light"},
|
||||
{ID: "dark", Type: "dark"},
|
||||
{ID: "system", Type: "dark"},
|
||||
`
|
||||
|
||||
err := filepath.WalkDir(themesPath, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only process json files
|
||||
if d.IsDir() || !strings.HasSuffix(d.Name(), ".json") {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileBytes, readErr := os.ReadFile(path)
|
||||
if readErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading file %s: %v\n", path, readErr)
|
||||
return nil // Continue processing other files
|
||||
}
|
||||
|
||||
var themeDef ThemeDefinition
|
||||
jsonErr := json.Unmarshal(fileBytes, &themeDef)
|
||||
if jsonErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing JSON from %s: %v\n", path, jsonErr)
|
||||
return nil // Continue processing other files
|
||||
}
|
||||
|
||||
themeId := themeDef.Id
|
||||
themeType := "dark" // default fallback
|
||||
if themeDef.Colors.Mode != "" {
|
||||
themeType = themeDef.Colors.Mode
|
||||
}
|
||||
|
||||
output += fmt.Sprintf("\t{ID: %q, Type: %q, IsExtra: true},\n", themeId, themeType)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error walking themes directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
output += "}\n"
|
||||
|
||||
// Write the generated file
|
||||
outputPath := filepath.Join("themes_generated.go")
|
||||
if err := os.WriteFile(outputPath, []byte(output), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing output file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully generated themes_generated.go\n")
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:generate go run generate_themes.go
|
||||
|
||||
package pref
|
||||
|
||||
type ThemeDTO struct {
|
||||
@@ -6,24 +8,6 @@ type ThemeDTO struct {
|
||||
IsExtra bool `json:"isExtra"`
|
||||
}
|
||||
|
||||
var themes = []ThemeDTO{
|
||||
{ID: "light", Type: "light"},
|
||||
{ID: "dark", Type: "dark"},
|
||||
{ID: "system", Type: "dark"},
|
||||
{ID: "debug", Type: "dark", IsExtra: true},
|
||||
{ID: "aubergine", Type: "dark", IsExtra: true},
|
||||
{ID: "desertbloom", Type: "light", IsExtra: true},
|
||||
{ID: "gildedgrove", Type: "dark", IsExtra: true},
|
||||
{ID: "mars", Type: "dark", IsExtra: true},
|
||||
{ID: "matrix", Type: "dark", IsExtra: true},
|
||||
{ID: "sapphiredusk", Type: "dark", IsExtra: true},
|
||||
{ID: "synthwave", Type: "dark", IsExtra: true},
|
||||
{ID: "tron", Type: "dark", IsExtra: true},
|
||||
{ID: "victorian", Type: "dark", IsExtra: true},
|
||||
{ID: "zen", Type: "light", IsExtra: true},
|
||||
{ID: "gloom", Type: "dark", IsExtra: true},
|
||||
}
|
||||
|
||||
func GetThemeByID(id string) *ThemeDTO {
|
||||
for _, theme := range themes {
|
||||
if theme.ID == id {
|
||||
|
||||
21
pkg/services/preference/themes_generated.go
Normal file
21
pkg/services/preference/themes_generated.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
|
||||
package pref
|
||||
|
||||
var themes = []ThemeDTO{
|
||||
{ID: "light", Type: "light"},
|
||||
{ID: "dark", Type: "dark"},
|
||||
{ID: "system", Type: "dark"},
|
||||
{ID: "aubergine", Type: "dark", IsExtra: true},
|
||||
{ID: "debug", Type: "dark", IsExtra: true},
|
||||
{ID: "desertbloom", Type: "light", IsExtra: true},
|
||||
{ID: "gildedgrove", Type: "dark", IsExtra: true},
|
||||
{ID: "gloom", Type: "dark", IsExtra: true},
|
||||
{ID: "mars", Type: "dark", IsExtra: true},
|
||||
{ID: "matrix", Type: "dark", IsExtra: true},
|
||||
{ID: "sapphiredusk", Type: "dark", IsExtra: true},
|
||||
{ID: "synthwave", Type: "dark", IsExtra: true},
|
||||
{ID: "tron", Type: "dark", IsExtra: true},
|
||||
{ID: "victorian", Type: "dark", IsExtra: true},
|
||||
{ID: "zen", Type: "light", IsExtra: true},
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -378,8 +379,10 @@ func setupOpenFeatureProvider(t *testing.T, flagValue bool) {
|
||||
|
||||
err := featuremgmt.InitOpenFeature(featuremgmt.OpenFeatureConfig{
|
||||
ProviderType: setting.StaticProviderType,
|
||||
StaticFlags: map[string]bool{
|
||||
featuremgmt.FlagPluginsAutoUpdate: flagValue,
|
||||
StaticFlags: map[string]memprovider.InMemoryFlag{
|
||||
featuremgmt.FlagPluginsAutoUpdate: {
|
||||
Key: featuremgmt.FlagPluginsAutoUpdate, Variants: map[string]any{"": flagValue},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// DefaultVariantName a placeholder name for config-based Feature Flags
|
||||
const DefaultVariantName = "default"
|
||||
|
||||
// Deprecated: should use `featuremgmt.FeatureToggles`
|
||||
func (cfg *Cfg) readFeatureToggles(iniFile *ini.File) error {
|
||||
section := iniFile.Section("feature_toggles")
|
||||
@@ -15,18 +22,27 @@ func (cfg *Cfg) readFeatureToggles(iniFile *ini.File) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO IsFeatureToggleEnabled has been deprecated for 2 years now, we should remove this function completely
|
||||
// nolint:staticcheck
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool { return toggles[key] }
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool {
|
||||
toggle, ok := toggles[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
value, ok := toggle.Variants[toggle.DefaultVariant].(bool)
|
||||
return value && ok
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadFeatureTogglesFromInitFile(featureTogglesSection *ini.Section) (map[string]bool, error) {
|
||||
featureToggles := make(map[string]bool, 10)
|
||||
func ReadFeatureTogglesFromInitFile(featureTogglesSection *ini.Section) (map[string]memprovider.InMemoryFlag, error) {
|
||||
featureToggles := make(map[string]memprovider.InMemoryFlag, 10)
|
||||
|
||||
// parse the comma separated list in `enable`.
|
||||
featuresTogglesStr := valueAsString(featureTogglesSection, "enable", "")
|
||||
for _, feature := range util.SplitString(featuresTogglesStr) {
|
||||
featureToggles[feature] = true
|
||||
featureToggles[feature] = memprovider.InMemoryFlag{Key: feature, DefaultVariant: DefaultVariantName, Variants: map[string]any{DefaultVariantName: true}}
|
||||
}
|
||||
|
||||
// read all other settings under [feature_toggles]. If a toggle is
|
||||
@@ -36,7 +52,7 @@ func ReadFeatureTogglesFromInitFile(featureTogglesSection *ini.Section) (map[str
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := strconv.ParseBool(v.Value())
|
||||
b, err := ParseFlag(v.Name(), v.Value())
|
||||
if err != nil {
|
||||
return featureToggles, err
|
||||
}
|
||||
@@ -45,3 +61,57 @@ func ReadFeatureTogglesFromInitFile(featureTogglesSection *ini.Section) (map[str
|
||||
}
|
||||
return featureToggles, nil
|
||||
}
|
||||
|
||||
func ParseFlag(name, value string) (memprovider.InMemoryFlag, error) {
|
||||
var structure map[string]any
|
||||
|
||||
if integer, err := strconv.Atoi(value); err == nil {
|
||||
return NewInMemoryFlag(name, integer), nil
|
||||
}
|
||||
if float, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return NewInMemoryFlag(name, float), nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(value), &structure); err == nil {
|
||||
return NewInMemoryFlag(name, structure), nil
|
||||
}
|
||||
if boolean, err := strconv.ParseBool(value); err == nil {
|
||||
return NewInMemoryFlag(name, boolean), nil
|
||||
}
|
||||
|
||||
return NewInMemoryFlag(name, value), nil
|
||||
}
|
||||
|
||||
func NewInMemoryFlag(name string, value any) memprovider.InMemoryFlag {
|
||||
return memprovider.InMemoryFlag{Key: name, DefaultVariant: DefaultVariantName, Variants: map[string]any{DefaultVariantName: value}}
|
||||
}
|
||||
|
||||
func AsStringMap(m map[string]memprovider.InMemoryFlag) map[string]string {
|
||||
var res = map[string]string{}
|
||||
for k, v := range m {
|
||||
res[k] = serializeFlagValue(v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func serializeFlagValue(flag memprovider.InMemoryFlag) string {
|
||||
value := flag.Variants[flag.DefaultVariant]
|
||||
|
||||
switch castedValue := value.(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(castedValue)
|
||||
case int64:
|
||||
return strconv.FormatInt(castedValue, 10)
|
||||
case float64:
|
||||
// handle cases with a single or no zeros after the decimal point
|
||||
if math.Trunc(castedValue) == castedValue {
|
||||
return strconv.FormatFloat(castedValue, 'f', 1, 64)
|
||||
}
|
||||
|
||||
return strconv.FormatFloat(castedValue, 'g', -1, 64)
|
||||
case string:
|
||||
return castedValue
|
||||
default:
|
||||
val, _ := json.Marshal(value)
|
||||
return string(val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/open-feature/go-sdk/openfeature/memprovider"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
@@ -12,17 +14,16 @@ func TestFeatureToggles(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
conf map[string]string
|
||||
err error
|
||||
expectedToggles map[string]bool
|
||||
expectedToggles map[string]memprovider.InMemoryFlag
|
||||
}{
|
||||
{
|
||||
name: "can parse feature toggles passed in the `enable` array",
|
||||
conf: map[string]string{
|
||||
"enable": "feature1,feature2",
|
||||
},
|
||||
expectedToggles: map[string]bool{
|
||||
"feature1": true,
|
||||
"feature2": true,
|
||||
expectedToggles: map[string]memprovider.InMemoryFlag{
|
||||
"feature1": NewInMemoryFlag("feature1", true),
|
||||
"feature2": NewInMemoryFlag("feature2", true),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -31,10 +32,10 @@ func TestFeatureToggles(t *testing.T) {
|
||||
"enable": "feature1,feature2",
|
||||
"feature3": "true",
|
||||
},
|
||||
expectedToggles: map[string]bool{
|
||||
"feature1": true,
|
||||
"feature2": true,
|
||||
"feature3": true,
|
||||
expectedToggles: map[string]memprovider.InMemoryFlag{
|
||||
"feature1": NewInMemoryFlag("feature1", true),
|
||||
"feature2": NewInMemoryFlag("feature2", true),
|
||||
"feature3": NewInMemoryFlag("feature3", true),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -43,19 +44,26 @@ func TestFeatureToggles(t *testing.T) {
|
||||
"enable": "feature1,feature2",
|
||||
"feature2": "false",
|
||||
},
|
||||
expectedToggles: map[string]bool{
|
||||
"feature1": true,
|
||||
"feature2": false,
|
||||
expectedToggles: map[string]memprovider.InMemoryFlag{
|
||||
"feature1": NewInMemoryFlag("feature1", true),
|
||||
"feature2": NewInMemoryFlag("feature2", false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid boolean value should return syntax error",
|
||||
name: "feature flags of different types are handled correctly",
|
||||
conf: map[string]string{
|
||||
"enable": "feature1,feature2",
|
||||
"feature2": "invalid",
|
||||
"feature1": "1", "feature2": "1.0",
|
||||
"feature3": `{"foo":"bar"}`, "feature4": "bar",
|
||||
"feature5": "t", "feature6": "T",
|
||||
},
|
||||
expectedToggles: map[string]memprovider.InMemoryFlag{
|
||||
"feature1": NewInMemoryFlag("feature1", 1),
|
||||
"feature2": NewInMemoryFlag("feature2", 1.0),
|
||||
"feature3": NewInMemoryFlag("feature3", map[string]any{"foo": "bar"}),
|
||||
"feature4": NewInMemoryFlag("feature4", "bar"),
|
||||
"feature5": NewInMemoryFlag("feature5", true),
|
||||
"feature6": NewInMemoryFlag("feature6", true),
|
||||
},
|
||||
expectedToggles: map[string]bool{},
|
||||
err: strconv.ErrSyntax,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -69,12 +77,35 @@ func TestFeatureToggles(t *testing.T) {
|
||||
}
|
||||
|
||||
featureToggles, err := ReadFeatureTogglesFromInitFile(toggles)
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
require.NoError(t, err)
|
||||
|
||||
if err == nil {
|
||||
for k, v := range featureToggles {
|
||||
require.Equal(t, tc.expectedToggles[k], v, tc.name)
|
||||
}
|
||||
for k, v := range featureToggles {
|
||||
toggle := tc.expectedToggles[k]
|
||||
require.Equal(t, toggle, v, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagValueSerialization(t *testing.T) {
|
||||
testCases := []memprovider.InMemoryFlag{
|
||||
NewInMemoryFlag("int", 1),
|
||||
NewInMemoryFlag("1.0f", 1.0),
|
||||
NewInMemoryFlag("1.01f", 1.01),
|
||||
NewInMemoryFlag("1.10f", 1.10),
|
||||
NewInMemoryFlag("struct", map[string]any{"foo": "bar"}),
|
||||
NewInMemoryFlag("string", "bar"),
|
||||
NewInMemoryFlag("true", true),
|
||||
NewInMemoryFlag("false", false),
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
asStringMap := AsStringMap(map[string]memprovider.InMemoryFlag{tt.Key: tt})
|
||||
|
||||
deserialized, err := ParseFlag(tt.Key, asStringMap[tt.Key])
|
||||
assert.NoError(t, err)
|
||||
|
||||
if diff := cmp.Diff(tt, deserialized); diff != "" {
|
||||
t.Errorf("(-want, +got) = %v", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,6 @@ kubernetesFolders = true
|
||||
unifiedStorage = true
|
||||
unifiedStorageHistoryPruner = true
|
||||
unifiedStorageSearchPermissionFiltering = false
|
||||
unifiedStorageSearchSprinkles = false
|
||||
|
||||
[unified_storage]
|
||||
enable_search = true
|
||||
@@ -315,9 +314,6 @@ To enable it, add the following to your `custom.ini` under the `[feature_toggles
|
||||
; Used by the Grafana instance
|
||||
unifiedStorageSearchUI = true
|
||||
|
||||
; (optional) Allows you to sort dashboards by usage insights fields when using enterprise
|
||||
; unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
; Used by unified storage server
|
||||
enable_search = true
|
||||
@@ -934,7 +930,6 @@ Unified Search requires several feature flags to be enabled depending on the des
|
||||
| Feature Flag | Purpose | Stage | Required For |
|
||||
|--------------|---------|-------|--------------|
|
||||
| `unifiedStorageSearchUI` | Frontend search interface | Experimental | Grafana UI search |
|
||||
| `unifiedStorageSearchSprinkles` | Usage insights integration | Experimental | Dashboard usage sorting (Enterprise) |
|
||||
| `unifiedStorageSearchDualReaderEnabled` | Shadow traffic to unified search | Experimental | Shadow traffic during migration |
|
||||
|
||||
#### Unified Search Specific Configuration
|
||||
@@ -955,9 +950,6 @@ unifiedStorageSearchUI = true
|
||||
; Enable shadow traffic during migration (optional)
|
||||
unifiedStorageSearchDualReaderEnabled = true
|
||||
|
||||
; Enable usage insights sorting (Enterprise only)
|
||||
unifiedStorageSearchSprinkles = true
|
||||
|
||||
[unified_storage]
|
||||
; Enable core search functionality (required)
|
||||
enable_search = true
|
||||
|
||||
@@ -271,7 +271,7 @@ func grpcConn(address string, metrics *clientMetrics, clientKeepaliveTime time.D
|
||||
retryCfg := retryConfig{
|
||||
Max: 3,
|
||||
Backoff: time.Second,
|
||||
BackoffJitter: 0.5,
|
||||
BackoffJitter: 0.1,
|
||||
}
|
||||
unary = append(unary, unaryRetryInterceptor(retryCfg))
|
||||
unary = append(unary, unaryRetryInstrument(metrics.requestRetries))
|
||||
@@ -288,13 +288,15 @@ func grpcConn(address string, metrics *clientMetrics, clientKeepaliveTime time.D
|
||||
opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
// Use round_robin to balances requests more evenly over the available Storage server.
|
||||
// Use round_robin to balance requests more evenly over the available Storage server.
|
||||
opts = append(opts, grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`))
|
||||
|
||||
// Disable looking up service config from TXT DNS records.
|
||||
// This reduces the number of requests made to the DNS servers.
|
||||
opts = append(opts, grpc.WithDisableServiceConfig())
|
||||
|
||||
opts = append(opts, connectionBackoffOptions())
|
||||
|
||||
if clientKeepaliveTime > 0 {
|
||||
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: clientKeepaliveTime,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
@@ -44,3 +45,17 @@ func unaryRetryInstrument(metric *prometheus.CounterVec) grpc.UnaryClientInterce
|
||||
return invoker(ctx, method, req, resp, cc, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// connectionBackoffOptions configures connection backoff parameters for faster recovery from
|
||||
// transient connection failures (e.g., during pod restarts).
|
||||
func connectionBackoffOptions() grpc.DialOption {
|
||||
return grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
BaseDelay: 100 * time.Millisecond,
|
||||
Multiplier: 1.6,
|
||||
Jitter: 0.2,
|
||||
MaxDelay: 10 * time.Second,
|
||||
},
|
||||
MinConnectTimeout: 5 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -78,13 +78,13 @@ func (n *notifier) Watch(ctx context.Context, opts watchOptions) <-chan Event {
|
||||
cache := gocache.New(cacheTTL, cacheCleanupInterval)
|
||||
events := make(chan Event, opts.BufferSize)
|
||||
|
||||
initialRV, err := n.lastEventResourceVersion(ctx)
|
||||
lastRV, err := n.lastEventResourceVersion(ctx)
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
initialRV = snowflakeFromTime(time.Now()) // No events yet, start from the beginning
|
||||
lastRV = 0 // No events yet, start from the beginning
|
||||
} else if err != nil {
|
||||
n.log.Error("Failed to get last event resource version", "error", err)
|
||||
}
|
||||
lastRV := initialRV + 1 // We want to start watching from the next event
|
||||
lastRV = lastRV + 1 // We want to start watching from the next event
|
||||
|
||||
go func() {
|
||||
defer close(events)
|
||||
@@ -110,7 +110,7 @@ func (n *notifier) Watch(ctx context.Context, opts watchOptions) <-chan Event {
|
||||
}
|
||||
|
||||
// Skip old events lower than the requested resource version
|
||||
if evt.ResourceVersion <= initialRV {
|
||||
if evt.ResourceVersion < lastRV {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ func setupTestNotifier(t *testing.T) (*notifier, *eventStore) {
|
||||
return notifier, eventStore
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func setupTestNotifierSqlKv(t *testing.T) (*notifier, *eventStore) {
|
||||
dbstore := db.InitTestDB(t)
|
||||
eDB, err := dbimpl.ProvideResourceDB(dbstore, setting.NewCfg(), nil)
|
||||
@@ -60,8 +59,7 @@ func runNotifierTestWith(t *testing.T, storeName string, newStoreFn func(*testin
|
||||
|
||||
func TestNotifier_lastEventResourceVersion(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierLastEventResourceVersion)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierLastEventResourceVersion)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierLastEventResourceVersion)
|
||||
}
|
||||
|
||||
func testNotifierLastEventResourceVersion(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -112,8 +110,7 @@ func testNotifierLastEventResourceVersion(t *testing.T, ctx context.Context, not
|
||||
|
||||
func TestNotifier_cachekey(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierCachekey)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierCachekey)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierCachekey)
|
||||
}
|
||||
|
||||
func testNotifierCachekey(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -167,8 +164,7 @@ func testNotifierCachekey(t *testing.T, ctx context.Context, notifier *notifier,
|
||||
|
||||
func TestNotifier_Watch_NoEvents(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierWatchNoEvents)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchNoEvents)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchNoEvents)
|
||||
}
|
||||
|
||||
func testNotifierWatchNoEvents(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -209,8 +205,7 @@ func testNotifierWatchNoEvents(t *testing.T, ctx context.Context, notifier *noti
|
||||
|
||||
func TestNotifier_Watch_WithExistingEvents(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierWatchWithExistingEvents)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchWithExistingEvents)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchWithExistingEvents)
|
||||
}
|
||||
|
||||
func testNotifierWatchWithExistingEvents(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -284,8 +279,7 @@ func testNotifierWatchWithExistingEvents(t *testing.T, ctx context.Context, noti
|
||||
|
||||
func TestNotifier_Watch_EventDeduplication(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierWatchEventDeduplication)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchEventDeduplication)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchEventDeduplication)
|
||||
}
|
||||
|
||||
func testNotifierWatchEventDeduplication(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -351,8 +345,7 @@ func testNotifierWatchEventDeduplication(t *testing.T, ctx context.Context, noti
|
||||
|
||||
func TestNotifier_Watch_ContextCancellation(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierWatchContextCancellation)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchContextCancellation)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchContextCancellation)
|
||||
}
|
||||
|
||||
func testNotifierWatchContextCancellation(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
@@ -398,8 +391,7 @@ func testNotifierWatchContextCancellation(t *testing.T, ctx context.Context, not
|
||||
|
||||
func TestNotifier_Watch_MultipleEvents(t *testing.T) {
|
||||
runNotifierTestWith(t, "badger", setupTestNotifier, testNotifierWatchMultipleEvents)
|
||||
// enable this when sqlkv is ready
|
||||
// runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchMultipleEvents)
|
||||
runNotifierTestWith(t, "sqlkv", setupTestNotifierSqlKv, testNotifierWatchMultipleEvents)
|
||||
}
|
||||
|
||||
func testNotifierWatchMultipleEvents(t *testing.T, ctx context.Context, notifier *notifier, eventStore *eventStore) {
|
||||
|
||||
@@ -346,7 +346,8 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in
|
||||
return 0, fmt.Errorf("failed to write data: %w", err)
|
||||
}
|
||||
|
||||
dataKey.ResourceVersion = rvmanager.SnowflakeFromRv(rv)
|
||||
rv = rvmanager.SnowflakeFromRv(rv)
|
||||
dataKey.ResourceVersion = rv
|
||||
} else {
|
||||
err := k.dataStore.Save(ctx, dataKey, bytes.NewReader(event.Value))
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/snowflake"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
@@ -82,6 +81,12 @@ func RunSQLStorageBackendCompatibilityTest(t *testing.T, newSqlBackend, newKvBac
|
||||
|
||||
kvbackend, db := newKvBackend(t.Context())
|
||||
sqlbackend, _ := newSqlBackend(t.Context())
|
||||
|
||||
// Skip on SQLite due to concurrency limitations
|
||||
if db.DriverName() == "sqlite3" {
|
||||
t.Skip("Skipping concurrent operations stress test on SQLite")
|
||||
}
|
||||
|
||||
tc.fn(t, sqlbackend, kvbackend, opts.NSPrefix, db)
|
||||
})
|
||||
}
|
||||
@@ -187,13 +192,30 @@ func runKeyPathTest(t *testing.T, backend resource.StorageBackend, nsPrefix stri
|
||||
|
||||
// verifyKeyPath is a helper function to verify key_path generation
|
||||
func verifyKeyPath(t *testing.T, db sqldb.DB, ctx context.Context, key *resourcepb.ResourceKey, action string, resourceVersion int64, expectedFolder string) {
|
||||
// For SQL backend (namespace contains "-sql"), resourceVersion is in microsecond format
|
||||
// but key_path stores snowflake RV, so convert to snowflake
|
||||
// For KV backend (namespace contains "-kv"), resourceVersion is already in snowflake format
|
||||
isSqlBackend := strings.Contains(key.Namespace, "-sql")
|
||||
|
||||
var keyPathRV int64
|
||||
if isSqlBackend {
|
||||
// Convert microsecond RV to snowflake for key_path construction
|
||||
keyPathRV = rvmanager.SnowflakeFromRv(resourceVersion)
|
||||
} else {
|
||||
// KV backend already provides snowflake RV
|
||||
keyPathRV = resourceVersion
|
||||
}
|
||||
|
||||
// Build the expected key_path using DataKey format: unified/data/group/resource/namespace/name/resourceVersion~action~folder
|
||||
expectedKeyPath := fmt.Sprintf("unified/data/%s/%s/%s/%s/%d~%s~%s", key.Group, key.Resource, key.Namespace, key.Name, keyPathRV, action, expectedFolder)
|
||||
|
||||
var query string
|
||||
if db.DriverName() == "postgres" {
|
||||
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE namespace = $1 AND name = $2 AND resource_version = $3"
|
||||
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE key_path = $1"
|
||||
} else {
|
||||
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE namespace = ? AND name = ? AND resource_version = ?"
|
||||
query = "SELECT key_path, resource_version, action, folder FROM resource_history WHERE key_path = ?"
|
||||
}
|
||||
rows, err := db.QueryContext(ctx, query, key.Namespace, key.Name, resourceVersion)
|
||||
rows, err := db.QueryContext(ctx, query, expectedKeyPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, rows.Next(), "Resource not found in resource_history table - both SQL and KV backends should write to this table")
|
||||
@@ -220,10 +242,6 @@ func verifyKeyPath(t *testing.T, db sqldb.DB, ctx context.Context, key *resource
|
||||
// Verify action suffix
|
||||
require.Contains(t, keyPath, fmt.Sprintf("~%s~", action))
|
||||
|
||||
// Verify snowflake calculation
|
||||
expectedSnowflake := (((resourceVersion / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (resourceVersion % 1000)
|
||||
require.Contains(t, keyPath, fmt.Sprintf("/%d~", expectedSnowflake), "actual RV: %d", actualRV)
|
||||
|
||||
// Verify folder if specified
|
||||
if expectedFolder != "" {
|
||||
require.Equal(t, expectedFolder, actualFolder)
|
||||
@@ -492,10 +510,10 @@ func verifyResourceHistoryRecord(t *testing.T, record ResourceHistoryRecord, exp
|
||||
}
|
||||
|
||||
// Validate previous_resource_version
|
||||
// For KV backend operations, resource versions are stored as snowflake format
|
||||
// but expectedPrevRV is in microsecond format, so we need to use IsRvEqual for comparison
|
||||
// For KV backend operations, expectedPrevRV is now in snowflake format (returned by KV backend)
|
||||
// but resource_history table stores microsecond RV, so we need to use IsRvEqual for comparison
|
||||
if strings.Contains(record.Namespace, "-kv") {
|
||||
require.True(t, rvmanager.IsRvEqual(record.PreviousResourceVersion, expectedPrevRV),
|
||||
require.True(t, rvmanager.IsRvEqual(expectedPrevRV, record.PreviousResourceVersion),
|
||||
"Previous resource version should match (KV backend snowflake format)")
|
||||
} else {
|
||||
require.Equal(t, expectedPrevRV, record.PreviousResourceVersion)
|
||||
@@ -505,9 +523,10 @@ func verifyResourceHistoryRecord(t *testing.T, record ResourceHistoryRecord, exp
|
||||
require.Equal(t, expectedGeneration, record.Generation)
|
||||
|
||||
// Validate resource_version
|
||||
// For KV backend operations, resource versions are stored as snowflake format
|
||||
// For KV backend operations, expectedRV is now in snowflake format (returned by KV backend)
|
||||
// but resource_history table stores microsecond RV, so we need to use IsRvEqual for comparison
|
||||
if strings.Contains(record.Namespace, "-kv") {
|
||||
require.True(t, rvmanager.IsRvEqual(record.ResourceVersion, expectedRV),
|
||||
require.True(t, rvmanager.IsRvEqual(expectedRV, record.ResourceVersion),
|
||||
"Resource version should match (KV backend snowflake format)")
|
||||
} else {
|
||||
require.Equal(t, expectedRV, record.ResourceVersion)
|
||||
@@ -574,7 +593,7 @@ func verifyResourceTable(t *testing.T, db sqldb.DB, namespace string, resources
|
||||
// Resource version should match the expected version for test-resource-3 (updated version)
|
||||
expectedRV := resourceVersions[2][1] // test-resource-3's update version
|
||||
if strings.Contains(namespace, "-kv") {
|
||||
require.True(t, rvmanager.IsRvEqual(record.ResourceVersion, expectedRV),
|
||||
require.True(t, rvmanager.IsRvEqual(expectedRV, record.ResourceVersion),
|
||||
"Resource version should match (KV backend snowflake format)")
|
||||
} else {
|
||||
require.Equal(t, expectedRV, record.ResourceVersion)
|
||||
@@ -625,9 +644,16 @@ func verifyResourceVersionTable(t *testing.T, db sqldb.DB, namespace string, res
|
||||
|
||||
// The resource_version table should contain the latest RV for the group+resource
|
||||
// It might be slightly higher due to RV manager operations, so check it's at least our max
|
||||
require.GreaterOrEqual(t, record.ResourceVersion, maxRV, "resource_version should be at least the latest RV we tracked")
|
||||
// But it shouldn't be too much higher (within a reasonable range)
|
||||
require.LessOrEqual(t, record.ResourceVersion, maxRV+100, "resource_version shouldn't be much higher than expected")
|
||||
// For KV backend, maxRV is in snowflake format but record.ResourceVersion is in microsecond format
|
||||
// Use IsRvEqual for proper comparison between different RV formats
|
||||
isKvBackend := strings.Contains(namespace, "-kv")
|
||||
recordResourceVersion := record.ResourceVersion
|
||||
if isKvBackend {
|
||||
recordResourceVersion = rvmanager.SnowflakeFromRv(record.ResourceVersion)
|
||||
}
|
||||
|
||||
require.Less(t, recordResourceVersion, int64(9223372036854775807), "resource_version should be reasonable")
|
||||
require.Greater(t, recordResourceVersion, maxRV, "resource_version should be at least the latest RV we tracked")
|
||||
}
|
||||
|
||||
// runTestCrossBackendConsistency tests basic consistency between SQL and KV backends (lightweight)
|
||||
@@ -666,11 +692,6 @@ func runTestCrossBackendConsistency(t *testing.T, sqlBackend, kvBackend resource
|
||||
|
||||
// runTestConcurrentOperationsStress tests heavy concurrent operations between SQL and KV backends
|
||||
func runTestConcurrentOperationsStress(t *testing.T, sqlBackend, kvBackend resource.StorageBackend, nsPrefix string, db sqldb.DB) {
|
||||
// Skip on SQLite due to concurrency limitations
|
||||
if db.DriverName() == "sqlite3" {
|
||||
t.Skip("Skipping concurrent operations stress test on SQLite")
|
||||
}
|
||||
|
||||
ctx := testutil.NewDefaultTestContext(t)
|
||||
|
||||
// Create storage servers from both backends
|
||||
|
||||
@@ -38,7 +38,6 @@ func TestBadgerKVStorageBackend(t *testing.T) {
|
||||
|
||||
func TestSQLKVStorageBackend(t *testing.T) {
|
||||
skipTests := map[string]bool{
|
||||
TestHappyPath: true,
|
||||
TestWatchWriteEvents: true,
|
||||
TestList: true,
|
||||
TestBlobSupport: true,
|
||||
@@ -51,21 +50,24 @@ func TestSQLKVStorageBackend(t *testing.T) {
|
||||
TestGetResourceLastImportTime: true,
|
||||
TestOptimisticLocking: true,
|
||||
}
|
||||
// without RvManager
|
||||
RunStorageBackendTest(t, func(ctx context.Context) resource.StorageBackend {
|
||||
backend, _ := NewTestSqlKvBackend(t, ctx, false)
|
||||
return backend
|
||||
}, &TestOptions{
|
||||
NSPrefix: "sqlkvstorage-test",
|
||||
SkipTests: skipTests,
|
||||
|
||||
t.Run("Without RvManager", func(t *testing.T) {
|
||||
RunStorageBackendTest(t, func(ctx context.Context) resource.StorageBackend {
|
||||
backend, _ := NewTestSqlKvBackend(t, ctx, false)
|
||||
return backend
|
||||
}, &TestOptions{
|
||||
NSPrefix: "sqlkvstorage-test",
|
||||
SkipTests: skipTests,
|
||||
})
|
||||
})
|
||||
|
||||
// with RvManager
|
||||
RunStorageBackendTest(t, func(ctx context.Context) resource.StorageBackend {
|
||||
backend, _ := NewTestSqlKvBackend(t, ctx, true)
|
||||
return backend
|
||||
}, &TestOptions{
|
||||
NSPrefix: "sqlkvstorage-withrvmanager-test",
|
||||
SkipTests: skipTests,
|
||||
t.Run("With RvManager", func(t *testing.T) {
|
||||
RunStorageBackendTest(t, func(ctx context.Context) resource.StorageBackend {
|
||||
backend, _ := NewTestSqlKvBackend(t, ctx, true)
|
||||
return backend
|
||||
}, &TestOptions{
|
||||
NSPrefix: "sqlkvstorage-withrvmanager-test",
|
||||
SkipTests: skipTests,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ func TestIntegrationFeatures(t *testing.T) {
|
||||
"value": true,
|
||||
"key":"`+flag+`",
|
||||
"reason":"static provider evaluation result",
|
||||
"variant":"enabled"}`, string(rsp.Body))
|
||||
"variant":"default"}`, string(rsp.Body))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -166,8 +166,24 @@ func TestIntegrationProvisioning_ConnectionCRUDL(t *testing.T) {
|
||||
githubInfo = spec["github"].(map[string]any)
|
||||
assert.Equal(t, "454546", githubInfo["installationID"], "installationID should be updated")
|
||||
|
||||
// DELETE
|
||||
require.NoError(t, helper.Connections.Resource.Delete(ctx, "connection", metav1.DeleteOptions{}), "failed to delete resource")
|
||||
// DELETE - Retry delete to handle resource version conflicts
|
||||
// The controller may have updated the resource after our update, changing the resource version
|
||||
require.Eventually(t, func() bool {
|
||||
err := helper.Connections.Resource.Delete(ctx, "connection", metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
// Resource version conflict - retry
|
||||
return false
|
||||
}
|
||||
if k8serrors.IsNotFound(err) {
|
||||
// Already deleted - success
|
||||
return true
|
||||
}
|
||||
// Other error - fail the test
|
||||
require.NoError(t, err, "failed to delete resource")
|
||||
}
|
||||
return true
|
||||
}, 5*time.Second, 100*time.Millisecond, "should successfully delete resource")
|
||||
list, err = helper.Connections.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err, "failed to list resources")
|
||||
assert.Equal(t, 0, len(list.Items), "should have no connections")
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
func TestMain(m *testing.M) {
|
||||
// make sure we don't leak goroutines after tests in this package have
|
||||
// finished, which means we haven't leaked contexts either
|
||||
goleak.VerifyTestMain(m)
|
||||
// (Except for goroutines running specific functions. If possible we should fix this.)
|
||||
goleak.VerifyTestMain(m,
|
||||
goleak.IgnoreTopFunction("github.com/open-feature/go-sdk/openfeature.(*eventExecutor).startEventListener.func1.1"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestTestContextFunc(t *testing.T) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { t } from '@grafana/i18n';
|
||||
import { isFetchError } from '@grafana/runtime';
|
||||
import { clearFolders } from 'app/features/browse-dashboards/state/slice';
|
||||
import { getState } from 'app/store/store';
|
||||
import { ThunkDispatch } from 'app/types/store';
|
||||
|
||||
import { createSuccessNotification, createErrorNotification } from '../../../../core/copy/appNotification';
|
||||
import { notifyApp } from '../../../../core/reducers/appNotification';
|
||||
@@ -19,6 +20,26 @@ import { refetchChildren } from '../../../../features/browse-dashboards/state/ac
|
||||
import { handleError } from '../../../utils';
|
||||
import { createOnCacheEntryAdded } from '../utils/createOnCacheEntryAdded';
|
||||
|
||||
const handleProvisioningFormError = (e: unknown, dispatch: ThunkDispatch, title: string) => {
|
||||
if (typeof e === 'object' && e && 'error' in e && isFetchError(e.error)) {
|
||||
if (e.error.data.kind === 'Status' && e.error.data.status === 'Failure') {
|
||||
const statusError: Status = e.error.data;
|
||||
dispatch(notifyApp(createErrorNotification(title, new Error(statusError.message || 'Unknown error'))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(e.error.data.errors) && e.error.data.errors.length) {
|
||||
const nonFieldErrors = e.error.data.errors.filter((err: ErrorDetails) => !err.field);
|
||||
if (nonFieldErrors.length > 0) {
|
||||
dispatch(notifyApp(createErrorNotification(title)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleError(e, dispatch, title);
|
||||
};
|
||||
|
||||
export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
||||
endpoints: {
|
||||
listJob: {
|
||||
@@ -37,6 +58,17 @@ export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
||||
}),
|
||||
onCacheEntryAdded: createOnCacheEntryAdded<RepositorySpec, RepositoryStatus>('repositories'),
|
||||
},
|
||||
listConnection: {
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [
|
||||
{ type: 'Connection', id: 'LIST' },
|
||||
...result.items
|
||||
.map((connection) => ({ type: 'Connection' as const, id: connection.metadata?.name }))
|
||||
.filter(Boolean),
|
||||
]
|
||||
: [{ type: 'Connection', id: 'LIST' }],
|
||||
},
|
||||
deleteRepository: {
|
||||
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
|
||||
try {
|
||||
@@ -104,34 +136,7 @@ export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
||||
try {
|
||||
await queryFulfilled;
|
||||
} catch (e) {
|
||||
// Handle special cases first
|
||||
if (typeof e === 'object' && e && 'error' in e && isFetchError(e.error)) {
|
||||
// Handle Status error responses (Kubernetes style)
|
||||
if (e.error.data.kind === 'Status' && e.error.data.status === 'Failure') {
|
||||
const statusError: Status = e.error.data;
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createErrorNotification(
|
||||
'Error validating repository',
|
||||
new Error(statusError.message || 'Unknown error')
|
||||
)
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Handle TestResults error responses with field errors
|
||||
if (Array.isArray(e.error.data.errors) && e.error.data.errors.length) {
|
||||
const nonFieldErrors = e.error.data.errors.filter((err: ErrorDetails) => !err.field);
|
||||
// Only show notification if there are errors that don't have a field, field errors are handled by the form
|
||||
if (nonFieldErrors.length > 0) {
|
||||
dispatch(notifyApp(createErrorNotification('Error validating repository')));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// For all other cases, use handleError
|
||||
handleError(e, dispatch, 'Error validating repository');
|
||||
handleProvisioningFormError(e, dispatch, 'Error validating repository');
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -240,6 +245,70 @@ export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
||||
}
|
||||
},
|
||||
},
|
||||
createConnection: {
|
||||
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createSuccessNotification(t('provisioning.connection-form.alert-connection-saved', 'Connection saved'))
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
handleProvisioningFormError(
|
||||
e,
|
||||
dispatch,
|
||||
t('provisioning.connection-form.error-save-connection', 'Failed to save connection')
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
replaceConnection: {
|
||||
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createSuccessNotification(
|
||||
t('provisioning.connection-form.alert-connection-updated', 'Connection updated')
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
handleProvisioningFormError(
|
||||
e,
|
||||
dispatch,
|
||||
t('provisioning.connection-form.error-save-connection', 'Failed to save connection')
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
deleteConnection: {
|
||||
invalidatesTags: (result, error) => (error ? [] : [{ type: 'Connection', id: 'LIST' }]),
|
||||
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createSuccessNotification(
|
||||
t('provisioning.connection-form.alert-connection-deleted', 'Connection deleted')
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createErrorNotification(
|
||||
t('provisioning.connection-form.error-delete-connection', 'Failed to delete connection'),
|
||||
e
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import * as React from 'react';
|
||||
import SplitPane, { Split } from 'react-split-pane';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getDragStyles } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
interface Props {
|
||||
splitOrientation?: Split;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PluginState } from '@grafana/data';
|
||||
import { config, GrafanaBootConfig } from '@grafana/runtime';
|
||||
export { config, type GrafanaBootConfig as Settings };
|
||||
|
||||
let grafanaConfig: GrafanaBootConfig = config;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import deepEqual from 'fast-deep-equal';
|
||||
import memoize from 'micro-memoize';
|
||||
|
||||
import { getLanguage } from '@grafana/i18n/internal';
|
||||
import { config } from 'app/core/config';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
const deepMemoize: typeof memoize = (fn) => memoize(fn, { isEqual: deepEqual });
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getThemeById } from '@grafana/data/internal';
|
||||
import { ThemeChangedEvent } from '@grafana/runtime';
|
||||
import { config, ThemeChangedEvent } from '@grafana/runtime';
|
||||
|
||||
import { appEvents } from '../app_events';
|
||||
import { config } from '../config';
|
||||
import { contextSrv } from '../services/context_srv';
|
||||
|
||||
import { PreferencesService } from './PreferencesService';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Navigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
|
||||
import { config } from 'app/core/config';
|
||||
import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/types';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { config } from 'app/core/config';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
|
||||
export const hiddenReducerTypes = ['percent_diff', 'percent_diff_abs'];
|
||||
|
||||
@@ -162,7 +162,7 @@ export function useCombinedLabels(
|
||||
// This is called by Combobox when the dropdown menu opens
|
||||
const createAsyncValuesLoader = useCallback(
|
||||
(key: string): AsyncOptionsLoader => {
|
||||
return async (_inputValue: string): Promise<Array<ComboboxOption<string>>> => {
|
||||
return async (valueQuery: string): Promise<Array<ComboboxOption<string>>> => {
|
||||
if (!isKeyAllowed(key) || !key) {
|
||||
return [];
|
||||
}
|
||||
@@ -188,7 +188,10 @@ export function useCombinedLabels(
|
||||
// Combine: existing values first, then unique ops values (Set preserves first occurrence)
|
||||
const combinedValues = [...new Set([...existingValues, ...opsValues])];
|
||||
|
||||
return mapLabelsToOptions(combinedValues);
|
||||
const valueQueryLowerCase = valueQuery.toLowerCase();
|
||||
const filteredValues = combinedValues.filter((value) => value.toLowerCase().includes(valueQueryLowerCase));
|
||||
|
||||
return mapLabelsToOptions(filteredValues);
|
||||
};
|
||||
},
|
||||
[labelsByKeyFromExisingAlerts, labelsPluginInstalled, opsLabelKeysSet, fetchLabelValues]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user