merge main
CodeQL checks / Analyze (go) (push) Waiting to run Details
CodeQL checks / Analyze (javascript) (push) Waiting to run Details
CodeQL checks / Analyze (python) (push) Waiting to run Details

This commit is contained in:
Andres Martinez Gotor 2025-04-28 10:42:49 +02:00
commit 7b38c5078b
257 changed files with 8253 additions and 1897 deletions

View File

@ -134,7 +134,7 @@ steps:
environment:
HOST: start-storybook
PORT: "9001"
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-storybook-suite
trigger:
event:
@ -561,7 +561,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-dashboards-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite
@ -570,7 +570,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/dashboards-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite
@ -579,7 +579,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-smoke-tests-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite
@ -588,7 +588,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/smoke-tests-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite panels-suite
@ -597,7 +597,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-panels-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite
@ -606,7 +606,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/panels-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite various-suite
@ -615,7 +615,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-various-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite
@ -624,7 +624,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/various-suite
- commands:
- GITHUB_TOKEN=$(cat /github-app/token)
@ -1658,7 +1658,7 @@ steps:
environment:
HOST: start-storybook
PORT: "9001"
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-storybook-suite
trigger:
branch: main
@ -1851,7 +1851,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-dashboards-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite
@ -1860,7 +1860,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/dashboards-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite
@ -1869,7 +1869,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-smoke-tests-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite
@ -1878,7 +1878,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/smoke-tests-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite panels-suite
@ -1887,7 +1887,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-panels-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite
@ -1896,7 +1896,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/panels-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite various-suite
@ -1905,7 +1905,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-various-suite
- commands:
- ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite
@ -1914,7 +1914,7 @@ steps:
- build-test-plugins
environment:
HOST: grafana-server
image: cypress/included:13.10.0
image: cypress/included:14.3.2
name: end-to-end-tests-old-arch/various-suite
- commands:
- GITHUB_TOKEN=$(cat /github-app/token)
@ -4923,7 +4923,7 @@ steps:
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/drone-downstream
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docker-puppeteer:1.1.0
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docs-base:latest
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:13.10.0
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:14.3.2
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM jwilder/dockerize:0.6.1
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM koalaman/shellcheck:stable
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM rockylinux:9
@ -4961,7 +4961,7 @@ steps:
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/drone-downstream
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docker-puppeteer:1.1.0
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docs-base:latest
- trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:13.10.0
- trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:14.3.2
- trivy --exit-code 1 --severity HIGH,CRITICAL jwilder/dockerize:0.6.1
- trivy --exit-code 1 --severity HIGH,CRITICAL koalaman/shellcheck:stable
- trivy --exit-code 1 --severity HIGH,CRITICAL rockylinux:9
@ -5210,6 +5210,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: 8e25f1f786b8de4eb21dfbeca8c5fcb9701b1e62ecf98287d9225ecc6e8c29e8
hmac: 16029e3922ae0a13a31233717aa172c06bf0e6fc8cf01f5148de62147c259ac8
...

2
.github/CODEOWNERS vendored
View File

@ -138,6 +138,7 @@
/pkg/services/dashboardversion/ @grafana/grafana-backend-group
/pkg/services/encryption/ @grafana/grafana-operator-experience-squad
/pkg/services/folder/ @grafana/grafana-search-and-storage
/pkg/services/frontend/ @grafana/grafana-frontend-platform
/pkg/services/apiserver @grafana/grafana-app-platform-squad
/pkg/services/hooks/ @grafana/grafana-backend-group
/pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad
@ -435,6 +436,7 @@
/packages/grafana-ui/src/graveyard/GraphNG/ @grafana/dataviz-squad
/packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad
/packages/grafana-ui/src/utils/storybook/ @grafana/grafana-frontend-platform
/packages/grafana-alerting/ @grafana/alerting-frontend
# root files, mostly frontend
/.browserslistrc @grafana/frontend-ops

View File

@ -6,6 +6,10 @@ on:
- main
- release-*.*.*
permissions:
contents: read
id-token: write
jobs:
lint-frontend-verify-i18n:
name: Verify i18n
@ -30,6 +34,9 @@ jobs:
exit 1
fi
lint-frontend-prettier:
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
# the `lint-frontend-prettier-enterprise` workflow will run instead
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
name: Lint
runs-on: ubuntu-latest
steps:
@ -42,7 +49,29 @@ jobs:
- run: yarn install --immutable --check-cache
- run: yarn run prettier:check
- run: yarn run lint
lint-frontend-prettier-enterprise:
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
github-app-name: 'grafana-ci-bot'
- run: yarn install --immutable --check-cache
- run: yarn run prettier:check
- run: yarn run lint
lint-frontend-typecheck:
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
# the `lint-frontend-typecheck-enterprise` workflow will run instead
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
name: Typecheck
runs-on: ubuntu-latest
steps:
@ -54,6 +83,24 @@ jobs:
cache-dependency-path: 'yarn.lock'
- run: yarn install --immutable --check-cache
- run: yarn run typecheck
lint-frontend-typecheck-enterprise:
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
github-app-name: 'grafana-ci-bot'
- run: yarn install --immutable --check-cache
- run: yarn run typecheck
lint-frontend-betterer:
name: Betterer
runs-on: ubuntu-latest

View File

@ -6,8 +6,15 @@ on:
- main
- release-*.*.*
permissions:
contents: read
id-token: write
jobs:
frontend-unit-tests:
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
# the `frontend-unit-tests-enterprise` workflow will run instead
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
runs-on: ubuntu-latest-8-cores
name: "Unit tests (${{ matrix.chunk }} / 8)"
strategy:
@ -27,3 +34,30 @@ jobs:
TEST_MAX_WORKERS: 2
TEST_SHARD: ${{ matrix.chunk }}
TEST_SHARD_TOTAL: 8
frontend-unit-tests-enterprise:
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest-8-cores
name: "Unit tests (${{ matrix.chunk }} / 8)"
strategy:
fail-fast: false
matrix:
chunk: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
github-app-name: 'grafana-ci-bot'
- run: yarn install --immutable --check-cache
- run: yarn run test:ci
env:
TEST_MAX_WORKERS: 2
TEST_SHARD: ${{ matrix.chunk }}
TEST_SHARD_TOTAL: 8

View File

@ -3,7 +3,7 @@
name: Dispatch check for patch conflicts
run-name: dispatch-check-patch-conflicts-${{ github.base_ref }}-${{ github.head_ref }}
on:
pull_request:
pull_request_target:
types:
- opened
- reopened
@ -26,7 +26,6 @@ jobs:
# App needs Actions: Read/Write for the grafana/security-patch-actions repo
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: "Dispatch job"
uses: actions/github-script@v7
with:

View File

@ -23,22 +23,9 @@ jobs:
with:
go-version-file: go.mod
cache: true
- name: Restore GOCACHE
uses: actions/cache/restore@v4
with:
key: go-test-cache-${{ github.ref_name }}-sqlite
restore-keys: |
go-test-cache-${{ github.base_ref }}-sqlite
go-test-cache-main-sqlite
path: /home/runner/.cache/go-build
- run: |
make gen-go
go test -tags=sqlite -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u)
- name: Save GOCACHE
uses: actions/cache/save@v4
with:
key: go-test-cache-${{ github.ref_name }}-sqlite
path: /home/runner/.cache/go-build
mysql:
name: MySQL
runs-on: ubuntu-latest-8-cores
@ -64,24 +51,11 @@ jobs:
with:
go-version-file: go.mod
cache: true
- name: Restore GOCACHE
uses: actions/cache/restore@v4
with:
key: go-test-cache-${{ github.ref_name }}-mysql
restore-keys: |
go-test-cache-${{ github.base_ref }}-mysql
go-test-cache-main-mysql
path: /home/runner/.cache/go-build
- run: |
sudo apt-get update -yq && sudo apt-get install mariadb-client
cat devenv/docker/blocks/mysql_tests/setup.sql | mariadb -h 127.0.0.1 -P 3306 -u root -prootpass --disable-ssl-verify-server-cert
make gen-go
go test -tags=mysql -p=1 -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u)
- name: Save GOCACHE
uses: actions/cache/save@v4
with:
key: go-test-cache-${{ github.ref_name }}-mysql
path: /home/runner/.cache/go-build
postgres:
name: Postgres
runs-on: ubuntu-latest-8-cores
@ -102,14 +76,6 @@ jobs:
with:
go-version-file: go.mod
cache: true
- name: Restore GOCACHE
uses: actions/cache/restore@v4
with:
key: go-test-cache-${{ github.ref_name }}-postgres
restore-keys: |
go-test-cache-${{ github.base_ref }}-postgres
go-test-cache-main-postgres
path: /home/runner/.cache/go-build
- env:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
@ -119,8 +85,3 @@ jobs:
psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
make gen-go
go test -p=1 -tags=postgres -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u)
- name: Save GOCACHE
uses: actions/cache/save@v4
with:
key: go-test-cache-${{ github.ref_name }}-postgres
path: /home/runner/.cache/go-build

10
.vscode/launch.json vendored
View File

@ -82,6 +82,16 @@
"cwd": "${workspaceFolder}",
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
},
{
"name": "Run Frontend Server",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/pkg/cmd/grafana/",
"cwd": "${workspaceFolder}",
"env": { "GF_DEFAULT_TARGET": "frontend-server", "GF_SERVER_HTTP_PORT": "3003" },
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
},
{
"name": "Attach to Chrome",
"port": 9222,

View File

@ -70,7 +70,6 @@ COPY .citools/swagger .citools/swagger
# Include vendored dependencies
COPY pkg/util/xorm pkg/util/xorm
COPY pkg/apis/folder pkg/apis/folder
COPY pkg/apis/secret pkg/apis/secret
COPY pkg/apiserver pkg/apiserver
COPY pkg/apimachinery pkg/apimachinery

View File

@ -189,7 +189,16 @@ func runStepsInParallel(ctx context.Context, spec *advisorv0alpha1.CheckSpec, st
go func(step checks.Step, item any) {
defer wg.Done()
defer func() { <-limit }()
stepErr, err := step.Run(ctx, spec, item)
var stepErr *advisorv0alpha1.CheckReportFailure
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered in step %s: %v", step.ID(), r)
}
}()
stepErr, err = step.Run(ctx, spec, item)
}()
mu.Lock()
defer mu.Unlock()
if err != nil {

View File

@ -134,14 +134,6 @@ func TestProcessCheck_RunError(t *testing.T) {
}
func TestProcessCheck_IgnoreSteps(t *testing.T) {
obj := &advisorv0alpha1.Check{}
obj.SetAnnotations(map[string]string{})
meta, err := utils.MetaAccessor(obj)
if err != nil {
t.Fatal(err)
}
meta.SetCreatedBy("user:1")
client := &mockClient{}
checkType := &advisorv0alpha1.CheckType{}
checkType.SetAnnotations(map[string]string{checks.IgnoreStepsAnnotation: "mock"})
typesClient := &mockTypesClient{
@ -152,6 +144,14 @@ func TestProcessCheck_IgnoreSteps(t *testing.T) {
items: []any{"item"},
err: errors.New("run error, should not be triggered"),
}
obj := &advisorv0alpha1.Check{}
obj.SetAnnotations(map[string]string{})
meta, err := utils.MetaAccessor(obj)
if err != nil {
t.Fatal(err)
}
meta.SetCreatedBy("user:1")
client := &mockClient{}
err = processCheck(ctx, client, typesClient, obj, check)
assert.NoError(t, err)
@ -159,6 +159,29 @@ func TestProcessCheck_IgnoreSteps(t *testing.T) {
assert.Equal(t, "mock", obj.GetAnnotations()[checks.IgnoreStepsAnnotation])
}
func TestProcessCheck_RunRecoversFromPanic(t *testing.T) {
obj := &advisorv0alpha1.Check{}
obj.SetAnnotations(map[string]string{})
meta, err := utils.MetaAccessor(obj)
if err != nil {
t.Fatal(err)
}
meta.SetCreatedBy("user:1")
client := &mockClient{}
typesClient := &mockTypesClient{}
ctx := context.TODO()
check := &mockCheck{
items: []any{"item"},
runPanics: true,
}
err = processCheck(ctx, client, typesClient, obj, check)
assert.Error(t, err)
assert.Contains(t, err.Error(), "panic recovered in step")
assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation])
}
func TestProcessCheckRetry_NoRetry(t *testing.T) {
obj := &advisorv0alpha1.Check{}
obj.SetAnnotations(map[string]string{})
@ -257,8 +280,9 @@ func (m *mockTypesClient) Get(ctx context.Context, id resource.Identifier) (reso
}
type mockCheck struct {
err error
items []any
err error
items []any
runPanics bool
}
func (m *mockCheck) ID() string {
@ -275,15 +299,19 @@ func (m *mockCheck) Item(ctx context.Context, id string) (any, error) {
func (m *mockCheck) Steps() []checks.Step {
return []checks.Step{
&mockStep{err: m.err},
&mockStep{err: m.err, panics: m.runPanics},
}
}
type mockStep struct {
err error
err error
panics bool
}
func (m *mockStep) Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items any) (*advisorv0alpha1.CheckReportFailure, error) {
if m.panics {
panic("panic")
}
if m.err != nil {
return nil, m.err
}

View File

@ -78,7 +78,7 @@ AnnotationPanelFilter: {
exclude?: bool | *false
// Panel IDs that should be included or excluded
ids: [...uint8]
ids: [...uint32]
}
// "Off" for no shared crosshair or tooltip (default).

View File

@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of DashboardStatus
func (s *DashboardStatus) DeepCopy() *DashboardStatus {
cpy := &DashboardStatus{}

View File

@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of DashboardStatus
func (s *DashboardStatus) DeepCopy() *DashboardStatus {
cpy := &DashboardStatus{}

View File

@ -82,7 +82,7 @@ AnnotationPanelFilter: {
exclude?: bool | *false
// Panel IDs that should be included or excluded
ids: [...uint8]
ids: [...uint32]
}
// "Off" for no shared crosshair or tooltip (default).

View File

@ -73,7 +73,7 @@ type DashboardAnnotationPanelFilter struct {
// Should the specified panels be included or excluded
Exclude *bool `json:"exclude,omitempty"`
// Panel IDs that should be included or excluded
Ids []uint8 `json:"ids"`
Ids []uint32 `json:"ids"`
}
// NewDashboardAnnotationPanelFilter creates a new DashboardAnnotationPanelFilter object.

View File

@ -544,7 +544,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationPanelFilter(ref commo
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"integer"},
Format: "byte",
Format: "int64",
},
},
},

View File

@ -3,6 +3,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,BaseFilters
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,DefaultKeys
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,Filters
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAnnotationPanelFilter,Ids
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAutoGridLayoutSpec,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingGroupSpec,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardCustomVariableSpec,Options

View File

@ -11,8 +11,6 @@ import (
"github.com/grafana/grafana-app-sdk/app"
)
var ()
var appManifestData = app.ManifestData{
AppName: "dashboard",
Group: "dashboard.grafana.app",

View File

@ -1,7 +1,7 @@
---
aliases:
- administration/cli/
description: Guide to using grafana cli
description: Guide to using grafana server cli
keywords:
- grafana
- cli
@ -11,15 +11,15 @@ labels:
products:
- enterprise
- oss
title: Grafana CLI
title: Grafana server CLI
weight: 400
---
# Grafana CLI
# Grafana server CLI
Grafana CLI is a small executable that's bundled with Grafana server.
Grafana server CLI is a small executable that's bundled with Grafana server.
You can run it on the same machine Grafana server is running on.
Grafana CLI has `plugins` and `admin` commands, as well as global options.
Grafana server CLI has `plugins` and `admin` commands, as well as global options.
To list all commands and options:
@ -27,9 +27,9 @@ To list all commands and options:
grafana cli -h
```
## Run Grafana CLI
## Run Grafana server CLI
To run Grafana CLI, add the path to the Grafana binaries in your `PATH` environment variable.
To run Grafana server CLI, add the path to the Grafana binaries in your `PATH` environment variable.
Alternately, if your current directory is the `bin` directory, run `./grafana cli`.
Otherwise, you can specify full path to the binary.
For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and run it with `grafana cli`.
@ -41,7 +41,7 @@ If you're on Windows, run Windows PowerShell as Administrator.
## Grafana CLI command syntax
The general syntax for commands in Grafana CLI is:
The general syntax for commands in Grafana server CLI is:
```bash
grafana cli [global options] command [command options] [arguments...]
@ -49,11 +49,11 @@ grafana cli [global options] command [command options] [arguments...]
## Global options
Grafana CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers.
Grafana server CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers.
Each global option applies only to the command in which it is used. For example, `--pluginsDir value` does not permanently change where Grafana saves plugins. It only changes it for command in which you apply the option.
### Display Grafana CLI help
### Display Grafana server CLI help
`--help` or `-h` displays the help, including default paths and Docker configuration information.
@ -63,9 +63,9 @@ Each global option applies only to the command in which it is used. For example,
grafana cli -h
```
### Display Grafana CLI version
### Display Grafana server CLI version
`--version` or `-v` prints the version of Grafana CLI currently running.
`--version` or `-v` prints the version of Grafana server CLI currently running.
**Example:**

View File

@ -0,0 +1,99 @@
---
aliases:
- ../../http_api/new_api_structure/
- ../../http_api/new_api_structure/
canonical: /docs/grafana/latest/developers/http_api/new_api_structure/
description: ''
keywords:
- grafana
- http
- documentation
- api
labels:
products:
- enterprise
- oss
title: New API Structure
---
# Grafana's New API Structure
## Overview
Going forward, Grafana's HTTP API will follow a standardized API structure alongside consistent API versioning.
## API Path Structure
All Grafana APIs follow this standardized format:
```
/apis/<group>/<version>/namespaces/<namespace>/<resource>[/<name>]
```
Where the final `/<name>` segment is used for operations on individual resources (like Get, Update, Delete) and omitted for collection operations (like List, Create).
## Understanding the Components
### Group (`<group>`)
Groups organize related functionality into logical collections. For example `dashboard.grafana.app` will be used for all dashboard-related operations.
### Version (`<version>`)
These APIs will also uses semantic versioning with three stability levels:
| Level | Format | Description | Use Case |
| ----- | ---------- | --------------------------------------------------------------------------- | ------------------------ |
| Alpha | `v1alpha1` | Early development stage. Unstable, may contain bugs, and subject to removal | For testing new features |
| Beta | `v1beta1` | More stable than alpha, but may still have some changes | For early production use |
| GA | `v1` | Generally Available. Stable with backward compatibility guarantees | For production use |
### Namespace (`<namespace>`)
Namespaces isolate resources within your Grafana instance. The format varies by deployment type:
#### OSS & On-Premise Grafana
- Default organization (org 1): `default`
- Additional organizations: `org-<org_id>`
#### Grafana Cloud
- Format: `stacks-<stack_id>`
- Your instance ID is the `stack_id`. You can find this value by either:
- Going to grafana.com, clicking on your stack, and selecting "Details" on your Grafana instance
- Accessing the /swagger page in your cloud instance, where the namespace will be automatically populated on the relevant endpoints
### Resource (`<resource>`)
Represents the core resource you want to interact with, such as:
- `dashboards`
- `playlists`
- `folders`
### Name (<name>)
The `<name>` is the unique identifier for a specific instance of a resource within its namespace and resource type. `<name>` is distinct from the metadata.uid field. The URL path will always use the metadata.name.
For example, to get a dashboard defined as:
```
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "production-overview", // This value IS used in the URL path
"namespace": "default",
"uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8" // This value is NOT used in the URL path
// ... other metadata
},
"spec": {
// ... dashboard spec
}
}
```
You would use the following API call:
`GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/production-overview`

View File

@ -16,22 +16,859 @@ labels:
title: Dashboard HTTP API
---
# Dashboard API
# New Dashboard APIs
> If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information.
## Identifier (id) vs unique identifier (uid)
> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}).
The identifier (id) of a dashboard is an auto-incrementing numeric value and is only unique per Grafana install.
## Create Dashboard
The unique identifier (uid) of a dashboard can be used for uniquely identify a dashboard between multiple Grafana installs.
`POST /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards`
Creates a new dashboard.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
<!-- prettier-ignore-start -->
| Action | Scope |
| ------------------- | ------------------------------------------------------------------------------------------------------- |
| `dashboards:create` | <ul><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
| `dashboards:write` | <ul><li>`dashboards:*`</li><li>`dashboards:uid:*`</li><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
{ .no-spacing-list }
<!-- prettier-ignore-end -->
**Example Create Request**:
```http
POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"metadata": {
"name": "gdxccn",
"annotations": {
"grafana.app/folder": "fef30w4jaxla8b"
},
},
"spec": {
"annotations": {
"list": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": false,
"iconColor": "red",
"name": "Example annotation",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
}
}]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [
{
"asDropdown": false,
"icon": "external link",
"includeVars": false,
"keepTime": false,
"tags": [],
"targetBlank": false,
"title": "Example Link",
"tooltip": "",
"type": "dashboards",
"url": ""
}
],
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"description": "With a description",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.0.0",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"refId": "A"
}
],
"title": "Example panel",
"type": "timeseries"
}
],
"preload": false,
"schemaVersion": 41,
"tags": ["example"],
"templating": {
"list": [
{
"current": {
"text": "",
"value": ""
},
"definition": "",
"description": "example description",
"label": "ExampleLabel",
"name": "ExampleVariable",
"options": [],
"query": "",
"refresh": 1,
"regex": "cluster",
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Example Dashboard",
"version": 0
}
}
```
JSON Body schema:
- **metadata.name** The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName instead to the prefix you would like for the randomly generated uid (cannot be an empty string).
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created.
- **spec** The dashboard json.
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 485
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "gdxccn",
"namespace": "default",
"uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX",
"resourceVersion": "1",
"generation": 1,
"creationTimestamp": "2025-04-24T20:35:29Z",
"labels": {
"grafana.app/deprecatedInternalID": "11"
},
"annotations": {
"grafana.app/createdBy": "service-account:dejwtrofg77y8d",
"grafana.app/folder": "fef30w4jaxla8b"
},
"managedFields": [
{
"manager": "curl",
"operation": "Update",
"apiVersion": "dashboard.grafana.app/v0alpha1",
"time": "2025-04-24T20:35:29Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:annotations": {
".": {},
"f:list": {}
},
"f:editable": {},
"f:fiscalYearStartMonth": {},
"f:graphTooltip": {},
"f:links": {},
"f:panels": {},
"f:preload": {},
"f:schemaVersion": {},
"f:tags": {},
"f:templating": {
".": {},
"f:list": {}
},
"f:time": {
".": {},
"f:from": {},
"f:to": {}
},
"f:timepicker": {},
"f:timezone": {},
"f:title": {},
"f:version": {}
}
}
}
]
},
"spec": {
"annotations": {
"list": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": false,
"iconColor": "red",
"name": "Example annotation",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
}
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [
{
"asDropdown": false,
"icon": "external link",
"includeVars": false,
"keepTime": false,
"tags": [],
"targetBlank": false,
"title": "Example Link",
"tooltip": "",
"type": "dashboards",
"url": ""
}
],
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"description": "With a description",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.0.0",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"refId": "A"
}
],
"title": "Example panel",
"type": "timeseries"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [
"example"
],
"templating": {
"list": [
{
"current": {
"text": "",
"value": ""
},
"definition": "",
"description": "example description",
"label": "ExampleLabel",
"name": "ExampleVariable",
"options": [],
"query": "",
"refresh": 1,
"regex": "cluster",
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Example Dashboard"
},
"status": {}
```
Status Codes:
- **201** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Access denied
- **409** Conflict (dashboard with the same uid already exists)
## Update Dashboard
`PUT /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid`
Updates an existing dashboard via the dashboard uid.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
<!-- prettier-ignore-start -->
| Action | Scope |
| ------------------- | ------------------------------------------------------------------------------------------------------- |
| `dashboards:write` | <ul><li>`dashboards:*`</li><li>`dashboards:uid:*`</li><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
{ .no-spacing-list }
<!-- prettier-ignore-end -->
**Example Update Request**:
```http
POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"metadata": {
"name": "gdxccn",
"annotations": {
"grafana.app/folder": "fef30w4jaxla8b",
"grafana.app/message": "commit message"
},
},
"spec": {
"title": "New dashboard - updated",
"schemaVersion": 41,
...
}
}
```
JSON Body schema:
- **metadata.name** The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}).
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created.
- **metadata.annotations.grafana.app/message** - Optional field, to set a commit message for the version history.
- **spec** The dashboard json.
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 485
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "gdxccn",
"namespace": "default",
"uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-03-06T19:57:18Z",
"annotations": {
"grafana.app/folder": "fef30w4jaxla8b",
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z"
}
},
"spec": {
"schemaVersion": 41,
"title": "New dashboard - updated",
...
}
}
```
Status Codes:
- **200** OK
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Access denied
- **409** Conflict (dashboard with the same version already exists)
## Get Dashboard
`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid`
Gets a dashboard via the dashboard uid.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response
Note: For large dashboards, add `/dto` to the end of the URL to get the full dashboard body.
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
<!-- prettier-ignore-start -->
| Action | Scope |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `dashboards:read` | <ul><li>`dashboards:*`</li><li>`dashboards:uid:*`</li><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
{ .no-spacing-list }
<!-- prettier-ignore-end -->
**Example Get Request**:
```http
GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 485
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "gdxccn",
"namespace": "default",
"uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-03-06T19:57:18Z",
"annotations": {
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z"
}
},
"spec": {
"schemaVersion": 41,
"title": "New dashboard - updated",
...
}
}
```
Status Codes:
- **200** OK
- **401** Unauthorized
- **403** Access denied
- **404** Not Found
## List Dashboards
`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards`
Lists all dashboards in the given organization. You can control the maximum number of dashboards returned through the `limit` query parameter. You can then use the `continue` token returned to fetch the next page of dashboards.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
Note: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
<!-- prettier-ignore-start -->
| Action | Scope |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `dashboards:read` | <ul><li>`dashboards:*`</li><li>`dashboards:uid:*`</li><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
{ .no-spacing-list }
<!-- prettier-ignore-end -->
**Example Get Request**:
```http
GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards?limit=1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 644
{
"kind": "DashboardList",
"apiVersion": "dashboard.grafana.app/v1alpha1",
"metadata": {
"resourceVersion": "1741315830000",
"continue": "org:1/start:1158/folder:"
},
"items": [
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1alpha1",
"metadata": {
"name": "gpqcmf",
"namespace": "default",
"uid": "VQyL7pNTpfGPNlPM6HRJSePrBg5dXmxr4iPQL7txLtwX",
"resourceVersion": "1",
"generation": 1,
"creationTimestamp": "2025-03-06T19:50:30Z",
"annotations": {
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-06T19:50:30Z"
}
},
"spec": {
"schemaVersion": 41,
"title": "New dashboard",
"uid": "gpqcmf",
"version": 1,
...
}
}
]
}
```
Status Codes:
- **200** OK
- **401** Unauthorized
- **403** Access denied
## Delete Dashboard
`DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid`
Deletes a dashboard via the dashboard uid.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
<!-- prettier-ignore-start -->
| Action | Scope |
| ------------------- | ------------------------------------------------------------------------------------------------------- |
| `dashboards:delete` | <ul><li>`dashboards:*`</li><li>`dashboards:uid:*`</li><li>`folders:*`</li><li>`folders:uid:*`</li></ul> |
{ .no-spacing-list }
<!-- prettier-ignore-end -->
**Example Delete Request**:
```http
DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 78
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Success",
"details": {
"name": "gdxccn",
"group": "dashboard.grafana.app",
"kind": "dashboards",
"uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX"
}
}
```
Status Codes:
- **200** OK
- **401** Unauthorized
- **403** Access denied
- **404** Not found
## Gets the home dashboard
`GET /api/dashboards/home`
Will return the home dashboard.
**Example Request**:
```http
GET /api/dashboards/home HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"dashboard": {
"editable":false,
"nav":[
{
"enable":false,
"type":"timepicker"
}
],
"style":"dark",
"tags":[],
"templating":{
"list":[
]
},
"time":{
},
"timezone":"browser",
"title":"Home",
"version":5
},
"meta": {
"isHome":true,
"canSave":false,
"canEdit":false,
"canStar":false,
"url":"",
"expires":"0001-01-01T00:00:00Z",
"created":"0001-01-01T00:00:00Z"
}
}
```
## Tags for Dashboard
`GET /api/dashboards/tags`
Get all tags of dashboards
**Example Request**:
```http
GET /api/dashboards/tags HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"term":"tag1",
"count":1
},
{
"term":"tag2",
"count":4
}
]
```
## Dashboard Search
See [Folder/Dashboard Search API]({{< relref "folder_dashboard_search/" >}}).
## APIs
### Unique identifier (uid) vs identifier (id)
The unique identifier (uid) of a dashboard can be used to uniquely identify a dashboard within a given org.
It's automatically generated if not provided when creating a dashboard. The uid allows having consistent URLs for accessing
dashboards and when syncing dashboards between multiple Grafana installs, see [dashboard provisioning](/docs/grafana/latest/administration/provisioning/#dashboards)
for more information. This means that changing the title of a dashboard will not break any bookmarked links to that dashboard.
The uid can have a maximum length of 40 characters.
## Create / Update dashboard
The identifier (id) of a dashboard is deprecated in favor of the unique identifier (uid).
### Create / Update dashboard
`POST /api/dashboards/db`
@ -155,7 +992,7 @@ Content-Length: 97
}
```
## Get dashboard by uid
### Get dashboard by uid
`GET /api/dashboards/uid/:uid`
@ -214,7 +1051,7 @@ Status Codes:
- **403** Access denied
- **404** Not found
## Delete dashboard by uid
### Delete dashboard by uid
`DELETE /api/dashboards/uid/:uid`

View File

@ -16,19 +16,388 @@ labels:
title: Folder HTTP API
---
# Folder API
# New Folders APIs
> If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information.
> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}).
### Get all folders
`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders`
Returns all folders that the authenticated user has permission to view within the given organization. Use the `limit` query parameter to control the maximum number of dashboards returned. To retrieve additional dashboards, utilize the `continue` token provided in the response to fetch the next page.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
**Required permissions**
See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation.
| Action | Scope |
| -------------- | ----------- |
| `folders:read` | `folders:*` |
**Example Request**:
```http
GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders?limit=1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"kind": "FolderList",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"continue": "org:1/start:1158/folder:"
},
"items": [
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "aef30vrzxs3y8d",
"namespace": "default",
"uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X",
"resourceVersion": "1741343686000",
"creationTimestamp": "2025-03-07T10:34:46Z",
"annotations": {
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z"
}
},
"spec": {
"title": "example"
}
}
]
}
```
Status Codes:
- **200** OK
- **401** Unauthorized
- **403** Access Denied
### Get folder by uid
`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid`
Will return the folder given the folder uid.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response
**Required permissions**
See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation.
| Action | Scope |
| -------------- | ----------- |
| `folders:read` | `folders:*` |
**Example Request**:
```http
GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders/aef30vrzxs3y8d HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "aef30vrzxs3y8d",
"namespace": "default",
"uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X",
"resourceVersion": "1741343686000",
"creationTimestamp": "2025-03-07T10:34:46Z",
"annotations": {
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z",
"grafana.app/folder": "fef30w4jaxla8b"
}
},
"spec": {
"title": "test"
}
}
```
Note the annotation `grafana.app/folder` which contains the uid of the parent folder.
Status Codes:
- **200** Found
- **401** Unauthorized
- **403** Access Denied
- **404** Folder not found
### Create folder
`POST /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders`
Creates a new folder.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
**Required permissions**
See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation.
`folders:create` allows creating folders and subfolders. If granted with scope `folders:uid:general`, allows creating root level folders. Otherwise, allows creating subfolders under the specified folders.
| Action | Scope |
| ---------------- | ----------- |
| `folders:create` | `folders:*` |
| `folders:write` | `folders:*` |
**Example Request**:
```http
POST /apis/folder.grafana.app/v1beta1/namespaces/default/folders HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"metadata": {
"name": "aef30vrzxs3y8d",
"annotations": {
"grafana.app/folder": "fef30w4jaxla8b"
}
}
"spec": {
"title": "child-folder"
},
}
```
JSON Body schema:
- **metadata.name** The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName to the prefix you would like for the uid.
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be created. Requires nested folders to be enabled.
- **spec.title** The title of the folder.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "eef33r1fprd34d",
"namespace": "default",
"uid": "X8momvVZnsXdOqvLD9I4ngqLVif2CgRWXHy9xb2UgjQX",
"resourceVersion": "1741320415009",
"creationTimestamp": "2025-03-07T04:06:55Z",
"labels": {
"grafana.app/deprecatedInternalID": "1159"
},
"annotations": {
"grafana.app/folder": "fef30w4jaxla8b",
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb"
}
},
"spec": {
"title": "child-folder"
}
}
```
Status Codes:
- **201** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Access denied
- **409** Conflict (folder with the same uid already exists)
### Update folder
`PUT /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid`
Updates an existing folder identified by uid.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response
**Required permissions**
See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation.
| Action | Scope |
| --------------- | ----------- |
| `folders:write` | `folders:*` |
**Example Request**:
```http
PUT /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
"metadata": {
"name": "aef30vrzxs3y8d",
"annotations": {
"grafana.app/folder": "xkj92m5pqw3vn4"
}
}
"spec": {
"title": "updated title"
},
```
JSON Body schema:
- **metadata.name** The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}) of the folder.
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be - update this to move the folder under a different parent folder. Requires nested folders to be enabled.
- **spec.title** The title of the folder.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "fef30w4jaxla8b",
"namespace": "default",
"uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX",
"resourceVersion": "1741345736000",
"creationTimestamp": "2025-03-07T11:08:56Z",
"annotations": {
"grafana.app/folder": "xkj92m5pqw3vn4",
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z"
}
},
"spec": {
"title": "updated title"
}
}
```
Status Codes:
- **200** Updated
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Access Denied
- **404** Folder not found
- **412** Precondition failed (the folder has been changed by someone else). With this status code, the response body will have the following properties:
```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json; charset=UTF-8
Content-Length: 97
{
"message": "The folder has been changed by someone else",
"status": "version-mismatch"
}
```
### Delete folder
`DELETE /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid`
Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.
If [Grafana Alerting]({{< relref "/docs/grafana/latest/alerting" >}}) is enabled, you can set an optional query parameter `forceDeleteRules=false` so that requests will fail with 400 (Bad Request) error if the folder contains any Grafana alerts. However, if this parameter is set to `true` then it will delete any Grafana alerts under this folder.
- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}).
- uid: the unique identifier of the folder to delete. this will be the _name_ in the folder response
**Required permissions**
See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation.
| Action | Scope |
| ---------------- | ----------- |
| `folders:delete` | `folders:*` |
**Example Request**:
```http
DELETE /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "fef30w4jaxla8b",
"namespace": "default",
"uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX",
"resourceVersion": "1741345736000",
"creationTimestamp": "2025-03-07T11:08:56Z",
"annotations": {
"grafana.app/folder": "xkj92m5pqw3vn4",
"grafana.app/createdBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedBy": "service-account:cef2t2rfm73lsb",
"grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z"
}
},
"spec": {
"title": "updated title"
}
}
```
Status Codes:
- **200** Deleted
- **401** Unauthorized
- **400** Bad Request
- **403** Access Denied
- **404** Folder not found
## APIs
## Identifier (id) vs unique identifier (uid)
The identifier (id) of a folder is an auto-incrementing numeric value and is only unique per Grafana install.
The unique identifier (uid) of a folder can be used for uniquely identify folders between multiple Grafana installs. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder.
The unique identifier (uid) of a folder can be used for uniquely identify folders within an org. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder.
The uid can have a maximum length of 40 characters.
## Get all folders
The identifier (id) of a folder is deprecated in favor of the unique identifier (uid).
### Get all folders
`GET /api/folders`
@ -75,7 +444,7 @@ Content-Type: application/json
]
```
## Get folder by uid
### Get folder by uid
`GET /api/folders/:uid`
@ -133,7 +502,7 @@ Status Codes:
- **403** Access Denied
- **404** Folder not found
## Create folder
### Create folder
`POST /api/folders`
@ -207,7 +576,7 @@ Status Codes:
- **403** Access Denied
- **412** - Folder already exists
## Update folder
### Update folder
`PUT /api/folders/:uid`
@ -296,7 +665,7 @@ Content-Length: 97
}
```
## Delete folder
### Delete folder
`DELETE /api/folders/:uid`
@ -342,7 +711,7 @@ Status Codes:
- **403** Access Denied
- **404** Folder not found
## Move folder
### Move folder
`POST /api/folders/:uid/move`

View File

@ -0,0 +1,52 @@
---
_build:
list: false
noindex: true
cascade:
noindex: true
description: Overview of Grafana CLI, a command line tool for managing Grafana resources as code.
keywords:
- observability
- configuration
- as code
- as-code
- dashboards
- git integration
- git sync
- github
labels:
products:
- cloud
- enterprise
- oss
cards:
items:
- description: Learn how to install Grafana CLI
height: 24
href: ./install-grafana-cli/
title: Install Grafana CLI
- description: Set up Grafana CLI
height: 24
href: ./set-up-grafana-cli/
title: Set up your Grafana CLI
- description: Learn how to manage resources with Grafana CLI
height: 24
href: ./grafanacli-workflows
title: Manage resources with Grafana CLI
title_class: pt-0 lh-1
hero:
description: Grafana CLI (`grafanactl`) is a command-line tool designed to simplify interaction with Grafana instances. It enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafanas REST API, all from the terminal. Whether you're automating workflows in CI/CD pipelines or switching between staging and production environments, Grafana CLI provides a flexible and scriptable way to manage your Grafana setup efficiently.
height: 110
level: 1
title: Grafana CLI
width: 110
title: Introduction to Grafana CLI
menuTitle: Grafana CLI
weight: 130
---
{{< docs/hero-simple key="hero" >}}
## Explore
{{< card-grid key="cards" type="simple" >}}

View File

@ -0,0 +1,226 @@
---
_build:
list: false
noindex: true
cascade:
noindex: true
description: Learn more about the supported workflows and use cases for Grafana CLI
keywords:
- workflows
- Grafana CLI
- CLI
- command line
- grafanactl
labels:
products:
- cloud
- enterprise
- oss
title: Manage resources with Grafana CLI
weight: 300
---
# Manage resources with Grafana CLI
{{< admonition type="note" >}}
`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support.
{{< /admonition >}}
## Migrate resources between environments
Using the `config` and `resources` options, you can migrate Grafana resources from one environment to another, for example, from a development to production environment.
The `config` option lets you define the configuration context.
Using `resources` with `pull`, `push`, and `serve` lets you pull a defined resource from one instance, and push that resource to another instance. `Serve` allows you to preview changes locally before pushing.
Use these steps to migrate resources between environments:
{{< admonition type="note" >}}
Currently, the `serve` command only works with dashboards.
{{< /admonition >}}
Use these steps to migrate resources between environments:
{{< admonition type="note" >}}
Resources are pulled and pushed from the `./resources` directory by default.
This directory can be configured with the `--directory`/`-d` flags.
{{< /admonition >}}
1. Make changes to dashboards and other resources using the Grafana UI in your **development instance**.
1. Pull those resources from the development environment to your local machine:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "dev"
grafanactl resources pull -d ./resources/ -o yaml # or json
```
1. (Optional) Preview the resources locally before pushing:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
grafanactl resources serve -d ./resources/
```
1. Switch to the **production instance** and push the resources:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
grafanactl resources push -d ./resources/
```
## Back up Grafana resources
This workflow helps you back up all Grafana resources from one instance and later restore them. This is useful to replicate a configuration or perform disaster recovery.
1. Use `grafanactl` to pull all resources from your target environment:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
grafanactl resources pull -d ./resources/ -o yaml # or json
```
1. Save the exported resources to version control or cloud storage.
## Restore Grafana resources
1. (Optional) Preview the backup locally:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
grafanactl resources serve -d ./resources/
```
1. To restore the resources later or restore them on another instance, push the saved resources:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
grafanactl resources push -d ./resources/
```
## Manage dashboards as code
With this workflow, you can define and manage dashboards as code, saving them to a version control system like Git. This is useful for teams that want to maintain a history of changes, collaborate on dashboard design, and ensure consistency across environments.
1. Use a dashboard generation script (for example, with the [Foundation SDK](https://github.com/grafana/grafana-foundation-sdk)). You can find an example implementation in the Grafana as code [hands-on lab repository](https://github.com/grafana/dashboards-as-code-workshop/tree/main/part-one-golang).
1. Serve and preview the output of the dashboard generator locally:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "dev"
grafanactl resources serve --script 'go run scripts/generate-dashboard.go' --watch './scripts'
```
1. When the output looks correct, generate dashboard manifest files:
```bash
go run scripts/generate-dashboard.go --generate-resource-manifests --output './resources'
```
1. Push the generated resources to your Grafana instance:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "dev"
grafanactl resources push -d ./resources/
```
## Explore and modify resources from the terminal
This section describes how to use the Grafana CLI to interact with Grafana resources directly from your terminal. These commands allow you to browse, inspect, update, and delete resources without using the Grafana UI. This approach is useful for advanced users who want to manage resources more efficiently or integrate Grafana operations into automated workflows.
### Find and delete dashboards using invalid data sources
Use this workflow to identify dashboards that reference incorrect or outdated data sources, and remove them if necessary.
1. Set the context to the appropriate environment:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
```
1. Find dashboards using specific data sources:
```bash
grafanactl resources get dashboards -ojson | jq '.items | map({ uid: .metadata.name, datasources: .spec.panels | map(.datasource.uid) })'
[
{
"uid": "important-production-dashboard",
"datasources": [
"mimir-prod"
]
},
{
"uid": "test-dashboard-from-dev",
"datasources": [
"mimir-prod",
"mimir-dev"
]
},
{
"uid": "test-dashboard-from-stg",
"datasources": [
"mimir-prod",
"mimir-stg",
"mimir-dev"
]
}
]
```
This command lists dashboard UIDs along with the data source UIDs used in their panels. You can then identify the dashboards that are using invalid or unexpected data sources.
1. Delete the identified dashboards directly:
```bash
grafanactl resources delete dashboards/test-dashboard-from-stg,test-dashboard-from-dev
✔ 2 resources deleted, 0 errors
```
### Find and deprecate dashboards using the old API version
Use this workflow to locate dashboards using a deprecated API version and mark them accordingly.
1. Set the context to the appropriate environment:
```bash
grafanactl config use-context YOUR_CONTEXT # for example "prod"
```
1. List all available resources types and versions:
```bash
grafanactl resources list
```
This command returns a list of resources, including their versions, types, and quantities:
```bash
GROUP VERSION KIND
folder.grafana.app v1 folder
dashboard.grafana.app v1 dashboard
dashboard.grafana.app v1 librarypanel
dashboard.grafana.app v2 dashboard
dashboard.grafana.app v2 librarypanel
playlist.grafana.app v1 playlist
```
1. Find dashboards that are still using an old API version:
```bash
grafanactl resources get dashboards.v1.dashboard.grafana.app
```
This command returns a table displaying the resource type, resource name, and associated namespace:
```bash
KIND NAME NAMESPACE
dashboards really-old-dashboard default
```
1. Edit each of these dashboards to add a `deprecated` tag:
```bash
grafanactl resources edit dashboards.v1.dashboard.grafana.app/really-old-dashboard -p '{"spec":{"tags":["deprecated"]}}'
```
{{< admonition type="tip" >}}
You can get help by using the `grafanactl --help` command.
{{< /admonition >}}

View File

@ -0,0 +1,51 @@
---
_build:
list: false
noindex: true
cascade:
noindex: true
description: Installation guide for Grafana CLI, a command line tool for managing Grafana Observability as Code
keywords:
- configuration
- Grafana CLI
- CLI
- command line
- grafanactl
- installation
labels:
products:
- cloud
- enterprise
- oss
title: Install Grafana CLI
weight: 100
---
# Install Grafana CLI
You can install the project using one of the following supported methods:
## 1. Download a pre-built binary
Download the latest binary for your platform from the [Releases page](https://github.com/grafana/grafanactl/releases).
Prebuilt binaries are available for a variety of operating systems and architectures. Visit the latest release page, and scroll down to the Assets section.
To install the binary, follow the instructions below:
1. Download the archive for the desired operating system and architecture
1. Extract the archive
1. Move the executable to the desired directory
1. Ensure this directory is included in the PATH environment variable
1. Verify that you have execute permission on the file
## 2. Build from source
To build `grafanactl` from source you must:
- Have `git` installed
- Have `go` v1.24 (or greater) installed
```bash
go install github.com/grafana/grafanactl/cmd@latest
```

View File

@ -0,0 +1,122 @@
---
_build:
list: false
noindex: true
cascade:
noindex: true
description: Configuration guide for Grafana CLI, a command line tool for managing Grafana resources as code.
keywords:
- configuration
- Grafana CLI
- CLI
- command line
- grafanactl
labels:
products:
- cloud
- enterprise
- oss
title: Set up Grafana CLI
weight: 200
---
# Set up Grafana CLI
You can configure Grafana CLI in two ways: using environment variables or through a configuration file.
- **Environment variables** are ideal for CI environments and support a single context.
- **Configuration files** can manage multiple contexts, making it easier to switch between different Grafana instances.
## Use environment variables
Grafana CLI communicates with Grafana via its REST API, which requires authentication credentials.
At a minimum, set the URL of your Grafana instance and the organization ID:
```bash
GRAFANA_SERVER='http://localhost:3000' GRAFANA_ORG_ID='1' grafanactl config check
```
Depending on your authentication method, you may also need to set:
- A [token](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_token) for a [Grafana service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) (recommended)
- A [username](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_user) and [password](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_password) for basic authentication
To persist your configuration, consider [creating a context](#defining-contexts).
A full list of supported environment variables is available in the [reference documentation](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#environment-variables-reference).
## Define contexts
Contexts allow you to easily switch between multiple Grafana instances. By default, the CLI uses a context named `default`.
To configure the `default` context:
```bash
grafanactl config set contexts.default.grafana.server http://localhost:3000
grafanactl config set contexts.default.grafana.org-id 1
# Authenticate with a service account token
grafanactl config set contexts.default.grafana.token service-account-token
# Or use basic authentication
grafanactl config set contexts.default.grafana.user admin
grafanactl config set contexts.default.grafana.password admin
```
You can define additional contexts in the same way:
```bash
grafanactl config set contexts.staging.grafana.server https://staging.grafana.example
grafanactl config set contexts.staging.grafana.org-id 1
```
{{< admonition type="note" >}}
In these examples, `default` and `staging` are the names of the contexts.
{{< /admonition >}}
## Configuration file
Grafana CLI stores its configuration in a YAML file. The CLI determines the configuration file location in the following order:
1. If the `--config` flag is provided, the specified file is used.
2. If `$XDG_CONFIG_HOME` is set:
`$XDG_CONFIG_HOME/grafanactl/config.yaml`
3. If `$HOME` is set:
`$HOME/.config/grafanactl/config.yaml`
4. If `$XDG_CONFIG_DIRS` is set:
`$XDG_CONFIG_DIRS/grafanactl/config.yaml`
{{< admonition type="note" >}}
Use `grafanactl config check` to display the configuration file currently in use.
{{< /admonition >}}
## Useful commands
Check the current configuration:
```bash
grafanactl config check
```
{{< admonition type="note" >}}
This command is useful to troubleshoot your configuration.
{{< /admonition >}}
List all available contexts:
```bash
grafanactl config list-contexts
```
Switch to a specific context:
```bash
grafanactl config use-context staging
```
View the full configuration:
```bash
grafanactl config view
```

View File

@ -855,6 +855,23 @@ Path to the default home dashboard. If this value is empty, then Grafana uses St
On Linux, Grafana uses `/usr/share/grafana/public/dashboards/home.json` as the default home dashboard location.
{{< /admonition >}}
### `[dashboard_cleanup]`
Settings related to cleaning up associated dashboards information if the dashboard was deleted through /apis.
#### `interval`
How often to run the job to cleanup associated resources. The default interval is `30s`. The minimum allowed value is `10s` to ensure the system isn't overloaded.
The interval string must include a unit suffix (ms, s, m, h), e.g. 30s or 1m.
#### `batch_size`
Number of deleted dashboards to process in each batch during the cleanup process.
Default: `10`, Minimum: `5`, Maximum: `200`.
Increasing this value allows processing more dashboards in each cleanup cycle but may impact system performance.
<hr />
### `[datasources]`

View File

@ -46,7 +46,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |

View File

@ -46,22 +46,30 @@ We provide release documentation in multiple places to address different needs:
## When to expect releases
Currently, Grafana is on a monthly release cycle. Heres a look at scheduled releases for the first half of 2025:
Currently, Grafana is on a monthly release cycle. Heres a look at scheduled releases for 2025:
| **Anticipated release date** | **Grafana versions** | **Release type** |
| ---------------------------- | --------------------------------- | ---------------- |
| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching |
| Feb. 18, 2025 | Supported versions | Patching |
| March 25, 2025 | 11.6 & Supported versions | Minor & patching |
| April 15, 2025 | Supported versions | Patching |
| May 5, 2025 | Grafana 12.0 & Supported versions | Major & patching |
| **Release date** | **Grafana versions** | **Release type** |
| ---------------- | ------------------------- | ---------------- |
| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching |
| Feb. 18, 2025 | Supported versions | Patching |
| March 25, 2025 | 11.6 & Supported versions | Minor & patching |
| April 23, 2025 | Supported versions | Patching |
| May 5, 2025 | Grafana 12.0 | Major only |
| May 20, 2025 | Supported versions | Patching |
| June 17, 2025 | Supported versions | Patching |
| July 22, 2025 | 12.1 & Supported versions | Minor & patching |
| Aug. 12, 2025 | Supported versions | Patching |
| Sept. 23, 2025 | 12.2 & Supported versions | Minor & patching |
| Oct. 21, 2025 | Supported versions | Patching |
| Nov. 18, 2025 | 12.3 & Supported versions | Minor & patching |
| Dec. 16, 2025 | Supported versions | Patching |
### A few important notes
- The schedule above outlines how we plan release dates. However, unforeseen events and circumstances may cause dates to change.
- High severity security and feature degradation incidents will result in ad-hoc releases that are not scheduled ahead of time.
- Patching releases are for the current (last released) minor version of Grafana. Additional older versions of Grafana may be included if there is a critical bug or security vulnerability that needs to be patched.
- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season, these dates will be announced as the holiday season approaches. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident.
- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident.
## Grafana security releases: improved version naming convention
@ -81,34 +89,57 @@ This naming convention should make it easier to identify security updates and th
## What to know about version support
Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether it's time to upgrade, its important that you understand the level of support provided for your current version.
Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether its time to upgrade, its important that you understand the level of support provided for your current version.
For self-managed Grafana (both Enterprise and OSS), the support for versions is as follows:
For self-managed Grafana (both Enterprise and OSS), the support for versions follows these rules:
- Support for each minor release extends to nine months after the release date.
- Support for the last minor release of a major version is extended an additional six months, for a total of 15 months of support after the release date.
- Each minor release is supported for 9 months after its release date
- The last minor release of a major version receives extended support for 15 months after its release date
- Support levels change as new versions are released:
- **Full Support**: The most recently released major/minor (and the last minor of the previous major) version receive full support including new features, bug fixes, and security patches
- **Security & Critical Bugs Only**: Versions that are not the most recently released major/minor (or the last minor of the previous major) version, but still within their support period, receive only security patches and critical bug fixes
- **Not Supported**: Versions beyond their support period receive no updates
Here is an overview of projected version support through 2025:
Here is an overview of version support through 2026:
| **Version** | **Release date** | **Support end of life (EOL)** |
| ----------------------- | ---------------- | --------------------------------------- |
| 10.4 (Last minor of 10) | March 2024 | June 2025 (extended support) |
| 11.0 | May 2024 | NO LONGER SUPPORTED as of February 2025 |
| 11.1 | June 2024 | NO LONGER SUPPORTED as of March 2025 |
| 11.2 | August 2024 | May 2025 |
| 11.3 | October 2024 | July 2025 |
| 11.4 | December 2024 | September 2025 |
| 11.5 | January 2025 | October 2025 |
| 11.6 (Last minor of 11) | March 2025 | June 2026 |
| 12.0 | May 2025 | January 2026 |
| **Version** | **Release date** | **Support end date** | **Support level** |
| ------------------------- | ------------------ | -------------------- | ----------------------------- |
| 9.5.x (Last minor of 9) | April 26, 2023 | July 26, 2024 | Supported for Azure Only |
| 10.0.x | June 13, 2023 | March 13, 2024 | Not Supported |
| 10.1.x | August 22, 2023 | May 22, 2024 | Not Supported |
| 10.2.x | October 24, 2023 | July 24, 2024 | Not Supported |
| 10.3.x | January 23, 2024 | October 23, 2024 | Not Supported |
| 10.4.x (Last minor of 10) | March 5, 2024 | June 5, 2025 | Security & Critical Bugs Only |
| 11.0.x | May 14, 2024 | February 14, 2025 | Security & Critical Bugs Only |
| 11.1.x | June 25, 2024 | April 23, 2025 | Security & Critical Bugs Only |
| 11.2.x | August 27, 2024 | May 27, 2025 | Security & Critical Bugs Only |
| 11.3.x | October 22, 2024 | July 22, 2025 | Security & Critical Bugs Only |
| 11.4.x | December 5, 2024 | September 5, 2025 | Security & Critical Bugs Only |
| 11.5.x | January 28, 2025 | October 28, 2025 | Security & Critical Bugs Only |
| 11.6.x (Last minor of 11) | March 25, 2025 | May 25, 2026 | Full Support |
| 12.0.x | May 5, 2025 | February 5, 2026 | Full Support until next minor |
| 12.1.x | July 22, 2025 | April 22, 2026 | Full Support until next minor |
| 12.2.x | September 23, 2025 | June 23, 2026 | Full Support until next minor |
| 12.3.x | November 18, 2025 | August 18, 2026 | Full Support until next minor |
## How are these versions supported?
The level of support changes as new versions of Grafana are released. Here are a few details to keep in mind:
The level of support changes as new versions of Grafana are released. Here are the key details:
- The current (most recently released) version of Grafana gets the highest level of support. Releases for this version include all the new features along with all bug fixes.
- All supported versions receive security patches for vulnerabilities impacting that version.
- All supported versions receive patches for bugs that cause critical feature degradation incidents.
- **Full Support**:
- All new features
- All bug fixes
- Security patches
- Regular updates
- **Security & Critical Bugs Only**:
- Security vulnerability patches
- Critical bug fixes that cause feature degradation
- No new features
- **Not Supported**: Versions beyond their support period receive no updates and should be upgraded.
Keeping all this in mind, users that want to receive the most recent features and all bug fixes should be on the current (most recently released) version of Grafana.

View File

@ -1,6 +1,6 @@
{
"name": "@test-plugins/extensions-test-app",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"private": true,
"scripts": {
"build": "webpack -c ./webpack.config.ts --env production",

View File

@ -1,6 +1,6 @@
{
"name": "@test-plugins/grafana-e2etest-datasource",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"private": true,
"scripts": {
"build": "webpack -c ./webpack.config.ts --env production",

2
go.mod
View File

@ -77,7 +77,7 @@ require (
github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 // @grafana/alerting-backend
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics

4
go.sum
View File

@ -1565,8 +1565,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 h1:xdTzIQqFJxzRLgODsFMAT/8LIWtqCXLqltaWo6AmFVA=
github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1/go.mod h1:XTbf+jPVVMC1C2NuSGa3hIVbO+KSQD0qHjiUr1GoWVI=
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb h1:Nkqatz7R7K/2YaK+7pw8CAYgylKfksPVeT7gMARKjwI=
github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA=
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y=
github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us=
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=

View File

@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"npmClient": "yarn",
"version": "12.0.0-pre"
"version": "12.1.0-pre"
}

View File

@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"repository": "github:grafana/grafana",
"scripts": {
"build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress",
@ -64,7 +64,8 @@
"plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'",
"plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100",
"process-specs": "node --experimental-strip-types scripts/process-specs.ts",
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts"
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts",
"generate:api-client": "NODE_OPTIONS='--experimental-strip-types' plop --plopfile public/app/api/generator/plopfile.ts"
},
"grafana": {
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/",
@ -168,7 +169,7 @@
"crashme": "0.0.15",
"css-loader": "7.1.2",
"css-minimizer-webpack-plugin": "7.0.0",
"cypress": "13.10.0",
"cypress": "14.3.2",
"cypress-file-upload": "5.0.8",
"cypress-recurse": "^1.35.3",
"esbuild": "0.25.0",
@ -216,6 +217,7 @@
"nx": "20.7.1",
"openapi-types": "^12.1.3",
"pdf-parse": "^1.1.1",
"plop": "^4.0.1",
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"postcss-reporter": "7.1.0",
@ -255,9 +257,10 @@
"@emotion/css": "11.13.5",
"@emotion/react": "11.14.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@floating-ui/react": "0.27.7",
"@floating-ui/react": "0.27.8",
"@formatjs/intl-durationformat": "^0.7.0",
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/alerting": "workspace:*",
"@grafana/aws-sdk": "0.6.0",
"@grafana/azure-sdk": "0.0.7",
"@grafana/data": "workspace:*",
@ -274,8 +277,8 @@
"@grafana/plugin-ui": "0.10.5",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "6.10.0",
"@grafana/scenes-react": "6.10.0",
"@grafana/scenes": "6.10.2",
"@grafana/scenes-react": "6.10.2",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
@ -344,7 +347,7 @@
"lru-cache": "11.0.2",
"lru-memoize": "^1.1.0",
"lucene": "^2.1.1",
"marked": "15.0.6",
"marked": "15.0.11",
"memoize-one": "6.0.0",
"micro-memoize": "^4.1.2",
"ml-regression-polynomial": "^3.0.0",

View File

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,9 @@
# Grafana Alerting
> **@grafana/alerting is currently in ALPHA**.
This package is a collection of types, libraries, utilities, React components and hooks for interacting with the Grafana alerting system and is meant to be the foundation of alerting-related features in Grafana and its plugins.
We plan to eventually publish this package on NPM; however, it is currently in ALPHA and is not yet ready for public consumption. We are actively working on this package and will be making breaking changes to it as we iterate on the design and implementation.
Once we've settled on a public API that we feel is stable and useful, we will publish this package on NPM and provide documentation on how to use it.

View File

@ -0,0 +1,63 @@
{
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/alerting",
"version": "12.0.0-pre",
"private": true,
"description": "Grafana Alerting Library Build vertical integrations on top of the industry-leading alerting solution",
"keywords": [
"typescript",
"grafana",
"alerting",
"alertmanager",
"prometheus"
],
"sideEffects": false,
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git",
"directory": "packages/grafana-alerting"
},
"main": "src/index.ts",
"types": "src/index.ts",
"module": "src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"require": "./src/index.ts"
},
"./internal": {
"import": "./src/internal.ts",
"require": "./src/internal.ts"
},
"./unstable": {
"import": "./src/unstable.ts",
"require": "./src/unstable.ts"
}
},
"scripts": {
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"codegen": "yarn run rtk-query-codegen-openapi ./scripts/codegen.ts"
},
"devDependencies": {
"@grafana/tsconfig": "^2.0.0",
"@rtk-query/codegen-openapi": "^2.0.0",
"@types/lodash": "^4",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"react": "18.3.1",
"react-dom": "18.3.1",
"type-fest": "^4.40.0",
"typescript": "5.7.3"
},
"peerDependencies": {
"@grafana/runtime": "^12.0.0-pre",
"@grafana/ui": "^12.0.0-pre",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"dependencies": {
"@reduxjs/toolkit": "^2.7.0",
"lodash": "^4.17.21"
}
}

View File

@ -0,0 +1,6 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"tags": ["scope:package", "type:ui"],
"targets": {}
}

View File

@ -0,0 +1,21 @@
/**
* This script will generate TypeScript type definitions and a RTKQ client for the alerting k8s APIs.
* It downloads the OpenAPI schema from a running Grafana instance and generates the types.
*
* Run `yarn run codegen` from the "grafana-alerting" package to invoke this script.
*/
import { type ConfigFile } from '@rtk-query/codegen-openapi';
import { resolve } from 'node:path';
// these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go", see the README in the "openapi_snapshots" directory
const OPENAPI_SCHEMA_LOCATION = resolve(
'../../../pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json'
);
export default {
exportName: 'alertingAPI',
schemaFile: OPENAPI_SCHEMA_LOCATION,
apiFile: '../src/grafana/api.ts',
outputFile: resolve('../src/grafana/api.gen.ts'),
tag: true,
} satisfies ConfigFile;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const BASE_URL = '/';
export const api = createApi({
reducerPath: 'grafanaAlertingAPI',
baseQuery: fetchBaseQuery({
baseUrl: BASE_URL,
}),
endpoints: () => ({}),
});

View File

@ -0,0 +1,50 @@
import { chain } from 'lodash';
import { Combobox, ComboboxOption } from '@grafana/ui';
import { useListContactPoints } from '../hooks/useContactPoints';
import { ContactPoint } from '../types';
import { getContactPointDescription } from '../utils';
const collator = new Intl.Collator('en', { sensitivity: 'accent' });
type ContactPointSelectorProps = {
onChange: (contactPoint: ContactPoint) => void;
};
/**
* Contact Point Combobox which lists all available contact points
* @TODO make ComboBox accept a ReactNode so we can use icons and such
*/
function ContactPointSelector({ onChange }: ContactPointSelectorProps) {
const { currentData: contactPoints, isLoading } = useListContactPoints();
// Create a mapping of options with their corresponding contact points
const contactPointOptions = chain(contactPoints?.items)
.toArray()
.map((contactPoint) => ({
option: {
label: contactPoint.spec.title,
value: contactPoint.metadata.uid ?? contactPoint.spec.title,
description: getContactPointDescription(contactPoint),
},
contactPoint,
}))
.value()
.sort((a, b) => collator.compare(a.option.label, b.option.label));
const options = contactPointOptions.map<ComboboxOption>((item) => item.option);
const handleChange = ({ value }: ComboboxOption<string>) => {
const selectedItem = contactPointOptions.find(({ option }) => option.value === value);
if (!selectedItem) {
return;
}
onChange(selectedItem.contactPoint);
};
return <Combobox loading={isLoading} onChange={handleChange} options={options} />;
}
export { ContactPointSelector };

View File

@ -0,0 +1,29 @@
import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react';
import { config } from '@grafana/runtime';
import { alertingAPI, ListReceiverApiArg } from '../../api.gen';
import { EnhancedListReceiverResponse } from '../types';
const { namespace } = config;
// this is a workaround for the fact that the generated types are not narrow enough
type EnhancedHookResult = TypedUseQueryHookResult<
EnhancedListReceiverResponse,
ListReceiverApiArg,
ReturnType<typeof fetchBaseQuery>
>;
/**
* useListContactPoints is a hook that fetches a list of contact points
*
* This function wraps the alertingAPI.useListReceiverQuery with proper typing
* to ensure that the returned ContactPoints are correctly typed in the data.items array.
*
* It automatically uses the configured namespace for the query.
*/
function useListContactPoints() {
return alertingAPI.useListReceiverQuery<EnhancedHookResult>({ namespace });
}
export { useListContactPoints };

View File

@ -0,0 +1,76 @@
/**
* This file contains enhanced and type-narrowed versions of the types generated by the RTKQ codegen package.
*/
import { MergeDeep, MergeExclusive, OverrideProperties } from 'type-fest';
import {
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver as ContactPointV0Alpha1,
ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration as IntegrationV0Alpha1,
ListReceiverApiResponse,
} from '../api.gen';
type GenericIntegration = OverrideProperties<
IntegrationV0Alpha1,
{
settings: Record<string, unknown>;
}
>;
// Based on https://github.com/grafana/alerting/blob/main/receivers/email/config.go#L20-L25
type EmailIntegration = OverrideProperties<
GenericIntegration,
{
type: 'email';
settings: {
singleEmail?: boolean;
addresses: string;
message?: string;
subject?: string;
};
secureFields: never; // email doesn't have any secure fields
}
>;
// Based on https://github.com/grafana/alerting/blob/main/receivers/slack/config.go
type SlackIntegration = OverrideProperties<
GenericIntegration,
{
type: 'slack';
settings: {
endpointUrl?: string;
url?: string;
recipient?: string;
text?: string;
title?: string;
username?: string;
icon_emoji?: string;
icon_url?: string;
mentionChannel?: string;
mentionUsers?: string; // comma separated string
mentionGroups?: string; // comma separated string
color?: string;
};
// secureFields is a union type that can be either a token or a URL but you can't have both
secureFields: MergeExclusive<{ token: string }, { url: string }>;
}
>;
export type Integration = EmailIntegration | SlackIntegration | GenericIntegration;
// Enhanced version of ContactPoint with typed integrations
// ⚠️ MergeDeep does not check if the property you are overriding exists in the base type and there is no "DeepOverrideProperties" helper
export type ContactPoint = MergeDeep<
ContactPointV0Alpha1,
{
spec: {
integrations: Integration[];
};
}
>;
export type EnhancedListReceiverResponse = OverrideProperties<
ListReceiverApiResponse,
{
items: ContactPoint[];
}
>;

View File

@ -0,0 +1,31 @@
import { countBy, isEmpty } from 'lodash';
import { ContactPoint } from './types';
/**
* Generates a human-readable description of a ContactPoint by summarizing its integrations.
* If the ContactPoint has no integrations, it returns an empty placeholder text.
*
* For integrations, it counts the occurrences of each type and formats them as a comma-separated list.
* Multiple integrations of the same type are indicated with a count in parentheses.
*
* @param contactPoint - The ContactPoint object to describe
* @returns A string description of the ContactPoint's integrations
*/
export function getContactPointDescription(contactPoint: ContactPoint): string {
if (isEmpty(contactPoint.spec.integrations)) {
return '<empty contact point>';
}
// Count the occurrences of each integration type
const integrationCounts = countBy(contactPoint.spec.integrations, (integration) => integration.type);
const description = Object.entries(integrationCounts)
.map(([type, count]) => {
// either "email" or "email (2)" but not "email (1)"
return count > 1 ? `${type} (${count})` : type;
})
.join(', ');
return description;
}

View File

@ -0,0 +1,9 @@
/**
* Export things here that you want to be available under @grafana/alerting
*
* This implies everything in here is public API and should be considered stable and treated as such make sure to
* think carefully about what you export here and the interfaces / data structures.
*
* Breaking changes should be avoided to maintain backwards compatibility for consumers of this package.
*/
export default {};

View File

@ -0,0 +1,6 @@
/**
* Export things here that you want to be available under @grafana/alerting/internal
*/
export { alertingAPI } from './grafana/api.gen';
export default {};

View File

@ -0,0 +1,8 @@
/**
* Export things here that you want to be available under @grafana/alerting/unstable
*/
// Contact Points
export * from './grafana/contactPoints/types';
export { useListContactPoints } from './grafana/contactPoints/hooks/useContactPoints';
export { ContactPointSelector } from './grafana/contactPoints/components/ContactPointSelector';

View File

@ -0,0 +1,4 @@
{
"exclude": ["dist", "node_modules", "test", "**/*.test.ts*"],
"extends": "./tsconfig.json"
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"declarationDir": "./compiled",
"emitDeclarationOnly": true,
"isolatedModules": true,
"rootDirs": ["."]
},
"exclude": ["dist/**/*"],
"extends": "@grafana/tsconfig",
"include": ["typings/jest", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts", "src/**/*.ts*"]
}

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@ -56,7 +56,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
"@grafana/schema": "12.0.0-pre",
"@grafana/schema": "12.1.0-pre",
"@types/d3-interpolate": "^3.0.0",
"@types/string-hash": "1.1.3",
"@types/systemjs": "6.15.1",
@ -67,7 +67,7 @@
"fast_array_intersect": "1.1.0",
"history": "4.10.1",
"lodash": "4.17.21",
"marked": "15.0.6",
"marked": "15.0.11",
"marked-mangle": "1.1.10",
"moment": "2.30.1",
"moment-timezone": "0.5.47",

View File

@ -354,11 +354,6 @@ export interface FeatureToggles {
*/
cloudWatchBatchQueries?: boolean;
/**
* Enables feature recovery threshold (aka hysteresis) for threshold server-side expression
* @default true
*/
recoveryThreshold?: boolean;
/**
* Enables the loki data source to request structured metadata from the Loki server
* @default true
*/
@ -1019,6 +1014,10 @@ export interface FeatureToggles {
*/
pluginsAutoUpdate?: boolean;
/**
* Register MT frontend
*/
multiTenantFrontend?: boolean;
/**
* Enables the alerting list view v2 preview toggle
*/
alertingListViewV2PreviewToggle?: boolean;
@ -1027,4 +1026,9 @@ export interface FeatureToggles {
* @default false
*/
alertRuleUseFiredAtForStartsAt?: boolean;
/**
* Enables the alerting bulk actions in the UI
* @default true
*/
alertingBulkActionsInUI?: boolean;
}

View File

@ -139,6 +139,7 @@ export const availableIconsIndex = {
'gf-layout-simple': true,
'gf-logs': true,
'gf-ml': true,
'gf-ml-alt': true,
'gf-movepane-left': true,
'gf-movepane-right': true,
'gf-portrait': true,

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana End-to-End Test Selectors Library",
"keywords": [
"cli",

View File

@ -1,7 +1,7 @@
{
"name": "@grafana/eslint-plugin",
"description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"main": "./index.cjs",
"author": "Grafana Labs",
"license": "Apache-2.0",

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/flamegraph",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana flamegraph visualization component",
"keywords": [
"grafana",
@ -44,8 +44,8 @@
],
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "12.0.0-pre",
"@grafana/ui": "12.0.0-pre",
"@grafana/data": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"@leeoniya/ufuzzy": "1.0.18",
"d3": "^7.8.5",
"lodash": "4.17.21",

View File

@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"name": "@grafana/o11y-ds-frontend",
"private": true,
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Library to manage traces in Grafana.",
"sideEffects": false,
"repository": {
@ -18,12 +18,12 @@
},
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "12.0.0-pre",
"@grafana/e2e-selectors": "12.0.0-pre",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
"@grafana/plugin-ui": "0.10.5",
"@grafana/runtime": "12.0.0-pre",
"@grafana/schema": "12.0.0-pre",
"@grafana/ui": "12.0.0-pre",
"@grafana/runtime": "12.1.0-pre",
"@grafana/schema": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"react-select": "5.10.0",
"react-use": "17.6.0",
"rxjs": "7.8.1",

View File

@ -2,7 +2,7 @@
"name": "@grafana/plugin-configs",
"description": "Shared dependencies and files for core plugins",
"private": true,
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"dependencies": {
"tslib": "2.8.1"
},

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "AGPL-3.0-only",
"name": "@grafana/prometheus",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana Prometheus Library",
"keywords": [
"typescript"
@ -37,13 +37,13 @@
},
"dependencies": {
"@emotion/css": "11.13.5",
"@floating-ui/react": "0.27.7",
"@grafana/data": "12.0.0-pre",
"@grafana/e2e-selectors": "12.0.0-pre",
"@floating-ui/react": "0.27.8",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
"@grafana/plugin-ui": "0.10.5",
"@grafana/runtime": "12.0.0-pre",
"@grafana/schema": "12.0.0-pre",
"@grafana/ui": "12.0.0-pre",
"@grafana/runtime": "12.1.0-pre",
"@grafana/schema": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"@hello-pangea/dnd": "17.0.0",
"@leeoniya/ufuzzy": "1.0.18",
"@lezer/common": "1.2.3",

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@ -53,11 +53,11 @@
"postpack": "mv package.json.bak package.json && rimraf ./unstable"
},
"dependencies": {
"@grafana/data": "12.0.0-pre",
"@grafana/e2e-selectors": "12.0.0-pre",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
"@grafana/faro-web-sdk": "^1.13.2",
"@grafana/schema": "12.0.0-pre",
"@grafana/ui": "12.0.0-pre",
"@grafana/schema": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"@types/systemjs": "6.15.1",
"history": "4.10.1",
"lodash": "4.17.21",

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/schema",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana Schema Library",
"keywords": [
"typescript"

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
limit: number;

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting {
/**

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions {
displayMode: common.BarGaugeDisplayMode;

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export enum VizDisplayMode {
Candles = 'candles',

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export enum HorizontalConstraint {
Center = 'center',

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface MetricStat {
/**

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
/**

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
selectedSeries: number;

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export type UpdateConfig = {
render: boolean,

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.SingleStatBaseOptions {
minVizHeight: number;

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
basemap: ui.MapLayerOptions;

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
/**
* Controls the color mode of the heatmap

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip {
/**

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
controlsStorageKey?: string;

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
dedupStrategy: common.LogsDedupStrategy;

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export enum QueryEditorMode {
Builder = 'builder',

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
/**

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface ArcOption {
/**

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
/**
* Select the pie chart display style.

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.SingleStatBaseOptions {
colorMode: common.BigValueColorMode;

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones {
/**

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones {
/**

View File

@ -10,7 +10,7 @@
import * as ui from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options {
/**

View File

@ -8,7 +8,7 @@
//
// Run 'make gen-cue' from repository root to regenerate.
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export enum TextMode {
Code = 'code',

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export interface Options extends common.OptionsWithTimezones {
legend: common.VizLegendOptions;

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
/**
* Identical to timeseries... except it does not have timezone settings

View File

@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.0.0-pre";
export const pluginVersion = "12.1.0-pre";
export enum PointShape {
Circle = 'circle',

View File

@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "@grafana/sql",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git",
@ -15,11 +15,11 @@
},
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "12.0.0-pre",
"@grafana/e2e-selectors": "12.0.0-pre",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
"@grafana/plugin-ui": "0.10.5",
"@grafana/runtime": "12.0.0-pre",
"@grafana/ui": "12.0.0-pre",
"@grafana/runtime": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"@react-awesome-query-builder/ui": "6.6.14",
"immutable": "5.0.3",
"lodash": "4.17.21",

View File

@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "12.0.0-pre",
"version": "12.1.0-pre",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@ -65,11 +65,11 @@
"@emotion/css": "11.13.5",
"@emotion/react": "11.14.0",
"@emotion/serialize": "1.3.3",
"@floating-ui/react": "0.27.7",
"@grafana/data": "12.0.0-pre",
"@grafana/e2e-selectors": "12.0.0-pre",
"@floating-ui/react": "0.27.8",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
"@grafana/faro-web-sdk": "^1.13.2",
"@grafana/schema": "12.0.0-pre",
"@grafana/schema": "12.1.0-pre",
"@hello-pangea/dnd": "17.0.0",
"@leeoniya/ufuzzy": "1.0.18",
"@monaco-editor/react": "4.6.0",

View File

@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { autoUpdate, flip, shift, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { ChangeEvent, useState } from 'react';
import { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react';
import { GrafanaTheme2, dateTime } from '@grafana/data';
@ -11,7 +11,7 @@ import { DatePicker } from '../DatePicker/DatePicker';
export const formatDate = (date: Date | string) => dateTime(date).format('L');
/** @public */
export interface DatePickerWithInputProps extends Omit<InputProps, 'ref' | 'value' | 'onChange'> {
export interface DatePickerWithInputProps extends Omit<InputProps, 'value' | 'onChange'> {
/** Value selected by the DatePicker */
value?: Date | string;
/** The minimum date the value can be set to */
@ -27,78 +27,78 @@ export interface DatePickerWithInputProps extends Omit<InputProps, 'ref' | 'valu
}
/** @public */
export const DatePickerWithInput = ({
value,
minDate,
maxDate,
onChange,
closeOnSelect,
placeholder = 'Date',
...rest
}: DatePickerWithInputProps) => {
const [open, setOpen] = useState(false);
const styles = useStyles2(getStyles);
export const DatePickerWithInput = forwardRef<HTMLInputElement, DatePickerWithInputProps>(
({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => {
const [open, setOpen] = useState(false);
const styles = useStyles2(getStyles);
// the order of middleware is important!
// see https://floating-ui.com/docs/arrow#order
const middleware = [
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
// the order of middleware is important!
// see https://floating-ui.com/docs/arrow#order
const middleware = [
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const { context, refs, floatingStyles } = useFloating({
open,
placement: 'bottom-start',
onOpenChange: setOpen,
middleware,
whileElementsMounted: autoUpdate,
strategy: 'fixed',
});
const { context, refs, floatingStyles } = useFloating<HTMLInputElement>({
open,
placement: 'bottom-start',
onOpenChange: setOpen,
middleware,
whileElementsMounted: autoUpdate,
strategy: 'fixed',
});
const click = useClick(context);
const dismiss = useDismiss(context);
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
const click = useClick(context);
const dismiss = useDismiss(context);
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
return (
<div className={styles.container}>
<Input
ref={refs.setReference}
type="text"
autoComplete={'off'}
placeholder={placeholder}
value={value ? formatDate(value) : value}
onChange={(ev: ChangeEvent<HTMLInputElement>) => {
// Allow resetting the date
if (ev.target.value === '') {
onChange('');
}
}}
className={styles.input}
{...rest}
{...getReferenceProps()}
/>
<div className={styles.popover} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
<DatePicker
isOpen={open}
value={value && typeof value !== 'string' ? value : dateTime().toDate()}
minDate={minDate}
maxDate={maxDate}
onChange={(ev) => {
onChange(ev);
if (closeOnSelect) {
setOpen(false);
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => refs.domReference.current, [
refs.domReference,
]);
return (
<div className={styles.container}>
<Input
ref={refs.setReference}
type="text"
autoComplete={'off'}
placeholder={placeholder}
value={value ? formatDate(value) : value}
onChange={(ev: ChangeEvent<HTMLInputElement>) => {
// Allow resetting the date
if (ev.target.value === '') {
onChange('');
}
}}
onClose={() => setOpen(false)}
className={styles.input}
{...rest}
{...getReferenceProps()}
/>
<div className={styles.popover} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
<DatePicker
isOpen={open}
value={value && typeof value !== 'string' ? value : dateTime().toDate()}
minDate={minDate}
maxDate={maxDate}
onChange={(ev) => {
onChange(ev);
if (closeOnSelect) {
setOpen(false);
}
}}
onClose={() => setOpen(false)}
/>
</div>
</div>
</div>
);
};
);
}
);
DatePickerWithInput.displayName = 'DatePickerWithInput';
const getStyles = (theme: GrafanaTheme2) => {
return {

View File

@ -5,6 +5,6 @@ import { Icon } from '../Icon/Icon';
export function DropdownIndicator({ selectProps }: DropdownIndicatorProps) {
const isOpen = selectProps.menuIsOpen;
const icon = isOpen ? 'search' : 'angle-down';
const size = isOpen ? 'sm' : 'md';
const size = 'md';
return <Icon name={icon} size={size} />;
}

View File

@ -1,5 +1,5 @@
import { isArray, negate } from 'lodash';
import { ComponentProps, useCallback, useEffect, useRef, useState } from 'react';
import { ComponentProps, useCallback, useEffect, useRef, useState, useImperativeHandle } from 'react';
import * as React from 'react';
import {
default as ReactSelect,
@ -152,16 +152,19 @@ export function SelectBase<T, Rest = {}>({
isValidNewOption,
formatOptionLabel,
hideSelectedOptions,
selectRef,
...rest
}: SelectBaseProps<T> & Rest) {
const theme = useTheme2();
const styles = getSelectStyles(theme);
const reactSelectRef = useRef<{ controlRef: HTMLElement }>(null);
const reactSelectRef = useRef<HTMLElement & { controlRef: HTMLElement }>(null);
const [closeToBottom, setCloseToBottom] = useState<boolean>(false);
const selectStyles = useCustomSelectStyles(theme, width);
const [hasInputValue, setHasInputValue] = useState<boolean>(!!inputValue);
useImperativeHandle(selectRef, () => reactSelectRef.current!, []);
// Infer the menu position for asynchronously loaded options. menuPlacement="auto" doesn't work when the menu is
// automatically opened when the component is created (it happens in SegmentSelect by setting menuIsOpen={true}).
// We can remove this workaround when the bug in react-select is fixed: https://github.com/JedWatson/react-select/issues/4936

View File

@ -116,6 +116,8 @@ export interface SelectCommonProps<T> {
loadingMessage?: string;
/** Disables wrapping of multi value values when closed */
noMultiValueWrap?: boolean;
/** Use a custom ref because generic component as output of React.forwardRef is not directly possible */
selectRef?: React.Ref<HTMLElement>;
}
export interface SelectAsyncProps<T> {

View File

@ -48,6 +48,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/frontend"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
@ -85,6 +86,17 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/login", hs.LoginView)
r.Get("/invite/:code", hs.Index)
if hs.Features.IsEnabledGlobally(featuremgmt.FlagMultiTenantFrontend) {
index, err := frontend.NewIndexProvider(hs.Cfg, hs.License)
if err != nil {
panic(err) // ???
}
r.Get("/mtfe", index.HandleRequest)
// Temporarily expose the full bootdata via API
r.Get("/bootdata", reqNoAuth, hs.GetBootdata)
}
// authed views
r.Get("/", reqSignedIn, hs.Index)
r.Get("/profile/", reqSignedInNoAnonymous, hs.Index)

View File

@ -536,12 +536,6 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
return apierrors.ToDashboardErrorResponse(ctx, hs.pluginStore, saveErr)
}
// Clear permission cache for the user who's created the dashboard, so that new permissions are fetched for their next call
// Required for cases when caller wants to immediately interact with the newly created object
if newDashboard {
hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
}
// connect library panels for this dashboard after the dashboard is stored and has an ID
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard)
if err != nil {

View File

@ -946,7 +946,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
if dashboardService == nil {
dashboardService, err = service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(),
)
@ -956,7 +956,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(), sort.ProvideService(),
serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(),
)

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