Compare commits

..

44 Commits

Author SHA1 Message Date
dependabot[bot] 051a622dce deps(go): bump github.com/dolthub/vitess
Bumps [github.com/dolthub/vitess](https://github.com/dolthub/vitess) from 0.0.0-20250930230441-70c2c6a98e33 to 3.0.0-rc.3+incompatible.
- [Commits](https://github.com/dolthub/vitess/commits/v3.0.0-rc.3)

---
updated-dependencies:
- dependency-name: github.com/dolthub/vitess
  dependency-version: 3.0.0-rc.3+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 11:50:12 +00:00
Tito Lins b57b8d4359 fix: handle go mod issues (#116187) 2026-01-13 12:48:16 +01:00
Mustafa Sencer Özcan 5219ccddb6 fix: improve resilience for unified storage and search service grpc clients (#116122)
* fix: reliability

* fix: resilience

* fix: add connection backoff

* fix: reduce backoff
2026-01-13 11:42:21 +00:00
Ashley Harrison c95e3da2d5 Theme: Convert themes to json and define schemas using zod (#116006)
* convert all theme files to json

* automatically discover extra themes in go backend

* use zod

* error tidy up

* error tidy up p2

* generate theme json schema from zod

* generate theme list at build time, don't do it at runtime

* make name and id required in the theme schema
2026-01-13 11:13:11 +00:00
Gareth 43d9fbc056 Tempo: Fix search streaming queries (#116136)
* Tempo: Fix search queries

* apply variables for metrics streaming queries
2026-01-13 19:47:44 +09:00
Konrad Lalik 7b80c44ac7 Alerting: Fix label value search not filtering results (#116133)
Fixes the issue where typing in the label value dropdown would display
all values instead of filtering them based on the search input.

The bug was in `createAsyncValuesLoader` which was ignoring the
`valueQuery` parameter and returning all combined values instead of
the filtered subset.

Changes:
- Rename `_inputValue` parameter to `valueQuery` to indicate it should be used
- Filter combined values based on case-insensitive search query
- Return only filtered values instead of all values

Tests:
- Add test to verify correct values are shown for each label key
- Add test to verify search filtering works correctly
- Improve test infrastructure with proper portal container and element mocking
  for virtualized dropdown rendering
2026-01-13 11:43:07 +01:00
Rafael Bortolon Paulovic 98f271f345 chore(unified): remove unifiedStorageSearchSprinkles feature toggle (#116139)
chore: remove unifiedStorageSearchSprinkles feature flag

The feature flag is no longer needed because:
- OSS: usageinsights code doesn't exist in OSS builds
- Enterprise On-Prem: uses local SQL storage when enable_search=true
- Cloud: explicitly configures sprinkles_api_server URL

The sprinkles functionality now works automatically based on:
- enable_search config (enforced true for unified storage mode 5)
- sprinkles_api_server config (empty = local storage, set = remote API)
2026-01-13 11:24:13 +01:00
Vardan Torosyan 60c4fab063 [Docs] Add Synthentic Monitoring app to the list of RBAC supported apps (#116167)
* [Docs] Add Synthentic Monitoring app to the list of RBAC supported apps

* Run prettier
2026-01-13 11:23:33 +01:00
Ihor Yeromin ce8663ac24 SQL Expressions: Filter Dashboard datasource queries from schema fetching (#116129)
* fix(sql expression): sql schema frontend datasources filtering

* add one more test
2026-01-13 10:26:33 +01:00
Yulia Shanyrova 5dd9a14903 Plugins: Fix the flaky configuration tab on the plugin details page for cloud instances (#114922)
Fix flaky configuration tab for plugin details page at cloud instances
2026-01-13 09:55:52 +01:00
Roberto Jiménez Sánchez 68bf19d840 Provisioning: handle resource version conflicts in connection CRUDL test (#116184)
fix: handle resource version conflicts in connection CRUDL test

After updating a connection resource, the controller may update the
resource status, changing the resource version. This causes the delete
operation to fail with a resource version conflict.

Add retry logic to handle conflicts gracefully by retrying the delete
operation when encountering resource version conflicts.
2026-01-13 08:53:54 +00:00
Costa Alexoglou 220c29de89 fix: 401 in grafana live spam (#116140) 2026-01-13 09:46:06 +01:00
Oscar Kilhed 91ab753368 Dynamic Dashboards: Fix navigation to repeated panels and update outline when lazy items repeat (#116030)
Dashboard Outline: Fix navigation to repeated panels and lazy-loaded repeats

- Remove cursor: not-allowed styling from repeated panels in outline
- Add RepeatsUpdatedEvent to notify when panel repeats are populated
- Subscribe to RepeatsUpdatedEvent in DashboardEditPane to refresh outline
- Remove memoization from visibleChildren to ensure outline updates on re-render
2026-01-13 08:43:50 +01:00
Alex Khomenko 250ca7985f Provisioning: Add Connections page (#116060)
* Provisioning: Add connections page

* Provisioning: Add connections form

* Provisioning: Add connections form

* Update fields

* Fix generated name

* Update connection name

* Add edit page

* error handling

* Form validation

* Add Connections button

* Cleanup

* Extract ConnectionFormData type

* Add list test and separate empty states

* Add form test

* Update tests

* i18n

* Cleanup

* Use SecretTextArea from grafana-ui

* Fix breadcrumbs

* tweaks

* Add missing URL

* Switch to ShowConfirmModalEvent

* i18n

* redirect to list on success

* add timeout

* Fix tags invalidation
2026-01-13 08:25:40 +02:00
Hugo Häggmark b57ed32484 chore: remove app/core/config barrel files (#116068) 2026-01-13 06:23:21 +01:00
Galen Kistler d0217588a3 LogsDrilldown: Remove exploreLogsLimitedTimeRange flag (#116177)
chore: remove flag
2026-01-12 22:43:01 +00:00
Denis Vodopianov ce9ab6a89a Add non-boolean feature flags support to the StaticProvider (#115085)
* initial commit

* add support of integerts

* finialise the static provider

* minor refactoring

* the rest

* revert:  the rest

* add new thiongs

* more tests added

* add ff parsing tests to check if types are handled correctly

* update tests according to recent changes

* address golint issues

* Update pkg/setting/setting_feature_toggles.go

Co-authored-by: Dave Henderson <dave.henderson@grafana.com>

* fix rebase issues

* addressing review comments

* add test cases for enterprise

* handle enterprise cases

* minor refactoring to make api a bit easier to debug

* make test names a bit more precise

* fix linter

* add openfeature sdk to goleak ignore in testutil

* Remove only boolean check in ff gen tests

* add non-boolean types top the doc in default.ini and doc string in FeatureFlag type

* apply remarks, add docs to sample.ini

* reflect changes in feature flags in the public grafana configuration doc

* fix doc formatting

* apply suggestions to the doc file

---------

Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
2026-01-12 22:53:23 +01:00
Will Assis 8c8efd2494 unified-storage: skip sqlkv/sqlbackend compatibility tests in sqlite (#116164) 2026-01-12 16:31:29 -05:00
Will Assis 69ccfd6bfc unified-storage: fix sharedwithme search not returning folders (#116089)
* unified-storage: fix dashboard sharedwithme search not returning folders shared with the user
2026-01-12 15:33:34 -05:00
Nick Richmond 53aa5e8f7f MetricsDrilldown: Remove exploreMetricsRelatedLogs feature toggle (#116090)
chore: remove unused exploreMetricsRelatedLogs feature toggle
2026-01-12 12:52:40 -05:00
Ida Štambuk 69bf3068b3 Dashboards: Never show scopes variables (#116132) 2026-01-12 18:52:23 +01:00
Will Assis 1263a3d364 unified-storage: HappyPath and notifier tests + couple of bugfixes (#116087)
* unified-storage: couple of bugfixes and enable HappyPath and notifier sqlkv tests
2026-01-12 12:17:41 -05:00
Daniele Stefano Ferru e4b79e2fc8 Provisioning: Add Validation and Mutation for Connection resource (#115596)
* WIP: mutator added, start working on validator

* first validator iteration

* second validator iteration

* wip: working on integration tests

* re-working mutation and validation, using Connection interface

* fixing some rebase things

* fixing integration tests

* formatting

* fixing unit tests

* k8s codegen

* linting

* moving tests which are available only for enterprise

* addressing comments: using repo config for connections, updating tests

* addressing comments: adding some more info in the app and installation

* fixing app data

* addressing comments: updating connection implementation

* addressing comments

* formatting

* fixing tests
2026-01-12 17:52:00 +01:00
Haris Rozajac 0d1ec94548 Dashboard Schema V2: Activate dashboard ds queries (#116085)
* support dashboard ds

* add test for getPanelDataSource
2026-01-12 08:15:10 -07:00
Jack Westbrook 23a51ec9c5 CI: Fix frontend package validation (#116104)
* ci(frontend-lint): add frontend package change detection and add validate packed packages lint step

* ci(change-detection): add validate-npm-packages.sh to frontend-packages list

* ci(gh-workflows): add actions globs to frontend-packages detection

* ci(gh-workflows): fix typo - > _

* ci(frontend-lint): add missing needs

* chore(i18n): fix publint erroring for custom condition pointing to .cjs file

* ci(validate-npm-packages): make profile node16 default

* chore(validate-npm-packages): remove shellcheck disable comment
2026-01-12 16:08:32 +01:00
Dafydd 51dcdd3499 Datasources: Experimental API group names use full plugin IDs (#112961) 2026-01-12 15:01:40 +00:00
Larissa Wandzura 880bc23c85 Docs: Added a troubleshooting guide for MSSQL, plus some updates (#116088)
* added troubleshooting guide

* cleaned up the intro doc

* cleaned up the before you begin section in the configure doc

* changed a note to a tip

* changed to troubleshooting for cohesion

* final edits

* minor clean up item

* added note about Kerberos not being supported in Cloud

* punctuation fixes
2026-01-12 14:57:45 +00:00
Yunwen Zheng 6dc604c2ea RecentlyViewedDashboards: Add instrumentations (#116036)
RecentlyViewedDashboards: Add instrumentation
2026-01-12 09:56:15 -05:00
Matias Chomicki 77c500dc01 logsExploreTableDefaultVisualization: remove feature flag (#116127) 2026-01-12 14:46:47 +00:00
Kristina Demeshchik bec4d225b3 FieldConfig: Fix multiple value mappings in field overrides being overwritten (#116027)
* multiple value mappingg overrides

* add comment for clarity

* remove extra check
2026-01-12 09:29:16 -05:00
Ivana Huckova b91ca14f48 Icons: Add brain icon (#116023)
* Icons: Add brain icon

* lint

* Add brain to cached icons
2026-01-12 13:38:18 +01:00
Matias Chomicki 2aedbdb76f processing: support duplicated keys when parsing json logs (#116116)
* processing: support duplicated keys when parsing json logs

* Add regression test

* prettier
2026-01-12 13:37:17 +01:00
Rafael Bortolon Paulovic 0d7f46c08a chore(unified): remove unifiedStorageSearch feature toggle (#116109) 2026-01-12 13:22:48 +01:00
Ryan McKinley 1b52718c23 Search: include panel titles and types in index (#115742) 2026-01-12 13:21:03 +01:00
Naimesh Patel e61e406440 Explore: Add keyboard shortcut to run queries (#111675) (#115811)
* Explore: add keyboard shortcut to run queries (#111675)

* Update mock

* Fix linting

---------

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
2026-01-12 13:19:47 +01:00
Jack Westbrook 5cb4c311dc Chore: Eslint ignore webpack.config barrel files (#116115)
chore(eslint): ignore decoupled plugins webpack configs barrel files
2026-01-12 11:13:11 +00:00
Matheus Macabu 586410d8b5 Build: Fix running e2e tests for Cypress with Dagger (#116105) 2026-01-12 11:12:40 +01:00
james-rms a0e894c6d8 Documentation: Fix typo in plugin-sign.md heading (#115812) 2026-01-12 09:57:06 +00:00
Roberto Jiménez Sánchez e4796b1de3 Provisioning: Add fieldSelector for Repository by spec.connection.name (#116063)
* Provisioning: Add fieldSelector for Repository by spec.connection.name

This change adds the ability to filter repositories by their connection
name using Kubernetes field selectors, enabling queries like:

  kubectl get repositories --field-selector spec.connection.name=my-connection

Implementation:
- Add RepositoryGetAttrs and RepositoryToSelectableFields functions
- Register field label conversion for spec.connection.name in InstallSchema
- Extend generic storage to support custom selectable fields via
  NewRegistryStoreWithSelectableFields
- Add unit tests for repository field functions
- Add integration tests for field selector functionality

* Simplify predicateFunc handling with custom attrFunc

Remove unnecessary custom predicateFunc wrapper when using a custom
GetAttrs function. When attrFunc is provided via StoreOptions, passing
nil for predicateFunc allows the default behavior to create the
appropriate SelectionPredicate automatically.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 09:31:25 +00:00
Miklós Tolnai 86a3aae204 InteractiveTable: Extend sort options with disableSortRemove and sortDescFirst (#115352)
* add disableSortRemove option

* add sortDescFirst to Column

* pass sortDescFirst only if it is set
2026-01-12 10:30:38 +01:00
Jack Westbrook e0ad4eb7ed Chore: Remove core actions barrel file (#98149)
* refactor(frontend): update core/actions imports to avoid barrel file

* chore(frontend): delete app/core/actions barrel file

* refactor(frontend): replace more barrel file imports

* refactor(frontend): replace more core/actions imports

* rerun ci
2026-01-12 10:17:02 +01:00
Gonzalo Trigueros Manzanas f0c95a0a10 Provisioning: Add new error framework to handle folder creation failures gracefully. (#114824)
* Implement hierarchical error handling for folder creation failures

This commit implements hierarchical error handling to improve sync robustness
when folder creation fails. Instead of failing the entire sync, the system now:

1. Tracks failed folder creations and automatically skips nested resources
2. Records skipped resources with FileActionIgnored (doesn't count toward error limits)
3. Allows other folder hierarchies to continue processing
4. Prevents folder deletion when child resource deletions fail

Key Changes:

- Add PathCreationError type to track which folder path failed
- Modify progress recorder to automatically detect and track failures via Record()
- Add IsNestedUnderFailedCreation() and HasFailedDeletionsUnder() checks
- Update full and incremental sync to skip nested resources after folder failures
- Deletions proceed even if parent folder creation failed (resource may exist from previous sync)
- FileActionIgnored results don't count toward error limits

Example behavior improvement:
Before: /monitoring folder creation fails → all nested resources fail → other folders never processed
After: /monitoring folder creation fails → nested resources ignored → /applications folder succeeds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* provisioning: refactor hierarchical errors in folder management.

* Move test to the corresponding package

* Refactor timeout handling in applyChanges functions

- Introduced wrapWithTimeout function to streamline timeout context management for applyChange calls.
- Updated applyFoldersSerially and applyIncrementalChanges to utilize the new timeout wrapper.
- Removed redundant logging and error handling code related to timeout in favor of centralized handling in wrapWithTimeout.
- Adjusted test expectations to reflect changes in error reporting for context deadlines.

---------

Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 09:07:04 +00:00
Gareth 0b46123300 Tempo: remove backend migration feature toggle (#116054)
* remove unused frontend code

* remove feature toggle definition

* fix tests
2026-01-12 17:21:48 +09:00
Gabriel MABILLE 81b868ae91 grafana-iam: Split AuthZ apis feature toggle per apis (#116010)
* WIP: switched to feature toggles

* Add timeout
2026-01-12 09:00:51 +01:00
390 changed files with 12419 additions and 90597 deletions
-4
View File
@@ -754,10 +754,6 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-api-clients/src/clients/rtkq/provisioning/ @grafana/grafana-git-ui-sync-team
/packages/grafana-api-clients/src/clients/rtkq/shorturl/ @grafana/sharing-squad
# @grafana/openapi
/packages/grafana-openapi/ @grafana/plugins-platform-frontend @grafana/grafana-search-navigate-organise @grafana/grafana-frontend-platform
# root files, mostly frontend
/.browserslistrc @grafana/frontend-ops
/package.json @grafana/frontend-ops
@@ -14,6 +14,9 @@ outputs:
frontend:
description: Whether the frontend or self has changed in any way
value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}
frontend-packages:
description: Whether any frontend packages have changed
value: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}
e2e:
description: Whether the e2e tests or self have changed in any way
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
@@ -97,6 +100,12 @@ runs:
- '.yarn/**'
- 'apps/dashboard/pkg/migration/**'
- '${{ inputs.self }}'
frontend_packages:
- '.github/actions/checkout/**'
- '.github/actions/change-detection/**'
- 'packages/**'
- './scripts/validate-npm-packages.sh'
- '${{ inputs.self }}'
e2e:
- 'e2e/**'
- 'e2e-playwright/**'
@@ -153,6 +162,8 @@ runs:
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
echo "Frontend packages: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.frontend_packages_all_changed_files }}"
echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}"
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
+2 -2
View File
@@ -4,8 +4,8 @@ description: Sets up a node.js environment with presets for the Grafana reposito
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
cache-dependency-path: 'yarn.lock'
+36 -67
View File
@@ -17,6 +17,7 @@ jobs:
outputs:
changed: ${{ steps.detect-changes.outputs.frontend }}
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
steps:
- uses: actions/checkout@v5
with:
@@ -42,11 +43,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- run: yarn run prettier:check
- run: yarn run lint
@@ -63,11 +61,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -89,11 +84,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- run: yarn run typecheck
lint-frontend-typecheck-enterprise:
@@ -109,11 +101,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -133,11 +122,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- run: yarn install --immutable --check-cache
- name: Generate API clients
run: |
@@ -152,43 +138,6 @@ jobs:
echo "${uncommited_error_message}"
exit 1
fi
lint-frontend-openapi:
# Run this workflow for OSS only
permissions:
contents: read
id-token: write
name: Verify OpenAPI specs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- run: yarn install --immutable --check-cache
- name: Free up disk space
run: |
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /usr/local/.ghcup || true
- name: Generate OpenAPI specs
run: |
extract_error_message='ERROR! OpenAPI generation failed!'
yarn generate:openapi || (echo "${extract_error_message}" && false)
- name: Verify generated specs
run: |
git add -N .
uncommited_error_message="ERROR! OpenAPI generation has not been committed. Please run 'yarn generate:openapi', commit the changes and push again."
file_diff="$(git diff --name-only ':!conf')"
if [ -n "$file_diff" ]; then
echo "$file_diff"
echo "${uncommited_error_message}"
exit 1
fi
lint-frontend-api-clients-enterprise:
permissions:
contents: read
@@ -201,11 +150,8 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
@@ -224,3 +170,26 @@ jobs:
echo "${uncommited_error_message}"
exit 1
fi
lint-frontend-packed-packages:
needs: detect-changes
permissions:
contents: read
id-token: write
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
name: Verify packed frontend packages
runs-on: ubuntu-latest
steps:
- name: Checkout build commit
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Install dependencies
run: yarn install --immutable
- name: Build and pack packages
run: |
yarn run packages:build
yarn run packages:pack
- name: Validate packages
run: ./scripts/validate-npm-packages.sh
+6 -6
View File
@@ -127,11 +127,6 @@ OAPI_SPEC_TARGET = public/openapi3.json
openapi3-gen: swagger-gen ## Generates OpenApi 3 specs from the Swagger 2 already generated
$(GO) run $(GO_RACE_FLAG) scripts/openapi3/openapi3conv.go $(MERGED_SPEC_TARGET) $(OAPI_SPEC_TARGET)
.PHONY: generate-openapi
generate-openapi: openapi3-gen
$(GO) test ./pkg/tests/apis || true
yarn workspace @grafana/openapi process-specs
##@ Internationalisation
.PHONY: i18n-extract-enterprise
ENTERPRISE_FE_EXT_FILE = public/app/extensions/index.ts
@@ -140,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
@@ -232,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"
@@ -249,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
@@ -852,6 +852,194 @@
}
}
}
},
"panel-7": {
"kind": "Panel",
"spec": {
"id": 7,
"title": "Single Dashboard DS Query",
"description": "Panel with a single -- Dashboard -- datasource query",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "stat",
"spec": {
"pluginVersion": "12.1.0-pre",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
},
"panel-8": {
"kind": "Panel",
"spec": {
"id": 8,
"title": "Multiple Dashboard DS Queries",
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "A",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 2,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "B",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 3,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "C",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "stat",
"spec": {
"pluginVersion": "12.1.0-pre",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
@@ -914,6 +1102,24 @@
"name": "panel-6"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-7"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-8"
}
}
}
]
}
@@ -879,6 +879,200 @@
}
}
}
},
"panel-7": {
"kind": "Panel",
"spec": {
"id": 7,
"title": "Single Dashboard DS Query",
"description": "Panel with a single -- Dashboard -- datasource query",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "stat",
"version": "12.1.0-pre",
"spec": {
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
},
"panel-8": {
"kind": "Panel",
"spec": {
"id": 8,
"title": "Multiple Dashboard DS Queries",
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"refId": "A",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 2,
"withTransforms": true
}
},
"refId": "B",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 3,
"withTransforms": true
}
},
"refId": "C",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "stat",
"version": "12.1.0-pre",
"spec": {
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
@@ -973,6 +1167,32 @@
"name": "panel-6"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 6,
"width": 8,
"height": 3,
"element": {
"kind": "ElementReference",
"name": "panel-7"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 8,
"y": 6,
"width": 8,
"height": 3,
"element": {
"kind": "ElementReference",
"name": "panel-8"
}
}
}
]
}
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"description": "Panel with a single -- Dashboard -- datasource query",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 9,
"w": 8,
"x": 0,
"y": 18
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
}
],
"title": "Single Dashboard DS Query",
"type": "stat"
},
{
"datasource": {
"type": "mixed",
"uid": "-- Mixed --"
},
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 9,
"w": 8,
"x": 8,
"y": 18
},
"id": 8,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 2,
"refId": "B",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 3,
"refId": "C",
"withTransforms": true
}
],
"title": "Multiple Dashboard DS Queries",
"type": "stat"
}
],
"preload": false,
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"description": "Panel with a single -- Dashboard -- datasource query",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 9,
"w": 8,
"x": 0,
"y": 18
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
}
],
"title": "Single Dashboard DS Query",
"type": "stat"
},
{
"datasource": {
"type": "mixed",
"uid": "-- Mixed --"
},
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 9,
"w": 8,
"x": 8,
"y": 18
},
"id": 8,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 2,
"refId": "B",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 3,
"refId": "C",
"withTransforms": true
}
],
"title": "Multiple Dashboard DS Queries",
"type": "stat"
}
],
"preload": false,
@@ -879,6 +879,200 @@
}
}
}
},
"panel-7": {
"kind": "Panel",
"spec": {
"id": 7,
"title": "Single Dashboard DS Query",
"description": "Panel with a single -- Dashboard -- datasource query",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "stat",
"version": "12.1.0-pre",
"spec": {
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
},
"panel-8": {
"kind": "Panel",
"spec": {
"id": 8,
"title": "Multiple Dashboard DS Queries",
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"refId": "A",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 2,
"withTransforms": true
}
},
"refId": "B",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "datasource",
"version": "v0",
"datasource": {
"name": "-- Dashboard --"
},
"spec": {
"panelId": 3,
"withTransforms": true
}
},
"refId": "C",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "stat",
"version": "12.1.0-pre",
"spec": {
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
@@ -941,6 +1135,24 @@
"name": "panel-6"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-7"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-8"
}
}
}
]
}
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"description": "Panel with a single -- Dashboard -- datasource query",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 3,
"w": 8,
"x": 0,
"y": 6
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
}
],
"title": "Single Dashboard DS Query",
"type": "stat"
},
{
"datasource": {
"type": "mixed",
"uid": "-- Mixed --"
},
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 3,
"w": 8,
"x": 8,
"y": 6
},
"id": 8,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 2,
"refId": "B",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 3,
"refId": "C",
"withTransforms": true
}
],
"title": "Multiple Dashboard DS Queries",
"type": "stat"
}
],
"preload": false,
@@ -711,6 +711,146 @@
],
"title": "Mixed DS WITHOUT REFS",
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"description": "Panel with a single -- Dashboard -- datasource query",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 3,
"w": 8,
"x": 0,
"y": 6
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
}
],
"title": "Single Dashboard DS Query",
"type": "stat"
},
{
"datasource": {
"type": "mixed",
"uid": "-- Mixed --"
},
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
}
},
"gridPos": {
"h": 3,
"w": 8,
"x": 8,
"y": 6
},
"id": 8,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 2,
"refId": "B",
"withTransforms": true
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 3,
"refId": "C",
"withTransforms": true
}
],
"title": "Multiple Dashboard DS Queries",
"type": "stat"
}
],
"preload": false,
@@ -852,6 +852,194 @@
}
}
}
},
"panel-7": {
"kind": "Panel",
"spec": {
"id": 7,
"title": "Single Dashboard DS Query",
"description": "Panel with a single -- Dashboard -- datasource query",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "stat",
"spec": {
"pluginVersion": "12.1.0-pre",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
},
"panel-8": {
"kind": "Panel",
"spec": {
"id": 8,
"title": "Multiple Dashboard DS Queries",
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 1,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "A",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 2,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "B",
"hidden": false
}
},
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "datasource",
"spec": {
"panelId": 3,
"withTransforms": true
}
},
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"refId": "C",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "stat",
"spec": {
"pluginVersion": "12.1.0-pre",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
@@ -946,6 +1134,32 @@
"name": "panel-6"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 6,
"width": 8,
"height": 3,
"element": {
"kind": "ElementReference",
"name": "panel-7"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 8,
"y": 6,
"width": 8,
"height": 3,
"element": {
"kind": "ElementReference",
"name": "panel-8"
}
}
}
]
}
@@ -1195,16 +1195,36 @@ func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, quer
// getPanelDatasource determines the panel-level datasource for V1.
// Returns:
// - Mixed datasource reference if queries use different datasources
// - Mixed datasource reference if multiple queries use Dashboard datasource (they fetch from different panels)
// - Dashboard datasource reference if a single query uses Dashboard datasource
// - First query's datasource if all queries use the same datasource
// - nil if no queries exist
// Compares based on V2 input without runtime resolution:
// - If query has explicit datasource.uid → use that UID and type
// - Else → use query.Kind as type (empty UID)
func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
const sharedDashboardQuery = "-- Dashboard --"
if len(queries) == 0 {
return nil
}
// Count how many queries use Dashboard datasource
// Multiple dashboard queries need mixed mode because they fetch from different panels
// which may have different underlying datasources
dashboardDsQueryCount := 0
for _, query := range queries {
if query.Spec.Datasource != nil && query.Spec.Datasource.Uid != nil && *query.Spec.Datasource.Uid == sharedDashboardQuery {
dashboardDsQueryCount++
}
}
if dashboardDsQueryCount > 1 {
return map[string]interface{}{
"type": "mixed",
"uid": "-- Mixed --",
}
}
var firstUID, firstType string
var hasFirst bool
@@ -1239,6 +1259,16 @@ func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[stri
}
}
// Handle case when a single query uses Dashboard datasource.
// This is needed for the frontend to properly activate and fetch data from source panels.
// See DashboardDatasourceBehaviour.tsx for more details.
if firstUID == sharedDashboardQuery {
return map[string]interface{}{
"type": "datasource",
"uid": sharedDashboardQuery,
}
}
// Not mixed - return the first query's datasource so the panel has a datasource set.
// This is required because the frontend's legacy PanelModel.PanelQueryRunner.run uses panel.datasource
// to resolve the datasource, and if undefined, it falls back to the default datasource
@@ -32,7 +32,7 @@ type ConnectionSecure struct {
// Token is the reference of the token used to act as the Connection.
// This value is stored securely and cannot be read back
Token common.InlineSecureValue `json:"webhook,omitzero,omitempty"`
Token common.InlineSecureValue `json:"token,omitzero,omitempty"`
}
func (v ConnectionSecure) IsZero() bool {
@@ -320,7 +320,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref common.Reference
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
},
"webhook": {
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back",
Default: map[string]interface{}{},
@@ -22,7 +22,6 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSecure,Token
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSpec,GitHub
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs
@@ -0,0 +1,16 @@
package connection
import (
"context"
)
//go:generate mockery --name Connection --structname MockConnection --inpackage --filename connection_mock.go --with-expecter
type Connection interface {
// Validate ensures the resource _looks_ correct.
// It should be called before trying to upsert a resource into the Kubernetes API server.
// This is not an indication that the connection information works, just that they are reasonably configured.
Validate(ctx context.Context) error
// Mutate performs in place mutation of the underneath resource.
Mutate(context.Context) error
}
@@ -0,0 +1,128 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package connection
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// MockConnection is an autogenerated mock type for the Connection type
type MockConnection struct {
mock.Mock
}
type MockConnection_Expecter struct {
mock *mock.Mock
}
func (_m *MockConnection) EXPECT() *MockConnection_Expecter {
return &MockConnection_Expecter{mock: &_m.Mock}
}
// Mutate provides a mock function with given fields: _a0
func (_m *MockConnection) Mutate(_a0 context.Context) error {
ret := _m.Called(_a0)
if len(ret) == 0 {
panic("no return value specified for Mutate")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConnection_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate'
type MockConnection_Mutate_Call struct {
*mock.Call
}
// Mutate is a helper method to define mock.On call
// - _a0 context.Context
func (_e *MockConnection_Expecter) Mutate(_a0 interface{}) *MockConnection_Mutate_Call {
return &MockConnection_Mutate_Call{Call: _e.mock.On("Mutate", _a0)}
}
func (_c *MockConnection_Mutate_Call) Run(run func(_a0 context.Context)) *MockConnection_Mutate_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockConnection_Mutate_Call) Return(_a0 error) *MockConnection_Mutate_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConnection_Mutate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Mutate_Call {
_c.Call.Return(run)
return _c
}
// Validate provides a mock function with given fields: ctx
func (_m *MockConnection) Validate(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Validate")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConnection_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
type MockConnection_Validate_Call struct {
*mock.Call
}
// Validate is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockConnection_Expecter) Validate(ctx interface{}) *MockConnection_Validate_Call {
return &MockConnection_Validate_Call{Call: _e.mock.On("Validate", ctx)}
}
func (_c *MockConnection_Validate_Call) Run(run func(ctx context.Context)) *MockConnection_Validate_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockConnection_Validate_Call) Return(_a0 error) *MockConnection_Validate_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConnection_Validate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Validate_Call {
_c.Call.Return(run)
return _c
}
// NewMockConnection creates a new instance of MockConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockConnection(t interface {
mock.TestingT
Cleanup(func())
}) *MockConnection {
mock := &MockConnection{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -0,0 +1,141 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package connection
import (
context "context"
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
// MockExtra is an autogenerated mock type for the Extra type
type MockExtra struct {
mock.Mock
}
type MockExtra_Expecter struct {
mock *mock.Mock
}
func (_m *MockExtra) EXPECT() *MockExtra_Expecter {
return &MockExtra_Expecter{mock: &_m.Mock}
}
// Build provides a mock function with given fields: ctx, r
func (_m *MockExtra) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
ret := _m.Called(ctx, r)
if len(ret) == 0 {
panic("no return value specified for Build")
}
var r0 Connection
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
return rf(ctx, r)
}
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
r0 = rf(ctx, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Connection)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
r1 = rf(ctx, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockExtra_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
type MockExtra_Build_Call struct {
*mock.Call
}
// Build is a helper method to define mock.On call
// - ctx context.Context
// - r *v0alpha1.Connection
func (_e *MockExtra_Expecter) Build(ctx interface{}, r interface{}) *MockExtra_Build_Call {
return &MockExtra_Build_Call{Call: _e.mock.On("Build", ctx, r)}
}
func (_c *MockExtra_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockExtra_Build_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
})
return _c
}
func (_c *MockExtra_Build_Call) Return(_a0 Connection, _a1 error) *MockExtra_Build_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockExtra_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockExtra_Build_Call {
_c.Call.Return(run)
return _c
}
// Type provides a mock function with no fields
func (_m *MockExtra) Type() v0alpha1.ConnectionType {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Type")
}
var r0 v0alpha1.ConnectionType
if rf, ok := ret.Get(0).(func() v0alpha1.ConnectionType); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(v0alpha1.ConnectionType)
}
return r0
}
// MockExtra_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type'
type MockExtra_Type_Call struct {
*mock.Call
}
// Type is a helper method to define mock.On call
func (_e *MockExtra_Expecter) Type() *MockExtra_Type_Call {
return &MockExtra_Type_Call{Call: _e.mock.On("Type")}
}
func (_c *MockExtra_Type_Call) Run(run func()) *MockExtra_Type_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockExtra_Type_Call) Return(_a0 v0alpha1.ConnectionType) *MockExtra_Type_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockExtra_Type_Call) RunAndReturn(run func() v0alpha1.ConnectionType) *MockExtra_Type_Call {
_c.Call.Return(run)
return _c
}
// NewMockExtra creates a new instance of MockExtra. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockExtra(t interface {
mock.TestingT
Cleanup(func())
}) *MockExtra {
mock := &MockExtra{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -0,0 +1,75 @@
package connection
import (
"context"
"fmt"
"sort"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
//go:generate mockery --name=Extra --structname=MockExtra --inpackage --filename=extra_mock.go --with-expecter
type Extra interface {
Type() provisioning.ConnectionType
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
}
//go:generate mockery --name=Factory --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter
type Factory interface {
Types() []provisioning.ConnectionType
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
}
type factory struct {
extras map[provisioning.ConnectionType]Extra
enabled map[provisioning.ConnectionType]struct{}
}
func ProvideFactory(enabled map[provisioning.ConnectionType]struct{}, extras []Extra) (Factory, error) {
f := &factory{
enabled: enabled,
extras: make(map[provisioning.ConnectionType]Extra, len(extras)),
}
for _, e := range extras {
if _, exists := f.extras[e.Type()]; exists {
return nil, fmt.Errorf("connection type %q is already registered", e.Type())
}
f.extras[e.Type()] = e
}
return f, nil
}
func (f *factory) Types() []provisioning.ConnectionType {
var types []provisioning.ConnectionType
for t := range f.enabled {
if _, exists := f.extras[t]; exists {
types = append(types, t)
}
}
sort.Slice(types, func(i, j int) bool {
return string(types[i]) < string(types[j])
})
return types
}
func (f *factory) Build(ctx context.Context, c *provisioning.Connection) (Connection, error) {
for _, e := range f.extras {
if e.Type() == c.Spec.Type {
if _, enabled := f.enabled[e.Type()]; !enabled {
return nil, fmt.Errorf("connection type %q is not enabled", e.Type())
}
return e.Build(ctx, c)
}
}
return nil, fmt.Errorf("connection type %q is not supported", c.Spec.Type)
}
var (
_ Factory = (*factory)(nil)
)
@@ -0,0 +1,143 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package connection
import (
context "context"
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
// MockFactory is an autogenerated mock type for the Factory type
type MockFactory struct {
mock.Mock
}
type MockFactory_Expecter struct {
mock *mock.Mock
}
func (_m *MockFactory) EXPECT() *MockFactory_Expecter {
return &MockFactory_Expecter{mock: &_m.Mock}
}
// Build provides a mock function with given fields: ctx, r
func (_m *MockFactory) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
ret := _m.Called(ctx, r)
if len(ret) == 0 {
panic("no return value specified for Build")
}
var r0 Connection
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
return rf(ctx, r)
}
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
r0 = rf(ctx, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Connection)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
r1 = rf(ctx, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockFactory_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
type MockFactory_Build_Call struct {
*mock.Call
}
// Build is a helper method to define mock.On call
// - ctx context.Context
// - r *v0alpha1.Connection
func (_e *MockFactory_Expecter) Build(ctx interface{}, r interface{}) *MockFactory_Build_Call {
return &MockFactory_Build_Call{Call: _e.mock.On("Build", ctx, r)}
}
func (_c *MockFactory_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockFactory_Build_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
})
return _c
}
func (_c *MockFactory_Build_Call) Return(_a0 Connection, _a1 error) *MockFactory_Build_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockFactory_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockFactory_Build_Call {
_c.Call.Return(run)
return _c
}
// Types provides a mock function with no fields
func (_m *MockFactory) Types() []v0alpha1.ConnectionType {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Types")
}
var r0 []v0alpha1.ConnectionType
if rf, ok := ret.Get(0).(func() []v0alpha1.ConnectionType); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]v0alpha1.ConnectionType)
}
}
return r0
}
// MockFactory_Types_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Types'
type MockFactory_Types_Call struct {
*mock.Call
}
// Types is a helper method to define mock.On call
func (_e *MockFactory_Expecter) Types() *MockFactory_Types_Call {
return &MockFactory_Types_Call{Call: _e.mock.On("Types")}
}
func (_c *MockFactory_Types_Call) Run(run func()) *MockFactory_Types_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockFactory_Types_Call) Return(_a0 []v0alpha1.ConnectionType) *MockFactory_Types_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockFactory_Types_Call) RunAndReturn(run func() []v0alpha1.ConnectionType) *MockFactory_Types_Call {
_c.Call.Return(run)
return _c
}
// NewMockFactory creates a new instance of MockFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockFactory(t interface {
mock.TestingT
Cleanup(func())
}) *MockFactory {
mock := &MockFactory{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -0,0 +1,309 @@
package connection
import (
"context"
"errors"
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestProvideFactory(t *testing.T) {
t.Run("should create factory with valid extras", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
provisioning.GitlabConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.NoError(t, err)
require.NotNil(t, factory)
})
t.Run("should create factory with empty extras", func(t *testing.T) {
enabled := map[provisioning.ConnectionType]struct{}{}
factory, err := ProvideFactory(enabled, []Extra{})
require.NoError(t, err)
require.NotNil(t, factory)
})
t.Run("should create factory with nil enabled map", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
factory, err := ProvideFactory(nil, []Extra{extra1})
require.NoError(t, err)
require.NotNil(t, factory)
})
t.Run("should return error when duplicate repository types", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.Error(t, err)
assert.Nil(t, factory)
assert.Contains(t, err.Error(), "connection type \"github\" is already registered")
})
}
func TestFactory_Types(t *testing.T) {
t.Run("should return only enabled types that have extras", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
provisioning.GitlabConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 2)
assert.Contains(t, types, provisioning.GithubConnectionType)
assert.Contains(t, types, provisioning.GitlabConnectionType)
})
t.Run("should return sorted list of types", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GitlabConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
provisioning.GitlabConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 2)
// github should come before gitlab alphabetically
assert.Equal(t, provisioning.GithubConnectionType, types[0])
assert.Equal(t, provisioning.GitlabConnectionType, types[1])
})
t.Run("should return empty list when no types are enabled", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{}
factory, err := ProvideFactory(enabled, []Extra{extra1})
require.NoError(t, err)
types := factory.Types()
assert.Empty(t, types)
})
t.Run("should not return types that are enabled but have no extras", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
provisioning.GitlabConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1})
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 1)
assert.Contains(t, types, provisioning.GithubConnectionType)
assert.NotContains(t, types, provisioning.GitlabConnectionType)
})
t.Run("should not return types that have extras but are not enabled", func(t *testing.T) {
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 1)
assert.Contains(t, types, provisioning.GithubConnectionType)
assert.NotContains(t, types, provisioning.GitlabConnectionType)
})
t.Run("should return empty list when no extras are provided", func(t *testing.T) {
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{})
require.NoError(t, err)
types := factory.Types()
assert.Empty(t, types)
})
}
func TestFactory_Build(t *testing.T) {
t.Run("should successfully build connection when type is enabled and has extra", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
},
}
mockConnection := NewMockConnection(t)
extra := NewMockExtra(t)
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra})
require.NoError(t, err)
result, err := factory.Build(ctx, conn)
require.NoError(t, err)
assert.Equal(t, mockConnection, result)
})
t.Run("should return error when type is not enabled", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
},
}
extra := NewMockExtra(t)
extra.EXPECT().Type().Return(provisioning.GitlabConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra})
require.NoError(t, err)
result, err := factory.Build(ctx, conn)
require.Error(t, err)
assert.Nil(t, result)
assert.Contains(t, err.Error(), "connection type \"gitlab\" is not enabled")
})
t.Run("should return error when type is not supported", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
},
}
extra := NewMockExtra(t)
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra})
require.NoError(t, err)
result, err := factory.Build(ctx, conn)
require.Error(t, err)
assert.Nil(t, result)
assert.Contains(t, err.Error(), "connection type \"gitlab\" is not supported")
})
t.Run("should pass through errors from extra.Build()", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
},
}
expectedErr := errors.New("build error")
extra := NewMockExtra(t)
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra.EXPECT().Build(ctx, conn).Return(nil, expectedErr)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra})
require.NoError(t, err)
result, err := factory.Build(ctx, conn)
require.Error(t, err)
assert.Nil(t, result)
assert.Equal(t, expectedErr, err)
})
t.Run("should build with multiple extras registered", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
},
}
mockConnection := NewMockConnection(t)
extra1 := NewMockExtra(t)
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
extra2 := NewMockExtra(t)
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
extra2.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
enabled := map[provisioning.ConnectionType]struct{}{
provisioning.GithubConnectionType: {},
provisioning.GitlabConnectionType: {},
}
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
require.NoError(t, err)
result, err := factory.Build(ctx, conn)
require.NoError(t, err)
assert.Equal(t, mockConnection, result)
})
}
@@ -0,0 +1,93 @@
package github
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"github.com/google/go-github/v70/github"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
// API errors that we need to convey after parsing real GH errors (or faking them).
var (
//lint:ignore ST1005 this is not punctuation
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
)
//go:generate mockery --name Client --structname MockClient --inpackage --filename client_mock.go --with-expecter
type Client interface {
// Apps and installations
GetApp(ctx context.Context) (App, error)
GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error)
}
// App represents a Github App.
type App struct {
// ID represents the GH app ID.
ID int64
// Slug represents the GH app slug.
Slug string
// Owner represents the GH account/org owning the app
Owner string
}
// AppInstallation represents a Github App Installation.
type AppInstallation struct {
// ID represents the GH installation ID.
ID int64
// Whether the installation is enabled or not.
Enabled bool
}
type githubClient struct {
gh *github.Client
}
func NewClient(client *github.Client) Client {
return &githubClient{client}
}
// GetApp gets the app by using the given token.
func (r *githubClient) GetApp(ctx context.Context) (App, error) {
app, _, err := r.gh.Apps.Get(ctx, "")
if err != nil {
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return App{}, ErrServiceUnavailable
}
return App{}, err
}
// TODO(ferruvich): do we need any other info?
return App{
ID: app.GetID(),
Slug: app.GetSlug(),
Owner: app.GetOwner().GetLogin(),
}, nil
}
// GetAppInstallation gets the installation of the app related to the given token.
func (r *githubClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
id, err := strconv.Atoi(installationID)
if err != nil {
return AppInstallation{}, fmt.Errorf("invalid installation ID: %s", installationID)
}
installation, _, err := r.gh.Apps.GetInstallation(ctx, int64(id))
if err != nil {
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return AppInstallation{}, ErrServiceUnavailable
}
return AppInstallation{}, err
}
// TODO(ferruvich): do we need any other info?
return AppInstallation{
ID: installation.GetID(),
Enabled: installation.GetSuspendedAt().IsZero(),
}, nil
}
@@ -0,0 +1,149 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package github
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// MockClient is an autogenerated mock type for the Client type
type MockClient struct {
mock.Mock
}
type MockClient_Expecter struct {
mock *mock.Mock
}
func (_m *MockClient) EXPECT() *MockClient_Expecter {
return &MockClient_Expecter{mock: &_m.Mock}
}
// GetApp provides a mock function with given fields: ctx
func (_m *MockClient) GetApp(ctx context.Context) (App, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetApp")
}
var r0 App
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (App, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) App); ok {
r0 = rf(ctx)
} else {
r0 = ret.Get(0).(App)
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockClient_GetApp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetApp'
type MockClient_GetApp_Call struct {
*mock.Call
}
// GetApp is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockClient_Expecter) GetApp(ctx interface{}) *MockClient_GetApp_Call {
return &MockClient_GetApp_Call{Call: _e.mock.On("GetApp", ctx)}
}
func (_c *MockClient_GetApp_Call) Run(run func(ctx context.Context)) *MockClient_GetApp_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockClient_GetApp_Call) Return(_a0 App, _a1 error) *MockClient_GetApp_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_GetApp_Call) RunAndReturn(run func(context.Context) (App, error)) *MockClient_GetApp_Call {
_c.Call.Return(run)
return _c
}
// GetAppInstallation provides a mock function with given fields: ctx, installationID
func (_m *MockClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
ret := _m.Called(ctx, installationID)
if len(ret) == 0 {
panic("no return value specified for GetAppInstallation")
}
var r0 AppInstallation
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (AppInstallation, error)); ok {
return rf(ctx, installationID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) AppInstallation); ok {
r0 = rf(ctx, installationID)
} else {
r0 = ret.Get(0).(AppInstallation)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, installationID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockClient_GetAppInstallation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAppInstallation'
type MockClient_GetAppInstallation_Call struct {
*mock.Call
}
// GetAppInstallation is a helper method to define mock.On call
// - ctx context.Context
// - installationID string
func (_e *MockClient_Expecter) GetAppInstallation(ctx interface{}, installationID interface{}) *MockClient_GetAppInstallation_Call {
return &MockClient_GetAppInstallation_Call{Call: _e.mock.On("GetAppInstallation", ctx, installationID)}
}
func (_c *MockClient_GetAppInstallation_Call) Run(run func(ctx context.Context, installationID string)) *MockClient_GetAppInstallation_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *MockClient_GetAppInstallation_Call) Return(_a0 AppInstallation, _a1 error) *MockClient_GetAppInstallation_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_GetAppInstallation_Call) RunAndReturn(run func(context.Context, string) (AppInstallation, error)) *MockClient_GetAppInstallation_Call {
_c.Call.Return(run)
return _c
}
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockClient(t interface {
mock.TestingT
Cleanup(func())
}) *MockClient {
mock := &MockClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -0,0 +1,297 @@
package github_test
import (
"context"
"encoding/json"
"net/http"
"testing"
"time"
"github.com/google/go-github/v70/github"
conngh "github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
mockhub "github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGithubClient_GetApp(t *testing.T) {
tests := []struct {
name string
mockHandler *http.Client
token string
wantApp conngh.App
wantErr error
}{
{
name: "get app successfully",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetApp,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
app := &github.App{
ID: github.Ptr(int64(12345)),
Slug: github.Ptr("my-test-app"),
Owner: &github.User{
Login: github.Ptr("grafana"),
},
}
w.WriteHeader(http.StatusOK)
require.NoError(t, json.NewEncoder(w).Encode(app))
}),
),
),
token: "test-token",
wantApp: conngh.App{
ID: 12345,
Slug: "my-test-app",
Owner: "grafana",
},
wantErr: nil,
},
{
name: "service unavailable",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetApp,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusServiceUnavailable,
},
Message: "Service unavailable",
}))
}),
),
),
token: "test-token",
wantApp: conngh.App{},
wantErr: conngh.ErrServiceUnavailable,
},
{
name: "other error",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetApp,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusInternalServerError,
},
Message: "Internal server error",
}))
}),
),
),
token: "test-token",
wantApp: conngh.App{},
wantErr: &github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusInternalServerError,
},
Message: "Internal server error",
},
},
{
name: "unauthorized error",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetApp,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
},
Message: "Bad credentials",
}))
}),
),
),
token: "invalid-token",
wantApp: conngh.App{},
wantErr: &github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
},
Message: "Bad credentials",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a mock client
ghClient := github.NewClient(tt.mockHandler)
client := conngh.NewClient(ghClient)
// Call the method being tested
app, err := client.GetApp(context.Background())
// Check the error
if tt.wantErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.wantApp, app)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantApp, app)
}
})
}
}
func TestGithubClient_GetAppInstallation(t *testing.T) {
tests := []struct {
name string
mockHandler *http.Client
appToken string
installationID string
wantInstallation conngh.AppInstallation
wantErr bool
errContains string
}{
{
name: "get disabled app installation successfully",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetAppInstallationsByInstallationId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
installation := &github.Installation{
ID: github.Ptr(int64(67890)),
SuspendedAt: github.Ptr(github.Timestamp{Time: time.Now()}),
}
w.WriteHeader(http.StatusOK)
require.NoError(t, json.NewEncoder(w).Encode(installation))
}),
),
),
appToken: "test-app-token",
installationID: "67890",
wantInstallation: conngh.AppInstallation{
ID: 67890,
Enabled: false,
},
wantErr: false,
},
{
name: "get enabled app installation successfully",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetAppInstallationsByInstallationId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
installation := &github.Installation{
ID: github.Ptr(int64(67890)),
SuspendedAt: nil,
}
w.WriteHeader(http.StatusOK)
require.NoError(t, json.NewEncoder(w).Encode(installation))
}),
),
),
appToken: "test-app-token",
installationID: "67890",
wantInstallation: conngh.AppInstallation{
ID: 67890,
Enabled: true,
},
wantErr: false,
},
{
name: "invalid installation ID",
mockHandler: mockhub.NewMockedHTTPClient(),
appToken: "test-app-token",
installationID: "not-a-number",
wantInstallation: conngh.AppInstallation{},
wantErr: true,
errContains: "invalid installation ID",
},
{
name: "service unavailable",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetAppInstallationsByInstallationId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusServiceUnavailable,
},
Message: "Service unavailable",
}))
}),
),
),
appToken: "test-app-token",
installationID: "67890",
wantInstallation: conngh.AppInstallation{},
wantErr: true,
},
{
name: "installation not found",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetAppInstallationsByInstallationId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusNotFound,
},
Message: "Not Found",
}))
}),
),
),
appToken: "test-app-token",
installationID: "99999",
wantInstallation: conngh.AppInstallation{},
wantErr: true,
},
{
name: "other error",
mockHandler: mockhub.NewMockedHTTPClient(
mockhub.WithRequestMatchHandler(
mockhub.GetAppInstallationsByInstallationId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
Response: &http.Response{
StatusCode: http.StatusInternalServerError,
},
Message: "Internal server error",
}))
}),
),
),
appToken: "test-app-token",
installationID: "67890",
wantInstallation: conngh.AppInstallation{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a mock client
ghClient := github.NewClient(tt.mockHandler)
client := conngh.NewClient(ghClient)
// Call the method being tested
installation, err := client.GetAppInstallation(context.Background(), tt.installationID)
// Check the error
if tt.wantErr {
assert.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
} else {
assert.NoError(t, err)
}
// Check the result
assert.Equal(t, tt.wantInstallation, installation)
})
}
}
@@ -0,0 +1,192 @@
package github
import (
"context"
"encoding/base64"
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
)
//go:generate mockery --name GithubFactory --structname MockGithubFactory --inpackage --filename factory_mock.go --with-expecter
type GithubFactory interface {
New(ctx context.Context, ghToken common.RawSecureValue) Client
}
type Connection struct {
obj *provisioning.Connection
ghFactory GithubFactory
}
func NewConnection(
obj *provisioning.Connection,
factory GithubFactory,
) Connection {
return Connection{
obj: obj,
ghFactory: factory,
}
}
const (
//TODO(ferruvich): these probably need to be setup in API configuration.
githubInstallationURL = "https://github.com/settings/installations"
jwtExpirationMinutes = 10 // GitHub Apps JWT tokens expire in 10 minutes maximum
)
// Mutate performs in place mutation of the underneath resource.
func (c *Connection) Mutate(_ context.Context) error {
// Do nothing in case spec.Github is nil.
// If this field is required, we should fail at validation time.
if c.obj.Spec.GitHub == nil {
return nil
}
c.obj.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, c.obj.Spec.GitHub.InstallationID)
// Generate JWT token if private key is being provided.
// Same as for the spec.Github, if such a field is required, Validation will take care of that.
if !c.obj.Secure.PrivateKey.Create.IsZero() {
token, err := generateToken(c.obj.Spec.GitHub.AppID, c.obj.Secure.PrivateKey.Create)
if err != nil {
return fmt.Errorf("failed to generate JWT token: %w", err)
}
// Store the generated token
c.obj.Secure.Token = common.InlineSecureValue{Create: token}
}
return nil
}
// Token generates and returns the Connection token.
func generateToken(appID string, privateKey common.RawSecureValue) (common.RawSecureValue, error) {
// Decode base64-encoded private key
privateKeyPEM, err := base64.StdEncoding.DecodeString(string(privateKey))
if err != nil {
return "", fmt.Errorf("failed to decode base64 private key: %w", err)
}
// Parse the private key
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
if err != nil {
return "", fmt.Errorf("failed to parse private key: %w", err)
}
// Create the JWT token
now := time.Now()
claims := jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(jwtExpirationMinutes) * time.Minute)),
Issuer: appID,
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
signedToken, err := token.SignedString(key)
if err != nil {
return "", fmt.Errorf("failed to sign JWT token: %w", err)
}
return common.RawSecureValue(signedToken), nil
}
// Validate ensures the resource _looks_ correct.
func (c *Connection) Validate(ctx context.Context) error {
list := field.ErrorList{}
if c.obj.Spec.Type != provisioning.GithubConnectionType {
list = append(list, field.Invalid(field.NewPath("spec", "type"), c.obj.Spec.Type, "invalid connection type"))
// Doesn't make much sense to continue validating a connection which is not a Github one.
return toError(c.obj.GetName(), list)
}
if c.obj.Spec.GitHub == nil {
list = append(
list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
)
// Doesn't make much sense to continue validating a connection with no information.
return toError(c.obj.GetName(), list)
}
if c.obj.Secure.PrivateKey.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
}
if c.obj.Secure.Token.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "token"), "token must be specified for GitHub connection"))
}
if !c.obj.Secure.ClientSecret.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
}
// Validate GitHub configuration fields
if c.obj.Spec.GitHub.AppID == "" {
list = append(list, field.Required(field.NewPath("spec", "github", "appID"), "appID must be specified for GitHub connection"))
}
if c.obj.Spec.GitHub.InstallationID == "" {
list = append(list, field.Required(field.NewPath("spec", "github", "installationID"), "installationID must be specified for GitHub connection"))
}
// In case we have any error above, we don't go forward with the validation, and return the errors.
if len(list) > 0 {
return toError(c.obj.GetName(), list)
}
// Validating app content via GH API
if err := c.validateAppAndInstallation(ctx); err != nil {
list = append(list, err)
}
return toError(c.obj.GetName(), list)
}
// validateAppAndInstallation validates the appID and installationID against the given github token.
func (c *Connection) validateAppAndInstallation(ctx context.Context) *field.Error {
ghClient := c.ghFactory.New(ctx, c.obj.Secure.Token.Create)
app, err := ghClient.GetApp(ctx)
if err != nil {
if errors.Is(err, ErrServiceUnavailable) {
return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
}
return field.Invalid(field.NewPath("spec", "token"), "[REDACTED]", "invalid token")
}
if fmt.Sprintf("%d", app.ID) != c.obj.Spec.GitHub.AppID {
return field.Invalid(field.NewPath("spec", "appID"), c.obj.Spec.GitHub.AppID, "appID mismatch")
}
_, err = ghClient.GetAppInstallation(ctx, c.obj.Spec.GitHub.InstallationID)
if err != nil {
if errors.Is(err, ErrServiceUnavailable) {
return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
}
return field.Invalid(field.NewPath("spec", "installationID"), c.obj.Spec.GitHub.InstallationID, "invalid installation ID")
}
return nil
}
// toError converts a field.ErrorList to an error, returning nil if the list is empty
func toError(name string, list field.ErrorList) error {
if len(list) == 0 {
return nil
}
return apierrors.NewInvalid(
provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
name,
list,
)
}
var (
_ connection.Connection = (*Connection)(nil)
)
@@ -0,0 +1,434 @@
package github
import (
"context"
"encoding/base64"
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//nolint:gosec // Test RSA private key (generated for testing purposes only)
const testPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoInVbLY9io2Q/wHvUIXlEHg2Qyvd8eRzBAVEJ92DS6fx9H10
06V0VRm78S0MXyo6i+n8ZAbZ0/R+GWpP2Ephxm0Gs2zo+iO2mpB19xQFI4o6ZTOw
b2WyjSaa2Vr4oyDkqti6AvfjW4VUAu932e08GkgwmmQSHXj7FX2CMWjgUwTTcuaX
65SHNKLNYLUP0HTumLzoZeqDTdoMMpKNdgH9Avr4/8vkVJ0mD6rqvxnw3JHsseNO
WdQTxf2aApBNHIIKxWZ2i/ZmjLNey7kltgjEquGiBdJvip3fHhH5XHdkrXcjRtnw
OJDnDmi5lQwv5yUBOSkbvbXRv/L/m0YLoD/fbwIDAQABAoIBAFfl//hM8/cnuesV
+R1Con/ZAgTXQOdPqPXbmEyniVrkMqMmCdBUOBTcST4s5yg36+RtkeaGpb/ajyyF
PAB2AYDucwvMpudGpJWOYTiOOp4R8hU1LvZfXVrRd1lo6NgQi4NLtNUpOtACeVQ+
H4Yv0YemXQ47mnuOoRNMK/u3q5NoIdSahWptXBgUno8KklNpUrH3IYWaUxfBzDN3
2xsVRTn2SfTSyoDmTDdTgptJONmoK1/sV7UsgWksdFc6XyYhsFAZgOGEJrBABRvF
546dyQ0cWxuPyVXpM7CN3tqC5ssvLjElg3LicK1V6gnjpdRnnvX88d1Eh3Uc/9IM
OZInT2ECgYEA6W8sQXTWinyEwl8SDKKMbB2ApIghAcFgdRxprZE4WFxjsYNCNL70
dnSB7MRuzmxf5W77cV0N7JhH66N8HvY6Xq9olrpQ5dNttR4w8Pyv3wavDe8x7seL
5L2Xtbu7ihDr8Dk27MjiBSin3IxhBP5CJS910+pR6LrAWtEuU+FzFfECgYEAsA6y
qxHhCMXlTnauXhsnmPd1g61q7chW8kLQFYtHMLlQlgjHTW7irDZ9cPbPYDNjwRLO
7KLorcpv2NKe7rqq2ZyCm6hf1b9WnlQjo3dLpNWMu6fhy/smK8MgbRqcWpX+oTKF
79mK6hbY7o6eBzsQHBl7Z+LBNuwYmp9qOodPa18CgYEArv6ipKdcNhFGzRfMRiCN
OHederp6VACNuP2F05IsNUF9kxOdTEFirnKE++P+VU01TqA2azOhPp6iO+ohIGzi
MR06QNSH1OL9OWvasK4dggpWrRGF00VQgDgJRTnpS4WH+lxJ6pRlrAxgWpv6F24s
VAgSQr1Ejj2B+hMasdMvHWECgYBJ4uE4yhgXBnZlp4kmFV9Y4wF+cZkekaVrpn6N
jBYkbKFVVfnOlWqru3KJpgsB5I9IyAvvY68iwIKQDFSG+/AXw4dMrC0MF3DSoZ0T
TU2Br92QI7SvVod+djV1lGVp3ukt3XY4YqPZ+hywgUnw3uiz4j3YK2HLGup4ec6r
IX5DIQKBgHRLzvT3zqtlR1Oh0vv098clLwt+pGzXOxzJpxioOa5UqK13xIpFXbcg
iWUVh5YXCcuqaICUv4RLIEac5xQitk9Is/9IhP0NJ/81rHniosvdSpCeFXzxTImS
B8Uc0WUgheB4+yVKGnYpYaSOgFFI5+1BYUva/wDHLy2pWHz39Usb
-----END RSA PRIVATE KEY-----`
func TestConnection_Mutate(t *testing.T) {
t.Run("should add URL to Github connection", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
},
}
mockFactory := NewMockGithubFactory(t)
conn := NewConnection(c, mockFactory)
require.NoError(t, conn.Mutate(context.Background()))
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
})
t.Run("should generate JWT token when private key is provided", func(t *testing.T) {
privateKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testPrivateKeyPEM))
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue(privateKeyBase64),
},
},
}
mockFactory := NewMockGithubFactory(t)
conn := NewConnection(c, mockFactory)
require.NoError(t, conn.Mutate(context.Background()))
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
assert.False(t, c.Secure.Token.Create.IsZero(), "JWT token should be generated")
})
t.Run("should do nothing when GitHub config is nil", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "clientID",
},
},
}
mockFactory := NewMockGithubFactory(t)
conn := NewConnection(c, mockFactory)
require.NoError(t, conn.Mutate(context.Background()))
})
t.Run("should fail when private key is not base64", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("invalid-key"),
},
},
}
mockFactory := NewMockGithubFactory(t)
conn := NewConnection(c, mockFactory)
err := conn.Mutate(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to generate JWT token")
assert.Contains(t, err.Error(), "failed to decode base64 private key")
})
t.Run("should fail when private key is invalid", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue(base64.StdEncoding.EncodeToString([]byte("invalid-key"))),
},
},
}
mockFactory := NewMockGithubFactory(t)
conn := NewConnection(c, mockFactory)
err := conn.Mutate(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to generate JWT token")
assert.Contains(t, err.Error(), "failed to parse private key")
})
}
func TestConnection_Validate(t *testing.T) {
tests := []struct {
name string
connection *provisioning.Connection
setupMock func(*MockGithubFactory)
wantErr bool
errMsgContains []string
}{
{
name: "invalid type returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: "invalid",
},
},
wantErr: true,
errMsgContains: []string{"spec.type"},
},
{
name: "github type without github config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
},
},
wantErr: true,
errMsgContains: []string{"spec.github"},
},
{
name: "github type without private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
},
wantErr: true,
errMsgContains: []string{"secure.privateKey"},
},
{
name: "github type without token returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
},
},
wantErr: true,
errMsgContains: []string{"secure.token"},
},
{
name: "github type with client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
ClientSecret: common.InlineSecureValue{
Create: common.NewSecretValue("test-client-secret"),
},
},
},
wantErr: true,
errMsgContains: []string{"secure.clientSecret"},
},
{
name: "github type without appID returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
Token: common.InlineSecureValue{
Create: common.NewSecretValue("test-token"),
},
},
},
wantErr: true,
errMsgContains: []string{"spec.github.appID"},
},
{
name: "github type without installationID returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
Token: common.InlineSecureValue{
Name: "test-token",
},
},
},
wantErr: true,
errMsgContains: []string{"spec.github.installationID"},
},
{
name: "github type with valid config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
Token: common.InlineSecureValue{
Create: common.NewSecretValue("test-token"),
},
},
},
wantErr: false,
setupMock: func(mockFactory *MockGithubFactory) {
mockClient := NewMockClient(t)
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{ID: 456}, nil)
},
},
{
name: "problem getting app returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
Token: common.InlineSecureValue{
Create: common.NewSecretValue("test-token"),
},
},
},
wantErr: true,
errMsgContains: []string{"spec.token", "[REDACTED]"},
setupMock: func(mockFactory *MockGithubFactory) {
mockClient := NewMockClient(t)
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
mockClient.EXPECT().GetApp(mock.Anything).Return(App{}, assert.AnError)
},
},
{
name: "mismatched app ID returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
Token: common.InlineSecureValue{
Create: common.NewSecretValue("test-token"),
},
},
},
wantErr: true,
errMsgContains: []string{"spec.appID"},
setupMock: func(mockFactory *MockGithubFactory) {
mockClient := NewMockClient(t)
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 444, Slug: "test-app"}, nil)
},
},
{
name: "problem when getting installation returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
Token: common.InlineSecureValue{
Create: common.NewSecretValue("test-token"),
},
},
},
wantErr: true,
errMsgContains: []string{"spec.installationID", "456"},
setupMock: func(mockFactory *MockGithubFactory) {
mockClient := NewMockClient(t)
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{}, assert.AnError)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockFactory := NewMockGithubFactory(t)
if tt.setupMock != nil {
tt.setupMock(mockFactory)
}
conn := NewConnection(tt.connection, mockFactory)
err := conn.Validate(context.Background())
if tt.wantErr {
assert.Error(t, err)
for _, msg := range tt.errMsgContains {
assert.Contains(t, err.Error(), msg)
}
} else {
assert.NoError(t, err)
}
})
}
}
@@ -0,0 +1,36 @@
package github
import (
"context"
"fmt"
"github.com/grafana/grafana-app-sdk/logging"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
)
type extra struct {
factory GithubFactory
}
func (e *extra) Type() provisioning.ConnectionType {
return provisioning.GithubConnectionType
}
func (e *extra) Build(ctx context.Context, connection *provisioning.Connection) (connection.Connection, error) {
logger := logging.FromContext(ctx)
if connection == nil || connection.Spec.GitHub == nil {
logger.Error("connection is nil or github info is nil")
return nil, fmt.Errorf("invalid github connection")
}
c := NewConnection(connection, e.factory)
return &c, nil
}
func Extra(factory GithubFactory) connection.Extra {
return &extra{
factory: factory,
}
}
@@ -0,0 +1,126 @@
package github_test
import (
"context"
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestExtra_Type(t *testing.T) {
t.Run("should return GithubConnectionType", func(t *testing.T) {
mockFactory := github.NewMockGithubFactory(t)
e := github.Extra(mockFactory)
result := e.Type()
assert.Equal(t, provisioning.GithubConnectionType, result)
})
}
func TestExtra_Build(t *testing.T) {
t.Run("should successfully build connection", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Create: common.NewSecretValue("test-private-key"),
},
},
}
mockFactory := github.NewMockGithubFactory(t)
e := github.Extra(mockFactory)
result, err := e.Build(ctx, conn)
require.NoError(t, err)
require.NotNil(t, result)
})
t.Run("should handle different connection configurations", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "another-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "789",
InstallationID: "101112",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "existing-private-key",
},
Token: common.InlineSecureValue{
Name: "existing-token",
},
},
}
mockFactory := github.NewMockGithubFactory(t)
e := github.Extra(mockFactory)
result, err := e.Build(ctx, conn)
require.NoError(t, err)
require.NotNil(t, result)
})
t.Run("should build connection with background context", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
}
mockFactory := github.NewMockGithubFactory(t)
e := github.Extra(mockFactory)
result, err := e.Build(ctx, conn)
require.NoError(t, err)
require.NotNil(t, result)
})
t.Run("should always pass empty token to factory.New", func(t *testing.T) {
ctx := context.Background()
conn := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
Token: common.InlineSecureValue{
Create: common.NewSecretValue("some-token"),
},
},
}
mockFactory := github.NewMockGithubFactory(t)
e := github.Extra(mockFactory)
result, err := e.Build(ctx, conn)
require.NoError(t, err)
require.NotNil(t, result)
})
}
@@ -0,0 +1,39 @@
package github
import (
"context"
"net/http"
"github.com/google/go-github/v70/github"
"golang.org/x/oauth2"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
// Factory creates new GitHub clients.
// It exists only for the ability to test the code easily.
type Factory struct {
// Client allows overriding the client to use in the GH client returned. It exists primarily for testing.
// FIXME: we should replace in this way. We should add some options pattern for the factory.
Client *http.Client
}
func ProvideFactory() GithubFactory {
return &Factory{}
}
func (r *Factory) New(ctx context.Context, ghToken common.RawSecureValue) Client {
if r.Client != nil {
return NewClient(github.NewClient(r.Client))
}
if !ghToken.IsZero() {
tokenSrc := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: string(ghToken)},
)
tokenClient := oauth2.NewClient(ctx, tokenSrc)
return NewClient(github.NewClient(tokenClient))
}
return NewClient(github.NewClient(&http.Client{}))
}
@@ -0,0 +1,86 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package github
import (
context "context"
v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
// MockGithubFactory is an autogenerated mock type for the GithubFactory type
type MockGithubFactory struct {
mock.Mock
}
type MockGithubFactory_Expecter struct {
mock *mock.Mock
}
func (_m *MockGithubFactory) EXPECT() *MockGithubFactory_Expecter {
return &MockGithubFactory_Expecter{mock: &_m.Mock}
}
// New provides a mock function with given fields: ctx, ghToken
func (_m *MockGithubFactory) New(ctx context.Context, ghToken v0alpha1.RawSecureValue) Client {
ret := _m.Called(ctx, ghToken)
if len(ret) == 0 {
panic("no return value specified for New")
}
var r0 Client
if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.RawSecureValue) Client); ok {
r0 = rf(ctx, ghToken)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Client)
}
}
return r0
}
// MockGithubFactory_New_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'New'
type MockGithubFactory_New_Call struct {
*mock.Call
}
// New is a helper method to define mock.On call
// - ctx context.Context
// - ghToken v0alpha1.RawSecureValue
func (_e *MockGithubFactory_Expecter) New(ctx interface{}, ghToken interface{}) *MockGithubFactory_New_Call {
return &MockGithubFactory_New_Call{Call: _e.mock.On("New", ctx, ghToken)}
}
func (_c *MockGithubFactory_New_Call) Run(run func(ctx context.Context, ghToken v0alpha1.RawSecureValue)) *MockGithubFactory_New_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(v0alpha1.RawSecureValue))
})
return _c
}
func (_c *MockGithubFactory_New_Call) Return(_a0 Client) *MockGithubFactory_New_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubFactory_New_Call) RunAndReturn(run func(context.Context, v0alpha1.RawSecureValue) Client) *MockGithubFactory_New_Call {
_c.Call.Return(run)
return _c
}
// NewMockGithubFactory creates a new instance of MockGithubFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockGithubFactory(t interface {
mock.TestingT
Cleanup(func())
}) *MockGithubFactory {
mock := &MockGithubFactory{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -1,28 +0,0 @@
package connection
import (
"fmt"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
const (
githubInstallationURL = "https://github.com/settings/installations"
)
func MutateConnection(connection *provisioning.Connection) error {
switch connection.Spec.Type {
case provisioning.GithubConnectionType:
// Do nothing in case spec.Github is nil.
// If this field is required, we should fail at validation time.
if connection.Spec.GitHub == nil {
return nil
}
connection.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, connection.Spec.GitHub.InstallationID)
return nil
default:
// TODO: we need to setup the URL for bitbucket and gitlab.
return nil
}
}
@@ -1,35 +0,0 @@
package connection_test
import (
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestMutateConnection(t *testing.T) {
t.Run("should add URL to Github connection", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
},
}
require.NoError(t, connection.MutateConnection(c))
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
})
}
@@ -1,104 +0,0 @@
package connection
import (
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func ValidateConnection(connection *provisioning.Connection) error {
list := field.ErrorList{}
if connection.Spec.Type == "" {
list = append(list, field.Required(field.NewPath("spec", "type"), "type must be specified"))
}
switch connection.Spec.Type {
case provisioning.GithubConnectionType:
list = append(list, validateGithubConnection(connection)...)
case provisioning.BitbucketConnectionType:
list = append(list, validateBitbucketConnection(connection)...)
case provisioning.GitlabConnectionType:
list = append(list, validateGitlabConnection(connection)...)
default:
list = append(
list, field.NotSupported(
field.NewPath("spec", "type"),
connection.Spec.Type,
[]provisioning.ConnectionType{
provisioning.GithubConnectionType,
provisioning.BitbucketConnectionType,
provisioning.GitlabConnectionType,
}),
)
}
return toError(connection.GetName(), list)
}
func validateGithubConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.GitHub == nil {
list = append(
list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
)
}
if connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
}
if !connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
}
return list
}
func validateBitbucketConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.Bitbucket == nil {
list = append(
list, field.Required(field.NewPath("spec", "bitbucket"), "bitbucket info must be specified in Bitbucket connection"),
)
}
if connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Bitbucket connection"))
}
if !connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Bitbucket connection"))
}
return list
}
func validateGitlabConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.Gitlab == nil {
list = append(
list, field.Required(field.NewPath("spec", "gitlab"), "gitlab info must be specified in Gitlab connection"),
)
}
if connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Gitlab connection"))
}
if !connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Gitlab connection"))
}
return list
}
// toError converts a field.ErrorList to an error, returning nil if the list is empty
func toError(name string, list field.ErrorList) error {
if len(list) == 0 {
return nil
}
return apierrors.NewInvalid(
provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
name,
list,
)
}
@@ -1,253 +0,0 @@
package connection_test
import (
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestValidateConnection(t *testing.T) {
tests := []struct {
name string
connection *provisioning.Connection
wantErr bool
errMsg string
}{
{
name: "empty type returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{},
},
wantErr: true,
errMsg: "spec.type",
},
{
name: "invalid type returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: "invalid",
},
},
wantErr: true,
errMsg: "spec.type",
},
{
name: "github type without github config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
},
},
wantErr: true,
errMsg: "spec.github",
},
{
name: "github type without private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "github type with client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "github type with github config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
},
},
wantErr: false,
},
{
name: "bitbucket type without bitbucket config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
},
},
wantErr: true,
errMsg: "spec.bitbucket",
},
{
name: "bitbucket type without client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "bitbucket type with private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "bitbucket type with bitbucket config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
Secure: provisioning.ConnectionSecure{
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: false,
},
{
name: "gitlab type without gitlab config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
},
},
wantErr: true,
errMsg: "spec.gitlab",
},
{
name: "gitlab type without client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "gitlab type with private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "gitlab type with gitlab config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
Secure: provisioning.ConnectionSecure{
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := connection.ValidateConnection(tt.connection)
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}
@@ -13,7 +13,7 @@ import (
type ConnectionSecureApplyConfiguration struct {
PrivateKey *commonv0alpha1.InlineSecureValue `json:"privateKey,omitempty"`
ClientSecret *commonv0alpha1.InlineSecureValue `json:"clientSecret,omitempty"`
Token *commonv0alpha1.InlineSecureValue `json:"webhook,omitempty"`
Token *commonv0alpha1.InlineSecureValue `json:"token,omitempty"`
}
// ConnectionSecureApplyConfiguration constructs a declarative configuration of the ConnectionSecure type for use with
+7 -1
View File
@@ -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
+8 -3
View File
@@ -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/
@@ -25,7 +25,7 @@ Plugin signature verification, also known as _signing_, is a security measure to
Learn more at [plugin policies](https://grafana.com/legal/plugins/).
## How does verifiction work?
## How does verification work?
At startup, Grafana verifies the signatures of every plugin in the plugin directory.
@@ -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
+46 -3
View File
@@ -99,12 +99,27 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#query-and-resource-caching
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#query-and-resource-caching
mssql-troubleshoot:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
postgres:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/postgres/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/postgres/
mysql:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mysql/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mysql/
---
# Microsoft SQL Server (MSSQL) data source
Grafana ships with built-in support for Microsoft SQL Server (MSSQL).
You can query and visualize data from any Microsoft SQL Server 2005 or newer, including the Microsoft Azure SQL Database.
You can query and visualize data from any Microsoft SQL Server 2005 or newer, including Microsoft Azure SQL Database.
Use this data source to create dashboards, explore SQL data, and monitor MSSQL-based workloads in real time.
@@ -113,10 +128,33 @@ The following documentation helps you get started working with the Microsoft SQL
- [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source)
- [Microsoft SQL Server query editor](ref:mssql-query-editor)
- [Microsoft SQL Server template variables](ref:mssql-template-variables)
- [Troubleshoot Microsoft SQL Server data source issues](ref:mssql-troubleshoot)
## Get the most out of the data source
## Supported versions
After installing and configuring the Microsoft SQL Server data source, you can:
This data source supports the following Microsoft SQL Server versions:
- Microsoft SQL Server 2005 and newer
- Microsoft Azure SQL Database
- Azure SQL Managed Instance
Grafana recommends using the latest available service pack for your SQL Server version for optimal compatibility.
## Key capabilities
The Microsoft SQL Server data source supports:
- **Time series queries:** Visualize metrics over time using the built-in time grouping macros.
- **Table queries:** Display query results in table format for any valid SQL query.
- **Template variables:** Create dynamic dashboards with variable-driven queries.
- **Annotations:** Overlay events from SQL Server on your dashboard graphs.
- **Alerting:** Create alerts based on SQL Server query results.
- **Stored procedures:** Execute stored procedures and visualize results.
- **Macros:** Simplify queries with built-in macros for time filtering and grouping.
## Additional resources
After configuring the Microsoft SQL Server data source, you can:
- Create a wide variety of [visualizations](ref:visualizations)
- Configure and use [templates and variables](ref:variables)
@@ -124,3 +162,8 @@ After installing and configuring the Microsoft SQL Server data source, you can:
- Add [annotations](ref:annotate-visualizations)
- Set up [alerting](ref:alerting)
- Optimize performance with [query caching](ref:query-caching)
## Related data sources
- [PostgreSQL](ref:postgres) - For PostgreSQL databases.
- [MySQL](ref:mysql) - For MySQL and MariaDB databases.
@@ -89,6 +89,26 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
mssql-query-editor:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
mssql-template-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/template-variables/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/template-variables/
alerting:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/
mssql-troubleshoot:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
---
# Configure the Microsoft SQL Server data source
@@ -97,13 +117,28 @@ This document provides instructions for configuring the Microsoft SQL Server dat
## Before you begin
- Grafana comes with a built-in MSSQL data source plugin, eliminating the need to install a plugin.
Before configuring the Microsoft SQL Server data source, ensure you have the following:
- You must have the `Organization administrator` role to configure the MSSQL data source. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
- **Grafana permissions:** You must have the `Organization administrator` role to configure data sources. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
- Familiarize yourself with your MSSQL security configuration and gather any necessary security certificates and client keys.
- **A running SQL Server instance:** Microsoft SQL Server 2005 or newer, Azure SQL Database, or Azure SQL Managed Instance.
- Verify that data from MSSQL is being written to your Grafana instance.
- **Network access:** Grafana must be able to reach your SQL Server. The default port is `1433`.
- **Authentication credentials:** Depending on your authentication method, you need one of:
- SQL Server login credentials (username and password).
- Windows/Kerberos credentials and configuration (not supported in Grafana Cloud).
- Azure Entra ID app registration or managed identity.
- **Security certificates:** If using encrypted connections, gather any necessary TLS/SSL certificates.
{{< admonition type="note" >}}
Grafana ships with a built-in Microsoft SQL Server data source plugin. No additional installation is required.
{{< /admonition >}}
{{< admonition type="tip" >}}
**Grafana Cloud users:** If your SQL Server is in a private network, you can configure [Private data source connect](ref:private-data-source-connect) to establish connectivity.
{{< /admonition >}}
## Add the MSSQL data source
@@ -382,3 +417,48 @@ datasources:
secureJsonData:
password: 'Password!'
```
### Configure with Terraform
You can configure the Microsoft SQL Server data source using [Terraform](https://www.terraform.io/) with the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs).
For more information about provisioning resources with Terraform, refer to the [Grafana as code using Terraform](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/terraform/) documentation.
#### Terraform example
The following example creates a basic Microsoft SQL Server data source:
```hcl
resource "grafana_data_source" "mssql" {
name = "MSSQL"
type = "mssql"
url = "localhost:1433"
user = "grafana"
json_data_encoded = jsonencode({
database = "grafana"
maxOpenConns = 100
maxIdleConns = 100
maxIdleConnsAuto = true
connMaxLifetime = 14400
connectionTimeout = 0
encrypt = "false"
})
secure_json_data_encoded = jsonencode({
password = "Password!"
})
}
```
For all available configuration options, refer to the [Grafana provider data source resource documentation](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source).
## Next steps
After configuring your Microsoft SQL Server data source, you can:
- [Write queries](ref:mssql-query-editor) using the query editor to explore and visualize your data
- [Create template variables](ref:mssql-template-variables) to build dynamic, reusable dashboards
- [Add annotations](ref:annotate-visualizations) to overlay SQL Server events on your graphs
- [Set up alerting](ref:alerting) to create alert rules based on your SQL Server data
- [Troubleshoot issues](ref:mssql-troubleshoot) if you encounter problems with your data source
@@ -0,0 +1,333 @@
---
description: Troubleshoot common problems with the Microsoft SQL Server data source in Grafana
keywords:
- grafana
- MSSQL
- Microsoft
- SQL
- troubleshooting
- errors
labels:
products:
- cloud
- enterprise
- oss
menuTitle: Troubleshooting
title: Troubleshoot Microsoft SQL Server data source issues
weight: 400
refs:
configure-mssql-data-source:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/configure/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/configure/
mssql-query-editor:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
private-data-source-connect:
- pattern: /docs/grafana/
destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
---
# Troubleshoot Microsoft SQL Server data source issues
This document provides solutions to common issues you may encounter when configuring or using the Microsoft SQL Server (MSSQL) data source in Grafana.
## Connection errors
These errors occur when Grafana cannot establish or maintain a connection to the Microsoft SQL Server.
### Unable to connect to the server
**Error message:** "Unable to open tcp connection" or "dial tcp: connection refused"
**Cause:** Grafana cannot establish a network connection to the SQL Server.
**Solution:**
1. Verify that the SQL Server is running and accessible.
1. Check that the host and port are correct in the data source configuration. The default SQL Server port is `1433`.
1. Ensure there are no firewall rules blocking the connection between Grafana and SQL Server.
1. Verify that SQL Server is configured to allow remote connections.
1. For Grafana Cloud, ensure you have configured [Private data source connect](ref:private-data-source-connect) if your SQL Server instance is not publicly accessible.
### Connection timeout
**Error message:** "Connection timed out" or "I/O timeout"
**Cause:** The connection to SQL Server timed out before receiving a response.
**Solution:**
1. Check the network latency between Grafana and SQL Server.
1. Verify that SQL Server is not overloaded or experiencing performance issues.
1. Increase the **Connection timeout** setting in the data source configuration under **Additional settings**.
1. Check if any network devices (load balancers, proxies) are timing out the connection.
### Encryption-related connection failures
**Error message:** "TLS handshake failed" or "certificate verify failed"
**Cause:** There is a mismatch between the encryption settings in Grafana and what the SQL Server supports or requires.
**Solution:**
1. For older versions of SQL Server (2008, 2008R2), set the **Encrypt** option to **Disable** or **False** in the data source configuration.
1. Verify that the SQL Server has a valid SSL certificate if encryption is enabled.
1. Check that the certificate is trusted by the Grafana server.
1. Ensure you're using the latest available service pack for your SQL Server version for optimal compatibility.
### Named instance connection issues
**Error message:** "Cannot connect to named instance" or connection fails when using instance name
**Cause:** Grafana cannot resolve the SQL Server named instance.
**Solution:**
1. Use the format `hostname\instancename` or `hostname\instancename,port` in the **Host** field.
1. Verify that the SQL Server Browser service is running on the SQL Server machine.
1. If the Browser service is unavailable, specify the port number directly: `hostname,port`.
1. Check that UDP port 1434 is open if using the SQL Server Browser service.
## Authentication errors
These errors occur when there are issues with authentication credentials or permissions.
### Login failed for user
**Error message:** "Login failed for user 'username'" or "Authentication failed"
**Cause:** The authentication credentials are invalid or the user doesn't have permission to access the database.
**Solution:**
1. Verify that the username and password are correct.
1. Check that the user exists in SQL Server and is enabled.
1. Ensure the user has access to the specified database.
1. For Windows Authentication, verify that the credentials are in the correct format (`DOMAIN\User`).
1. Check that the SQL Server authentication mode allows the type of login you're using (SQL Server Authentication, Windows Authentication, or Mixed Mode).
### Access denied to database
**Error message:** "Cannot open database 'dbname' requested by the login"
**Cause:** The authenticated user doesn't have permission to access the specified database.
**Solution:**
1. Verify that the database name is correct in the data source configuration.
1. Ensure the user is mapped to the database with appropriate permissions.
1. Grant at least `SELECT` permission on the required tables:
```sql
USE [your_database]
GRANT SELECT ON dbo.YourTable TO [your_user]
```
1. Check that the user doesn't have any conflicting permissions from the public role.
### Windows Authentication (Kerberos) issues
**Error message:** "Kerberos authentication failed" or "Cannot initialize Kerberos"
**Cause:** Kerberos configuration is incorrect or incomplete.
**Solution:**
1. Verify that the Kerberos configuration file (`krb5.conf`) path is correct in the data source settings.
1. For keytab authentication, ensure the keytab file exists and is readable by Grafana.
1. Check that the realm and KDC settings are correct in the Kerberos configuration.
1. Verify DNS is correctly resolving the KDC servers.
1. Ensure the service principal name (SPN) is registered for the SQL Server instance.
{{< admonition type="note" >}}
Kerberos authentication is not supported in Grafana Cloud.
{{< /admonition >}}
### Azure Entra ID authentication errors
**Error message:** "AADSTS error codes" or "Azure AD authentication failed"
**Cause:** Azure Entra ID (formerly Azure AD) authentication is misconfigured.
**Solution:**
1. For **App Registration** authentication:
- Verify the tenant ID, client ID, and client secret are correct.
- Ensure the app registration has been added as a user in the Azure SQL database.
- Check that the client secret hasn't expired.
1. For **Managed Identity** authentication:
- Verify `managed_identity_enabled = true` is set in the Grafana server configuration.
- Ensure the managed identity has been added to the Azure SQL database.
- Confirm the Azure resource hosting Grafana has managed identity enabled.
1. For **Current User** authentication:
- Ensure `user_identity_enabled = true` is set in the Grafana server configuration.
- Verify the app registration is configured to issue both Access Tokens and ID Tokens.
- Check that the required API permissions are configured (`user_impersonation` for Azure SQL).
For detailed Azure authentication configuration, refer to [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source).
## Query errors
These errors occur when there are issues with query syntax or configuration.
### Time column not found or invalid
**Error message:** "Could not find time column" or time series visualization shows no data
**Cause:** The query doesn't return a properly formatted `time` column for time series visualization.
**Solution:**
1. Ensure your query includes a column named `time` when using the **Time series** format.
1. Use the `$__time()` macro to rename your date column: `$__time(your_date_column)`.
1. Verify the time column is of a valid SQL date/time type (`datetime`, `datetime2`, `date`) or contains Unix epoch values.
1. Ensure the result set is sorted by the time column using `ORDER BY`.
### Macro expansion errors
**Error message:** "Error parsing query" or macros appear unexpanded in the query
**Cause:** Grafana macros are being used incorrectly.
**Solution:**
1. Verify macro syntax: use `$__timeFilter(column)` not `$_timeFilter(column)`.
1. Macros don't work inside stored procedures—use explicit date parameters instead.
1. Check that the column name passed to macros exists in your table.
1. View the expanded query by clicking **Generated SQL** after running the query to debug macro expansion.
### Timezone and time shift issues
**Cause:** Time series data appears shifted or doesn't align with expected times.
**Solution:**
1. Store timestamps in UTC in your database to avoid timezone issues.
1. Time macros (`$__time`, `$__timeFilter`, etc.) always expand to UTC values.
1. If your timestamps are stored in local time, convert them to UTC in your query:
```sql
SELECT
your_datetime_column AT TIME ZONE 'Your Local Timezone' AT TIME ZONE 'UTC' AS time,
value
FROM your_table
```
1. Don't pass timezone parameters to time macros—they're not supported.
### Query returns too many rows
**Error message:** "Result set too large" or browser becomes unresponsive
**Cause:** The query returns more data than can be efficiently processed.
**Solution:**
1. Add time filters using `$__timeFilter(column)` to limit data to the dashboard time range.
1. Use aggregations (`AVG`, `SUM`, `COUNT`) with `GROUP BY` instead of returning raw rows.
1. Add a `TOP` clause to limit results: `SELECT TOP 1000 ...`.
1. Use the `$__timeGroup()` macro to aggregate data into time intervals.
### Stored procedure returns no data
**Cause:** Stored procedure output isn't being captured correctly.
**Solution:**
1. Ensure the stored procedure uses `SELECT` statements, not just variable assignments.
1. Remove `SET NOCOUNT ON` if present, or ensure it's followed by a `SELECT` statement.
1. Verify the stored procedure parameters are being passed correctly.
1. Test the stored procedure directly in SQL Server Management Studio with the same parameters.
For more information on using stored procedures, refer to the [query editor documentation](ref:mssql-query-editor).
## Performance issues
These issues relate to slow queries or high resource usage.
### Slow query execution
**Cause:** Queries take a long time to execute.
**Solution:**
1. Reduce the dashboard time range to limit data volume.
1. Add indexes to columns used in `WHERE` clauses and time filters.
1. Use aggregations instead of returning individual rows.
1. Increase the **Min time interval** setting to reduce the number of data points.
1. Review the query execution plan in SQL Server Management Studio to identify bottlenecks.
### Connection pool exhaustion
**Error message:** "Too many connections" or "Connection pool exhausted"
**Cause:** Too many concurrent connections to the database.
**Solution:**
1. Increase the **Max open** connection limit in the data source configuration.
1. Enable **Auto max idle** to automatically manage idle connections.
1. Reduce the number of panels querying the same data source simultaneously.
1. Check for long-running queries that might be holding connections.
## Other common issues
The following issues don't produce specific error messages but are commonly encountered.
### System databases appear in queries
**Cause:** Queries accidentally access system databases.
**Solution:**
1. The query editor automatically excludes `tempdb`, `model`, `msdb`, and `master` from the database dropdown.
1. Always specify the database in your data source configuration to restrict access.
1. Ensure the database user only has permissions on the intended database.
### Template variable queries fail
**Cause:** Variable queries return unexpected results or errors.
**Solution:**
1. Verify the variable query syntax is valid SQL that returns a single column.
1. Check that the data source connection is working.
1. Ensure the user has permission to access the tables referenced in the variable query.
1. Test the query in the query editor before using it as a variable query.
### Data appears incorrect or misaligned
**Cause:** Data formatting or type conversion issues.
**Solution:**
1. Use explicit column aliases to ensure consistent naming: `SELECT value AS metric`.
1. Verify numeric columns are actually numeric types, not strings.
1. Check for `NULL` values that might affect aggregations.
1. Use the `FILL` option in `$__timeGroup()` macro to handle missing data points.
## Get additional help
If you continue to experience issues after following this troubleshooting guide:
1. Check the [Grafana community forums](https://community.grafana.com/) for similar issues.
1. Review the [Grafana GitHub issues](https://github.com/grafana/grafana/issues) for known bugs.
1. Enable debug logging in Grafana to capture detailed error information.
1. Check SQL Server logs for additional error details.
1. Contact Grafana Support if you're an Enterprise or Cloud customer.
When reporting issues, include:
- Grafana version
- SQL Server version
- Error messages (redact sensitive information)
- Steps to reproduce
- Relevant query examples (redact sensitive data)
@@ -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>
@@ -66,7 +66,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `sharingDashboardImage` | Enables image sharing functionality for dashboards | Yes |
| `tabularNumbers` | Use fixed-width numbers globally in the UI | |
| `azureResourcePickerUpdates` | Enables the updated Azure Monitor resource picker | Yes |
| `tempoSearchBackendMigration` | Run search queries through the tempo backend | |
| `opentsdbBackendMigration` | Run queries through the data source backend | |
## Public preview feature toggles
-1
View File
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
-1
View File
@@ -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
-1
View File
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
-1
View File
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
-1
View File
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
-1
View File
@@ -3,7 +3,6 @@
[feature_toggles]
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
unifiedStorageSearchSprinkles = true
[unified_storage]
enable_search = true
+1 -16
View File
@@ -1021,11 +1021,6 @@
"count": 2
}
},
"public/app/core/actions/index.ts": {
"no-barrel-files/no-barrel-files": {
"count": 4
}
},
"public/app/core/components/AccessControl/PermissionList.tsx": {
"no-restricted-syntax": {
"count": 1
@@ -1161,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
@@ -4025,11 +4015,6 @@
"count": 1
}
},
"public/app/plugins/datasource/parca/webpack.config.ts": {
"no-barrel-files/no-barrel-files": {
"count": 1
}
},
"public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx": {
"no-restricted-syntax": {
"count": 1
@@ -4098,7 +4083,7 @@
"count": 1
},
"@typescript-eslint/no-explicit-any": {
"count": 2
"count": 1
}
},
"public/app/plugins/datasource/tempo/resultTransformer.ts": {
+2
View File
@@ -585,6 +585,8 @@ module.exports = [
// FIXME: Remove once all enterprise issues are fixed -
// we don't have a suppressions file/approach for enterprise code yet
...enterpriseIgnores,
// Ignore decoupled plugin webpack configs
'public/app/**/webpack.config.ts',
],
rules: {
'no-barrel-files/no-barrel-files': 'error',
+49 -36
View File
@@ -5,8 +5,8 @@ go 1.25.5
require (
buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1 // @grafana/observability-traces-and-profiling
buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1 // @grafana/observability-traces-and-profiling
cloud.google.com/go/kms v1.22.0 // @grafana/grafana-backend-group
cloud.google.com/go/storage v1.55.0 // @grafana/grafana-backend-group
cloud.google.com/go/kms v1.23.0 // @grafana/grafana-backend-group
cloud.google.com/go/storage v1.57.0 // @grafana/grafana-backend-group
connectrpc.com/connect v1.19.1 // @grafana/observability-traces-and-profiling
cuelang.org/go v0.11.1 // @grafana/grafana-as-code
dario.cat/mergo v1.0.2 // @grafana/grafana-app-platform-squad
@@ -32,14 +32,14 @@ require (
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/credentials v1.18.21 // 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/aws-sdk-go-v2/service/sts v1.39.1 // indirect; @grafana/grafana-operator-experience-squad
github.com/aws/smithy-go v1.23.2 // @grafana/aws-datasources
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
@@ -56,7 +56,7 @@ require (
github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group
github.com/docker/go-connections v0.6.0 // @grafana/grafana-app-platform-squad
github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // @grafana/grafana-datasources-core-services
github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // @grafana/grafana-datasources-core-services
github.com/dolthub/vitess v3.0.0-rc.3+incompatible // @grafana/grafana-datasources-core-services
github.com/dustin/go-humanize v1.0.1 // @grafana/observability-traces-and-profiling
github.com/emicklei/go-restful/v3 v3.13.0 // @grafana/grafana-app-platform-squad
github.com/fatih/color v1.18.0 // @grafana/grafana-backend-group
@@ -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
@@ -144,14 +145,14 @@ require (
github.com/mocktools/go-smtp-mock/v2 v2.5.1 // @grafana/grafana-backend-group
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // @grafana/alerting-backend
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad
github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group
github.com/olekukonko/tablewriter v1.1.0 // @grafana/grafana-backend-group
github.com/open-feature/go-sdk v1.16.0 // @grafana/grafana-backend-group
github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.6 // @grafana/grafana-backend-group
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.6 // @grafana/grafana-backend-group
github.com/openfga/api/proto v0.0.0-20250909172242-b4b2a12f5c67 // @grafana/identity-access-team
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20251027165255-0f8f255e5f6c // @grafana/identity-access-team
github.com/openfga/openfga v1.11.1 // @grafana/identity-access-team
github.com/opentracing-contrib/go-grpc v0.1.1 // @grafana/grafana-search-and-storage
github.com/opentracing-contrib/go-grpc v0.1.2 // @grafana/grafana-search-and-storage
github.com/opentracing/opentracing-go v1.2.0 // @grafana/grafana-search-and-storage
github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend
@@ -214,7 +215,7 @@ require (
golang.org/x/time v0.14.0 // @grafana/grafana-backend-group
golang.org/x/tools v0.40.0 // indirect; @grafana/grafana-as-code
gonum.org/v1/gonum v0.16.0 // @grafana/oss-big-tent
google.golang.org/api v0.242.0 // @grafana/grafana-backend-group
google.golang.org/api v0.251.0 // @grafana/grafana-backend-group
google.golang.org/grpc v1.77.0 // @grafana/plugins-platform-backend
google.golang.org/protobuf v1.36.11 // @grafana/plugins-platform-backend
gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend
@@ -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,12 +296,14 @@ 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 (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.121.4 // indirect
cloud.google.com/go/auth v0.16.3 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
@@ -320,8 +324,8 @@ require (
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect
github.com/FZambia/eagle v0.2.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
@@ -339,20 +343,20 @@ require (
github.com/armon/go-metrics v0.4.1 // indirect
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/aws/protocol/eventstream v1.7.1 // 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/feature/s3/manager v1.17.84 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.10 // 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/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/internal/v4a v1.4.9 // 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/checksum v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 // 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/s3shared v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // 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/s3 v1.88.3 // 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/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
@@ -420,7 +424,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gammazero/deque v0.2.1 // indirect
github.com/gammazero/deque v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -452,7 +456,7 @@ require (
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/hashicorp/consul/api v1.31.2 // indirect
github.com/hashicorp/consul/api v1.32.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
@@ -505,7 +509,7 @@ require (
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-ieproxy v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/maypok86/otter v1.2.4 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
@@ -547,10 +551,10 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/exporter-toolkit v0.14.0 // indirect
@@ -563,7 +567,7 @@ require (
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/encoding v0.5.3 // indirect
@@ -617,7 +621,7 @@ require (
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
go.opentelemetry.io/otel/log v0.12.2 // indirect
go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
@@ -630,7 +634,7 @@ require (
golang.org/x/term v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
google.golang.org/genproto v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
@@ -652,16 +656,18 @@ 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/clipperhouse/uax29/v2 v2.2.0 // 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
github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
@@ -673,10 +679,13 @@ require (
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/golang/glog v1.2.5 // indirect
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/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // 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-20250827001030-24949be3fa54 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
@@ -685,12 +694,16 @@ require (
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quagmt/udecimal v1.9.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.3 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.8 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
vitess.io/vitess v0.23.0 // indirect
)
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
+92 -69
View File
@@ -46,8 +46,8 @@ cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFO
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -109,8 +109,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
@@ -351,8 +351,8 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4
cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=
cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=
cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
cloud.google.com/go/kms v1.23.0 h1:WaqAZsUptyHwOo9II8rFC1Kd2I+yvNsNP2IJ14H2sUw=
cloud.google.com/go/kms v1.23.0/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
@@ -558,8 +558,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/storage v1.57.0 h1:4g7NB7Ta7KetVbOMpCqy89C+Vg5VE8scqlSHUPm7Rds=
cloud.google.com/go/storage v1.57.0/go.mod h1:329cwlpzALLgJuu8beyJ/uvQznDHpa2U5lGjWednkzg=
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
@@ -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=
@@ -726,17 +727,19 @@ github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYI
github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
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=
@@ -847,24 +852,24 @@ 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/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/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
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/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/feature/s3/manager v1.19.10 h1:i9EmUTyCWtUp1KsBwKGTtZUxaRym4LmiFiBcb//i8Kw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.10/go.mod h1:IPS1CSYQ8lfLYGytpMEPW4erZmVFUdxLpC0RCI/RCn8=
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/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=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 h1:Nn3qce+OHZuMj/edx4its32uxedAmquCDxtZkrdeiD4=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3/go.mod h1:aqsLGsPs+rJfwDBwWHLcIV8F7AFcikFTPLwUD4RwORQ=
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFieZUfVdINOiCTvChOMPfdLnmiLzs=
@@ -873,20 +878,20 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJ
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/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/checksum v1.8.9 h1:by3nYZLR9l8bUH7kgaMU4dJgYFjyRdFEfORlDpPILB4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8=
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/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/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2/go.mod h1:Pqd9k4TuespkireN206cK2QBsaBTL6X+VPAez5Qcijk=
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 h1:teOWtElLARLOhpYWwupjLbY9j5I/yZ/H1I8jg41An78=
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3/go.mod h1:wGhpdyftHX6/1U4egowHkYdypwBMjpb+KjAAprv6z20=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:PwbxovpcJvb25k019bkibvJfCpCmIANOFrXZIFPmRzk=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg=
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/s3 v1.88.3 h1:P18I4ipbk+b/3dZNq5YYh+Hq6XC0vp5RWkLp1tJldDA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3/go.mod h1:Rm3gw2Jov6e6kDuamDvyIlZJDMYk97VeCZ82wz/mVZ0=
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=
@@ -1026,6 +1031,10 @@ 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/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
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=
@@ -1132,16 +1141,16 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw=
github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84=
github.com/dolthub/vitess v3.0.0-rc.3+incompatible h1:rbRUgaqGLGIrj8TWc/XZWZj5FbabnqTJnOdeeTSpQCc=
github.com/dolthub/vitess v3.0.0-rc.3+incompatible/go.mod h1:3AZk0ixunKAxcUiEShgEf2HsR438c/DIZsivTv7ffyw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
@@ -1210,8 +1219,8 @@ github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6
github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo=
github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
@@ -1261,8 +1270,9 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@@ -1440,6 +1450,8 @@ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgR
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -1556,8 +1568,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
@@ -1664,8 +1676,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=
@@ -1709,12 +1719,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
github.com/hashicorp/consul/api v1.31.2 h1:NicObVJHcCmyOIl7Z9iHPvvFrocgTYo9cITSGg0/7pw=
github.com/hashicorp/consul/api v1.31.2/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40=
github.com/hashicorp/consul/api v1.32.4 h1:xNe27KcBNYHbqWX/6c6WTAlPoZlZv8onDEySmjcspO0=
github.com/hashicorp/consul/api v1.32.4/go.mod h1:jy0q71iTvUGfbCwo+ExBF0gEesE5cY2TSeAz2EoNG8E=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/hashicorp/consul/sdk v0.16.3 h1:kI/oax+yeaoremkh36G/f4Q13ivdFF4AE+Co/LlZa0Q=
github.com/hashicorp/consul/sdk v0.16.3/go.mod h1:TSPshuYdi1OQwpLund2vkTHpp4WnLyhf7Q/YihGMtp0=
github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A=
github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -1753,6 +1763,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 +1889,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=
@@ -1959,8 +1975,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/linode/linodego v1.47.0 h1:6MFNCyzWbr8Rhl4r7d5DwZLwxvFIsM4ARH6W0KS/R0U=
github.com/linode/linodego v1.47.0/go.mod h1:vyklQRzZUWhFVBZdYx4dcYJU/gG9yKB9VUcUs6ub0Lk=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
@@ -2011,9 +2027,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -2050,6 +2065,7 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1 h1:jw16EimP5oAEM/2wt+SiEUov/YDyTCTDuPtIKgQIvk0=
github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc=
github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -2153,8 +2169,14 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.1 h1:9Dfeed5/Mgaxb9lHRAftLK9pVfYETvHn+If6lywVhJc=
github.com/olekukonko/ll v0.1.1/go.mod h1:2dJo+hYZcJMLMbKwHEWvxCUbAOLc/CXWS9noET22Mdo=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -2192,8 +2214,8 @@ github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20251027165255-0f8f255e5f6c h
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20251027165255-0f8f255e5f6c/go.mod h1:BG26d1Fk4GSg0wMj60TRJ6Pe4ka2WQ33akhO+mzt3t0=
github.com/openfga/openfga v1.11.1 h1:+cJBPi/J+RWPRg+cXOjwWEwjauiW8rdE3kEzcFy1ME4=
github.com/openfga/openfga v1.11.1/go.mod h1:MuTGr/ghY7t2sEGwS/59pq9SkqO0QY1kQLIe8Upt+G8=
github.com/opentracing-contrib/go-grpc v0.1.1 h1:Ws7IN1zyiL1DFqKQPhRXuKe5pLYzMfdxnC1qtajE2PE=
github.com/opentracing-contrib/go-grpc v0.1.1/go.mod h1:Nu6sz+4zzgxXu8rvKfnwjBEmHsuhTigxRwV2RhELrS8=
github.com/opentracing-contrib/go-grpc v0.1.2 h1:MP16Ozc59kqqwn1v18aQxpeGZhsBanJ2iurZYaQSZ+g=
github.com/opentracing-contrib/go-grpc v0.1.2/go.mod h1:glU6rl1Fhfp9aXUHkE36K2mR4ht8vih0ekOVlWKEUHM=
github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w=
github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
@@ -2228,8 +2250,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -2239,8 +2261,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 h1:S1hI5JiKP7883xBzZAr1ydcxrKNSVNm7+3+JwjxZEsg=
github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25/go.mod h1:ZQntvDG8TkPgljxtA0R9frDoND4QORU1VXz015N5Ks4=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
@@ -2345,7 +2367,6 @@ github.com/redis/rueidis v1.0.68/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -2378,8 +2399,8 @@ github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c=
@@ -2398,8 +2419,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 h1:/4/IJi5iyTdh6mqOUaASW148HQpujYiHl0Wl78dSOSc=
github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps=
github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -2508,10 +2529,10 @@ github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1C
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc=
github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
@@ -2690,8 +2711,8 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iL
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
@@ -3364,8 +3385,8 @@ google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjY
google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4=
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg=
google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.251.0 h1:6lea5nHRT8RUmpy9kkC2PJYnhnDAB13LqrLSVQlMIE8=
google.golang.org/api v0.251.0/go.mod h1:Rwy0lPf/TD7+T2VhYcffCHhyyInyuxGjICxdfLqT7KI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -3516,8 +3537,8 @@ google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs=
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s=
google.golang.org/genproto v0.0.0-20250929231259-57b25ae835d4 h1:HmI33/XNQ1jVwhb5ZUgot40oiwFHa2l5ZNkQpj8VaEg=
google.golang.org/genproto v0.0.0-20250929231259-57b25ae835d4/go.mod h1:OqVwZqqGV3h7k+YCVWXoTtwC2cs55RnDEUVMMadhxrc=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
@@ -3774,5 +3795,7 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
vitess.io/vitess v0.23.0 h1:XEzcon9q9KpnEOF8VkFmgpEdEZZR2+N+vDSgfKTjW7k=
vitess.io/vitess v0.23.0/go.mod h1:79F6ICWYB/ma+BSMMHO7CcE4ByqbgPYyFmZC8i01NmI=
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+3 -1
View File
@@ -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=
+3 -6
View File
@@ -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'",
@@ -76,8 +75,7 @@
"plugin:test:ci": "nx run-many -t test:ci --projects='tag:scope:plugin' --maxParallel=2",
"plugin:i18n-extract": "nx run-many -t i18n-extract --projects='tag:scope:plugin'",
"generate-apis": "yarn workspace @grafana/api-clients generate-apis",
"generate:api-client": "yarn workspace @grafana/api-clients generate:api-client",
"generate:openapi": "yarn workspace @grafana/openapi generate:openapi"
"generate:api-client": "yarn workspace @grafana/api-clients generate:api-client"
},
"grafana": {
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/",
@@ -255,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",
@@ -266,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",
+2 -1
View File
@@ -163,7 +163,8 @@
"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",
"generate-apis": "yarn workspace @grafana/openapi process-specs && NODE_OPTIONS='--disable-warning=ExperimentalWarning' rtk-query-codegen-openapi ./src/scripts/generate-rtk-apis.ts",
"process-specs": "NODE_OPTIONS='--disable-warning=ExperimentalWarning' node --experimental-strip-types src/scripts/process-specs.ts",
"generate-apis": "yarn process-specs && NODE_OPTIONS='--disable-warning=ExperimentalWarning' rtk-query-codegen-openapi ./src/scripts/generate-rtk-apis.ts",
"generate:api-client": "NODE_OPTIONS='--experimental-strip-types --disable-warning=ExperimentalWarning' plop --plopfile src/generator/plopfile.ts"
},
"devDependencies": {
@@ -246,6 +246,8 @@ const injectedRtkApi = api
facetLimit: queryArg.facetLimit,
tags: queryArg.tags,
libraryPanel: queryArg.libraryPanel,
panelType: queryArg.panelType,
dataSourceType: queryArg.dataSourceType,
permission: queryArg.permission,
sort: queryArg.sort,
limit: queryArg.limit,
@@ -674,6 +676,10 @@ export type SearchDashboardsAndFoldersApiArg = {
tags?: string[];
/** find dashboards that reference a given libraryPanel */
libraryPanel?: string;
/** find dashboards using panels of a given plugin type */
panelType?: string;
/** find dashboards using datasources of a given plugin type */
dataSourceType?: string;
/** permission needed for the resource (view, edit, admin) */
permission?: 'view' | 'edit' | 'admin';
/** sortable field */
@@ -1452,7 +1452,7 @@ export type ConnectionSecure = {
/** PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back */
privateKey?: InlineSecureValue;
/** Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back */
webhook?: InlineSecureValue;
token?: InlineSecureValue;
};
export type BitbucketConnectionConfig = {
/** App client ID */
@@ -45,7 +45,7 @@ If an error about a missing OpenAPI schema appears, check that:
1. The API group and version exist in the backend
2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../../public/app/api/README.md)).
3. The schema file exists at `packages/grafana-openapi/src/apis/<group>-<version>.json`
3. The schema file exists at `data/openapi/<group>-<version>.json`
### Validation Errors
@@ -66,7 +66,7 @@ export const runGenerateApis =
let command;
if (isEnterprise) {
command =
'yarn workspace @grafana/openapi process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts';
'yarn workspace @grafana/api-clients process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts';
} else {
command = 'yarn workspace @grafana/api-clients generate-apis';
}
@@ -30,7 +30,7 @@ const createAPIConfig = (app: string, version: string, filterEndpoints?: Endpoin
const filePath = `../clients/rtkq/${app}/${version}/endpoints.gen.ts`;
return {
[filePath]: {
schemaFile: path.join(basePath, `packages/grafana-openapi/src/apis/${app}.grafana.app-${version}.json`),
schemaFile: path.join(basePath, `data/openapi/${app}.grafana.app-${version}.json`),
apiFile: `../clients/rtkq/${app}/${version}/baseAPI.ts`,
filterEndpoints,
tag: true,
@@ -175,16 +175,12 @@ function processDirectory(sourceDir: string, outputDir: string) {
// Grafana root path - navigate up from this script's directory
const basePath = path.resolve(import.meta.dirname, '../../../..');
const oss = {
source: path.join(basePath, 'pkg/tests/apis/openapi_snapshots'),
output: path.join(import.meta.dirname, '../apis'),
};
const sourceDirs = [
path.join(basePath, 'pkg/tests/apis/openapi_snapshots'),
path.join(basePath, 'pkg/extensions/apiserver/tests/openapi_snapshots'),
];
const outputDir = path.join(basePath, 'data/openapi');
const enterprise = {
source: path.join(basePath, 'pkg/extensions/apiserver/tests/openapi_snapshots'),
output: path.join(basePath, 'data/openapi'),
};
for (const config of [oss, enterprise]) {
processDirectory(config.source, config.output);
for (const sourceDir of sourceDirs) {
processDirectory(sourceDir, outputDir);
}
+7 -3
View File
@@ -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": {
+3 -2
View File
@@ -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,
},
@@ -9,6 +9,7 @@ import { FieldColorModeId } from '../types/fieldColor';
import { FieldConfigPropertyItem, FieldConfigSource } from '../types/fieldOverrides';
import { InterpolateFunction } from '../types/panel';
import { ThresholdsMode } from '../types/thresholds';
import { MappingType } from '../types/valueMapping';
import { Registry } from '../utils/Registry';
import { locationUtil } from '../utils/location';
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
@@ -999,6 +1000,45 @@ describe('setDynamicConfigValue', () => {
expect(config.custom.property3).toEqual({});
expect(config.displayName).toBeUndefined();
});
it('works correctly with multiple value mappings in the same override', () => {
const config: FieldConfig = {
mappings: [{ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } }],
};
setDynamicConfigValue(
config,
{
id: 'mappings',
value: [{ type: MappingType.ValueToText, options: { first: { text: 'first' } } }],
},
{
fieldConfigRegistry: customFieldRegistry,
data: [],
field: { type: FieldType.number } as Field,
dataFrameIndex: 0,
}
);
setDynamicConfigValue(
config,
{
id: 'mappings',
value: [{ type: MappingType.ValueToText, options: { second: { text: 'second' } } }],
},
{
fieldConfigRegistry: customFieldRegistry,
data: [],
field: { type: FieldType.number } as Field,
dataFrameIndex: 0,
}
);
expect(config.mappings).toHaveLength(3);
expect(config.mappings![0]).toEqual({ type: MappingType.ValueToText, options: { existing: { text: 'existing' } } });
expect(config.mappings![1]).toEqual({ type: MappingType.ValueToText, options: { first: { text: 'first' } } });
expect(config.mappings![2]).toEqual({ type: MappingType.ValueToText, options: { second: { text: 'second' } } });
});
});
describe('getLinksSupplier', () => {
@@ -341,7 +341,7 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV
return;
}
const val = item.process(value.value, context, item.settings);
let val = item.process(value.value, context, item.settings);
const remove = val === undefined || val === null;
@@ -352,6 +352,15 @@ export function setDynamicConfigValue(config: FieldConfig, value: DynamicConfigV
unset(config, item.path);
}
} else {
// Merge arrays (e.g. mappings) when multiple overrides target the same field
if (Array.isArray(val)) {
const existingValue = item.isCustom ? get(config.custom, item.path) : get(config, item.path);
if (Array.isArray(existingValue)) {
val = [...existingValue, ...val];
}
}
if (item.isCustom) {
if (!config.custom) {
config.custom = {};
@@ -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;
+24 -15
View File
@@ -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
+14 -11
View File
@@ -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() {
@@ -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
}
@@ -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;
@@ -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;
@@ -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';
@@ -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;
@@ -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
}
}

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