Merge branch 'grafana:main' into main
This commit is contained in:
+6
-6
@@ -4,12 +4,12 @@
|
||||
# 3. Run `make drone`
|
||||
# More information about this process here: https://github.com/grafana/deployment_tools/blob/master/docs/infrastructure/drone/signing.md
|
||||
|
||||
load('scripts/pr.star', 'pr_pipelines')
|
||||
load('scripts/main.star', 'main_pipelines')
|
||||
load('scripts/release.star', 'release_pipelines', 'test_release_pipelines')
|
||||
load('scripts/version.star', 'version_branch_pipelines')
|
||||
load('scripts/job.star', 'cronjobs')
|
||||
load('scripts/vault.star', 'secrets')
|
||||
load('scripts/drone/pipelines/pr.star', 'pr_pipelines')
|
||||
load('scripts/drone/pipelines/main.star', 'main_pipelines')
|
||||
load('scripts/drone/pipelines/release.star', 'release_pipelines', 'test_release_pipelines')
|
||||
load('scripts/drone/version.star', 'version_branch_pipelines')
|
||||
load('scripts/drone/pipelines/cron.star', 'cronjobs')
|
||||
load('scripts/drone/vault.star', 'secrets')
|
||||
|
||||
def main(ctx):
|
||||
edition = 'oss'
|
||||
|
||||
+21
-21
@@ -17,7 +17,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
@@ -73,7 +73,7 @@ steps:
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64 --no-pull-enterprise
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64,armv6 --no-pull-enterprise
|
||||
depends_on:
|
||||
- test-backend
|
||||
|
||||
@@ -112,7 +112,7 @@ steps:
|
||||
- name: package
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64
|
||||
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64,armv6
|
||||
depends_on:
|
||||
- gen-version
|
||||
|
||||
@@ -254,7 +254,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
@@ -595,7 +595,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
@@ -644,7 +644,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
environment:
|
||||
@@ -732,7 +732,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- ./bin/grabpl verify-version ${DRONE_TAG}
|
||||
@@ -1034,7 +1034,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
@@ -1084,7 +1084,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
@@ -1502,7 +1502,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
- git checkout ${DRONE_TAG}
|
||||
@@ -1570,7 +1570,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- ./bin/grabpl verify-version ${DRONE_TAG}
|
||||
@@ -1678,7 +1678,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- ./bin/grabpl verify-version v7.3.0-test
|
||||
@@ -1969,7 +1969,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
@@ -2019,7 +2019,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
@@ -2431,7 +2431,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
- git checkout main
|
||||
@@ -2499,7 +2499,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- ./bin/grabpl verify-version v7.3.0-test
|
||||
@@ -2607,7 +2607,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- ./bin/grabpl verify-drone
|
||||
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
@@ -2873,7 +2873,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
@@ -2919,7 +2919,7 @@ steps:
|
||||
image: grafana/build-container:1.4.2
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
@@ -3334,7 +3334,7 @@ steps:
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.2/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.5/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
- git checkout $$env:DRONE_BRANCH
|
||||
@@ -3478,6 +3478,6 @@ get:
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 4792c3d8d3bbbff15138c73482622af1a7bad000a3bf75c0b797957969cc205e
|
||||
hmac: 1ed78e5f23e4e069c2a2cdb34d5220b556a76c812b23fe4bdfad09289363370b
|
||||
|
||||
...
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ go.sum @grafana/backend-platform
|
||||
|
||||
# Continuous Integration
|
||||
.drone.yml @malcolmholmes @dsotirakis @zserge
|
||||
/scripts/*.star @malcolmholmes @dsotirakis @zserge
|
||||
/scripts/drone/ @malcolmholmes @dsotirakis @zserge
|
||||
|
||||
# Cloud Datasources backend code
|
||||
/pkg/tsdb/cloudwatch @grafana/cloud-datasources @grafana/observability-squad
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
<!-- 8.1.3 START -->
|
||||
|
||||
# 8.1.3 (2021-09-08)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Alerting:** Fix alert flapping in the internal alertmanager. [#38648](https://github.com/grafana/grafana/pull/38648), [@gotjosh](https://github.com/gotjosh)
|
||||
- **Alerting:** Fix request handler failed to convert dataframe "results" to plugins.DataTimeSeriesSlice: input frame is not recognized as a time series. [#38587](https://github.com/grafana/grafana/pull/38587), [@idafurjes](https://github.com/idafurjes)
|
||||
- **Dashboard:** Fix UIDs are not preserved when importing/creating dashboards thru importing .json file. [#38659](https://github.com/grafana/grafana/pull/38659), [@axelavargas](https://github.com/axelavargas)
|
||||
- **Dashboard:** Forces panel re-render when exiting panel edit. [#38913](https://github.com/grafana/grafana/pull/38913), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Dashboard:** Prevent folder from changing when navigating to general settings. [#38103](https://github.com/grafana/grafana/pull/38103), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Docker:** Force use of libcrypto1.1 and libssl1.1 versions to fix CVE-2021-3711. [#38585](https://github.com/grafana/grafana/pull/38585), [@dsotirakis](https://github.com/dsotirakis)
|
||||
- **Elasticsearch:** Fix metric names for alert queries. [#38546](https://github.com/grafana/grafana/pull/38546), [@dsotirakis](https://github.com/dsotirakis)
|
||||
- **Elasticsearch:** Limit Histogram field parameter to numeric values. [#38631](https://github.com/grafana/grafana/pull/38631), [@Elfo404](https://github.com/Elfo404)
|
||||
- **Elasticsearch:** Prevent pipeline aggregations to show up in terms order by options. [#38448](https://github.com/grafana/grafana/pull/38448), [@Elfo404](https://github.com/Elfo404)
|
||||
- **LibraryPanels:** Prevent duplicate repeated panels from being created. [#38804](https://github.com/grafana/grafana/pull/38804), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Loki:** Fix ad-hoc filter in dashboard when used with parser. [#38542](https://github.com/grafana/grafana/pull/38542), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
- **Plugins:** Track signed files + add warn log for plugin assets which are not signed. [#38938](https://github.com/grafana/grafana/pull/38938), [@wbrowne](https://github.com/wbrowne)
|
||||
- **Postgres/MySQL/MSSQL:** Fix region annotations not displayed correctly. [#38936](https://github.com/grafana/grafana/pull/38936), [@marefr](https://github.com/marefr)
|
||||
- **Prometheus:** Fix validate selector in metrics browser. [#38921](https://github.com/grafana/grafana/pull/38921), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
|
||||
<!-- 8.1.3 END -->
|
||||
<!-- 8.1.2 START -->
|
||||
|
||||
# 8.1.2 (2021-08-19)
|
||||
|
||||
@@ -25,6 +25,8 @@ WORKDIR $GOPATH/src/github.com/grafana/grafana
|
||||
|
||||
COPY go.mod go.sum embed.go ./
|
||||
COPY cue cue
|
||||
COPY cue.mod cue.mod
|
||||
COPY packages/grafana-schema packages/grafana-schema
|
||||
COPY public/app/plugins public/app/plugins
|
||||
COPY pkg pkg
|
||||
COPY build.go package.json ./
|
||||
|
||||
@@ -25,6 +25,8 @@ COPY go.mod go.sum embed.go ./
|
||||
COPY build.go package.json ./
|
||||
COPY pkg pkg/
|
||||
COPY cue cue/
|
||||
COPY cue.mod cue.mod/
|
||||
COPY packages/grafana-schema packages/grafana-schema/
|
||||
COPY public/app/plugins public/app/plugins/
|
||||
|
||||
RUN go mod verify
|
||||
|
||||
@@ -3,476 +3,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
windows = "windows"
|
||||
linux = "linux"
|
||||
)
|
||||
|
||||
var (
|
||||
//versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
|
||||
goarch string
|
||||
goos string
|
||||
gocc string
|
||||
cgo bool
|
||||
libc string
|
||||
pkgArch string
|
||||
version string = "v1"
|
||||
buildTags []string
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
linuxPackageVersion string = "v1"
|
||||
linuxPackageIteration string = ""
|
||||
race bool
|
||||
workingDir string
|
||||
includeBuildId bool = true
|
||||
buildId string = "0"
|
||||
serverBinary string = "grafana-server"
|
||||
cliBinary string = "grafana-cli"
|
||||
binaries []string = []string{serverBinary, cliBinary}
|
||||
isDev bool = false
|
||||
enterprise bool = false
|
||||
skipRpmGen bool = false
|
||||
skipDebGen bool = false
|
||||
printGenVersion bool = false
|
||||
"github.com/grafana/grafana/pkg/build"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(0)
|
||||
|
||||
var buildIdRaw string
|
||||
var buildTagsRaw string
|
||||
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.StringVar(&gocc, "cc", "", "CC")
|
||||
flag.StringVar(&libc, "libc", "", "LIBC")
|
||||
flag.StringVar(&buildTagsRaw, "build-tags", "", "Sets custom build tags")
|
||||
flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo")
|
||||
flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
flag.BoolVar(&includeBuildId, "includeBuildId", includeBuildId, "IncludeBuildId in package name")
|
||||
flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana")
|
||||
flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system")
|
||||
flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps")
|
||||
flag.BoolVar(&skipRpmGen, "skipRpm", skipRpmGen, "skip rpm package generation (default: false)")
|
||||
flag.BoolVar(&skipDebGen, "skipDeb", skipDebGen, "skip deb package generation (default: false)")
|
||||
flag.BoolVar(&printGenVersion, "gen-version", printGenVersion, "generate Grafana version and output (default: false)")
|
||||
flag.Parse()
|
||||
|
||||
buildId = shortenBuildId(buildIdRaw)
|
||||
|
||||
readVersionFromPackageJson()
|
||||
|
||||
if pkgArch == "" {
|
||||
pkgArch = goarch
|
||||
}
|
||||
|
||||
if printGenVersion {
|
||||
printGeneratedVersion()
|
||||
return
|
||||
}
|
||||
|
||||
if len(buildTagsRaw) > 0 {
|
||||
buildTags = strings.Split(buildTagsRaw, ",")
|
||||
}
|
||||
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
log.Println("Usage: go run build.go build")
|
||||
return
|
||||
}
|
||||
|
||||
workingDir, _ = os.Getwd()
|
||||
|
||||
for _, cmd := range flag.Args() {
|
||||
switch cmd {
|
||||
case "setup":
|
||||
setup()
|
||||
|
||||
case "build-srv", "build-server":
|
||||
clean()
|
||||
doBuild("grafana-server", "./pkg/cmd/grafana-server", buildTags)
|
||||
|
||||
case "build-cli":
|
||||
clean()
|
||||
doBuild("grafana-cli", "./pkg/cmd/grafana-cli", buildTags)
|
||||
|
||||
case "build":
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
doBuild(binary, "./pkg/cmd/"+binary, buildTags)
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
yarn("build")
|
||||
|
||||
case "sha-dist":
|
||||
shaFilesInDist()
|
||||
|
||||
case "latest":
|
||||
makeLatestDistCopies()
|
||||
|
||||
case "clean":
|
||||
clean()
|
||||
|
||||
default:
|
||||
log.Fatalf("Unknown command %q", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeLatestDistCopies() {
|
||||
files, err := ioutil.ReadDir("dist")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create latest copies. Cannot read from /dist")
|
||||
}
|
||||
|
||||
latestMapping := map[string]string{
|
||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||
".linux-amd64-musl.tar.gz": "dist/grafana-latest.linux-x64-musl.tar.gz",
|
||||
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
|
||||
".linux-armv7-musl.tar.gz": "dist/grafana-latest.linux-armv7-musl.tar.gz",
|
||||
".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
|
||||
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
|
||||
".linux-arm64-musl.tar.gz": "dist/grafana-latest.linux-arm64-musl.tar.gz",
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
for extension, fullName := range latestMapping {
|
||||
if strings.HasSuffix(file.Name(), extension) {
|
||||
runError("cp", path.Join("dist", file.Name()), fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readVersionFromPackageJson() {
|
||||
reader, err := os.Open("package.json")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to open package.json")
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
jsonObj := map[string]interface{}{}
|
||||
jsonParser := json.NewDecoder(reader)
|
||||
|
||||
if err := jsonParser.Decode(&jsonObj); err != nil {
|
||||
log.Fatal("Failed to decode package.json")
|
||||
}
|
||||
|
||||
version = jsonObj["version"].(string)
|
||||
linuxPackageVersion = version
|
||||
linuxPackageIteration = ""
|
||||
|
||||
// handle pre version stuff (deb / rpm does not support semver)
|
||||
parts := strings.Split(version, "-")
|
||||
|
||||
if len(parts) > 1 {
|
||||
linuxPackageVersion = parts[0]
|
||||
linuxPackageIteration = parts[1]
|
||||
}
|
||||
|
||||
// add timestamp to iteration
|
||||
if includeBuildId {
|
||||
if buildId != "0" {
|
||||
linuxPackageIteration = fmt.Sprintf("%s%s", buildId, linuxPackageIteration)
|
||||
} else {
|
||||
linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func yarn(params ...string) {
|
||||
runPrint(`yarn run`, params...)
|
||||
}
|
||||
|
||||
func genPackageVersion() string {
|
||||
if includeBuildId {
|
||||
return fmt.Sprintf("%v-%v", linuxPackageVersion, linuxPackageIteration)
|
||||
} else {
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
func setup() {
|
||||
args := []string{"install", "-v"}
|
||||
if goos == windows {
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
args = append(args, "./pkg/cmd/grafana-server")
|
||||
runPrint("go", args...)
|
||||
}
|
||||
|
||||
func printGeneratedVersion() {
|
||||
fmt.Print(genPackageVersion())
|
||||
}
|
||||
|
||||
func test(pkg string) {
|
||||
setBuildEnv()
|
||||
args := []string{"test", "-short", "-timeout", "60s"}
|
||||
if goos == windows {
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
args = append(args, pkg)
|
||||
runPrint("go", args...)
|
||||
}
|
||||
|
||||
func doBuild(binaryName, pkg string, tags []string) {
|
||||
libcPart := ""
|
||||
if libc != "" {
|
||||
libcPart = fmt.Sprintf("-%s", libc)
|
||||
}
|
||||
binary := fmt.Sprintf("./bin/%s-%s%s/%s", goos, goarch, libcPart, binaryName)
|
||||
if isDev {
|
||||
//don't include os/arch/libc in output path in dev environment
|
||||
binary = fmt.Sprintf("./bin/%s", binaryName)
|
||||
}
|
||||
|
||||
if goos == windows {
|
||||
binary += ".exe"
|
||||
}
|
||||
|
||||
if !isDev {
|
||||
rmr(binary, binary+".md5")
|
||||
}
|
||||
args := []string{"build", "-ldflags", ldflags()}
|
||||
if goos == windows {
|
||||
// Work around a linking error on Windows: "export ordinal too large"
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
args = append(args, "-tags", strings.Join(tags, ","))
|
||||
}
|
||||
if race {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
|
||||
args = append(args, "-o", binary)
|
||||
args = append(args, pkg)
|
||||
|
||||
if !isDev {
|
||||
setBuildEnv()
|
||||
runPrint("go", "version")
|
||||
libcPart := ""
|
||||
if libc != "" {
|
||||
libcPart = fmt.Sprintf("/%s", libc)
|
||||
}
|
||||
fmt.Printf("Targeting %s/%s%s\n", goos, goarch, libcPart)
|
||||
}
|
||||
|
||||
runPrint("go", args...)
|
||||
|
||||
if !isDev {
|
||||
// Create an md5 checksum of the binary, to be included in the archive for
|
||||
// automatic upgrades.
|
||||
err := md5File(binary)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ldflags() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("-w")
|
||||
b.WriteString(fmt.Sprintf(" -X main.version=%s", version))
|
||||
b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildBranch=%s", getGitBranch()))
|
||||
if v := os.Getenv("LDFLAGS"); v != "" {
|
||||
b.WriteString(fmt.Sprintf(" -extldflags \"%s\"", v))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func rmr(paths ...string) {
|
||||
for _, path := range paths {
|
||||
log.Println("rm -r", path)
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
|
||||
func clean() {
|
||||
if isDev {
|
||||
return
|
||||
}
|
||||
|
||||
rmr("dist")
|
||||
rmr("tmp")
|
||||
rmr(filepath.Join(build.Default.GOPATH, fmt.Sprintf("pkg/%s_%s/github.com/grafana", goos, goarch)))
|
||||
}
|
||||
|
||||
func setBuildEnv() {
|
||||
os.Setenv("GOOS", goos)
|
||||
if goos == windows {
|
||||
// require windows >=7
|
||||
os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601")
|
||||
}
|
||||
if goarch != "amd64" || goos != linux {
|
||||
// needed for all other archs
|
||||
cgo = true
|
||||
}
|
||||
if strings.HasPrefix(goarch, "armv") {
|
||||
os.Setenv("GOARCH", "arm")
|
||||
os.Setenv("GOARM", goarch[4:])
|
||||
} else {
|
||||
os.Setenv("GOARCH", goarch)
|
||||
}
|
||||
if cgo {
|
||||
os.Setenv("CGO_ENABLED", "1")
|
||||
}
|
||||
if gocc != "" {
|
||||
os.Setenv("CC", gocc)
|
||||
}
|
||||
}
|
||||
|
||||
func getGitBranch() string {
|
||||
v, err := runError("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if err != nil {
|
||||
return "main"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
func getGitSha() string {
|
||||
v, err := runError("git", "rev-parse", "--short", "HEAD")
|
||||
if err != nil {
|
||||
return "unknown-dev"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
func buildStamp() int64 {
|
||||
// use SOURCE_DATE_EPOCH if set.
|
||||
if s, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); s > 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
bs, err := runError("git", "show", "-s", "--format=%ct")
|
||||
if err != nil {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
s, _ := strconv.ParseInt(string(bs), 10, 64)
|
||||
return s
|
||||
}
|
||||
|
||||
func runError(cmd string, args ...string) ([]byte, error) {
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
bs, err := ecmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.TrimSpace(bs), nil
|
||||
}
|
||||
|
||||
func runPrint(cmd string, args ...string) {
|
||||
log.Println(cmd, strings.Join(args, " "))
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
ecmd.Stdout = os.Stdout
|
||||
ecmd.Stderr = os.Stderr
|
||||
err := ecmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func md5File(file string) error {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".md5")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func shaFilesInDist() {
|
||||
filepath.Walk("./dist", func(path string, f os.FileInfo, err error) error {
|
||||
if path == "./dist" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.Contains(path, ".sha256") {
|
||||
err := shaFile(path)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create sha file. error: %v\n", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func shaFile(file string) error {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
h := sha256.New()
|
||||
_, err = io.Copy(h, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".sha256")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func shortenBuildId(buildId string) string {
|
||||
buildId = strings.Replace(buildId, "-", "", -1)
|
||||
if len(buildId) < 9 {
|
||||
return buildId
|
||||
}
|
||||
return buildId[0:8]
|
||||
os.Exit(build.RunCmd())
|
||||
}
|
||||
|
||||
@@ -172,6 +172,12 @@ idle_conn_timeout_seconds = 90
|
||||
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request.
|
||||
send_user_header = false
|
||||
|
||||
# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests.
|
||||
response_limit = 0
|
||||
|
||||
# Limits the number of rows that Grafana will process from SQL data sources.
|
||||
row_limit = 1000000
|
||||
|
||||
#################################### Analytics ###########################
|
||||
[analytics]
|
||||
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
|
||||
@@ -498,9 +504,11 @@ role_attribute_path =
|
||||
role_attribute_strict = false
|
||||
groups_attribute_path =
|
||||
id_token_attribute_name =
|
||||
team_ids_attribute_path =
|
||||
auth_url =
|
||||
token_url =
|
||||
api_url =
|
||||
teams_url =
|
||||
allowed_domains =
|
||||
team_ids =
|
||||
allowed_organizations =
|
||||
|
||||
@@ -178,6 +178,12 @@
|
||||
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
|
||||
;send_user_header = false
|
||||
|
||||
# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests.
|
||||
;response_limit = 0
|
||||
|
||||
# Limits the number of rows that Grafana will process from SQL data sources.
|
||||
;row_limit = 1000000
|
||||
|
||||
#################################### Analytics ####################################
|
||||
[analytics]
|
||||
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
|
||||
@@ -483,12 +489,14 @@
|
||||
;auth_url = https://foo.bar/login/oauth/authorize
|
||||
;token_url = https://foo.bar/login/oauth/access_token
|
||||
;api_url = https://foo.bar/user
|
||||
;teams_url =
|
||||
;allowed_domains =
|
||||
;team_ids =
|
||||
;allowed_organizations =
|
||||
;role_attribute_path =
|
||||
;role_attribute_strict = false
|
||||
;groups_attribute_path =
|
||||
;team_ids_attribute_path =
|
||||
;tls_skip_verify_insecure = false
|
||||
;tls_client_cert =
|
||||
;tls_client_key =
|
||||
|
||||
@@ -6,6 +6,7 @@ This directory contains guides for contributors to the Grafana project.
|
||||
- [Contributing documentation](documentation.md)
|
||||
- [Developer guide](developer-guide.md)
|
||||
- [Triage issues](triage-issues.md)
|
||||
- [Merge a pull request](merge-pull-request.md)
|
||||
|
||||
The `style-guides` directory contains style guides for the Grafana software project and documentation.
|
||||
|
||||
|
||||
@@ -102,6 +102,8 @@ For changes to panels, the area should be the name of the panel, suffixed with P
|
||||
- `GraphPanel: Fix legend sorting issues`
|
||||
- `Docs: Changed url to URL in all documentation files`
|
||||
|
||||
If you're unsure, please have a look at the existing [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md) for inspiration/guidance.
|
||||
|
||||
### Pull request titles
|
||||
|
||||
The Grafana team _squashes_ all commits into one when we accept a pull request. The title of the pull request becomes the subject line of the squashed commit message. We still encourage contributors to write informative commit messages, as they becomes a part of the Git commit body.
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# Merge a pull request
|
||||
|
||||
When a pull request has been reviewed and approved by at least one person and all checks have passed it's time to merge the pull request.
|
||||
|
||||
## Who is expected to merge a pull request?
|
||||
|
||||
Maintainers are responsible for merging all pull requests. If a maintainer has opened a pull request the general rule is that the same maintainer merges the pull request. If a non-maintainer has opened a pull request it's suggested that one of the maintainers reviewing the pull request merges the pull request.
|
||||
|
||||
## Checklist/summary
|
||||
|
||||
The following checklist/summary should give you a quick overview of what to ask/consider before merging a pull request.
|
||||
|
||||
- Reviewed and approved?
|
||||
- All checks passed?
|
||||
- Proper pull request title?
|
||||
- Milestone assigned?
|
||||
- Add to changelog/release notes?
|
||||
- Needs backporting?
|
||||
|
||||
## Before merge
|
||||
|
||||
Before actually merging a pull request there's a couple of things to take into consideration.
|
||||
|
||||
### Format the pull request title
|
||||
|
||||
Make sure that the pull request title is properly formatted according to `<Area>: <Summary>` and try to make the summary short and understandable for the community as a whole.
|
||||
|
||||
All commits in a pull request are squashed when merged and the pull request title will be the default subject line of the squashed commit message. It's also used for [changelog/release notes](#include-in-changelog-and-release-notes).
|
||||
|
||||
See [formatting guidelines](create-pull-request.md#formatting-guidelines) for more information.
|
||||
|
||||
### Assign a milestone
|
||||
|
||||
It's recommended to add a milestone to every pull request. This makes it easier to track what changes did go into a certain release. Without this you're basically left with going through git commits which could be a lot harder.
|
||||
|
||||
There's also various tooling built that in some cases requires a pull request to be assigned a milestone, for example [generating changelog/release notes](#include-in-changelog-and-release-notes).
|
||||
|
||||
### Include in changelog and release notes?
|
||||
|
||||
At Grafana we generate the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md) and [release notes](https://grafana.com/docs/grafana/latest/release-notes/) based on merged pull requests. Including changes in the changelog/release notes is very important to provide a somewhat complete picture of what changes a Grafana release actually includes.
|
||||
|
||||
Exactly what changes should be added to the changelog is hard to answer but some general guidance would be any change that you think would be interesting for the community as a whole. Use your best judgement and/or ask other maintainers for advice.
|
||||
|
||||
There's a GitHub action available in the repository named [Update changelog](https://github.com/grafana/grafana/blob/main/.github/workflows/update-changelog.yml) that can manually be triggered to re-generate the changelog and release notes for any release.
|
||||
|
||||
To include a pull request in the changelog/release notes the general rule of thumb is that a milestone should be assigned and labeled with `add to changelog`.
|
||||
|
||||
The changelog/release notes are divided into sections and here's a description of how you make a pull request show up in a certain section.
|
||||
|
||||
**Features and enhancements:**
|
||||
|
||||
Milestone assigned and labeled with `add to changelog` and any of the other section rules don't apply.
|
||||
|
||||
**Bug fixes:**
|
||||
|
||||
Milestone assigned and labeled with `add to changelog` and either labeled with `type/bug` or the pull request title contains `fix` or `fixes`.
|
||||
|
||||
**Plugin development fixes & changes:**
|
||||
|
||||
Milestone assigned and labeled with `area/grafana/toolkit`, `area/grafana/ui` or `area/grafana/runtime`.
|
||||
|
||||
**Deprecations:**
|
||||
|
||||
In case the pull request introduces a deprecation you should document this. Label the pull request with `add to changelog` and use the following template at the end of the pull request description describing the deprecation change.
|
||||
|
||||
```md
|
||||
# Deprecation notice
|
||||
|
||||
<Deprecation description>
|
||||
```
|
||||
|
||||
**Breaking changes:**
|
||||
|
||||
In case the pull request introduces a breaking change you should document this. Label the pull request with `add to changelog` and `breaking change` and use the following template at the end of the pull request description describing the breaking change.
|
||||
|
||||
```md
|
||||
# Release notice breaking change
|
||||
|
||||
<Breaking change description>
|
||||
```
|
||||
|
||||
### Should the pull request be backported?
|
||||
|
||||
If your pull request has changes that need to go into one or several existing release branches you need to backport the changes. Please refer to [Backport PR](.github/bot.md#backport-pr) for detailed instructions.
|
||||
|
||||
Some examples when backport is required:
|
||||
|
||||
- The change needs to be released in the next upcoming patch release, e.g. v8.1.3, so you have to backport it, e.g. into the v8.1.x release branch.
|
||||
- You have a change to be released in the next major/minor release, e.g. v8.0.0, and there's already a release branch, e.g. v8.0.x, you have to backport it, e.g. into the v8.0.x release branch.
|
||||
- The change includes documentation changes that needs to be updated for one or multiple older versions, then you have to backport it to each release branch.
|
||||
|
||||
Some examples when backport is not required:
|
||||
|
||||
- The change is supposed to be released in the next major/minor release, e.g. v8.0.0, but the release branch, e.g. v8.0.x, has not yet been created.
|
||||
|
||||
> **Note:** You can still backport a pull request after it's been merged.
|
||||
|
||||
## Doing the actual merge
|
||||
|
||||
Time to actually merge the pull request changes. All commits in a pull request are squashed, hence the GitHub `Squash and merge` button is used to initialize the merge.
|
||||
|
||||
This will present you with options allowing you to optionally change the commit message before merging. Please remember that developers might use the commit information when reviewing changes of files, doing git blame and resolving merge conflicts etc., trying to quickly figure out what the actual change was. But there's not really any best practices around this, the following is an attempt to bring some guidance.
|
||||
|
||||
Do:
|
||||
|
||||
- Make sure the pull request title is formatted properly before merging, this will automatically give you a good and short summary of the commit/change.
|
||||
- Leave `Co-authored-by:` lines as is so that co-authors will be accounted for the contribution.
|
||||
- Remove any commit information that doesn't bring any context to the change.
|
||||
|
||||
Consider:
|
||||
|
||||
- Add any references to issues that the pull request fixes/closes/references to ease giving quick context to things. Doing this allows cross-reference between the commit and referenced issue(s).
|
||||
|
||||
Finalize the merge by clicking on the `Confirm squash and merge` button.
|
||||
|
||||
## After the merge
|
||||
|
||||
Make sure to close any referenced/related issues. It's recommended to assign the same milestone on the issues that the pull request fixes/closes, but not required.
|
||||
@@ -178,8 +178,8 @@ Prefix your selector string with "data-testid":
|
||||
```typescript
|
||||
export const Components = {
|
||||
Login: {
|
||||
openButton: 'data-testid-open', // this would look for a data-testid
|
||||
closeButton: 'close-button', // this would look for an aria-label
|
||||
openButton: 'open-button', // this would look for an aria-label
|
||||
closeButton: 'data-testid modal-close-button', // this would look for a data-testid
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -187,5 +187,5 @@ export const Components = {
|
||||
and in your component, import the selectors and add the data test id:
|
||||
|
||||
```
|
||||
<button data-testid={Selectors.Components.Login.openButton}>
|
||||
<button data-testid={Selectors.Components.Login.closeButton}>
|
||||
```
|
||||
|
||||
@@ -14,6 +14,7 @@ package scuemata
|
||||
// its position in the list of lineages - e.g., 0.0 corresponds to the first
|
||||
// schema in the first lineage.
|
||||
#Family: {
|
||||
compose?: {...}
|
||||
lineages: [#Lineage, ...#Lineage]
|
||||
migrations: [...#Migration]
|
||||
let lseq = lineages[len(lineages)-1]
|
||||
|
||||
@@ -94,7 +94,7 @@ aliases = ["/docs/grafana/v1.1", "/docs/grafana/latest/guides/reference/admin",
|
||||
<img src="/static/img/docs/logos/icon_cloudmonitoring.svg">
|
||||
<h5>Google Cloud Monitoring</h5>
|
||||
</a>
|
||||
<a href="{{< relref "datasources/cloudwatch.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||
<a href="{{< relref "datasources/aws-cloudwatch/_index.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||
<img src="/static/img/docs/logos/icon_cloudwatch.svg">
|
||||
<h5>AWS CloudWatch</h5>
|
||||
</a>
|
||||
|
||||
@@ -435,6 +435,14 @@ The length of time that Grafana maintains idle connections before closing them.
|
||||
|
||||
If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request. Default is `false`.
|
||||
|
||||
### response_limit
|
||||
|
||||
Limits the amount of bytes that will be read/accepted from responses of outgoing HTTP requests. Default is `0` which means disabled.
|
||||
|
||||
### row_limit
|
||||
|
||||
Limits the number of rows that Grafana will process from SQL (relational) data sources. Default is `1000000`.
|
||||
|
||||
<hr />
|
||||
|
||||
## [analytics]
|
||||
@@ -1426,7 +1434,7 @@ Optional extra path inside bucket.
|
||||
|
||||
### enable_signed_urls
|
||||
|
||||
If set to true, Grafana creates a [signed URL](https://cloud.google.com/storage/docs/access-control/signed-urls] for
|
||||
If set to true, Grafana creates a [signed URL](https://cloud.google.com/storage/docs/access-control/signed-urls) for
|
||||
the image uploaded to Google Cloud Storage.
|
||||
|
||||
### signed_url_expiration
|
||||
|
||||
@@ -51,7 +51,7 @@ These examples show how often and when reminders are sent for a triggered alert.
|
||||
## List of supported notifiers
|
||||
|
||||
| Name | Type | Supports images | Support alert rule tags |
|
||||
| --------------------------------------------- | ------------------------- | ------------------ | ----------------------- |
|
||||
| --------------------------------------------- | ------------------------- | ------------------ | ----------------------- | --- |
|
||||
| [DingDing](#dingdingdingtalk) | `dingding` | yes, external only | no |
|
||||
| [Discord](#discord) | `discord` | yes | no |
|
||||
| [Email](#email) | `email` | yes | no |
|
||||
@@ -70,7 +70,7 @@ These examples show how often and when reminders are sent for a triggered alert.
|
||||
| Telegram | `telegram` | yes | no |
|
||||
| Threema | `threema` | yes, external only | no |
|
||||
| VictorOps | `victorops` | yes, external only | yes |
|
||||
| [Webhook](#webhook) | `webhook` | yes, external only | yes | |
|
||||
| [Webhook](#webhook) | `webhook` | yes, external only | yes | |
|
||||
|
||||
### Email
|
||||
|
||||
@@ -258,7 +258,7 @@ Alertmanager handles alerts sent by client applications such as Prometheus serve
|
||||
|
||||
### Sensu Go
|
||||
|
||||
Grafana alert notifications can be sent to [Sensu]((https://sensu.io)) Go as events via the API. This operation requires an API key. For information on creating this key, refer to [Sensu Go documentation](https://docs.sensu.io/sensu-go/latest/operations/control-access/use-apikeys/#api-key-authentication).
|
||||
Grafana alert notifications can be sent to [Sensu](<(https://sensu.io)>) Go as events via the API. This operation requires an API key. For information on creating this key, refer to [Sensu Go documentation](https://docs.sensu.io/sensu-go/latest/operations/control-access/use-apikeys/#api-key-authentication).
|
||||
|
||||
## Enable images in notifications {#external-image-store}
|
||||
|
||||
|
||||
@@ -47,14 +47,14 @@ Alerting rules can only query backend data sources with alerting enabled:
|
||||
|
||||
The alerting engine publishes some internal metrics about itself. You can read more about how Grafana publishes [internal metrics]({{< relref "../../administration/view-server/internal-metrics.md" >}}).
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| ------------------------------------------- | --------- | ---------------------------------------------------------------------------------------- |
|
||||
| `alerting.alerts` | gauge | How many alerts by state |
|
||||
| `alerting.request_duration_seconds` | histogram | Histogram of requests to the Alerting API |
|
||||
| `alerting.active_configurations` | gauge | The number of active, non default Alertmanager configurations for grafana managed alerts |
|
||||
| `alerting.rule_evaluations_total` | counter | The total number of rule evaluations |
|
||||
| `alerting.rule_evaluation_failures_total` | counter | The total number of rule evaluation failures |
|
||||
| `alerting.rule_evaluation_duration_seconds` | summary | The duration for a rule to execute |
|
||||
| `alerting.rule_group_rules` | gauge | The number of rules |
|
||||
| Metric Name | Type | Description |
|
||||
| ------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------- |
|
||||
| `grafana_alerting_alerts` | gauge | How many alerts by state |
|
||||
| `grafana_alerting_request_duration` | histogram | Histogram of requests to the Alerting API |
|
||||
| `grafana_alerting_active_configurations` | gauge | The number of active, non default Alertmanager configurations for grafana managed alerts |
|
||||
| `grafana_alerting_rule_evaluations_total` | counter | The total number of rule evaluations |
|
||||
| `grafana_alerting_rule_evaluation_failures_total` | counter | The total number of rule evaluation failures |
|
||||
| `grafana_alerting_rule_evaluation_duration` | summary | The duration for a rule to execute |
|
||||
| `grafana_alerting_rule_group_rules` | gauge | The number of rules |
|
||||
|
||||
- [View alert rules and their current state]({{< relref "alerting-rules/rule-list.md" >}})
|
||||
|
||||
+2
-3
@@ -7,7 +7,7 @@ weight = 400
|
||||
|
||||
# Create a Cortex or Loki managed recording rule
|
||||
|
||||
You can create and manage recording rules for an external Cortex or Loki instance. Recording rules calculate frequently needed expressions or computationally expensive expressions in advance and save the result as a new set of time series. Querying this new time series is faster, especially for dashboards since they query the same expression every time the dashboards refresh.
|
||||
You can create and manage recording rules for an external Cortex or Loki instance. Recording rules calculate frequently needed expressions or computationally expensive expressions in advance and save the result as a new set of time series. Querying this new time series is faster, especially for dashboards since they query the same expression every time the dashboards refresh.
|
||||
|
||||
For both Cortex and Loki data sources to work with Grafana 8.0 alerting, enable the ruler API by configuring their respective services. The `local` rule storage type (default for Loki data source), only supports viewing of rules. If you want to edit rules, then configure one of the other rule storage types.
|
||||
|
||||
@@ -34,7 +34,7 @@ This section describes the fields you fill out to create a recording rule.
|
||||
|
||||
### Rule type
|
||||
|
||||
- **Rule name -** Enter a descriptive name. The name will get displayed in the alert rule list. It will also get added as an `alertname` label to every alert instance that is created from this rule. Recording rules names must be valid [metric names](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
||||
- **Rule name -** Enter a descriptive name. The name will get displayed in the alert rule list. It will also get added as an `alertname` label to every alert instance that is created from this rule. Recording rules names must be valid [metric names](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
|
||||
- **Rule type -** Select **Cortex / Loki managed recording rule**.
|
||||
- **Data source -** Select a Prometheus or Loki data source. Only data sources that support Cortex ruler API are available.
|
||||
- **Namespace -** Select an existing rule namespace or click **Add new** and enter a name to create a new one. Namespaces can contain one or more rule groups and have only organizational purpose.
|
||||
@@ -48,7 +48,6 @@ Enter a PromQL or LogQL expression. The result of this expression will get recor
|
||||
|
||||

|
||||
|
||||
|
||||
### Details
|
||||
|
||||
You can optionally define labels in the details section.
|
||||
|
||||
+4
-4
@@ -75,11 +75,11 @@ See or [expressions documentation]({{< relref "../../../panels/expressions.md" >
|
||||
|
||||
Toggle **Configure no data and error handling** switch to configure how the rule should handle cases where evaluation results in error or returns no data.
|
||||
|
||||
| No Data Option | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| No Data Option | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| No Data | Set alert state to `NoData` and rule state to `Normal` (notifications are not sent on NoData states). |
|
||||
| Alerting | Set alert rule state to `Alerting`. |
|
||||
| Ok | Set alert rule state to `Normal`. |
|
||||
| Alerting | Set alert rule state to `Alerting`. |
|
||||
| Ok | Set alert rule state to `Normal`. |
|
||||
|
||||
| Error or timeout option | Description |
|
||||
| ----------------------- | ---------------------------------- |
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ A namespace contains one or more groups. To rename a namespace, find a group tha
|
||||
|
||||
1. Hover your cursor over the Alerting (bell) icon in the side menu.
|
||||
1. Locate a group that belongs to the namespace you want to edit and click the edit (pen) icon.
|
||||
1. Enter a new name in the **Namespace** field, then click **Save changes**.
|
||||
1. Enter a new name in the **Namespace** field, then click **Save changes**.
|
||||
|
||||
A new namespace is created and all groups are copied into this namespace from the old one. The old namespace is deleted.
|
||||
|
||||
@@ -31,4 +31,4 @@ The rules within a group are run sequentially at a regular interval, the default
|
||||
If you remaned the group, a new group is created that has all the rules from the old group, and the old group deleted.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -8,7 +8,7 @@ weight = 400
|
||||
|
||||
# Message templating
|
||||
|
||||
Notifications sent via [contact points]({{< relref "../contact-points.md" >}}) are built using templates. Grafana comes with default templates which you can customize. Grafana's notification templates are based on the [Go templating system](https://golang.org/pkg/text/template) where some fields are evaluated as text, while others are evaluated as HTML which can affect escaping. Since most of the contact point fields can be templated, you can create reusable templates and them in multiple contact points. See [template data reference]({{< relref "./template-data.md" >}}) to check what variables are available in the templates.
|
||||
Notifications sent via [contact points]({{< relref "../contact-points.md" >}}) are built using templates. Grafana comes with default templates which you can customize. Grafana's notification templates are based on the [Go templating system](https://golang.org/pkg/text/template) where some fields are evaluated as text, while others are evaluated as HTML which can affect escaping. Since most of the contact point fields can be templated, you can create reusable templates and them in multiple contact points. See [template data reference]({{< relref "./template-data.md" >}}) to check what variables are available in the templates. The default template is defined in [default_template.go](https://github.com/grafana/grafana/blob/main/pkg/services/ngalert/notifier/channels/default_template.go) which can serve as a useful reference or starting point for custom templates.
|
||||
|
||||
## Using templating in contact point fields
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ To edit a specific policy, find it in the specific routing table and click **Edi
|
||||
### Root policy fields
|
||||
|
||||
- **Default contact point -** The [contact point]({{< relref "./contact-points.md" >}}) to send notifications to that did not match any specific policy.
|
||||
- **Group by -** Labels to group alerts by. If multiple alerts are matched for this policy, they will be grouped based on these labels and a notification will be sent per group. Mandatory for root policy, optional for nested specific policies. If a specific policy does not specify own grouping, root policy grouping will be used.
|
||||
- **Group by -** Labels to group alerts by. If multiple alerts are matched for this policy, they will be grouped based on these labels and a notification will be sent per group. If the field is empty (default), then all notifications are sent in a single group. Use a special label `...` to group alerts by all labels, which effectively disables grouping.
|
||||
|
||||
Group timing options
|
||||
|
||||
@@ -43,7 +43,7 @@ Group timing options
|
||||
- **Group interval -** - How long to wait before sending an notification when an alert has been added to a group for which there has already been a notification. Default is 5 minutes.
|
||||
- **Repeat interval -** - How long to wait before re-sending a notification after one has already been sent and no new alerts were added to the group. Default is 4 hours.
|
||||
|
||||
### Specific policy fields
|
||||
### Specific routing policy fields
|
||||
|
||||
- **Contact point -** The [contact point]({{< relref "./contact-points.md" >}}) to send notification to if alert matched this specific policy but did not match any of it's nested policies, or there were no nested specific policies.
|
||||
- **Matching labels -** Rules for matching alert labels. See ["How label matching works"](#how-label-matching-works) below for details.
|
||||
|
||||
@@ -12,7 +12,9 @@ Setting the `ngalert` feature toggle enables the new Grafana 8 alerting system.
|
||||
|
||||
At startup, when [the feature toggle is enabled]({{< relref "../../administration/configuration.md">}}#feature_toggles), the legacy Grafana dashboard alerting is disabled and existing dashboard alerts are migrated into a format that is compatible with the Grafana 8 alerting system. You can view these migrated rules, alongside any new alerts you create after the migration, from the Alerting page of your Grafana instance.
|
||||
|
||||
> **Note:** Since the new system stores the notification log and silences on disk, we require the use of persistent disks for using Grafana 8 alerts. Otherwise, the silences and notification log will get lost on a restart, and you might get unwanted or duplicate notifications.
|
||||
> **Note - v8.2 or earlier:** Since the new system stores the notification log and silences on disk, we require the use of persistent disks for using Grafana 8 alerts. Otherwise, the silences and notification log will get lost on a restart, and you might get unwanted or duplicate notifications.
|
||||
|
||||
> **Note - v8.3+**: We have removed the need of persistent disk. The notification log and silences are now stored in the database. If you used the file-based approach, we'll read those files and eventually (every 15 minutes) persist them to the database.
|
||||
|
||||
Read and write access to dashboard alerts in Grafana versions 7 and earlier were governed by the dashboard and folder permissions under which the alerts were stored. In Grafana 8, alerts are stored in folders and inherit the permissions of those folders. During the migration, dashboard alert permissions are matched to the new rules permissions as follows:
|
||||
|
||||
|
||||
@@ -55,24 +55,30 @@ You can also specify the SSL/TLS configuration used by the client.
|
||||
|
||||
Set `empty_scopes` to true to use an empty scope during authentication. By default, Grafana uses `user:email` as scope.
|
||||
|
||||
Grafana will attempt to determine the user's e-mail address by querying the OAuth provider as described below in the following order until an e-mail address is found:
|
||||
### Email address
|
||||
|
||||
Grafana determines a user's email address by querying the OAuth provider until it finds an e-mail address:
|
||||
|
||||
1. Check for the presence of an e-mail address via the `email` field encoded in the OAuth `id_token` parameter.
|
||||
1. Check for the presence of an e-mail address using the [JMESPath](http://jmespath.org/examples.html) specified via the `email_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option.
|
||||
**Note**: Only available in Grafana v6.4+.
|
||||
1. Check for the presence of an e-mail address in the `attributes` map encoded in the OAuth `id_token` parameter. By default Grafana will perform a lookup into the attributes map using the `email:primary` key, however, this is configurable and can be adjusted by using the `email_attribute_name` configuration option.
|
||||
1. Query the `/emails` endpoint of the OAuth provider's API (configured with `api_url`) and check for the presence of an e-mail address marked as a primary address.
|
||||
1. If no e-mail address is found in steps (1-4), then the e-mail address of the user is set to the empty string.
|
||||
1. Query the `/emails` endpoint of the OAuth provider's API (configured with `api_url`), then check for the presence of an email address marked as a primary address.
|
||||
1. If no email address is found in steps (1-4), then the email address of the user is set to an empty string.
|
||||
|
||||
Grafana will also attempt to do role mapping through OAuth as described below.
|
||||
### Roles
|
||||
|
||||
Check for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option. The result after evaluating the `role_attribute_path` JMESPath expression needs to be a valid Grafana role, i.e. `Viewer`, `Editor` or `Admin`.
|
||||
Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. The JMESPath is applied to the `id_token` first. If there is no match, then the UserInfo endpoint specified via the `api_url` configuration option is tried next. The result after evaluation of the `role_attribute_path` JMESPath expression should be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`.
|
||||
|
||||
Grafana also attempts to map teams through OAuth as described below.
|
||||
For more information, refer to the [JMESPath examples](#jmespath-examples).
|
||||
|
||||
Check for the presence of groups using the [JMESPath](http://jmespath.org/examples.html) specified via the `groups_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option. After evaluating the `groups_attribute_path` JMESPath expression, the result should be a string array of groups.
|
||||
### Groups / Teams
|
||||
|
||||
See [JMESPath examples](#jmespath-examples) for more information.
|
||||
Similarly, group mappings are made using [JMESPath](http://jmespath.org/examples.html) with the `groups_attribute_path` configuration option. The `id_token` is attempted first, followed by the UserInfo from the `api_url`. The result of the JMESPath expression should be a string array of groups.
|
||||
|
||||
Furthermore, Grafana will check for the presence of at least one of the teams specified via the `team_ids` configuration option using the [JMESPath](http://jmespath.org/examples.html) specified via the `team_ids_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the Teams endpoint specified via the `teams_url` configuration option (using `/teams` as a fallback endpoint). The result should be a string array of Grafana Team IDs. Using this setting ensures that only certain teams is allowed to authenticate to Grafana using your OAuth provider.
|
||||
|
||||
### Login
|
||||
|
||||
Customize user login using `login_attribute_path` configuration option. Order of operations is as follows:
|
||||
|
||||
@@ -126,6 +132,8 @@ scopes = account email
|
||||
auth_url = https://bitbucket.org/site/oauth2/authorize
|
||||
token_url = https://bitbucket.org/site/oauth2/access_token
|
||||
api_url = https://api.bitbucket.org/2.0/user
|
||||
teams_url = https://api.bitbucket.org/2.0/user/permissions/workspaces
|
||||
team_ids_attribute_path = values[*].workspace.slug
|
||||
team_ids =
|
||||
allowed_organizations =
|
||||
```
|
||||
|
||||
@@ -26,10 +26,10 @@ instance, if you access Grafana at `http://203.0.113.31:3000`, you should use
|
||||
http://203.0.113.31:3000/login/gitlab
|
||||
```
|
||||
|
||||
Finally, select _read_api_ as the _Scope_ and submit the form. Note that if you're
|
||||
Finally, select _read_api_as the_Scope_and submit the form. Note that if you're
|
||||
not going to use GitLab groups for authorization (i.e. not setting
|
||||
`allowed_groups`, see below), you can select _read_user_ instead of _read_api_ as
|
||||
the _Scope_, thus giving a more restricted access to your GitLab API.
|
||||
`allowed_groups`, see below), you can select_read_user_ instead of _read_api_as
|
||||
the_Scope_, thus giving a more restricted access to your GitLab API.
|
||||
|
||||
You'll get an _Application Id_ and a _Secret_ in return; we'll call them
|
||||
`GITLAB_APPLICATION_ID` and `GITLAB_SECRET` respectively for the rest of this
|
||||
@@ -94,8 +94,8 @@ display name, especially if the display name contains spaces or special
|
||||
characters. Make sure you always use the group or subgroup name as it appears
|
||||
in the URL of the group or subgroup.
|
||||
|
||||
Here's a complete example with `allow_sign_up` enabled, and access limited to
|
||||
the `example` and `foo/bar` groups:
|
||||
Here's a complete example with `allow_sign_up` enabled, with access limited to
|
||||
the `example` and `foo/bar` groups. The example also promotes all GitLab Admins to Grafana Admins:
|
||||
|
||||
```ini
|
||||
[auth.gitlab]
|
||||
@@ -103,13 +103,28 @@ enabled = true
|
||||
allow_sign_up = true
|
||||
client_id = GITLAB_APPLICATION_ID
|
||||
client_secret = GITLAB_SECRET
|
||||
scopes = api
|
||||
scopes = read_api
|
||||
auth_url = https://gitlab.com/oauth/authorize
|
||||
token_url = https://gitlab.com/oauth/token
|
||||
api_url = https://gitlab.com/api/v4
|
||||
allowed_groups = example, foo/bar
|
||||
role_attribute_path = is_admin && 'Admin' || 'Viewer'
|
||||
```
|
||||
|
||||
### Map roles
|
||||
|
||||
You can use GitLab OAuth to map roles. During mapping, Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option.
|
||||
|
||||
For the path lookup, Grafana uses JSON obtained from querying GitLab's API [`/api/v4/user`](https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users) endpoint. The result of evaluating the `role_attribute_path` JMESPath expression must be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`. For more information about roles and permissions in Grafana, refer to [Organization roles]({{< relref "../permissions/organization_roles.md" >}}).
|
||||
|
||||
An example Query could look like the following:
|
||||
|
||||
```bash
|
||||
role_attribute_path = is_admin && 'Admin' || 'Viewer'
|
||||
```
|
||||
|
||||
This allows every GitLab Admin to be an Admin in Grafana.
|
||||
|
||||
### Team Sync (Enterprise only)
|
||||
|
||||
> Only available in Grafana Enterprise v6.4+
|
||||
|
||||
@@ -121,6 +121,11 @@ Defaults to `false`.
|
||||
oauth_auto_login = true
|
||||
```
|
||||
|
||||
### Avoid automatic OAuth login
|
||||
|
||||
To sign in with a username and password and avoid automatic OAuth login, add the `disableAutoLogin` parameter to your login URL.
|
||||
For example: `grafana.example.com/login?disableAutoLogin` or `grafana.example.com/login?disableAutoLogin=true`
|
||||
|
||||
### Hide sign-out menu
|
||||
|
||||
Set the option detailed below to true to hide sign-out menu link. Useful if you use an auth proxy or JWT authentication.
|
||||
|
||||
@@ -17,7 +17,7 @@ Each data source has a specific Query Editor that is customized for the features
|
||||
The following data sources are officially supported:
|
||||
|
||||
- [Alertmanager]({{< relref "alertmanager.md" >}})
|
||||
- [AWS CloudWatch]({{< relref "cloudwatch.md" >}})
|
||||
- [AWS CloudWatch]({{< relref "aws-cloudwatch/_index.md" >}})
|
||||
- [Azure Monitor]({{< relref "azuremonitor/_index.md" >}})
|
||||
- [Elasticsearch]({{< relref "elasticsearch.md" >}})
|
||||
- [Google Cloud Monitoring]({{< relref "google-cloud-monitoring/_index.md" >}})
|
||||
|
||||
+14
-66
@@ -10,9 +10,9 @@ weight = 200
|
||||
|
||||
Grafana ships with built-in support for CloudWatch. Add it as a data source, then you are ready to build dashboards or use Explore with CloudWatch metrics and CloudWatch Logs.
|
||||
|
||||
This topic explains options, variables, querying, and other options specific to this data source. Refer to [Add a data source]({{< relref "add-a-data-source.md" >}}) for instructions on how to add a data source to Grafana. Only users with the organization admin role can add data sources.
|
||||
This topic describes queries, templates, variables, and other configuration specific to the CloudWatch data source. For instructions on how to add a data source to Grafana, refer to [Add a data source]({{< relref "../add-a-data-source.md" >}}). Only users with the organization admin role can add data sources.
|
||||
|
||||
> **Note:** If you have issues with getting this data source to work and Grafana is giving you undescriptive errors, then check your log file (try looking in /var/log/grafana/grafana.log).
|
||||
> **Note:** If you are having issues setting up the data source and Grafana is returning undescriptive errors, then check the log file located in /var/log/grafana/grafana.log).
|
||||
|
||||
## Cloudwatch settings
|
||||
|
||||
@@ -31,21 +31,9 @@ To access data source settings, hover your mouse over the **Configuration** (gea
|
||||
|
||||
## Authentication
|
||||
|
||||
### AWS credentials
|
||||
For authentication options and configuration details, see [AWS authentication]({{< relref "aws-authentication.md" >}}) topic.
|
||||
|
||||
There are three different authentication methods available. `AWS SDK Default` performs no custom configuration at all and instead uses the [default provider](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) as specified by the AWS SDK for Go. This requires you to configure your AWS credentials separately, such as if you've [configured the CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html), if you're [running on an EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html), [in an ECS task](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) or for a [Service Account in a Kubernetes cluster](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).
|
||||
|
||||
`Credentials file` corresponds directly to the [SharedCredentialsProvider](https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#SharedCredentialsProvider) provider in the Go SDK. In short, it will read the AWS shared credentials file and find the given profile. While `AWS SDK Default` will also find the shared credentials file, this option allows you to specify which profile to use without using environment variables. It doesn't have any implicit fallbacks to other credential providers, and will fail if using credentials from the credentials file doesn't work.
|
||||
|
||||
`Access & secret key` corresponds to the [StaticProvider](https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#StaticProvider) and uses the given access key ID and secret key to authenticate. This method doesn't have any fallbacks, and will fail if the provided key pair doesn't work.
|
||||
|
||||
### IAM roles
|
||||
|
||||
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. Providing you have chosen the _AWS SDK Default_ authentication method, and your Grafana server is running on AWS, you can use IAM Roles to handle authentication automatically.
|
||||
|
||||
See the AWS documentation on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||
|
||||
### IAM policies
|
||||
## IAM policies
|
||||
|
||||
Grafana needs permissions granted via IAM to be able to read CloudWatch metrics
|
||||
and EC2 tags/instances/regions. You can attach these permissions to IAM roles and
|
||||
@@ -99,40 +87,6 @@ Here is a minimal policy example:
|
||||
}
|
||||
```
|
||||
|
||||
### Assuming a role
|
||||
|
||||
The `Assume Role ARN` field allows you to specify which IAM role to assume, if any. When left blank, the provided credentials are used directly and the associated role or user should have the required permissions. If this field is non-blank, on the other hand, the provided credentials are used to perform an [sts:AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) call.
|
||||
|
||||
### Endpoint
|
||||
|
||||
The `Endpoint` field allows you to specify a custom endpoint URL that overrides the default generated endpoint for the CloudWatch API. Leave this field blank if you want to use the default generated endpoint. For more information on why and how to use Service endpoints, refer to the [AWS service endpoints documentation](https://docs.aws.amazon.com/general/latest/gr/rande.html).
|
||||
|
||||
### EKS IAM roles for service accounts
|
||||
|
||||
The Grafana process in the container runs as user 472 (called "grafana"). When Kubernetes mounts your projected credentials, they will by default only be available to the root user. In order to allow user 472 to access the credentials (and avoid it falling back to the IAM role attached to the EC2 instance), you will need to provide a [security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for your pod.
|
||||
|
||||
```yaml
|
||||
securityContext:
|
||||
fsGroup: 472
|
||||
runAsUser: 472
|
||||
runAsGroup: 472
|
||||
```
|
||||
|
||||
### AWS credentials file
|
||||
|
||||
Create a file at `~/.aws/credentials`. That is the `HOME` path for user running grafana-server.
|
||||
|
||||
> **Note:** If you think you have the credentials file in the right place and it is still not working, you might try moving your .aws file to '/usr/share/grafana/' and make sure your credentials file has at most 0644 permissions.
|
||||
|
||||
Example content:
|
||||
|
||||
```bash
|
||||
[default]
|
||||
aws_access_key_id = asdsadasdasdasd
|
||||
aws_secret_access_key = dasdasdsadasdasdasdsa
|
||||
region = us-west-2
|
||||
```
|
||||
|
||||
## Using the Query Editor
|
||||
|
||||
The CloudWatch data source can query data from both CloudWatch metrics and CloudWatch Logs APIs, each with its own specialized query editor. You select which API you want to query with using the query mode switch on top of the editor.
|
||||
@@ -146,22 +100,18 @@ To create a valid query, you need to specify the namespace, metric name and at l
|
||||
|
||||
### Dynamic queries using dimension wildcards
|
||||
|
||||
> Only available in Grafana v6.5+.
|
||||
|
||||
In Grafana 6.5 or higher, you’re able to monitor a dynamic list of metrics by using the asterisk (\*) wildcard for one or more dimension values.
|
||||
You can monitor a dynamic list of metrics by using the asterisk (\*) wildcard for one or more dimension values.
|
||||
|
||||
{{< figure src="/static/img/docs/v65/cloudwatch-dimension-wildcard.png" max-width="800px" class="docs-image--right" caption="CloudWatch dimension wildcard" >}}
|
||||
|
||||
In the example, all metrics in the namespace `AWS/EC2` with a metric name of `CPUUtilization` and ANY value for the `InstanceId` dimension are queried. This can help you monitor metrics for AWS resources, like EC2 instances or containers. For example, when new instances get created as part of an auto scaling event, they will automatically appear in the graph without you having to track the new instance IDs. This capability is currently limited to retrieving up to 100 metrics. You can click on `Show Query Preview` to see the search expression that is automatically built to support wildcards. To learn more about search expressions, visit the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/search-expression-syntax.html).
|
||||
In this example, the query returns all metrics in the namespace `AWS/EC2` with a metric name of `CPUUtilization` and ANY value for the `InstanceId` dimension are queried. This can help you monitor metrics for AWS resources, like EC2 instances or containers. For example, when new instances are created as part of an auto scaling event, they will automatically appear in the graph without needing to track the new instance IDs. This capability is currently limited to retrieving up to 100 metrics.
|
||||
|
||||
By default, the search expression is defined in such a way that the queried metrics must match the defined dimension names exactly. This means that in the example only metrics with exactly one dimension with name ‘InstanceId’ will be returned.
|
||||
Click on `Show Query Preview` to see the search expression that is automatically built to support wildcards. To learn more about search expressions, visit the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/search-expression-syntax.html). By default, the search expression is defined in such a way that the queried metrics must match the defined dimension names exactly. This means that in the example only metrics with exactly one dimension with name ‘InstanceId’ will be returned.
|
||||
|
||||
You can untoggle `Match Exact` to include metrics that have other dimensions defined. Disabling `Match Exact` also creates a search expression even if you don’t use wildcards. We simply search for any metric that matches at least the namespace, metric name, and all defined dimensions.
|
||||
|
||||
### Multi-value template variables
|
||||
|
||||
> Only available in Grafana v6.5+.
|
||||
|
||||
When defining dimension values based on multi-valued template variables, a search expression is used to query for the matching metrics. This enables the use of multiple template variables in one query and also allows you to use template variables for queries that have the `Match Exact` option disabled.
|
||||
|
||||
Search expressions are currently limited to 1024 characters, so your query may fail if you have a long list of values. We recommend using the asterisk (`*`) wildcard instead of the `All` option if you want to query all metrics that have any value for a certain dimension name.
|
||||
@@ -192,8 +142,6 @@ This feature is not available for metrics that are based on metric math expressi
|
||||
|
||||
## Using the Logs Query Editor
|
||||
|
||||
> **Note:** Available in Grafana v7.0+.
|
||||
|
||||
To query CloudWatch Logs, select the region and up to 20 log groups which you want to query. Use the main input area to write your query in [CloudWatch Logs Query Language](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html)
|
||||
|
||||
You can also write queries returning time series data by using the [`stats` command](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_Insights-Visualizing-Log-Data.html). When making `stats` queries in Explore, you have to make sure you are in Metrics Explore mode.
|
||||
@@ -206,8 +154,8 @@ To the right of the query input field is a CloudWatch Logs Insights link that op
|
||||
|
||||
### Using template variables
|
||||
|
||||
As with several other data sources, the CloudWatch data source supports the use of template variables in queries.
|
||||
See the [Templating]({{< relref "../variables/_index.md" >}}) documentation for an introduction to the templating feature and the different types of template variables.
|
||||
The CloudWatch data source supports use of template variables in queries.
|
||||
For an introduction to templating and template variables, refer to the [Templating]({{< relref "../../variables/_index.md" >}}) documentation.
|
||||
|
||||
### Deep linking from Grafana panels to the CloudWatch console
|
||||
|
||||
@@ -218,7 +166,7 @@ If you're not currently logged in to the CloudWatch console, the link will forwa
|
||||
### Alerting
|
||||
|
||||
Since CloudWatch Logs queries can return numeric data, for example through the use of the `stats` command, alerts are supported.
|
||||
See the [Alerting]({{< relref "../alerting/_index.md" >}}) documentation for more on Grafana alerts.
|
||||
For more information on Grafana alerts, refer to [Alerting]({{< relref "../../alerting/_index.md" >}}) documentation.
|
||||
|
||||
## Curated dashboards
|
||||
|
||||
@@ -236,9 +184,9 @@ To import the pre-configured dashboards, go to the configuration page of your Cl
|
||||
|
||||
## Templated queries
|
||||
|
||||
Instead of hard-coding things like server, application and sensor name in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard.
|
||||
Instead of hard-coding server, application, and sensor names in your metric queries, you can use variables. The variables are listed as dropdown select boxes at the top of the dashboard. These dropdowns make it easy to change the display of data in your dashboard.
|
||||
|
||||
See the [Templating]({{< relref "../variables/_index.md" >}}) documentation for an introduction to the templating feature and the different types of template variables.
|
||||
For an introduction to templating and template variables, refer to the [Templating]({{< relref "../../variables/_index.md" >}}) documentation.
|
||||
|
||||
### Query variable
|
||||
|
||||
@@ -371,7 +319,7 @@ Please see the AWS documentation for [Service Quotas](https://docs.aws.amazon.co
|
||||
|
||||
## Configure the data source with grafana.ini
|
||||
|
||||
In the Grafana [configuration]({{< relref "../administration/configuration.md#aws" >}}) file, there's an `AWS` section that allows you to customize the data source.
|
||||
The Grafana [configuration]({{< relref "../../administration/configuration.md#aws" >}}) file includes an `AWS` section where you can customize the data source.
|
||||
|
||||
### allowed_auth_providers
|
||||
|
||||
@@ -387,7 +335,7 @@ When a custom namespace is specified in the query editor, the [List Metrics API]
|
||||
|
||||
## Configure the data source with provisioning
|
||||
|
||||
It's now possible to configure data sources using config files with Grafana's provisioning system. You can read more about how it works and all the settings you can set for data sources on the [provisioning docs page]({{< relref "../administration/provisioning/#datasources" >}})
|
||||
You can configure the CloudWatch data source by customizing configuration files in Grafana's provisioning system. To know more about provisioning and learn about available configuration options, refer to the [Provisioning Grafana]({{< relref "../../administration/provisioning/#datasources" >}}) topic.
|
||||
|
||||
Here are some provisioning examples for this data source.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
+++
|
||||
title = "Authentication"
|
||||
description = "AWS authentication"
|
||||
keywords = ["grafana", "aws", "authentication"]
|
||||
aliases = ["/docs/grafana/latest/datasources/cloudwatch"]
|
||||
weight = 205
|
||||
+++
|
||||
|
||||
# AWS authentication
|
||||
|
||||
You can use one of the following authentication methods. All of these methods are enabled by default. You can disable them if necessary if you have server configuration access. For more information, refer to [allowed_auth_providers]({{< relref "../../administration/configuration.md#allowed_auth_providers" >}}) documentation.
|
||||
|
||||
- `AWS SDK Default` performs no custom configuration and instead uses the [default provider](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) as specified by the AWS SDK for Go. It requires you to configure your AWS credentials separately, such as if you've [configured the CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html), if you're [running on an EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html), [in an ECS task](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html), or for a [Service Account in a Kubernetes cluster](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).
|
||||
|
||||
- `Credentials file` corresponds directly to the [SharedCredentialsProvider](https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#SharedCredentialsProvider) provider in the Go SDK. It reads the AWS shared credentials file to find a given profile. While `AWS SDK Default` will also find the shared credentials file, this option allows you to specify which profile to use without using environment variables. This option doesn't have any implicit fallbacks to other credential providers, and it fails if the credentials provided from the file aren't correct.
|
||||
|
||||
- `Access and secret key` corresponds to the [StaticProvider](https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#StaticProvider) and uses the given access key ID and secret key to authenticate. This method doesn't have any fallbacks, and will fail if the provided key pair doesn't work.
|
||||
|
||||
> **Note:** Grafana also supports the `Workspace IAM role` method. We will be add documentation for this authentication method soon.
|
||||
|
||||
## IAM roles
|
||||
|
||||
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If you are using the _AWS SDK Default_ authentication method, and your Grafana server is running on AWS, use IAM Roles to handle authentication automatically.
|
||||
|
||||
For more information, refer to the AWS documentation on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html).
|
||||
|
||||
> **Note:** IAM policies are data source specific. Refer to Authentication section of the [Cloudwatch]({{< relref "./_index.md" >}}) topic for more information.
|
||||
|
||||
## Assuming a role
|
||||
|
||||
The `Assume Role ARN` field allows you to specify which IAM role to assume. When left blank, the provided credentials are used directly and the associated role or user should have the required permissions. If this field is non-blank, on the other hand, the provided credentials are used to perform an [sts:AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) call.
|
||||
|
||||
If you set this field to `false` then AWS authentication no longer uses an assumed role. For more information, refer to [assume_role_enabled]({{< relref "../../administration/configuration.md#assume_role_enabled" >}}) documentation.
|
||||
|
||||
## Endpoint
|
||||
|
||||
The `Endpoint` field allows you to specify a custom endpoint URL that overrides the default generated endpoint for the CloudWatch API. Leave this field blank if you want to use the default generated endpoint. For more information on why and how to use Service endpoints, refer to the [AWS service endpoints documentation](https://docs.aws.amazon.com/general/latest/gr/rande.html).
|
||||
|
||||
## EKS IAM roles for service accounts
|
||||
|
||||
The Grafana process in the container runs as user 472 (called "grafana"). When Kubernetes mounts your projected credentials, they will by default only be available to the root user. To allow user 472 to access the credentials (and avoid falling back to the IAM role attached to the EC2 instance), you need to provide a [security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for your pod.
|
||||
|
||||
```yaml
|
||||
securityContext:
|
||||
fsGroup: 472
|
||||
runAsUser: 472
|
||||
runAsGroup: 472
|
||||
```
|
||||
|
||||
## AWS credentials file
|
||||
|
||||
Create a file at `~/.aws/credentials`. That is the `HOME` path for user running grafana-server.
|
||||
|
||||
> **Note:** If you think you have the credentials file in the right place and it is still not working, you might try moving your .aws file to '/usr/share/grafana/' and make sure your credentials file has at most 0644 permissions.
|
||||
|
||||
Example content:
|
||||
|
||||
```bash
|
||||
[default]
|
||||
aws_access_key_id = asdsadasdasdasd
|
||||
aws_secret_access_key = dasdasdsadasdasdasdsa
|
||||
region = us-west-2
|
||||
```
|
||||
@@ -274,6 +274,6 @@ For more details on AWS SigV4, refer to the [AWS documentation](https://docs.aws
|
||||
|
||||
In order to sign requests to your Amazon Elasticsearch Service domain, SigV4 can be enabled in the Grafana [configuration]({{< relref "../administration/configuration.md#sigv4_auth_enabled" >}}).
|
||||
|
||||
Once AWS SigV4 is enabled, it can be configured on the Elasticsearch data source configuration page. Refer to [Cloudwatch authentication]({{<relref "./cloudwatch.md#authentication" >}}) for more information about authentication options.
|
||||
Once AWS SigV4 is enabled, it can be configured on the Elasticsearch data source configuration page. Refer to [Cloudwatch authentication]({{< relref "../datasources/aws-cloudwatch/aws-authentication.md" >}}) for more information about authentication options.
|
||||
|
||||
{{< figure src="/static/img/docs/v73/elasticsearch-sigv4-config-editor.png" max-width="500px" class="docs-image--no-shadow" caption="SigV4 configuration for AWS Elasticsearch Service" >}}
|
||||
|
||||
@@ -35,7 +35,7 @@ These options apply if you are using the InfluxQL query language. If you are usi
|
||||
| `Access` | Server (default) = URL needs to be accessible from the Grafana backend/server, Browser = URL needs to be accessible from the browser. |
|
||||
|
||||
**Note**: Browser access is deprecated and will be removed in a future release.
|
||||
`Whitelisted Cookies`| Cookies that will be forwarded to the data source. All other cookies will be deleted.
|
||||
`Allowed cookies`| Cookies that will be forwarded to the data source. All other cookies will be deleted.
|
||||
`Database` | The ID of the bucket you want to query from, copied from the [Buckets page](https://docs.influxdata.com/influxdb/v2.0/organizations/buckets/view-buckets/) of the InfluxDB UI.
|
||||
`User` | The username you use to sign into InfluxDB.
|
||||
`Password` | The token you use to query the bucket above, copied from the [Tokens page](https://docs.influxdata.com/influxdb/v2.0/security/tokens/view-tokens/) of the InfluxDB UI.
|
||||
|
||||
@@ -34,8 +34,10 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
|
||||
- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0.
|
||||
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
|
||||
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.
|
||||
- **Filter by Span ID -** Toggle to append the span ID to the Loki query.
|
||||
|
||||

|
||||

|
||||
|
||||
## Query traces
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ You can run Loki on your own hardware or use [Grafana Cloud](https://grafana.com
|
||||
|
||||
To access Loki settings, click the **Configuration** (gear) icon, then click **Data Sources**, and then click the Loki data source.
|
||||
|
||||
| Name | Description |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels, queries, and Explore. |
|
||||
| `Default` | Default data source that is pre-selected for new panels. |
|
||||
| `URL` | URL of the Loki instance, e.g., `http://localhost:3100`. |
|
||||
| `Whitelisted Cookies` | Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source. |
|
||||
| `Maximum lines` | Upper limit for the number of log lines returned by Loki (default is 1000). Lower this limit if your browser is sluggish when displaying logs in Explore. |
|
||||
| Name | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels, queries, and Explore. |
|
||||
| `Default` | Default data source that is pre-selected for new panels. |
|
||||
| `URL` | URL of the Loki instance, e.g., `http://localhost:3100`. |
|
||||
| `Allowed cookies` | Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source. |
|
||||
| `Maximum lines` | Upper limit for the number of log lines returned by Loki (default is 1000). Lower this limit if your browser is sluggish when displaying logs in Explore. |
|
||||
|
||||
> **Note:** To troubleshoot configuration and other issues, check the log file located at /var/log/grafana/grafana.log on Unix systems or in <grafana_install_dir>/data/log on other platforms and manual installations.
|
||||
|
||||
|
||||
@@ -168,122 +168,96 @@ The resulting table panel:
|
||||
|
||||
## Time series queries
|
||||
|
||||
If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must have a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you omit the `metric` column, the name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric.
|
||||
If you return multiple value columns and a column named `metric` then this column is used as prefix for the series name (only available in Grafana 5.3+).
|
||||
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
|
||||
|
||||
Result sets of time series queries need to be sorted by time.
|
||||
A time series query result is returned in a [wide data frame format]({{< relref "../developers/plugins/data-frames.md#wide-format" >}}). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
|
||||
|
||||
**Example database table:**
|
||||
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
|
||||
|
||||
```sql
|
||||
CREATE TABLE [event] (
|
||||
time_sec bigint,
|
||||
description nvarchar(100),
|
||||
tags nvarchar(100),
|
||||
)
|
||||
```
|
||||
You can optionally customize the default series name formatting using instructions in [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}).
|
||||
|
||||
```sql
|
||||
CREATE TABLE metric_values (
|
||||
time datetime,
|
||||
measurement nvarchar(100),
|
||||
valueOne int,
|
||||
valueTwo int,
|
||||
)
|
||||
|
||||
INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 12:30:00', 'Metric A', 62, 6)
|
||||
INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 12:30:00', 'Metric B', 49, 11)
|
||||
...
|
||||
INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 13:55:00', 'Metric A', 14, 25)
|
||||
INSERT metric_values (time, measurement, valueOne, valueTwo) VALUES('2018-03-15 13:55:00', 'Metric B', 48, 10)
|
||||
|
||||
```
|
||||
|
||||
{{< figure src="/static/img/docs/v51/mssql_time_series_one.png" class="docs-image--no-shadow docs-image--right" >}}
|
||||
|
||||
**Example with one `value` and one `metric` column.**
|
||||
**Example with `metric` column:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
time,
|
||||
valueOne,
|
||||
measurement as metric
|
||||
FROM
|
||||
metric_values
|
||||
WHERE
|
||||
$__timeFilter(time)
|
||||
$__timeGroup(time_date_time, '5m') as time,
|
||||
min("value_double"),
|
||||
'min' as metric
|
||||
FROM test_data
|
||||
WHERE $__timeFilter(time_date_time)
|
||||
GROUP BY $__timeGroup(time_date_time, '5m')
|
||||
ORDER BY 1
|
||||
```
|
||||
|
||||
When the above query is used in a graph panel, it will produce two series named `Metric A` and `Metric B` with the values `valueOne` and `valueTwo` plotted over `time`.
|
||||
Data frame result:
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
{{< figure src="/static/img/docs/v51/mssql_time_series_two.png" class="docs-image--no-shadow docs-image--right" >}}
|
||||
|
||||
**Example with multiple `value` columns:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
time,
|
||||
valueOne,
|
||||
valueTwo
|
||||
FROM
|
||||
metric_values
|
||||
WHERE
|
||||
$__timeFilter(time)
|
||||
ORDER BY 1
|
||||
```text
|
||||
+---------------------+-----------------+
|
||||
| Name: time | Name: min |
|
||||
| Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+---------------------+-----------------+
|
||||
| 2020-01-02 03:05:00 | 3 |
|
||||
| 2020-01-02 03:10:00 | 6 |
|
||||
+---------------------+-----------------+
|
||||
```
|
||||
|
||||
When the above query is used in a graph panel, it will produce two series named `Metric A` and `Metric B` with the values `valueOne` and `valueTwo` plotted over `time`.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
{{< figure src="/static/img/docs/v51/mssql_time_series_three.png" class="docs-image--no-shadow docs-image--right" >}}
|
||||
|
||||
**Example using the \$\_\_timeGroup macro:**
|
||||
**Example using the fill parameter in the $\_\_timeGroup macro to convert null values to be zero instead:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
$__timeGroup(time, '3m') as time,
|
||||
measurement as metric,
|
||||
avg(valueOne)
|
||||
FROM
|
||||
metric_values
|
||||
$__timeGroup(createdAt, '5m', 0) as time,
|
||||
sum(value) as value,
|
||||
hostname
|
||||
FROM test_data
|
||||
WHERE
|
||||
$__timeFilter(time)
|
||||
$__timeFilter(createdAt)
|
||||
GROUP BY
|
||||
$__timeGroup(time, '3m'),
|
||||
measurement
|
||||
$__timeGroup(createdAt, '5m', 0),
|
||||
hostname
|
||||
ORDER BY 1
|
||||
```
|
||||
|
||||
When the above query is used in a graph panel, it will produce two series named `Metric A` and `Metric B` with the values `valueOne` and `valueTwo` plotted over `time`.
|
||||
Any two series lacking a value in a three-minute window will render a line between those two lines. You'll notice that the graph to the right never goes down to zero.
|
||||
Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}) value of `${__field.labels.hostname}`.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
Data frame result:
|
||||
|
||||
{{< figure src="/static/img/docs/v51/mssql_time_series_four.png" class="docs-image--no-shadow docs-image--right" >}}
|
||||
```text
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| Name: time | Name: value | Name: value |
|
||||
| Labels: | Labels: hostname=10.0.1.1 | Labels: hostname=10.0.1.2 |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| 2020-01-02 03:05:00 | 3 | 4 |
|
||||
| 2020-01-02 03:10:00 | 6 | 7 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
```
|
||||
|
||||
**Example using the \$\_\_timeGroup macro with fill parameter set to zero:**
|
||||
**Example with multiple columns:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
$__timeGroup(time, '3m', 0) as time,
|
||||
measurement as metric,
|
||||
sum(valueTwo)
|
||||
FROM
|
||||
metric_values
|
||||
WHERE
|
||||
$__timeFilter(time)
|
||||
GROUP BY
|
||||
$__timeGroup(time, '3m'),
|
||||
measurement
|
||||
$__timeGroup(time_date_time, '5m'),
|
||||
min(value_double) as min_value,
|
||||
max(value_double) as max_value
|
||||
FROM test_data
|
||||
WHERE $__timeFilter(time_date_time)
|
||||
GROUP BY $__timeGroup(time_date_time, '5m')
|
||||
ORDER BY 1
|
||||
```
|
||||
|
||||
When the above query is used in a graph panel, the result is two series named `Metric A` and `Metric B` with a sum of `valueTwo` plotted over `time`.
|
||||
Any series lacking a value in a 3 minute window will have a value of zero which you'll see rendered in the graph to the right.
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+-----------------+-----------------+
|
||||
| Name: time | Name: min_value | Name: max_value |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
| 2020-01-02 03:04:00 | 3 | 4 |
|
||||
| 2020-01-02 03:05:00 | 6 | 7 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
```
|
||||
|
||||
## Templating
|
||||
|
||||
|
||||
@@ -180,12 +180,13 @@ The resulting table panel:
|
||||
|
||||
## Time series queries
|
||||
|
||||
If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must return a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch.
|
||||
Any column except `time` and `metric` is treated as a value column.
|
||||
You may return a column named `metric` that is used as metric name for the value column.
|
||||
If you return multiple value columns and a column named `metric` then this column is used as prefix for the series name (only available in Grafana 5.3+).
|
||||
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
|
||||
|
||||
Resultsets of time series queries need to be sorted by time.
|
||||
A time series query result is returned in a [wide data frame format]({{< relref "../developers/plugins/data-frames.md#wide-format" >}}). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
|
||||
|
||||
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
|
||||
|
||||
You can optionally customize the default series name formatting using instructions in [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}).
|
||||
|
||||
**Example with `metric` column:**
|
||||
|
||||
@@ -200,20 +201,48 @@ GROUP BY time
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+-----------------+
|
||||
| Name: time | Name: min |
|
||||
| Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+---------------------+-----------------+
|
||||
| 2020-01-02 03:05:00 | 3 |
|
||||
| 2020-01-02 03:10:00 | 6 |
|
||||
+---------------------+-----------------+
|
||||
```
|
||||
|
||||
**Example using the fill parameter in the $\_\_timeGroup macro to convert null values to be zero instead:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
$__timeGroup(createdAt,'5m',0),
|
||||
sum(value_double) as value,
|
||||
measurement
|
||||
hostname
|
||||
FROM test_data
|
||||
WHERE
|
||||
$__timeFilter(createdAt)
|
||||
GROUP BY time, measurement
|
||||
GROUP BY time, hostname
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}) value of `${__field.labels.hostname}`.
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| Name: time | Name: value | Name: value |
|
||||
| Labels: | Labels: hostname=10.0.1.1 | Labels: hostname=10.0.1.2 |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| 2020-01-02 03:05:00 | 3 | 4 |
|
||||
| 2020-01-02 03:10:00 | 6 | 7 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
```
|
||||
|
||||
**Example with multiple columns:**
|
||||
|
||||
```sql
|
||||
@@ -227,6 +256,19 @@ GROUP BY time
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+-----------------+-----------------+
|
||||
| Name: time | Name: min_value | Name: max_value |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
| 2020-01-02 03:04:00 | 3 | 4 |
|
||||
| 2020-01-02 03:05:00 | 6 | 7 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
```
|
||||
|
||||
Currently, there is no support for a dynamic group by time based on time range and panel width.
|
||||
This is something we plan to add.
|
||||
|
||||
|
||||
@@ -14,15 +14,15 @@ Grafana ships with advanced support for OpenTSDB. This topic explains options, v
|
||||
|
||||
To access OpenTSDB settings, hover your mouse over the **Configuration** (gear) icon, then click **Data Sources**, and then click the OpenTSDB data source.
|
||||
|
||||
| Name | Description |
|
||||
| --------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
|
||||
| `Default` | Default data source means that it will be pre-selected for new panels. |
|
||||
| `URL` | The HTTP protocol, IP, and port of your OpenTSDB server (default port is usually 4242) |
|
||||
| `Whitelisted Cookies` | List the names of cookies to forward to the data source. |
|
||||
| `Version` | Version = opentsdb version, either <=2.1 or 2.2 |
|
||||
| `Resolution` | Metrics from opentsdb may have datapoints with either second or millisecond resolution. |
|
||||
| `Lookup Limit` | Default is 1000. |
|
||||
| Name | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
|
||||
| `Default` | Default data source means that it will be pre-selected for new panels. |
|
||||
| `URL` | The HTTP protocol, IP, and port of your OpenTSDB server (default port is usually 4242) |
|
||||
| `Allowed cookies` | List the names of cookies to forward to the data source. |
|
||||
| `Version` | Version = opentsdb version, either <=2.1 or 2.2 |
|
||||
| `Resolution` | Metrics from opentsdb may have datapoints with either second or millisecond resolution. |
|
||||
| `Lookup limit` | Default is 1000. |
|
||||
|
||||
## Query editor
|
||||
|
||||
|
||||
@@ -185,12 +185,13 @@ The resulting table panel:
|
||||
|
||||
## Time series queries
|
||||
|
||||
If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must return a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch.
|
||||
Any column except `time` and `metric` are treated as a value column.
|
||||
You may return a column named `metric` that is used as metric name for the value column.
|
||||
If you return multiple value columns and a column named `metric` then this column is used as prefix for the series name (only available in Grafana 5.3+).
|
||||
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
|
||||
|
||||
Resultsets of time series queries need to be sorted by time.
|
||||
A time series query result is returned in a [wide data frame format]({{< relref "../developers/plugins/data-frames.md#wide-format" >}}). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
|
||||
|
||||
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
|
||||
|
||||
You can optionally customize the default series name formatting using instructions in [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}).
|
||||
|
||||
**Example with `metric` column:**
|
||||
|
||||
@@ -205,20 +206,48 @@ GROUP BY time
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+-----------------+
|
||||
| Name: time | Name: min |
|
||||
| Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 |
|
||||
+---------------------+-----------------+
|
||||
| 2020-01-02 03:05:00 | 3 |
|
||||
| 2020-01-02 03:10:00 | 6 |
|
||||
+---------------------+-----------------+
|
||||
```
|
||||
|
||||
**Example using the fill parameter in the $\_\_timeGroup macro to convert null values to be zero instead:**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
$__timeGroup("createdAt",'5m',0),
|
||||
sum(value) as value,
|
||||
measurement
|
||||
hostname
|
||||
FROM test_data
|
||||
WHERE
|
||||
$__timeFilter("createdAt")
|
||||
GROUP BY time, measurement
|
||||
GROUP BY time, hostname
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}) value of `${__field.labels.hostname}`.
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| Name: time | Name: value | Name: value |
|
||||
| Labels: | Labels: hostname=10.0.1.1 | Labels: hostname=10.0.1.2 |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
| 2020-01-02 03:05:00 | 3 | 4 |
|
||||
| 2020-01-02 03:10:00 | 6 | 7 |
|
||||
+---------------------+---------------------------+---------------------------+
|
||||
```
|
||||
|
||||
**Example with multiple columns:**
|
||||
|
||||
```sql
|
||||
@@ -232,6 +261,19 @@ GROUP BY time
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
Data frame result:
|
||||
|
||||
```text
|
||||
+---------------------+-----------------+-----------------+
|
||||
| Name: time | Name: min_value | Name: max_value |
|
||||
| Labels: | Labels: | Labels: |
|
||||
| Type: []time.Time | Type: []float64 | Type: []float64 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
| 2020-01-02 03:04:00 | 3 | 4 |
|
||||
| 2020-01-02 03:05:00 | 6 | 7 |
|
||||
+---------------------+-----------------+-----------------+
|
||||
```
|
||||
|
||||
## Templating
|
||||
|
||||
Instead of hard-coding things like server, application and sensor name in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard.
|
||||
|
||||
@@ -46,19 +46,17 @@ Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key w
|
||||
{{< figure src="/static/img/docs/v45/prometheus_query_editor_still.png"
|
||||
animated-gif="/static/img/docs/v45/prometheus_query_editor.gif" >}}
|
||||
|
||||
| Name | Description |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Query expression` | Prometheus query expression. For more information, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
|
||||
| `Legend format` | Controls the name of the time series, using name or pattern. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. |
|
||||
| `Step` | Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements are not frequent (taken hourly). Use the 'Exact' step mode to set a precise interval between data points. `$__interval` and `$__rate_interval` are supported. |
|
||||
|
||||
| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, you can pick lower resolutions. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Both _Min time interval_ and _Step_ limit the final value of `$__interval` and `step`. |
|
||||
|
||||
| `Metric lookup` | Search for metric names in this input field. |
|
||||
| `Format as` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
|
||||
| `Instant` | Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. |
|
||||
| `Min time interval` | This value multiplied by the denominator from the _Resolution_ setting sets a lower limit to both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Defaults to _Scrape interval_ as specified in the data source options. |
|
||||
| `Exemplars` | Run and show exemplars in the graph. |
|
||||
| Name | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `Query expression` | Prometheus query expression. For more information, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
|
||||
| `Legend format` | Controls the name of the time series, using name or pattern. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. |
|
||||
| `Step` | Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements are not frequent (taken hourly). Use the 'Exact' step mode to set a precise interval between data points. `$__interval` and `$__rate_interval` are supported. |
|
||||
| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, you can pick lower resolutions. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Both _Min time interval_ and _Step_ limit the final value of `$__interval` and `step`. |
|
||||
| `Metric lookup` | Search for metric names in this input field. |
|
||||
| `Format as` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
|
||||
| `Instant` | Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. |
|
||||
| `Min time interval` | This value multiplied by the denominator from the _Resolution_ setting sets a lower limit to both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Defaults to _Scrape interval_ as specified in the data source options. |
|
||||
| `Exemplars` | Run and show exemplars in the graph. |
|
||||
|
||||
> **Note:** Grafana modifies the request dates for queries to align them with the dynamically calculated step. This ensures consistent display of metrics data, but it can result in a small gap of data at the right edge of a graph.
|
||||
|
||||
|
||||
@@ -33,8 +33,10 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
|
||||
- **Span start time shift -** A shift in the start time for the Loki query based on the start time for the span. To extend the time to the past, use a negative value. You can use time units, for example, 5s, 1m, 3h. The default is 0.
|
||||
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
|
||||
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.
|
||||
- **Filter by Span ID -** Toggle to append the span ID to the Loki query.
|
||||
|
||||

|
||||

|
||||
|
||||
## Query traces
|
||||
|
||||
|
||||
@@ -34,8 +34,10 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
|
||||
- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0.
|
||||
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
|
||||
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.
|
||||
- **Filter by Span ID -** Toggle to append the span ID to the Loki query.
|
||||
|
||||

|
||||

|
||||
|
||||
## Query traces
|
||||
|
||||
|
||||
@@ -184,17 +184,17 @@ For data source plugins. Proxy routes used for plugin authentication and adding
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
| -------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
|
||||
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
|
||||
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
|
||||
| `method` | string | No | For data source plugins. Route method matches the HTTP verb like GET or POST. |
|
||||
| `path` | string | No | For data source plugins. The route path that is replaced by the route URL field when proxying the call. |
|
||||
| `reqRole` | string | No | |
|
||||
| `reqSignedIn` | boolean | No | |
|
||||
| `tokenAuth` | [object](#tokenauth) | No | For data source plugins. Token authentication section used with an OAuth API. |
|
||||
| `url` | string | No | For data source plugins. Route URL is where the request is proxied to. |
|
||||
| Property | Type | Required | Description |
|
||||
| -------------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
|
||||
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
|
||||
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
|
||||
| `method` | string | No | For data source plugins. Route method matches the HTTP verb like GET or POST. Multiple methods can be provided as a comma-separated list. |
|
||||
| `path` | string | No | For data source plugins. The route path that is replaced by the route URL field when proxying the call. |
|
||||
| `reqRole` | string | No | |
|
||||
| `reqSignedIn` | boolean | No | |
|
||||
| `tokenAuth` | [object](#tokenauth) | No | For data source plugins. Token authentication section used with an OAuth API. |
|
||||
| `url` | string | No | For data source plugins. Route URL is where the request is proxied to. |
|
||||
|
||||
### body
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Project version of this commit, e.g. `6.7.x`.",
|
||||
"pattern": "^([0-9x]+\\.[0-9x]+\\.*[0-9x]*$|\\%VERSION\\%)"
|
||||
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*$|\\%VERSION\\%)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -340,7 +340,7 @@
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"description": "For data source plugins. Route method matches the HTTP verb like GET or POST."
|
||||
"description": "For data source plugins. Route method matches the HTTP verb like GET or POST. Multiple methods can be provided as a comma-separated list."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
|
||||
@@ -57,6 +57,14 @@ Grafana generates a TXT file in your default browser download location. You can
|
||||
1. Inspect the log query results as described above.
|
||||
1. Click **Download logs**.
|
||||
|
||||
### Download trace results
|
||||
|
||||
Based on the data source type, Grafana generates a JSON file for the trace results in one of the supported formats: Jaeger, Zipkin, or OTLP formats.
|
||||
|
||||
1. Open the inspector.
|
||||
1. Inspect the trace query results [as described above](#inspect-raw-query-results).
|
||||
1. Click **Download traces**.
|
||||
|
||||
### Inspect query performance
|
||||
|
||||
The Stats tab displays statistics that tell you how long your query takes, how many queries you send, and the number of rows returned. This information can help you troubleshoot your queries, especially if any of the numbers are unexpectedly high or low.
|
||||
|
||||
@@ -293,7 +293,7 @@ The new authentication method, _AWS SDK Default_, uses the default AWS Go SDK cr
|
||||
|
||||
The other authentication methods, _Access & secret key_ and _Credentials file_, have changed in regards to fallbacks. If these methods fail, they no longer fallback to other methods. e.g. environment variables. If you want fallbacks, you should use _AWS SDK Default_ instead.
|
||||
|
||||
For more information and details, please refer to [Using AWS CloudWatch in Grafana]({{< relref "../datasources/cloudwatch.md#authentication" >}}).
|
||||
For more information and details, please refer to [Using AWS CloudWatch in Grafana]({{< relref "../datasources/aws-cloudwatch/aws-authentication.md" >}}).
|
||||
|
||||
### User invites database migration
|
||||
|
||||
@@ -347,12 +347,34 @@ Grafana v8.0 changes the underlying data structure to [data frames]({{< relref "
|
||||
For any existing panels/visualizations using a _Time series_ query, where the time column is only needed for filtering the time range, for example, using the bar gauge or pie chart panel, we recommend that you use a _Table query_ instead and exclude the time column as a field in the response.
|
||||
Refer to this [issue comment](https://github.com/grafana/grafana/issues/35534#issuecomment-861519658) for detailed instructions and workarounds.
|
||||
|
||||
#### Prefix added to series names
|
||||
|
||||
When you have a query where there's a time value and a numeric value selected together with a string value that's not named _metric_, the graph panel renders series names as `value <hostname>` rather than just `<hostname>` which was the case before Grafana 8.
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
$__timeGroup("createdAt",'10m'),
|
||||
avg(value) as "value",
|
||||
hostname
|
||||
FROM grafana_metric
|
||||
WHERE $__timeFilter("createdAt")
|
||||
GROUP BY time, hostname
|
||||
ORDER BY time
|
||||
```
|
||||
|
||||
There are two possible workarounds to resolve this problem:
|
||||
|
||||
1. In Grafana v8.0.3, use an alias of the string column selected as `metric`. for example, `hostname as metric`.
|
||||
2. Use the [Standard field options/Display name]({{< relref "../panels/standard-options.md#display-name" >}}) to format the alias. For the preceding example query, you would use `${__field.labels.hostname}` option.
|
||||
|
||||
For more information, refer to the our relational databases documentation of [Postgres]({{< relref "../datasources/postgres.md#time-series-queries" >}}), [MySQL]({{< relref "../datasources/mysql.md#time-series-queries" >}}), [Microsoft SQL Server]({{< relref "../datasources/mssql.md#time-series-queries" >}}).
|
||||
|
||||
## Upgrading to v8.1
|
||||
|
||||
### Use of unencrypted passwords for data sources no longer supported
|
||||
|
||||
As of Grafana v8.1, we no longer support unencrypted storage of passwords and basic auth passwords.
|
||||
|
||||
>**Note":** Since Grafana v6.2, new or updated data sources store passwords and basic auth passwords encrypted. See [upgrade note]({{< relref "#ensure-encryption-of-data-source-secrets" >}}) for more information. However, unencrypted passwords and basic auth passwords were also allowed.
|
||||
|
||||
> **Note":** Since Grafana v6.2, new or updated data sources store passwords and basic auth passwords encrypted. See [upgrade note]({{< relref "#ensure-encryption-of-data-source-secrets" >}}) for more information. However, unencrypted passwords and basic auth passwords were also allowed.
|
||||
|
||||
To migrate to encrypted storage, follow the instructions from the [v6.2 upgrade notes]({{< relref "#ensure-encryption-of-data-source-secrets" >}}). You can also use a `grafana-cli` command to migrate all of your data sources to use encrypted storage of secrets. See [migrate data and encrypt passwords]({{< relref "../administration/cli.md#migrate-data-and-encrypt-passwords" >}}) for further instructions.
|
||||
|
||||
@@ -11,19 +11,19 @@ Among other places, these calculations are used in the Transform tab and the bar
|
||||
|
||||
| Calculation | Description |
|
||||
| :----------------- | :-------------------------------------------------------- |
|
||||
| All nulls | true when all values are null |
|
||||
| All zeros | true when all values are 0 |
|
||||
| Change count | number of times the field's value changes |
|
||||
| Count | number of values in a field |
|
||||
| Delta | cumulative change in value |
|
||||
| Difference | difference between first and last value of a field |
|
||||
| Difference percent | percentage change between first and last value of a field |
|
||||
| Distinct count | number of unique values in a field |
|
||||
| First (not null) | first, not null value in a field |
|
||||
| Max | maximum value of a field |
|
||||
| Mean | mean value of all values in a field |
|
||||
| Min | minimum value of a field |
|
||||
| Min (above zero) | minimum, positive value of a field |
|
||||
| Range | difference between maximum and minimum values of a field |
|
||||
| Step | minimal interval between values of a field |
|
||||
| Total | sum of all values in a field |
|
||||
| All nulls | True when all values are null |
|
||||
| All zeros | True when all values are 0 |
|
||||
| Change count | Number of times the field's value changes |
|
||||
| Count | Number of values in a field |
|
||||
| Delta | Cumulative change in value, only counts increments |
|
||||
| Difference | Difference between first and last value of a field |
|
||||
| Difference percent | Percentage change between first and last value of a field |
|
||||
| Distinct count | Number of unique values in a field |
|
||||
| First (not null) | First, not null value in a field |
|
||||
| Max | Maximum value of a field |
|
||||
| Mean | Mean value of all values in a field |
|
||||
| Min | Minimum value of a field |
|
||||
| Min (above zero) | Minimum, positive value of a field |
|
||||
| Range | Difference between maximum and minimum values of a field |
|
||||
| Step | Minimal interval between values of a field |
|
||||
| Total | Sum of all values in a field |
|
||||
|
||||
@@ -10,6 +10,7 @@ Grafana comes with the following transformations:
|
||||
- [Add field from calculation]({{< relref "./types-options.md#add-field-from-calculation" >}})
|
||||
- [Concatenate fields]({{< relref "./types-options.md#concatenate-fields" >}})
|
||||
- [Config from query results]({{< relref "./config-from-query.md" >}})
|
||||
- [Convert field type]({{< relref "./types-options.md#convert-field-type" >}})
|
||||
- [Filter data by name]({{< relref "./types-options.md#filter-data-by-name" >}})
|
||||
- [Filter data by query]({{< relref "./types-options.md#filter-data-by-query" >}})
|
||||
- [Filter data by value]({{< relref "./types-options.md#filter-data-by-value" >}})
|
||||
@@ -324,6 +325,36 @@ After you concatenate the fields, the data frame would be:
|
||||
| ---- | ------- | --- | ------ |
|
||||
| 15.4 | 1230233 | 3.2 | 5 |
|
||||
|
||||
## Convert field type
|
||||
|
||||
This transformation changes the field type of the specified field.
|
||||
|
||||
- **Field -** Select from available fields
|
||||
- **as -** Select the FieldType to convert to
|
||||
- **Numeric -** attempts to make the values numbers
|
||||
- **String -** will make the values strings
|
||||
- **Time -** attempts to parse the values as time
|
||||
- Will show an option to specify a DateFormat as input by a string like yyyy-mm-dd or DD MM YYYY hh:mm:ss
|
||||
- **Boolean -** will make the values booleans
|
||||
|
||||
For example the following query could be modified by selecting the time field, as Time, and Date Format as YYYY.
|
||||
|
||||
| Time | Mark | Value |
|
||||
| ---------- | ----- | ----- |
|
||||
| 2017-07-01 | above | 25 |
|
||||
| 2018-08-02 | below | 22 |
|
||||
| 2019-09-02 | below | 29 |
|
||||
| 2020-10-04 | above | 22 |
|
||||
|
||||
The result:
|
||||
|
||||
| Time | Mark | Value |
|
||||
| ------------------- | ----- | ----- |
|
||||
| 2017-01-01 00:00:00 | above | 25 |
|
||||
| 2018-01-01 00:00:00 | below | 22 |
|
||||
| 2019-01-01 00:00:00 | below | 29 |
|
||||
| 2020-01-01 00:00:00 | above | 22 |
|
||||
|
||||
## Series to rows
|
||||
|
||||
> **Note:** This transformation is available in Grafana 7.1+.
|
||||
|
||||
@@ -8,6 +8,7 @@ weight = 10000
|
||||
Here you can find detailed release notes that list everything that is included in every release as well as notices
|
||||
about deprecations, breaking changes as well as changes that relate to plugin development.
|
||||
|
||||
- [Release notes for 8.1.3]({{< relref "release-notes-8-1-3" >}})
|
||||
- [Release notes for 8.1.2]({{< relref "release-notes-8-1-2" >}})
|
||||
- [Release notes for 8.1.1]({{< relref "release-notes-8-1-1" >}})
|
||||
- [Release notes for 8.1.0]({{< relref "release-notes-8-1-0" >}})
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 8.1.3"
|
||||
[_build]
|
||||
list = false
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 8.1.3
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Alerting:** Fix alert flapping in the internal alertmanager. [#38648](https://github.com/grafana/grafana/pull/38648), [@gotjosh](https://github.com/gotjosh)
|
||||
- **Alerting:** Fix request handler failed to convert dataframe "results" to plugins.DataTimeSeriesSlice: input frame is not recognized as a time series. [#38587](https://github.com/grafana/grafana/pull/38587), [@idafurjes](https://github.com/idafurjes)
|
||||
- **Dashboard:** Fix UIDs are not preserved when importing/creating dashboards thru importing .json file. [#38659](https://github.com/grafana/grafana/pull/38659), [@axelavargas](https://github.com/axelavargas)
|
||||
- **Dashboard:** Forces panel re-render when exiting panel edit. [#38913](https://github.com/grafana/grafana/pull/38913), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Dashboard:** Prevent folder from changing when navigating to general settings. [#38103](https://github.com/grafana/grafana/pull/38103), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Docker:** Force use of libcrypto1.1 and libssl1.1 versions to fix CVE-2021-3711. [#38585](https://github.com/grafana/grafana/pull/38585), [@dsotirakis](https://github.com/dsotirakis)
|
||||
- **Elasticsearch:** Fix metric names for alert queries. [#38546](https://github.com/grafana/grafana/pull/38546), [@dsotirakis](https://github.com/dsotirakis)
|
||||
- **Elasticsearch:** Limit Histogram field parameter to numeric values. [#38631](https://github.com/grafana/grafana/pull/38631), [@Elfo404](https://github.com/Elfo404)
|
||||
- **Elasticsearch:** Prevent pipeline aggregations to show up in terms order by options. [#38448](https://github.com/grafana/grafana/pull/38448), [@Elfo404](https://github.com/Elfo404)
|
||||
- **LibraryPanels:** Prevent duplicate repeated panels from being created. [#38804](https://github.com/grafana/grafana/pull/38804), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Loki:** Fix ad-hoc filter in dashboard when used with parser. [#38542](https://github.com/grafana/grafana/pull/38542), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
- **Plugins:** Track signed files + add warn log for plugin assets which are not signed. [#38938](https://github.com/grafana/grafana/pull/38938), [@wbrowne](https://github.com/wbrowne)
|
||||
- **Postgres/MySQL/MSSQL:** Fix region annotations not displayed correctly. [#38936](https://github.com/grafana/grafana/pull/38936), [@marefr](https://github.com/marefr)
|
||||
- **Prometheus:** Fix validate selector in metrics browser. [#38921](https://github.com/grafana/grafana/pull/38921), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
@@ -83,7 +83,7 @@ For more information, refer to [Deprecating Application Insights and Insights An
|
||||
- You can now enable or disable authentication providers and assume a role other than default by changing the [allowed_auth_providers]({{< relref "../administration/configuration.md#allowed-auth-providers" >}}) and [assume_role_enabled]({{< relref "../administration/configuration.md#assume-role-enabled" >}}) options in the Grafana configuration file. By default, the allowed authentication providers are _AWS SDK Default_, _Access && secret key_, and _Credentials File_, and role is _Assume role (ARN)_.
|
||||
- You can now specify a custom endpoint in the CloudWatch data source configuration page. This field is optional, and if it is left empty, then the default endpoint for CloudWatch is used. By specifying a regional endpoint, you can reduce request latency.
|
||||
|
||||
[AWS Cloudwatch data source]({{< relref "../datasources/cloudwatch.md#endpoint" >}}) was updated as a result of this change.
|
||||
[AWS Cloudwatch data source]({{< relref "../datasources/aws-cloudwatch/_index.md" >}}) was updated as a result of this change.
|
||||
|
||||
### Increased API limit for CloudMonitoring Services
|
||||
|
||||
|
||||
@@ -14,20 +14,21 @@ replace k8s.io/client-go => k8s.io/client-go v0.22.1
|
||||
require (
|
||||
cloud.google.com/go/storage v1.14.0
|
||||
cuelang.org/go v0.3.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.16.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.9.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
||||
github.com/aws/aws-sdk-go v1.38.68
|
||||
github.com/aws/aws-sdk-go v1.40.11
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/benbjohnson/clock v1.1.0
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||
github.com/centrifugal/centrifuge v0.17.1
|
||||
github.com/centrifugal/centrifuge v0.18.3
|
||||
github.com/cortexproject/cortex v1.8.2-0.20210428155238-d382e1d80eaf
|
||||
github.com/crewjam/saml v0.4.6-0.20201227203850-bca570abb2ce
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200910202707-1e08a3fab204
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/gchaincl/sqlhooks v1.3.0
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
@@ -39,14 +40,16 @@ require (
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/wire v0.5.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.112.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.113.0
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/hashicorp/go-hclog v0.16.1
|
||||
@@ -69,17 +72,18 @@ require (
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/alertmanager v0.22.2
|
||||
github.com/prometheus/alertmanager v0.23.0-rc.0.0.20210906104939-8da517524a87
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.29.0
|
||||
github.com/prometheus/common v0.30.0
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210621150501-ff58416a0b02
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
github.com/spyzhov/ajson v0.4.2
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff
|
||||
@@ -94,7 +98,7 @@ require (
|
||||
go.opentelemetry.io/collector/model v0.31.0
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
@@ -118,7 +122,7 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.90.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 // indirect
|
||||
github.com/FZambia/eagle v0.0.1 // indirect
|
||||
github.com/FZambia/sentinel v1.1.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
@@ -130,13 +134,14 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||
github.com/centrifugal/protocol v0.5.0 // indirect
|
||||
github.com/centrifugal/protocol v0.7.2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/cockroachdb/apd/v2 v2.0.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.3.13 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||
@@ -149,17 +154,15 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/loads v0.20.2 // indirect
|
||||
github.com/go-openapi/runtime v0.19.28 // indirect
|
||||
github.com/go-openapi/runtime v0.19.29 // indirect
|
||||
github.com/go-openapi/spec v0.20.3 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-openapi/validate v0.20.2 // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/gogo/status v1.0.3 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/flatbuffers v1.12.0 // indirect
|
||||
@@ -173,7 +176,7 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/memberlist v0.2.3 // indirect
|
||||
github.com/hashicorp/memberlist v0.2.4 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
||||
github.com/igm/sockjs-go/v3 v3.0.0 // indirect
|
||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||
@@ -182,6 +185,7 @@ require (
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/jstemmer/go-junit-report v0.9.1 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.5 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e // indirect
|
||||
github.com/mattetti/filebuffer v1.0.1 // indirect
|
||||
@@ -190,7 +194,7 @@ require (
|
||||
github.com/miekg/dns v1.1.42 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mna/redisc v1.1.7 // indirect
|
||||
github.com/mna/redisc v1.2.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
|
||||
@@ -200,12 +204,15 @@ require (
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect
|
||||
github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.6.1 // indirect
|
||||
github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
|
||||
github.com/rs/cors v1.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/segmentio/encoding v0.2.19 // indirect
|
||||
github.com/sercand/kuberesolver v2.4.0+incompatible // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||
@@ -215,6 +222,7 @@ require (
|
||||
github.com/smartystreets/assertions v1.0.1 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/weaveworks/promrus v1.2.0 // indirect
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||
go.mongodb.org/mongo-driver v1.5.2 // indirect
|
||||
|
||||
@@ -83,14 +83,12 @@ github.com/Azure/azure-sdk-for-go v51.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
||||
github.com/Azure/azure-sdk-for-go v52.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v55.2.0+incompatible h1:TL2/vJWJEPOrmv97nHcbvjXES0Ntlb9P95hqGA1J2dU=
|
||||
github.com/Azure/azure-sdk-for-go v55.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.14.0/go.mod h1:pElNP+u99BvCZD+0jOlhI9OC/NB2IDTOTGZOZH0Qhq8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.16.1 h1:yQw8Ah26gBP4dv66ZNjZpRBRV+gaHH/0TLn1taU4FZ4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.16.1/go.mod h1:MVdrcUC4Hup35qHym3VdzoW+NBgBxrta9Vei97jRtM8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.9.1 h1:KchdKK3XlOjkzBROV+q3D+YgfRTvwoeBwbaoX4aVkjI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.9.1/go.mod h1:acANgl9stsT5xflESXKjZx4rhZJSr0TGgTDYY0xJPIE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.0/go.mod h1:k4KbFSunV/+0hOHL1vyFaPsiYQ1Vmvy1TBpmtvCDLZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1 h1:vx8McI56N5oLSQu8xa+xdiE0fjQq8W8Zt49vHP8Rygw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1/go.mod h1:k4KbFSunV/+0hOHL1vyFaPsiYQ1Vmvy1TBpmtvCDLZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0 h1:jq5Urf8QJK6h0wr8CMiwggo4OSMkXwpArQlkSjSpaBk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||
github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
|
||||
github.com/Azure/azure-storage-queue-go v0.0.0-20181215014128-6ed74e755687/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8=
|
||||
@@ -283,9 +281,11 @@ github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9
|
||||
github.com/aws/aws-sdk-go v1.35.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.37.8/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.60/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.68 h1:aOG8geU4SohNp659eKBHRBgbqSrZ6jNZlfimIuJAwL8=
|
||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.40.11 h1:iIRx5w2FbiaEKnCFcai+NSnYa9zKFe6Lzt6aLLUh61A=
|
||||
github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.5.0/go.mod h1:acH3+MQoiMzozT/ivU+DbRg7Ooo2298RdRaWcOv+4vM=
|
||||
@@ -337,10 +337,10 @@ github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1P
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/centrifugal/centrifuge v0.17.1 h1:UWORzEE5SEhJSy8omW50AVKbpKaqDBpUFp3kDnMgsXs=
|
||||
github.com/centrifugal/centrifuge v0.17.1/go.mod h1:AEFs3KPGRpvX1jCe24NDlGWQu7DPa7vdzeY/aUluOm0=
|
||||
github.com/centrifugal/protocol v0.5.0 h1:h71u2Q53yhplftmUk1tjc+Mu6TKJ/eO3YRD3h7Qjvj4=
|
||||
github.com/centrifugal/protocol v0.5.0/go.mod h1:ru2N4pwiND/jE+XLtiLYbUo3YmgqgniGNW9f9aRgoVI=
|
||||
github.com/centrifugal/centrifuge v0.18.3 h1:JCsbj+v/7HGeSM/Zb02twGbZuONX9wrp/Im+/XpXwzM=
|
||||
github.com/centrifugal/centrifuge v0.18.3/go.mod h1:YKHJsWpYw2t7qREy8QLn99+LipbAFslqvik7lX+daVI=
|
||||
github.com/centrifugal/protocol v0.7.2 h1:t/JdnNLnjf4mWta+VNO8GCBvBjVyS+KQUKv3lIwZPRo=
|
||||
github.com/centrifugal/protocol v0.7.2/go.mod h1:qoeBHrRCi5mJ5XZfrKHnedz9UWpYbDLXr8iZUO3pTtc=
|
||||
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -479,7 +479,10 @@ github.com/digitalocean/godo v1.58.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2x
|
||||
github.com/digitalocean/godo v1.62.0 h1:7Gw2KFsWkxl36qJa0s50tgXaE0Cgm51JdRP+MFQvNnM=
|
||||
github.com/digitalocean/godo v1.62.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
@@ -503,6 +506,9 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20181012153825-d7b61745d166/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 h1:CtSi0QlA2Hy+nOh8JAZoiEBLW5pliAiKJ3l1Iq1472I=
|
||||
github.com/dop251/goja v0.0.0-20210804101310-32956a348b49/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
@@ -626,8 +632,6 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b h1:U65wj9SF7qUBTGrnt6VxbHCT0Dw8dz4uch52G+5SdfA=
|
||||
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b/go.mod h1:AG8Z6qkQM8s47aUDJOco/SNwJ8Czif2hMm7rc0abDog=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
@@ -696,8 +700,9 @@ github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2g
|
||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
|
||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||
github.com/go-openapi/runtime v0.19.26/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
|
||||
github.com/go-openapi/runtime v0.19.28 h1:9lYu6axek8LJrVkMVViVirRcpoaCxXX7+sSvmizGVnA=
|
||||
github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
|
||||
github.com/go-openapi/runtime v0.19.29 h1:5IIvCaIDbxetN674vX9eOxvoZ9mYGQ16fV1Q0VSG+NA=
|
||||
github.com/go-openapi/runtime v0.19.29/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
@@ -887,6 +892,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -956,7 +962,6 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
@@ -1015,8 +1020,8 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0r
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0 h1:D+Lhxi3P/7vpyDHUK/fdX9bL2mRz8hLG04ucNf1E02o=
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.112.0 h1:c+3kKJQ1wVZ5pMSF40eUizB5YzGPAbO+2QG0CJ3d4bI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.112.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.113.0 h1:X46np4UNgM0YLhxC0oLa2q7WOHdU5T/oppZ+XlYusMk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.113.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 h1:qCmofFVwQR9QnsinstVqI1NPLMVl33jNCnOCXEAVn6E=
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103/go.mod h1:GHIsn+EohCChsdu5YouNZewqLeV9L2FNw4DEJU3P9qE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
@@ -1066,7 +1071,10 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
|
||||
github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU=
|
||||
github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0=
|
||||
github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg=
|
||||
@@ -1136,8 +1144,9 @@ github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.2.3 h1:BwZa5IjREr75J0am7nblP+X5i95Rmp8EEbMI5vkUWdA=
|
||||
github.com/hashicorp/memberlist v0.2.3/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.2.4 h1:OOhYzSvFnkFQXm1ysE8RjXTHsqSRDyP4emusC9K7DYg=
|
||||
github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q=
|
||||
github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
|
||||
github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
|
||||
@@ -1319,7 +1328,10 @@ github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
|
||||
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.5 h1:qnfhwbFriwDIX51QncuNU5mEMf+6KE3t7O8V2KQl3Dg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.5/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||||
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -1504,8 +1516,8 @@ github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs=
|
||||
github.com/mna/redisc v1.1.7 h1:FdmtJsfTjoIjNXiQf4ozgNjuE+zxWH+fJSe+I/dD4vc=
|
||||
github.com/mna/redisc v1.1.7/go.mod h1:GXeOb7zyYKiT+K8MKdIiJvuv7MfhDoQGcuzfiJQmqQI=
|
||||
github.com/mna/redisc v1.2.1 h1:7rI/qv2sa0OT8rsxDbKg7XPysr5AIDeXwL0T0vFOvlM=
|
||||
github.com/mna/redisc v1.2.1/go.mod h1:OxLEDNNDFOYJBo7MuSC+SEoP3k8bZY2dFW7T12TzX4c=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -1515,6 +1527,7 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
@@ -1649,8 +1662,8 @@ github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuR
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
||||
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982 h1:TdFv+3Gr3GaghJ/o80aulO4ian7GHGWMdLBXoLZH1Is=
|
||||
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -1674,8 +1687,9 @@ github.com/prometheus/alertmanager v0.21.1-0.20200911160112-1fdff6b3f939/go.mod
|
||||
github.com/prometheus/alertmanager v0.21.1-0.20201106142418-c39b78780054/go.mod h1:imXRHOP6QTsE0fFsIsAV/cXimS32m7gVZOiUj11m6Ig=
|
||||
github.com/prometheus/alertmanager v0.21.1-0.20210310093010-0f9cab6991e6/go.mod h1:MTqVn+vIupE0dzdgo+sMcNCp37SCAi8vPrvKTTnTz9g=
|
||||
github.com/prometheus/alertmanager v0.21.1-0.20210422101724-8176f78a70e1/go.mod h1:gsEqwD5BHHW9RNKvCuPOrrTMiP5I+faJUyLXvnivHik=
|
||||
github.com/prometheus/alertmanager v0.22.2 h1:JrDZalSEMb2/2bqGAhls6ZnvOxbC5jMIu29JV+uWTC0=
|
||||
github.com/prometheus/alertmanager v0.22.2/go.mod h1:rYinOWxFuCnNssc3iOjn2oMTlhLaPcUuqV5yk5JKUAE=
|
||||
github.com/prometheus/alertmanager v0.23.0-rc.0.0.20210906104939-8da517524a87 h1:98dGpT8+lYi0ADl1oN/JHPbr5pyzSA9M4id68zFkh3Y=
|
||||
github.com/prometheus/alertmanager v0.23.0-rc.0.0.20210906104939-8da517524a87/go.mod h1:U7pGu+z7A9ZKhK8lq1MvIOp5GdVlZjwOYk+S0h3LSbA=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
@@ -1725,10 +1739,15 @@ github.com/prometheus/common v0.21.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
|
||||
github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.29.0 h1:3jqPBvKT4OHAbje2Ql7KeaaSicDBCxMYwEJU1zRJceE=
|
||||
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
|
||||
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
|
||||
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
|
||||
github.com/prometheus/exporter-toolkit v0.5.0/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg=
|
||||
github.com/prometheus/exporter-toolkit v0.5.1/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg=
|
||||
github.com/prometheus/exporter-toolkit v0.6.1 h1:Aqk75wQD92N9CqmTlZwjKwq6272nOGrWIbc8Z7+xQO0=
|
||||
github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g=
|
||||
github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289 h1:dTUS1vaLWq+Y6XKOTnrFpoVsQKLCbCp1OLj24TDi7oM=
|
||||
github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289/go.mod h1:FGbBv5OPKjch+jNUJmEQpMZytIdyW0NdBtWFcfSKusc=
|
||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@@ -1829,6 +1848,8 @@ github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE=
|
||||
github.com/segmentio/encoding v0.2.19 h1:Kshkmoz080qvUtdtakR8Bjk2sIlLS8wSvijFMEHRGow=
|
||||
github.com/segmentio/encoding v0.2.19/go.mod h1:7E68jTSWMnNoYhHi1JbLd7NBSB6XfE4vzqhR88hDBQc=
|
||||
github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M=
|
||||
github.com/segmentio/fasthash v1.0.2/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
@@ -1872,7 +1893,6 @@ github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHei
|
||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/snowflakedb/gosnowflake v1.3.4/go.mod h1:NsRq2QeiMUuoNUJhp5Q6xGC4uBrsS9g6LwZVEkTWgsE=
|
||||
@@ -1909,6 +1929,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/spyzhov/ajson v0.4.2 h1:JMByd/jZApPKDvNsmO90X2WWGbmT2ahDFp73QhZbg3s=
|
||||
github.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
@@ -1990,7 +2012,6 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@@ -2000,6 +2021,7 @@ github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
@@ -2320,7 +2342,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@@ -2339,8 +2360,9 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -2501,6 +2523,7 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||
@@ -2565,7 +2588,6 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "8.1.1",
|
||||
"testing": "8.1.1"
|
||||
"stable": "8.1.3",
|
||||
"testing": "8.1.3"
|
||||
}
|
||||
|
||||
+7
-7
@@ -121,7 +121,7 @@
|
||||
"@types/react-grid-layout": "1.1.1",
|
||||
"@types/react-highlight-words": "^0.16.2",
|
||||
"@types/react-loadable": "5.5.2",
|
||||
"@types/react-redux": "7.1.7",
|
||||
"@types/react-redux": "7.1.18",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react-select": "4.0.13",
|
||||
"@types/react-test-renderer": "17.0.1",
|
||||
@@ -143,7 +143,7 @@
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "0.6.2",
|
||||
"angular-mocks": "1.6.6",
|
||||
"autoprefixer": "10.2.6",
|
||||
"axios": "0.21.1",
|
||||
"axios": "0.21.2",
|
||||
"babel-jest": "26.6.3",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-angularjs-annotate": "0.10.0",
|
||||
@@ -232,7 +232,7 @@
|
||||
"@opentelemetry/exporter-collector": "0.23.0",
|
||||
"@opentelemetry/semantic-conventions": "0.23.0",
|
||||
"@popperjs/core": "2.5.4",
|
||||
"@reduxjs/toolkit": "1.5.0",
|
||||
"@reduxjs/toolkit": "1.6.1",
|
||||
"@sentry/browser": "5.25.0",
|
||||
"@sentry/types": "5.24.2",
|
||||
"@sentry/utils": "5.24.2",
|
||||
@@ -268,7 +268,7 @@
|
||||
"file-saver": "2.0.2",
|
||||
"history": "4.10.1",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immer": "8.0.1",
|
||||
"immer": "9.0.6",
|
||||
"jquery": "3.5.1",
|
||||
"json-source-map": "0.6.1",
|
||||
"jsurl": "^0.1.5",
|
||||
@@ -284,7 +284,7 @@
|
||||
"monaco-promql": "^1.7.2",
|
||||
"mousetrap": "1.6.5",
|
||||
"mousetrap-global-bind": "1.1.0",
|
||||
"ol": "^6.5.0",
|
||||
"ol": "6.7.0",
|
||||
"papaparse": "5.3.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"prismjs": "1.24.0",
|
||||
@@ -299,7 +299,7 @@
|
||||
"react-highlight-words": "0.17.0",
|
||||
"react-loadable": "5.5.0",
|
||||
"react-popper": "2.2.4",
|
||||
"react-redux": "7.2.0",
|
||||
"react-redux": "7.2.5",
|
||||
"react-reverse-portal": "^2.0.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "4.3.0",
|
||||
@@ -308,7 +308,7 @@
|
||||
"react-use": "17.2.4",
|
||||
"react-virtualized-auto-sizer": "1.0.2",
|
||||
"react-window": "1.8.5",
|
||||
"redux": "4.0.5",
|
||||
"redux": "4.1.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"regenerator-runtime": "0.13.3",
|
||||
"reselect": "4.0.0",
|
||||
|
||||
@@ -5,20 +5,28 @@ import { DataTransformerID } from './ids';
|
||||
import { DataFrame, Field, FieldType } from '../../types/dataFrame';
|
||||
import { dateTimeParse } from '../../datetime';
|
||||
import { ArrayVector } from '../../vector';
|
||||
import { fieldMatchers } from '../matchers';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
|
||||
export interface ConvertFieldTypeTransformerOptions {
|
||||
conversions: ConvertFieldTypeOptions[];
|
||||
}
|
||||
|
||||
export interface ConvertFieldTypeOptions {
|
||||
/**
|
||||
* The field to convert field type
|
||||
*/
|
||||
targetField?: string;
|
||||
/**
|
||||
* The field type to convert to
|
||||
*/
|
||||
destinationType?: FieldType;
|
||||
/**
|
||||
* Date format to parse a string datetime
|
||||
*/
|
||||
dateFormat?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export const convertFieldTypeTransformer: SynchronousDataTransformerInfo<ConvertFieldTypeTransformerOptions> = {
|
||||
id: DataTransformerID.convertFieldType,
|
||||
name: 'Convert field type',
|
||||
@@ -43,32 +51,44 @@ export const convertFieldTypeTransformer: SynchronousDataTransformerInfo<Convert
|
||||
};
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Convert field types for dataframe(s)
|
||||
* @param options - field type conversion options
|
||||
* @param frames - dataframe(s) with field types to convert
|
||||
* @returns dataframe(s) with converted field types
|
||||
*/
|
||||
export function convertFieldTypes(options: ConvertFieldTypeTransformerOptions, frames: DataFrame[]): DataFrame[] {
|
||||
if (!options.conversions.length) {
|
||||
return frames;
|
||||
}
|
||||
|
||||
const frameCopy: DataFrame[] = [];
|
||||
const framesCopy = frames.map((frame) => ({ ...frame }));
|
||||
|
||||
frames.forEach((frame) => {
|
||||
for (let fieldIdx = 0; fieldIdx < frame.fields.length; fieldIdx++) {
|
||||
let field = frame.fields[fieldIdx];
|
||||
for (let cIdx = 0; cIdx < options.conversions.length; cIdx++) {
|
||||
if (field.name === options.conversions[cIdx].targetField) {
|
||||
//check in about matchers with Ryan
|
||||
const conversion = options.conversions[cIdx];
|
||||
frame.fields[fieldIdx] = convertFieldType(field, conversion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const conversion of options.conversions) {
|
||||
if (!conversion.targetField) {
|
||||
continue;
|
||||
}
|
||||
frameCopy.push(frame);
|
||||
});
|
||||
return frameCopy;
|
||||
const matches = fieldMatchers.get(FieldMatcherID.byName).get(conversion.targetField);
|
||||
for (const frame of framesCopy) {
|
||||
frame.fields = frame.fields.map((field) => {
|
||||
if (matches(field, frame, framesCopy)) {
|
||||
return convertFieldType(field, conversion);
|
||||
}
|
||||
return field;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return framesCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single field type to specifed field type.
|
||||
* @param field - field to convert
|
||||
* @param opts - field conversion options
|
||||
* @returns converted field
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): Field {
|
||||
switch (opts.destinationType) {
|
||||
case FieldType.time:
|
||||
@@ -84,6 +104,9 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function fieldToTimeField(field: Field, dateFormat?: string): Field {
|
||||
let opts = dateFormat ? { format: dateFormat } : undefined;
|
||||
|
||||
@@ -109,12 +132,8 @@ function fieldToNumberField(field: Field): Field {
|
||||
const numValues = field.values.toArray().slice();
|
||||
|
||||
for (let n = 0; n < numValues.length; n++) {
|
||||
if (numValues[n]) {
|
||||
let number = +numValues[n];
|
||||
numValues[n] = Number.isFinite(number) ? number : null;
|
||||
} else {
|
||||
numValues[n] = null;
|
||||
}
|
||||
const number = +numValues[n];
|
||||
numValues[n] = Number.isFinite(number) ? number : null;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -128,7 +147,7 @@ function fieldToBooleanField(field: Field): Field {
|
||||
const booleanValues = field.values.toArray().slice();
|
||||
|
||||
for (let b = 0; b < booleanValues.length; b++) {
|
||||
booleanValues[b] = Boolean(booleanValues[b]);
|
||||
booleanValues[b] = Boolean(!!booleanValues[b]);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -153,7 +172,12 @@ function fieldToStringField(field: Field): Field {
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Checks the first value. Assumes any number should be time fieldtype. Otherwise attempts to make the fieldtype time.
|
||||
* @param field - field to ensure is a time fieldtype
|
||||
* @param dateFormat - date format used to parse a string datetime
|
||||
* @returns field as time
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function ensureTimeField(field: Field, dateFormat?: string): Field {
|
||||
const firstValueTypeIsNumber = typeof field.values.get(0) === 'number';
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface FeatureToggles {
|
||||
tempoServiceGraph: boolean;
|
||||
tempoSearch: boolean;
|
||||
prometheusMonaco: boolean;
|
||||
newNavigation: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RawTimeRange } from './time';
|
||||
import { DataQuery } from './query';
|
||||
import { RawTimeRange, TimeRange } from './time';
|
||||
|
||||
/** @internal */
|
||||
export interface ExploreUrlState {
|
||||
@@ -8,3 +9,10 @@ export interface ExploreUrlState {
|
||||
originPanelId?: number;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SplitOpen type is used in Explore and related components.
|
||||
*/
|
||||
export type SplitOpen = <T extends DataQuery = any>(
|
||||
options?: { datasourceUid: string; query: T; range?: TimeRange } | undefined
|
||||
) => void;
|
||||
|
||||
@@ -87,6 +87,7 @@ interface PluginDependencyInfo {
|
||||
}
|
||||
|
||||
export interface PluginDependencies {
|
||||
grafanaDependency?: string;
|
||||
grafanaVersion: string;
|
||||
plugins: PluginDependencyInfo[];
|
||||
}
|
||||
|
||||
@@ -246,4 +246,14 @@ export class PanelOptionsEditorBuilder<TOptions> extends OptionsUIRegistryBuilde
|
||||
editor: standardEditorsRegistry.get('field-name').editor as any,
|
||||
});
|
||||
}
|
||||
|
||||
addDashboardPicker<TSettings = any>(
|
||||
config: PanelOptionsEditorConfig<TOptions, TSettings & FieldNamePickerConfigSettings, string>
|
||||
): this {
|
||||
return this.addCustomEditor({
|
||||
...config,
|
||||
id: config.path,
|
||||
editor: standardEditorsRegistry.get('dashboard-uid').editor as any, // added at runtime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ type Panel = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
type Dashboard = { title: string; panels: Panel[]; [key: string]: unknown };
|
||||
type Dashboard = { title: string; panels: Panel[]; uid: string; [key: string]: unknown };
|
||||
|
||||
/**
|
||||
* Smoke test a datasource by quickly importing a test dashboard for it
|
||||
@@ -17,10 +17,13 @@ export const importDashboard = (dashboardToImport: Dashboard) => {
|
||||
e2e().visit(fromBaseUrl('/dashboard/import'));
|
||||
|
||||
// Note: normally we'd use 'click' and then 'type' here, but the json object is so big that using 'val' is much faster
|
||||
e2e.components.DashboardImportPage.textarea().click({ force: true }).invoke('val', JSON.stringify(dashboardToImport));
|
||||
e2e.components.DashboardImportPage.submit().click({ force: true });
|
||||
e2e.components.ImportDashboardForm.name().click({ force: true }).clear().type(dashboardToImport.title);
|
||||
e2e.components.ImportDashboardForm.submit().click({ force: true });
|
||||
e2e.components.DashboardImportPage.textarea()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.invoke('val', JSON.stringify(dashboardToImport));
|
||||
e2e.components.DashboardImportPage.submit().should('be.visible').click();
|
||||
e2e.components.ImportDashboardForm.name().should('be.visible').click().clear().type(dashboardToImport.title);
|
||||
e2e.components.ImportDashboardForm.submit().should('be.visible').click();
|
||||
e2e().wait(3000);
|
||||
|
||||
// save the newly imported dashboard to context so it'll get properly deleted later
|
||||
@@ -35,21 +38,23 @@ export const importDashboard = (dashboardToImport: Dashboard) => {
|
||||
addedDashboards: [...addedDashboards, { title: dashboardToImport.title, uid }],
|
||||
});
|
||||
});
|
||||
|
||||
expect(dashboardToImport.uid).to.equal(uid);
|
||||
});
|
||||
|
||||
// inspect first panel and verify data has been processed for it
|
||||
e2e.components.Panels.Panel.title(dashboardToImport.panels[0].title).click({ force: true });
|
||||
e2e.components.Panels.Panel.headerItems('Inspect').click({ force: true });
|
||||
e2e.components.Tab.title('JSON').click({ force: true });
|
||||
e2e.components.Panels.Panel.title(dashboardToImport.panels[0].title).should('be.visible').click();
|
||||
e2e.components.Panels.Panel.headerItems('Inspect').should('be.visible').click();
|
||||
e2e.components.Tab.title('JSON').should('be.visible').click();
|
||||
e2e().wait(3000);
|
||||
e2e.components.PanelInspector.Json.content().contains('Panel JSON').click({ force: true });
|
||||
e2e.components.PanelInspector.Json.content().should('be.visible').contains('Panel JSON').click();
|
||||
e2e().wait(3000);
|
||||
e2e.components.Select.option().contains('Data').click({ force: true });
|
||||
e2e.components.Select.option().should('be.visible').contains('Data').click();
|
||||
e2e().wait(3000);
|
||||
|
||||
// ensures that panel has loaded without knowingly hitting an error
|
||||
// note: this does not prove that data came back as we expected it,
|
||||
// it could get `state: Done` for no data for example
|
||||
// but it ensures we didn't hit a 401 or 500 or something like that
|
||||
e2e.components.CodeEditor.container().contains('"state": "Done"');
|
||||
e2e.components.CodeEditor.container().should('be.visible').contains('"state": "Done"');
|
||||
};
|
||||
|
||||
@@ -13,10 +13,11 @@ export interface PanelRendererProps<P extends object = any, F extends object = a
|
||||
data: PanelData;
|
||||
pluginId: string;
|
||||
title: string;
|
||||
fieldConfig?: FieldConfigSource<F>;
|
||||
options?: P;
|
||||
onOptionsChange?: (options: P) => void;
|
||||
onChangeTimeRange?: (timeRange: AbsoluteTimeRange) => void;
|
||||
fieldConfig?: FieldConfigSource<F>;
|
||||
onFieldConfigChange?: (config: FieldConfigSource<F>) => void;
|
||||
timeZone?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
@@ -66,6 +66,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
tempoServiceGraph: false,
|
||||
tempoSearch: false,
|
||||
prometheusMonaco: false,
|
||||
newNavigation: false,
|
||||
};
|
||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||
rendererAvailable = false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// NOTE: This file will be auto generated from models.cue
|
||||
// It is currenty hand written but will serve as the target for cuetsy
|
||||
// It is currently hand written but will serve as the target for cuetsy
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
export enum AxisPlacement {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package dashboard
|
||||
|
||||
import "github.com/grafana/grafana/cue/scuemata"
|
||||
import (
|
||||
"list"
|
||||
|
||||
"github.com/grafana/grafana/cue/scuemata"
|
||||
)
|
||||
|
||||
Family: scuemata.#Family & {
|
||||
lineages: [
|
||||
[
|
||||
[
|
||||
{ // 0.0
|
||||
// Unique numeric identifier for the dashboard.
|
||||
// TODO must isolate or remove identifiers local to a Grafana instance...?
|
||||
@@ -131,9 +135,7 @@ Family: scuemata.#Family & {
|
||||
|
||||
// Dashboard panels. Panels are canonically defined inline
|
||||
// because they share a version timeline with the dashboard
|
||||
// schema; they do not vary independently. We create a separate,
|
||||
// synthetic Family to represent them in Go, for ease of generating
|
||||
// e.g. JSON Schema.
|
||||
// schema; they do not evolve independently.
|
||||
#Panel: {
|
||||
// The panel plugin type id.
|
||||
type: !=""
|
||||
@@ -155,7 +157,8 @@ Family: scuemata.#Family & {
|
||||
// _pv: { maj: int, min: int }
|
||||
// The major and minor versions of the panel plugin for this schema.
|
||||
// TODO 2-tuple list instead of struct?
|
||||
panelSchema?: { maj: number, min: number }
|
||||
// panelSchema?: { maj: number, min: number }
|
||||
panelSchema?: [number, number]
|
||||
|
||||
// TODO docs
|
||||
targets?: [...#Target]
|
||||
@@ -218,9 +221,8 @@ Family: scuemata.#Family & {
|
||||
// TODO tighter constraint
|
||||
timeShift?: string
|
||||
|
||||
// The allowable options are specified by the panel plugin's
|
||||
// schema.
|
||||
// FIXME same conundrum as with the closed validation for fieldConfig.
|
||||
// options is specified by the PanelOptions field in panel
|
||||
// plugin schemas.
|
||||
options: {}
|
||||
|
||||
fieldConfig: {
|
||||
@@ -283,16 +285,8 @@ Family: scuemata.#Family & {
|
||||
// Alternative to empty string
|
||||
noValue?: string
|
||||
|
||||
// TODO conundrum: marking this struct as open would
|
||||
// - i think - preclude closed validation of
|
||||
// plugin-defined config bits. But, marking it
|
||||
// closed makes it impossible to use just this
|
||||
// schema (the "base" variant) to validate the base
|
||||
// components of a dashboard.
|
||||
//
|
||||
// Can always exist. Valid fields within this are
|
||||
// defined by the panel plugin - that's the
|
||||
// PanelFieldConfig that comes from the plugin.
|
||||
// custom is specified by the PanelFieldConfig field
|
||||
// in panel plugin schemas.
|
||||
custom?: {}
|
||||
}
|
||||
overrides: [...{
|
||||
@@ -306,7 +300,29 @@ Family: scuemata.#Family & {
|
||||
}]
|
||||
}]
|
||||
}
|
||||
// Embed the disjunction of all injected panel schema, if any were injected.
|
||||
if len(compose._panelSchemas) > 0 {
|
||||
or(compose._panelSchemas) // TODO try to stick graph in here
|
||||
}
|
||||
|
||||
// Make the plugin-composed subtrees open if the panel is
|
||||
// of unknown types. This is important in every possible case:
|
||||
// - Base (this file only): no real dashboard json
|
||||
// containing any panels would ever validate
|
||||
// - Dist (this file + core plugin schema): dashboard json containing
|
||||
// panels with any third-party panel plugins would fail to validate,
|
||||
// as well as any core plugins lacking a models.cue. The latter case
|
||||
// is not normally expected, but this is not the appropriate place
|
||||
// to enforce the invariant, anyway.
|
||||
// - Instance (this file + core + third-party plugin schema): dashboard
|
||||
// json containing panels with a third-party plugin that exists but
|
||||
// is not currently installed would fail to validate.
|
||||
if !list.Contains(compose._panelTypes, type) {
|
||||
options: {...}
|
||||
fieldConfig: defaults: custom: {...}
|
||||
}
|
||||
}
|
||||
|
||||
// Row panel
|
||||
#RowPanel: {
|
||||
type: "row"
|
||||
@@ -329,86 +345,84 @@ Family: scuemata.#Family & {
|
||||
static?: bool
|
||||
}
|
||||
id: number
|
||||
panels: [...#Panel | #GraphPanel]
|
||||
panels: [...(#Panel | #GraphPanel)]
|
||||
}
|
||||
// Support for legacy graph panels.
|
||||
#GraphPanel: {
|
||||
...
|
||||
type: "graph"
|
||||
thresholds: [...{...}]
|
||||
timeRegions: [...{...}]
|
||||
// FIXME this one is quite complicated, as it duplicates the #Panel object's own structure (...?)
|
||||
timeRegions?: [...{...}]
|
||||
seriesOverrides: [...{...}]
|
||||
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
aliasColors?: [string]: string
|
||||
|
||||
// TODO docs
|
||||
bars: bool | *false
|
||||
// TODO docs
|
||||
dashes: bool | *false
|
||||
// TODO docs
|
||||
dashLength: number | *10
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
fill?: number
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
fillGradient?: number
|
||||
|
||||
// TODO docs
|
||||
hiddenSeries: bool | *false
|
||||
|
||||
// FIXME idk where this comes from, leaving it very open and very wrong for now
|
||||
legend: {...}
|
||||
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
lines: bool | *false
|
||||
// TODO docs
|
||||
linewidth?: number
|
||||
// TODO docs
|
||||
nullPointMode: *"null" | "connected" | "null as zero"
|
||||
// TODO docs
|
||||
percentage: bool | *false
|
||||
// TODO docs
|
||||
points: bool | *false
|
||||
// TODO docs
|
||||
// FIXME this is the kind of case that makes
|
||||
// optional/non-default tricky: it's optional because it
|
||||
// only makes sense when points is true (right?), but if it
|
||||
// is, then there actually is a default value. Easier way to
|
||||
// represent this would be to wrap up this handling into a
|
||||
// struct
|
||||
pointradius?: number
|
||||
// TODO docs
|
||||
// TODO tighter constraint
|
||||
renderer: string
|
||||
// TODO docs
|
||||
spaceLength: number | *10
|
||||
// TODO docs
|
||||
stack: bool | *false
|
||||
// TODO docs
|
||||
steppedLine: bool | *false
|
||||
// TODO docs
|
||||
tooltip?: {
|
||||
// TODO docs
|
||||
shared?: bool
|
||||
// TODO docs
|
||||
sort: number | *0
|
||||
// TODO docs
|
||||
// FIXME literally no idea if these values are sane
|
||||
value_type: *"individual" | "cumulative"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
compose: {
|
||||
// Scuemata families for all panel types that should be composed into the
|
||||
// dashboard schema.
|
||||
Panel: [string]: scuemata.#PanelFamily
|
||||
|
||||
#Latest: {
|
||||
#Dashboard: Family.latest
|
||||
#Panel: Family.latest._Panel
|
||||
}
|
||||
// _panelTypes: [for typ, _ in Panel {typ}]
|
||||
_panelTypes: [for typ, _ in Panel {typ}, "graph", "row"]
|
||||
_panelSchemas: [for typ, scue in Panel {
|
||||
for lv, lin in scue.lineages {
|
||||
for sv, sch in lin {
|
||||
(_mapPanel & {arg: {
|
||||
type: typ
|
||||
v: [lv, sv] // TODO add optionality for exact, at least, at most, any
|
||||
model: sch // TODO Does this need to be close()d?
|
||||
}}).out
|
||||
}
|
||||
}
|
||||
}, { type: string }]
|
||||
_mapPanel: {
|
||||
arg: {
|
||||
type: string & !=""
|
||||
v: [number, number]
|
||||
model: {...}
|
||||
}
|
||||
// Until CUE introduces the must() constraint, we have to enforce
|
||||
// that the input model is as expected by checking for unification
|
||||
// in this hidden property (see https://github.com/cue-lang/cue/issues/575).
|
||||
// If we unified arg.model with the scuemata.#PanelSchema
|
||||
// meta-schema directly, the struct openness (PanelOptions: {...})
|
||||
// would be applied to the actual schema instance in the arg. Here,
|
||||
// where we're actually putting those in the dashboard schema, want
|
||||
// those to be closed, or at least preserve closed-ness.
|
||||
_checkSchema: scuemata.#PanelSchema & arg.model
|
||||
out: {
|
||||
type: arg.type
|
||||
panelSchema: arg.v // TODO add optionality for exact, at least, at most, any
|
||||
options: arg.model.PanelOptions
|
||||
fieldConfig: defaults: custom: {}
|
||||
if arg.model.PanelFieldConfig != _|_ {
|
||||
fieldConfig: defaults: custom: arg.model.PanelFieldConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
"@types/webpack": "4.41.7",
|
||||
"@typescript-eslint/eslint-plugin": "4.28.0",
|
||||
"@typescript-eslint/parser": "4.28.0",
|
||||
"axios": "0.21.1",
|
||||
"axios": "0.21.2",
|
||||
"babel-jest": "26.6.3",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-angularjs-annotate": "0.10.0",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@grafana/schema": "8.2.0-pre",
|
||||
"@grafana/slate-react": "0.22.10-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@monaco-editor/react": "4.1.1",
|
||||
"@monaco-editor/react": "4.2.2",
|
||||
"@popperjs/core": "2.5.4",
|
||||
"@sentry/browser": "5.25.0",
|
||||
"ansicolor": "1.1.95",
|
||||
@@ -50,7 +50,7 @@
|
||||
"jquery": "3.5.1",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"monaco-editor": "0.21.2",
|
||||
"monaco-editor": "0.27.0",
|
||||
"papaparse": "5.3.0",
|
||||
"rc-cascader": "1.5.0",
|
||||
"rc-drawer": "4.4.0",
|
||||
|
||||
@@ -163,7 +163,7 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
width={13}
|
||||
tooltip="Grafana proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source."
|
||||
>
|
||||
Whitelisted Cookies
|
||||
Allowed cookies
|
||||
</InlineFormLabel>
|
||||
<TagsInput
|
||||
tags={dataSourceConfig.jsonData.keepCookies}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { DataFrame, Field, getFieldDisplayName, SelectableValue } from '@grafana/data';
|
||||
import { getFieldTypeIcon } from '../../types';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -79,9 +80,11 @@ export function useSelectOptions(
|
||||
if (!found && name === currentName) {
|
||||
found = true;
|
||||
}
|
||||
const field = displayNames.fields.get(name);
|
||||
options.push({
|
||||
value: name,
|
||||
label: name,
|
||||
icon: field ? getFieldTypeIcon(field) : undefined,
|
||||
});
|
||||
}
|
||||
for (const name of displayNames.raw) {
|
||||
|
||||
@@ -12,19 +12,24 @@ export const StringValueEditor: React.FC<StandardEditorProps<string, StringField
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(e: React.SyntheticEvent) => {
|
||||
let nextValue = value ?? '';
|
||||
if (e.hasOwnProperty('key')) {
|
||||
// handling keyboard event
|
||||
const evt = e as React.KeyboardEvent<HTMLInputElement>;
|
||||
if (evt.key === 'Enter' && !item.settings?.useTextarea) {
|
||||
onChange(evt.currentTarget.value.trim() === '' ? undefined : evt.currentTarget.value);
|
||||
nextValue = evt.currentTarget.value.trim();
|
||||
}
|
||||
} else {
|
||||
// handling form event
|
||||
const evt = e as React.FormEvent<HTMLInputElement>;
|
||||
onChange(evt.currentTarget.value.trim() === '' ? undefined : evt.currentTarget.value);
|
||||
nextValue = evt.currentTarget.value.trim();
|
||||
}
|
||||
if (nextValue === value) {
|
||||
return; // no change
|
||||
}
|
||||
onChange(nextValue === '' ? undefined : nextValue);
|
||||
},
|
||||
[item.settings?.useTextarea, onChange]
|
||||
[value, item.settings?.useTextarea, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventBusSrv, EventBus, DashboardCursorSync, AnnotationEventUIModel } from '@grafana/data';
|
||||
import { AnnotationEventUIModel, DashboardCursorSync, EventBus, EventBusSrv, SplitOpen } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { SeriesVisibilityChangeMode } from '.';
|
||||
|
||||
@@ -22,6 +22,11 @@ export interface PanelContext {
|
||||
onAnnotationCreate?: (annotation: AnnotationEventUIModel) => void;
|
||||
onAnnotationUpdate?: (annotation: AnnotationEventUIModel) => void;
|
||||
onAnnotationDelete?: (id: string) => void;
|
||||
/**
|
||||
* onSplitOpen is used in Explore to open the split view. It can be used in panels which has intercations and used in Explore as well.
|
||||
* For example TimeSeries panel.
|
||||
*/
|
||||
onSplitOpen?: SplitOpen;
|
||||
}
|
||||
|
||||
export const PanelContextRoot = React.createContext<PanelContext>({
|
||||
|
||||
@@ -4,6 +4,8 @@ import { getSelectStyles } from './getSelectStyles';
|
||||
import { cx } from '@emotion/css';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconName } from '../../types';
|
||||
|
||||
interface SelectMenuProps {
|
||||
maxHeight: number;
|
||||
@@ -49,6 +51,7 @@ export const SelectMenuOptions = React.forwardRef<HTMLDivElement, React.PropsWit
|
||||
{...innerProps}
|
||||
aria-label="Select option"
|
||||
>
|
||||
{data.icon && <Icon name={data.icon as IconName} className={styles.optionIcon} />}
|
||||
{data.imgUrl && <img className={styles.optionImage} src={data.imgUrl} />}
|
||||
<div className={styles.optionBody}>
|
||||
<span>{renderOptionLabel ? renderOptionLabel(data) : children}</span>
|
||||
|
||||
@@ -27,6 +27,9 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
background: ${theme.colors.action.hover};
|
||||
}
|
||||
`,
|
||||
optionIcon: css`
|
||||
margin-right: ${theme.spacing(1)};
|
||||
`,
|
||||
optionImage: css`
|
||||
label: grafana-select-option-image;
|
||||
width: 16px;
|
||||
|
||||
@@ -6,14 +6,16 @@ import { getTableStyles, TableStyles } from './styles';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Filter } from './Filter';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { getFieldTypeIcon } from '../../types';
|
||||
|
||||
export interface HeaderRowProps {
|
||||
headerGroups: HeaderGroup[];
|
||||
data: DataFrame;
|
||||
showTypeIcons?: boolean;
|
||||
}
|
||||
|
||||
export const HeaderRow = (props: HeaderRowProps) => {
|
||||
const { headerGroups, data } = props;
|
||||
const { headerGroups, data, showTypeIcons } = props;
|
||||
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
|
||||
@@ -30,7 +32,7 @@ export const HeaderRow = (props: HeaderRowProps) => {
|
||||
role="row"
|
||||
>
|
||||
{headerGroup.headers.map((column: Column, index: number) =>
|
||||
renderHeaderCell(column, tableStyles, data.fields[index])
|
||||
renderHeaderCell(column, tableStyles, data.fields[index], showTypeIcons)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -39,7 +41,7 @@ export const HeaderRow = (props: HeaderRowProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field) {
|
||||
function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field, showTypeIcons?: boolean) {
|
||||
const headerProps = column.getHeaderProps();
|
||||
|
||||
if (column.canResize) {
|
||||
@@ -58,6 +60,9 @@ function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field)
|
||||
className={tableStyles.headerCellLabel}
|
||||
title={column.render('Header')}
|
||||
>
|
||||
{showTypeIcons && (
|
||||
<Icon name={getFieldTypeIcon(field)} title={field?.type} size="sm" style={{ marginRight: '8px' }} />
|
||||
)}
|
||||
<div>{column.render('Header')}</div>
|
||||
<div>
|
||||
{column.isSorted && (column.isSortedDesc ? <Icon name="arrow-down" /> : <Icon name="arrow-up" />)}
|
||||
|
||||
@@ -58,7 +58,7 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
padding: ${theme.spacing(0.5)};
|
||||
`,
|
||||
json: css`
|
||||
max-width: fit-content;
|
||||
width: fit-content;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
`,
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface Props {
|
||||
/** Minimal column width specified in pixels */
|
||||
columnMinWidth?: number;
|
||||
noHeader?: boolean;
|
||||
showTypeIcons?: boolean;
|
||||
resizable?: boolean;
|
||||
initialSortBy?: TableSortByFieldState[];
|
||||
onColumnResize?: TableColumnResizeActionCallback;
|
||||
@@ -124,6 +125,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
resizable = true,
|
||||
initialSortBy,
|
||||
footerValues,
|
||||
showTypeIcons,
|
||||
} = props;
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
|
||||
@@ -204,7 +206,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
<div {...getTableProps()} className={tableStyles.table} aria-label={ariaLabel} role="table">
|
||||
<CustomScrollbar hideVerticalTrack={true}>
|
||||
<div style={{ width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%' }}>
|
||||
{!noHeader && <HeaderRow data={data} headerGroups={headerGroups} />}
|
||||
{!noHeader && <HeaderRow data={data} headerGroups={headerGroups} showTypeIcons={showTypeIcons} />}
|
||||
{rows.length > 0 ? (
|
||||
<FixedSizeList
|
||||
height={height - headerHeight}
|
||||
|
||||
@@ -59,7 +59,11 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
nextValue = steps[steps.length - 1].value + 10;
|
||||
}
|
||||
|
||||
const color = colors.filter((c) => !steps.some((t) => t.color === c))[1];
|
||||
let color = colors.filter((c) => !steps.some((t) => t.color === c))[1];
|
||||
if (!color) {
|
||||
// Default color when all colors are used
|
||||
color = '#CCCCCC';
|
||||
}
|
||||
|
||||
const add = {
|
||||
value: nextValue,
|
||||
@@ -154,15 +158,13 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
value={'Base'}
|
||||
disabled
|
||||
prefix={
|
||||
threshold.color && (
|
||||
<div className={styles.colorPicker}>
|
||||
<ColorPicker
|
||||
color={threshold.color}
|
||||
onChange={(color) => this.onChangeThresholdColor(threshold, color)}
|
||||
enableNamedColors={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<div className={styles.colorPicker}>
|
||||
<ColorPicker
|
||||
color={threshold.color}
|
||||
onChange={(color) => this.onChangeThresholdColor(threshold, color)}
|
||||
enableNamedColors={true}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -179,15 +181,13 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
onBlur={this.onBlur}
|
||||
prefix={
|
||||
<div className={styles.inputPrefix}>
|
||||
{threshold.color && (
|
||||
<div className={styles.colorPicker}>
|
||||
<ColorPicker
|
||||
color={threshold.color}
|
||||
onChange={(color) => this.onChangeThresholdColor(threshold, color)}
|
||||
enableNamedColors={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.colorPicker}>
|
||||
<ColorPicker
|
||||
color={threshold.color}
|
||||
onChange={(color) => this.onChangeThresholdColor(threshold, color)}
|
||||
enableNamedColors={true}
|
||||
/>
|
||||
</div>
|
||||
{isPercent && <div className={styles.percentIcon}>%</div>}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -250,7 +250,6 @@ export { UPlotChart } from './uPlot/Plot';
|
||||
export { PlotLegend } from './uPlot/PlotLegend';
|
||||
export * from './uPlot/geometries';
|
||||
export * from './uPlot/plugins';
|
||||
export { usePlotContext } from './uPlot/context';
|
||||
export { PlotTooltipInterpolator, PlotSelection } from './uPlot/types';
|
||||
export { GraphNG, GraphNGProps, FIXED_UNIT } from './GraphNG/GraphNG';
|
||||
export { TimeSeries } from './TimeSeries/TimeSeries';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { createRef, MutableRefObject } from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import uPlot, { Options } from 'uplot';
|
||||
import { PlotContext, PlotContextType } from './context';
|
||||
import { DEFAULT_PLOT_CONFIG, pluginLog } from './utils';
|
||||
import { PlotProps } from './types';
|
||||
|
||||
@@ -27,7 +26,7 @@ function sameTimeRange(prevProps: PlotProps, nextProps: PlotProps) {
|
||||
}
|
||||
|
||||
type UPlotChartState = {
|
||||
ctx: PlotContextType;
|
||||
plot: uPlot | null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -44,35 +43,24 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
ctx: {
|
||||
plot: null,
|
||||
getCanvasBoundingBox: () => {
|
||||
return this.plotCanvasBBox.current;
|
||||
},
|
||||
},
|
||||
plot: null,
|
||||
};
|
||||
}
|
||||
|
||||
reinitPlot() {
|
||||
let { ctx } = this.state;
|
||||
let { width, height, plotRef } = this.props;
|
||||
|
||||
ctx.plot?.destroy();
|
||||
this.state.plot?.destroy();
|
||||
|
||||
if (width === 0 && height === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.config.addHook('syncRect', (u, rect) => {
|
||||
(this.plotCanvasBBox as MutableRefObject<any>).current = rect;
|
||||
});
|
||||
|
||||
this.props.config.addHook('setSize', (u) => {
|
||||
const canvas = u.over;
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
(this.plotCanvasBBox as MutableRefObject<any>).current = canvas.getBoundingClientRect();
|
||||
});
|
||||
|
||||
const config: Options = {
|
||||
@@ -90,13 +78,7 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
plotRef(plot);
|
||||
}
|
||||
|
||||
this.setState((s) => ({
|
||||
...s,
|
||||
ctx: {
|
||||
...s.ctx,
|
||||
plot,
|
||||
},
|
||||
}));
|
||||
this.setState({ plot });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -104,23 +86,23 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.state.ctx.plot?.destroy();
|
||||
this.state.plot?.destroy();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: PlotProps) {
|
||||
let { ctx } = this.state;
|
||||
let { plot } = this.state;
|
||||
|
||||
if (!sameDims(prevProps, this.props)) {
|
||||
ctx.plot?.setSize({
|
||||
plot?.setSize({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
});
|
||||
} else if (!sameConfig(prevProps, this.props)) {
|
||||
this.reinitPlot();
|
||||
} else if (!sameData(prevProps, this.props)) {
|
||||
ctx.plot?.setData(this.props.data);
|
||||
plot?.setData(this.props.data);
|
||||
} else if (!sameTimeRange(prevProps, this.props)) {
|
||||
ctx.plot?.setScale('x', {
|
||||
plot?.setScale('x', {
|
||||
min: this.props.timeRange.from.valueOf(),
|
||||
max: this.props.timeRange.to.valueOf(),
|
||||
});
|
||||
@@ -129,12 +111,10 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PlotContext.Provider value={this.state.ctx}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div ref={this.plotContainer} data-testid="uplot-main-div" />
|
||||
{this.props.children}
|
||||
</div>
|
||||
</PlotContext.Provider>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div ref={this.plotContainer} data-testid="uplot-main-div" />
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const PlotLegend: React.FC<PlotLegendProps> = ({
|
||||
const seriesColor = scaleColor.color;
|
||||
|
||||
return {
|
||||
disabled: !seriesConfig.show ?? false,
|
||||
disabled: !(seriesConfig.show ?? true),
|
||||
fieldIndex,
|
||||
color: seriesColor,
|
||||
label,
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
export interface PlotContextType {
|
||||
plot: uPlot | null;
|
||||
getCanvasBoundingBox: () => DOMRect | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export const PlotContext = React.createContext<PlotContextType>({} as PlotContextType);
|
||||
|
||||
// Exposes uPlot instance and bounding box of the entire canvas and plot area
|
||||
export const usePlotContext = (): PlotContextType => {
|
||||
return useContext<PlotContextType>(PlotContext);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DataFrame, DataFrameFieldIndex } from '@grafana/data';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { usePlotContext } from '../context';
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMountedState } from 'react-use';
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
import { Marker } from './Marker';
|
||||
import { XYCanvas } from './XYCanvas';
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
|
||||
interface EventsCanvasProps {
|
||||
id: string;
|
||||
@@ -17,21 +17,29 @@ interface EventsCanvasProps {
|
||||
}
|
||||
|
||||
export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords, config }: EventsCanvasProps) {
|
||||
const plotCtx = usePlotContext();
|
||||
const plotInstance = useRef<uPlot>();
|
||||
// render token required to re-render annotation markers. Rendering lines happens in uPlot and the props do not change
|
||||
// so we need to force the re-render when the draw hook was performed by uPlot
|
||||
const [renderToken, setRenderToken] = useState(0);
|
||||
const isMounted = useMountedState();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
config.addHook('init', (u) => {
|
||||
plotInstance.current = u;
|
||||
});
|
||||
|
||||
config.addHook('draw', () => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setRenderToken((s) => s + 1);
|
||||
});
|
||||
}, [config, setRenderToken]);
|
||||
|
||||
const eventMarkers = useMemo(() => {
|
||||
const markers: React.ReactNode[] = [];
|
||||
const plotInstance = plotCtx.plot;
|
||||
if (!plotInstance || events.length === 0) {
|
||||
|
||||
if (!plotInstance.current || events.length === 0) {
|
||||
return markers;
|
||||
}
|
||||
|
||||
@@ -51,11 +59,18 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords
|
||||
}
|
||||
|
||||
return <>{markers}</>;
|
||||
}, [events, renderEventMarker, renderToken, plotCtx]);
|
||||
}, [events, renderEventMarker, renderToken]);
|
||||
|
||||
if (!plotCtx.plot) {
|
||||
if (!plotInstance.current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <XYCanvas>{eventMarkers}</XYCanvas>;
|
||||
return (
|
||||
<XYCanvas
|
||||
left={plotInstance.current.bbox.left / window.devicePixelRatio}
|
||||
top={plotInstance.current.bbox.top / window.devicePixelRatio}
|
||||
>
|
||||
{eventMarkers}
|
||||
</XYCanvas>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import { usePlotContext } from '../context';
|
||||
import React, { useMemo } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
interface XYCanvasProps {}
|
||||
interface XYCanvasProps {
|
||||
top: number; // css pxls
|
||||
left: number; // css pxls
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders absolutely positioned element on top of the uPlot's plotting area (axes are not included!).
|
||||
* Useful when you want to render some overlay with canvas-independent elements on top of the plot.
|
||||
*/
|
||||
export const XYCanvas: React.FC<XYCanvasProps> = ({ children }) => {
|
||||
const plotCtx = usePlotContext();
|
||||
const plotInstance = plotCtx.plot;
|
||||
|
||||
if (!plotInstance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const XYCanvas: React.FC<XYCanvasProps> = ({ children, left, top }) => {
|
||||
const className = useMemo(() => {
|
||||
return css`
|
||||
position: absolute;
|
||||
overflow: visible;
|
||||
left: ${plotInstance.bbox.left / window.devicePixelRatio}px;
|
||||
top: ${plotInstance.bbox.top / window.devicePixelRatio}px;
|
||||
left: ${left}px;
|
||||
top: ${top}px;
|
||||
`;
|
||||
}, [plotInstance.bbox.left, plotInstance.bbox.top]);
|
||||
}, [left, top]);
|
||||
|
||||
return <div className={className}>{children}</div>;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { Portal } from '../../Portal/Portal';
|
||||
import { usePlotContext } from '../context';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import {
|
||||
CartesianCoords2D,
|
||||
DashboardCursorSync,
|
||||
@@ -13,11 +9,15 @@ import {
|
||||
getFieldDisplayName,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useMountedState } from 'react-use';
|
||||
import uPlot from 'uplot';
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import { Portal } from '../../Portal/Portal';
|
||||
import { SeriesTable, SeriesTableRowProps, VizTooltipContainer } from '../../VizTooltip';
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
import { findMidPointYPosition, pluginLog } from '../utils';
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
interface TooltipPluginProps {
|
||||
timeZone: TimeZone;
|
||||
@@ -44,13 +44,12 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
...otherProps
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
const plotCtx = usePlotContext();
|
||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||
const [focusedPointIdxs, setFocusedPointIdxs] = useState<Array<number | null>>([]);
|
||||
const [coords, setCoords] = useState<CartesianCoords2D | null>(null);
|
||||
const plotInstance = plotCtx.plot;
|
||||
const [isActive, setIsActive] = useState<boolean>(false);
|
||||
const isMounted = useMountedState();
|
||||
|
||||
const pluginId = `TooltipPlugin`;
|
||||
|
||||
@@ -59,42 +58,46 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
pluginLog(pluginId, true, `Focused series: ${focusedSeriesIdx}, focused point: ${focusedPointIdx}`);
|
||||
}, [focusedPointIdx, focusedSeriesIdx]);
|
||||
|
||||
useEffect(() => {
|
||||
// Add uPlot hooks to the config, or re-add when the config changed
|
||||
useLayoutEffect(() => {
|
||||
let plotInstance: uPlot | undefined = undefined;
|
||||
let bbox: DOMRect | undefined = undefined;
|
||||
|
||||
const plotMouseLeave = () => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setCoords(null);
|
||||
setIsActive(false);
|
||||
if (plotCtx.plot) {
|
||||
plotCtx.plot.root.classList.remove('plot-active');
|
||||
}
|
||||
plotInstance?.root.classList.remove('plot-active');
|
||||
};
|
||||
|
||||
const plotMouseEnter = () => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setIsActive(true);
|
||||
if (plotCtx.plot) {
|
||||
plotCtx.plot.root.classList.add('plot-active');
|
||||
}
|
||||
plotInstance?.root.classList.add('plot-active');
|
||||
};
|
||||
|
||||
if (plotCtx && plotCtx.plot) {
|
||||
plotCtx.plot.over.addEventListener('mouseleave', plotMouseLeave);
|
||||
plotCtx.plot.over.addEventListener('mouseenter', plotMouseEnter);
|
||||
// cache uPlot plotting area bounding box
|
||||
config.addHook('syncRect', (u, rect) => {
|
||||
bbox = rect;
|
||||
});
|
||||
|
||||
config.addHook('init', (u) => {
|
||||
plotInstance = u;
|
||||
|
||||
u.over.addEventListener('mouseleave', plotMouseLeave);
|
||||
u.over.addEventListener('mouseenter', plotMouseEnter);
|
||||
|
||||
if (sync === DashboardCursorSync.Crosshair) {
|
||||
plotCtx.plot.root.classList.add('shared-crosshair');
|
||||
u.root.classList.add('shared-crosshair');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
setCoords(null);
|
||||
if (plotCtx && plotCtx.plot) {
|
||||
plotCtx.plot.over.removeEventListener('mouseleave', plotMouseLeave);
|
||||
plotCtx.plot.over.removeEventListener('mouseenter', plotMouseEnter);
|
||||
}
|
||||
};
|
||||
}, [plotCtx.plot?.root]);
|
||||
|
||||
// Add uPlot hooks to the config, or re-add when the config changed
|
||||
useLayoutEffect(() => {
|
||||
const tooltipInterpolator = config.getTooltipInterpolator();
|
||||
|
||||
if (tooltipInterpolator) {
|
||||
// Custom toolitp positioning
|
||||
config.addHook('setCursor', (u) => {
|
||||
@@ -107,7 +110,6 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const bbox = plotCtx.getCanvasBoundingBox();
|
||||
if (!bbox) {
|
||||
return;
|
||||
}
|
||||
@@ -122,14 +124,16 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
});
|
||||
} else {
|
||||
config.addHook('setLegend', (u) => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setFocusedPointIdx(u.legend.idx!);
|
||||
setFocusedPointIdxs(u.legend.idxs!.slice());
|
||||
});
|
||||
|
||||
// default series/datapoint idx retireval
|
||||
config.addHook('setCursor', (u) => {
|
||||
const bbox = plotCtx.getCanvasBoundingBox();
|
||||
if (!bbox) {
|
||||
if (!bbox || !isMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,12 +146,23 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
});
|
||||
|
||||
config.addHook('setSeries', (_, idx) => {
|
||||
if (!isMounted()) {
|
||||
return;
|
||||
}
|
||||
setFocusedSeriesIdx(idx);
|
||||
});
|
||||
}
|
||||
}, [plotCtx, config]);
|
||||
|
||||
if (!plotInstance || focusedPointIdx === null || (!isActive && sync === DashboardCursorSync.Crosshair)) {
|
||||
return () => {
|
||||
setCoords(null);
|
||||
if (plotInstance) {
|
||||
plotInstance.over.removeEventListener('mouseleave', plotMouseLeave);
|
||||
plotInstance.over.removeEventListener('mouseenter', plotMouseEnter);
|
||||
}
|
||||
};
|
||||
}, [config, setCoords, setIsActive, setFocusedPointIdx, setFocusedPointIdxs]);
|
||||
|
||||
if (focusedPointIdx === null || (!isActive && sync === DashboardCursorSync.Crosshair)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -189,10 +204,10 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
|
||||
if (mode === TooltipDisplayMode.Multi) {
|
||||
let series: SeriesTableRowProps[] = [];
|
||||
const plotSeries = plotInstance.series;
|
||||
const frame = otherProps.data;
|
||||
const fields = frame.fields;
|
||||
|
||||
for (let i = 0; i < plotSeries.length; i++) {
|
||||
const frame = otherProps.data;
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = frame.fields[i];
|
||||
if (
|
||||
!field ||
|
||||
|
||||
@@ -10,6 +10,11 @@ const getCopiedText = (textBlocks: string[], startOffset: number, endOffset: num
|
||||
return textBlocks.join('\n').slice(startOffset, excludingLastLineLength + endOffset);
|
||||
};
|
||||
|
||||
// Remove unicode special symbol - byte order mark (BOM), U+FEFF.
|
||||
const removeBom = (str: string | undefined): string | undefined => {
|
||||
return str?.replace(/[\uFEFF]/g, '');
|
||||
};
|
||||
|
||||
export function ClipboardPlugin(): Plugin {
|
||||
const clipboardPlugin: Plugin = {
|
||||
onCopy(event: Event, editor: CoreEditor, next: () => any) {
|
||||
@@ -26,7 +31,7 @@ export function ClipboardPlugin(): Plugin {
|
||||
.toArray()
|
||||
.map((block) => block.text);
|
||||
|
||||
const copiedText = getCopiedText(selectedBlocks, startOffset, endOffset);
|
||||
const copiedText = removeBom(getCopiedText(selectedBlocks, startOffset, endOffset));
|
||||
if (copiedText && clipEvent.clipboardData) {
|
||||
clipEvent.clipboardData.setData('Text', copiedText);
|
||||
}
|
||||
@@ -38,10 +43,10 @@ export function ClipboardPlugin(): Plugin {
|
||||
const clipEvent = event as ClipboardEvent;
|
||||
clipEvent.preventDefault();
|
||||
if (clipEvent.clipboardData) {
|
||||
const pastedValue = clipEvent.clipboardData.getData('Text');
|
||||
const lines = pastedValue.split('\n');
|
||||
const pastedValue = removeBom(clipEvent.clipboardData.getData('Text'));
|
||||
const lines = pastedValue?.split('\n');
|
||||
|
||||
if (lines.length) {
|
||||
if (lines && lines.length) {
|
||||
editor.insertText(lines[0]);
|
||||
for (const line of lines.slice(1)) {
|
||||
editor.splitBlock().insertText(line);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Field, FieldType } from '@grafana/data';
|
||||
import { ComponentSize } from './size';
|
||||
export type IconType = 'mono' | 'default';
|
||||
export type IconSize = ComponentSize | 'xl' | 'xxl' | 'xxxl';
|
||||
@@ -68,6 +69,7 @@ export const getAvailableIcons = () =>
|
||||
'file-copy-alt',
|
||||
'filter',
|
||||
'folder',
|
||||
'font',
|
||||
'fire',
|
||||
'folder-open',
|
||||
'folder-plus',
|
||||
@@ -140,6 +142,7 @@ export const getAvailableIcons = () =>
|
||||
'table',
|
||||
'tag-alt',
|
||||
'times',
|
||||
'toggle-on',
|
||||
'trash-alt',
|
||||
'unlock',
|
||||
'upload',
|
||||
@@ -152,3 +155,24 @@ export const getAvailableIcons = () =>
|
||||
type BrandIconNames = 'google' | 'microsoft' | 'github' | 'gitlab' | 'okta';
|
||||
|
||||
export type IconName = ReturnType<typeof getAvailableIcons>[number] | BrandIconNames;
|
||||
|
||||
/** Get the icon for a given field type */
|
||||
export function getFieldTypeIcon(field?: Field): IconName {
|
||||
if (field) {
|
||||
switch (field.type) {
|
||||
case FieldType.time:
|
||||
return 'clock-nine';
|
||||
case FieldType.string:
|
||||
return 'font';
|
||||
case FieldType.number:
|
||||
return 'calculator-alt';
|
||||
case FieldType.boolean:
|
||||
return 'toggle-on';
|
||||
case FieldType.trace:
|
||||
return 'info-circle';
|
||||
case FieldType.other:
|
||||
return 'brackets-curly';
|
||||
}
|
||||
}
|
||||
return 'question-circle';
|
||||
}
|
||||
|
||||
@@ -229,6 +229,8 @@ export const getStandardFieldConfigs = () => {
|
||||
|
||||
/**
|
||||
* Returns collection of standard option editors definitions
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const getStandardOptionEditors = () => {
|
||||
const number: StandardEditorsRegistryItem<number> = {
|
||||
|
||||
@@ -26,12 +26,13 @@ import TraceName from '../common/TraceName';
|
||||
import { getTraceName } from '../model/trace-viewer';
|
||||
import { TNil } from '../types';
|
||||
import { Trace } from '../types/trace';
|
||||
import { formatDatetime, formatDuration } from '../utils/date';
|
||||
import { formatDuration } from '../utils/date';
|
||||
import { getTraceLinks } from '../model/link-patterns';
|
||||
|
||||
import ExternalLinks from '../common/ExternalLinks';
|
||||
import { createStyle } from '../Theme';
|
||||
import { uTxMuted } from '../uberUtilityStyles';
|
||||
import { dateTimeFormat, TimeZone } from '@grafana/data';
|
||||
|
||||
const getStyles = createStyle((theme: Theme) => {
|
||||
return {
|
||||
@@ -157,14 +158,16 @@ type TracePageHeaderEmbedProps = {
|
||||
searchValue: string;
|
||||
onSearchValueChange: (value: string) => void;
|
||||
hideSearchButtons?: boolean;
|
||||
timeZone: TimeZone;
|
||||
};
|
||||
|
||||
export const HEADER_ITEMS = [
|
||||
{
|
||||
key: 'timestamp',
|
||||
label: 'Trace Start',
|
||||
renderer(trace: Trace, styles: ReturnType<typeof getStyles>) {
|
||||
const dateStr = formatDatetime(trace.startTime);
|
||||
renderer(trace: Trace, timeZone: TimeZone, styles: ReturnType<typeof getStyles>) {
|
||||
// Convert date from micro to milli seconds
|
||||
const dateStr = dateTimeFormat(trace.startTime / 1000, { timeZone });
|
||||
const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/);
|
||||
return match ? (
|
||||
<span className={styles.TracePageHeaderOverviewItemValue}>
|
||||
@@ -219,6 +222,7 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
searchValue,
|
||||
onSearchValueChange,
|
||||
hideSearchButtons,
|
||||
timeZone,
|
||||
} = props;
|
||||
|
||||
const styles = getStyles(useTheme());
|
||||
@@ -238,7 +242,7 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
!slimView &&
|
||||
HEADER_ITEMS.map((item) => {
|
||||
const { renderer, ...rest } = item;
|
||||
return { ...rest, value: renderer(trace, styles) };
|
||||
return { ...rest, value: renderer(trace, timeZone, styles) };
|
||||
});
|
||||
|
||||
const title = (
|
||||
|
||||
@@ -21,4 +21,4 @@ PLUGINS_DIR=/var/lib/grafana/plugins
|
||||
PROVISIONING_CFG_DIR=/etc/grafana/provisioning
|
||||
|
||||
# Only used on systemd systems
|
||||
PID_FILE_DIR=/var/run/grafana
|
||||
PID_FILE_DIR=/run/grafana
|
||||
|
||||
+14
-6
@@ -72,9 +72,12 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/admin/stats", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)), hs.Index)
|
||||
r.Get("/admin/ldap", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), hs.Index)
|
||||
|
||||
r.Get("/styleguide", reqSignedIn, hs.Index)
|
||||
|
||||
r.Get("/live", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/live/pipeline", reqGrafanaAdmin, hs.Index)
|
||||
r.Get("/live/cloud", reqGrafanaAdmin, hs.Index)
|
||||
|
||||
r.Get("/plugins", reqSignedIn, hs.Index)
|
||||
r.Get("/plugins/:id/", reqSignedIn, hs.Index)
|
||||
r.Get("/plugins/:id/edit", reqSignedIn, hs.Index) // deprecated
|
||||
@@ -133,7 +136,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/api/login/ping", quota("session"), routing.Wrap(hs.LoginAPIPing))
|
||||
|
||||
// expose plugin file system assets
|
||||
r.Get("/public/plugins/:pluginId/*", hs.GetPluginAssets)
|
||||
r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets)
|
||||
|
||||
// authed api
|
||||
r.Group("/api", func(apiRoute routing.RouteRegister) {
|
||||
@@ -266,7 +269,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// Data sources
|
||||
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
|
||||
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)), routing.Wrap(hs.GetDataSources))
|
||||
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead, ScopeDatasourcesAll)), routing.Wrap(hs.GetDataSources))
|
||||
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesCreate)), quota("data_source"), bind(models.AddDataSourceCommand{}), routing.Wrap(AddDataSource))
|
||||
datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesWrite, ScopeDatasourceID)), bind(models.UpdateDataSourceCommand{}), routing.Wrap(hs.UpdateDataSource))
|
||||
datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceID)), routing.Wrap(hs.DeleteDataSourceById))
|
||||
@@ -370,8 +373,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics))
|
||||
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, routing.Wrap(GenerateSQLTestData))
|
||||
apiRoute.Get("/tsdb/testdata/random-walk", routing.Wrap(hs.GetTestDataRandomWalk))
|
||||
|
||||
// DataSource w/ expressions
|
||||
apiRoute.Post("/ds/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2))
|
||||
@@ -423,7 +424,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
// the channel path is in the name
|
||||
liveRoute.Post("/publish", bind(dtos.LivePublishCmd{}), routing.Wrap(hs.Live.HandleHTTPPublish))
|
||||
|
||||
// POST influx line protocol
|
||||
// POST influx line protocol.
|
||||
liveRoute.Post("/push/:streamId", hs.LivePushGateway.Handle)
|
||||
|
||||
// List available streams and fields
|
||||
@@ -431,6 +432,13 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// Some channels may have info
|
||||
liveRoute.Get("/info/*", routing.Wrap(hs.Live.HandleInfoHTTP))
|
||||
|
||||
if hs.Cfg.FeatureToggles["live-pipeline"] {
|
||||
// POST Live data to be processed according to channel rules.
|
||||
liveRoute.Post("/push/:streamId/:path", hs.LivePushGateway.HandlePath)
|
||||
liveRoute.Get("/channel-rules", routing.Wrap(hs.Live.HandleChannelRulesListHTTP), reqOrgAdmin)
|
||||
liveRoute.Get("/remote-write-backends", routing.Wrap(hs.Live.HandleRemoteWriteBackendsListHTTP), reqOrgAdmin)
|
||||
}
|
||||
})
|
||||
|
||||
// short urls
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
||||
@@ -47,7 +48,9 @@ func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
|
||||
}
|
||||
}
|
||||
handlers = append(handlers, AppPluginRoute(route, plugin.Id, hs))
|
||||
r.Handle(route.Method, url, handlers)
|
||||
for _, method := range strings.Split(route.Method, ",") {
|
||||
r.Handle(strings.TrimSpace(method), url, handlers)
|
||||
}
|
||||
log.Debugf("Plugins: Adding proxy route %s", url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
|
||||
desc: "DatasourcesGet should return 200 for user with correct permissions",
|
||||
url: "/api/datasources/",
|
||||
method: http.MethodGet,
|
||||
permissions: []*accesscontrol.Permission{{Action: ActionDatasourcesRead}},
|
||||
permissions: []*accesscontrol.Permission{{Action: ActionDatasourcesRead, Scope: ScopeDatasourcesAll}},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -112,11 +113,16 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
|
||||
// the datasource table)
|
||||
for _, ds := range hs.PluginManager.DataSources() {
|
||||
if ds.BuiltIn {
|
||||
dataSources[ds.Name] = map[string]interface{}{
|
||||
info := map[string]interface{}{
|
||||
"type": ds.Type,
|
||||
"name": ds.Name,
|
||||
"meta": hs.PluginManager.GetDataSource(ds.Id),
|
||||
}
|
||||
if ds.Name == grafanads.DatasourceName {
|
||||
info["id"] = grafanads.DatasourceID
|
||||
info["uid"] = grafanads.DatasourceUID
|
||||
}
|
||||
dataSources[ds.Name] = info
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-1
@@ -18,6 +18,16 @@ const (
|
||||
darkName = "dark"
|
||||
)
|
||||
|
||||
// dataSourcesConfigurationAccessEvaluator is used to protect the "Configure > Data sources" tab access
|
||||
var dataSourcesConfigurationAccessEvaluator = ac.EvalAll(
|
||||
ac.EvalPermission(ActionDatasourcesRead, ScopeDatasourcesAll),
|
||||
ac.EvalAny(
|
||||
ac.EvalPermission(ActionDatasourcesCreate),
|
||||
ac.EvalPermission(ActionDatasourcesDelete),
|
||||
ac.EvalPermission(ActionDatasourcesWrite),
|
||||
),
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) getProfileNode(c *models.ReqContext) *dtos.NavLink {
|
||||
// Only set login if it's different from the name
|
||||
var login string
|
||||
@@ -253,7 +263,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
|
||||
configNodes := []*dtos.NavLink{}
|
||||
|
||||
if hasAccess(ac.ReqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead)) {
|
||||
if hasAccess(ac.ReqOrgAdmin, dataSourcesConfigurationAccessEvaluator) {
|
||||
configNodes = append(configNodes, &dtos.NavLink{
|
||||
Text: "Data sources",
|
||||
Icon: "database",
|
||||
@@ -308,6 +318,30 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
})
|
||||
}
|
||||
|
||||
if true {
|
||||
liveNavLinks := []*dtos.NavLink{}
|
||||
|
||||
liveNavLinks = append(liveNavLinks, &dtos.NavLink{
|
||||
Text: "Status", Id: "live-status", Url: hs.Cfg.AppSubURL + "/live", Icon: "exchange-alt",
|
||||
})
|
||||
liveNavLinks = append(liveNavLinks, &dtos.NavLink{
|
||||
Text: "Pipeline", Id: "live-pipeline", Url: hs.Cfg.AppSubURL + "/live/pipeline", Icon: "arrow-to-right",
|
||||
})
|
||||
liveNavLinks = append(liveNavLinks, &dtos.NavLink{
|
||||
Text: "Cloud", Id: "live-cloud", Url: hs.Cfg.AppSubURL + "/live/cloud", Icon: "cloud-upload",
|
||||
})
|
||||
|
||||
navTree = append(navTree, &dtos.NavLink{
|
||||
Id: "live",
|
||||
Text: "Live",
|
||||
SubTitle: "Event Streaming",
|
||||
Icon: "exchange-alt",
|
||||
Url: hs.Cfg.AppSubURL + "/live",
|
||||
Children: liveNavLinks,
|
||||
HideFromMenu: true,
|
||||
})
|
||||
}
|
||||
|
||||
if len(configNodes) > 0 {
|
||||
navTree = append(navTree, &dtos.NavLink{
|
||||
Id: dtos.NavIDCfg,
|
||||
|
||||
+29
-63
@@ -1,19 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// QueryMetricsV2 returns query metrics.
|
||||
@@ -32,31 +30,42 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq
|
||||
}
|
||||
|
||||
// Loop to see if we have an expression.
|
||||
prevType := ""
|
||||
var ds *models.DataSource
|
||||
for _, query := range reqDTO.Queries {
|
||||
if query.Get("datasource").MustString("") == expr.DatasourceName {
|
||||
dsType := query.Get("datasource").MustString("")
|
||||
if dsType == expr.DatasourceName {
|
||||
return hs.handleExpressions(c, reqDTO)
|
||||
}
|
||||
}
|
||||
|
||||
var ds *models.DataSource
|
||||
for i, query := range reqDTO.Queries {
|
||||
hs.log.Debug("Processing metrics query", "query", query)
|
||||
|
||||
datasourceID, err := query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
if prevType != "" && prevType != dsType {
|
||||
// For mixed datasource case, each data source is sent in a single request.
|
||||
// So only the datasource from the first query is needed. As all requests
|
||||
// should be the same data source.
|
||||
hs.log.Debug("Can't process query since it's missing data source ID")
|
||||
return response.Error(http.StatusBadRequest, "Query missing data source ID", nil)
|
||||
return response.Error(http.StatusBadRequest, "All queries must use the same datasource", nil)
|
||||
}
|
||||
|
||||
// For mixed datasource case, each data source is sent in a single request.
|
||||
// So only the datasource from the first query is needed. As all requests
|
||||
// should be the same data source.
|
||||
if i == 0 {
|
||||
ds, err = hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
|
||||
if ds == nil {
|
||||
// require ID for everything
|
||||
dsID, err := query.Get("datasourceId").Int64()
|
||||
if err != nil {
|
||||
return hs.handleGetDataSourceError(err, datasourceID)
|
||||
hs.log.Debug("Can't process query since it's missing data source ID")
|
||||
return response.Error(http.StatusBadRequest, "Query missing data source ID", nil)
|
||||
}
|
||||
if dsID == grafanads.DatasourceID {
|
||||
ds = grafanads.DataSourceModel(c.OrgId)
|
||||
} else {
|
||||
ds, err = hs.DataSourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache)
|
||||
if err != nil {
|
||||
return hs.handleGetDataSourceError(err, dsID)
|
||||
}
|
||||
}
|
||||
}
|
||||
prevType = dsType
|
||||
}
|
||||
|
||||
for _, query := range reqDTO.Queries {
|
||||
hs.log.Debug("Processing metrics query", "query", query)
|
||||
|
||||
request.Queries = append(request.Queries, plugins.DataSubQuery{
|
||||
RefID: query.Get("refId").MustString("A"),
|
||||
@@ -213,46 +222,3 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque
|
||||
|
||||
return response.JSON(statusCode, &resp)
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/gensql
|
||||
func GenerateSQLTestData(c *models.ReqContext) response.Response {
|
||||
if err := bus.Dispatch(&models.InsertSQLTestDataCommand{}); err != nil {
|
||||
return response.Error(500, "Failed to insert test data", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, &util.DynMap{"message": "OK"})
|
||||
}
|
||||
|
||||
// GET /api/tsdb/testdata/random-walk
|
||||
func (hs *HTTPServer) GetTestDataRandomWalk(c *models.ReqContext) response.Response {
|
||||
from := c.Query("from")
|
||||
to := c.Query("to")
|
||||
intervalMS := c.QueryInt64("intervalMs")
|
||||
|
||||
timeRange := plugins.NewDataTimeRange(from, to)
|
||||
request := plugins.DataQuery{TimeRange: &timeRange}
|
||||
|
||||
dsInfo := &models.DataSource{
|
||||
Type: "testdata",
|
||||
JsonData: simplejson.New(),
|
||||
}
|
||||
request.Queries = append(request.Queries, plugins.DataSubQuery{
|
||||
RefID: "A",
|
||||
IntervalMS: intervalMS,
|
||||
Model: simplejson.NewFromAny(&util.DynMap{
|
||||
"scenario": "random_walk",
|
||||
}),
|
||||
DataSource: dsInfo,
|
||||
})
|
||||
|
||||
resp, err := hs.DataService.HandleRequest(context.Background(), dsInfo, request)
|
||||
if err != nil {
|
||||
return response.Error(500, "Metric request error", err)
|
||||
}
|
||||
|
||||
qdr, err := resp.ToBackendDataResponse()
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "error converting results", err)
|
||||
}
|
||||
return toMacronResponse(qdr)
|
||||
}
|
||||
|
||||
+7
-29
@@ -3,12 +3,10 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
@@ -21,14 +19,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var permittedFileExts = []string{
|
||||
".html", ".xhtml", ".css", ".js", ".json", ".jsonld", ".map", ".mjs",
|
||||
".jpeg", ".jpg", ".png", ".gif", ".svg", ".webp", ".ico",
|
||||
".woff", ".woff2", ".eot", ".ttf", ".otf",
|
||||
".wav", ".mp3",
|
||||
".md", ".pdf", ".txt",
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
|
||||
typeFilter := c.Query("type")
|
||||
enabledFilter := c.Query("enabled")
|
||||
@@ -262,10 +252,10 @@ func (hs *HTTPServer) CollectPluginMetrics(c *models.ReqContext) response.Respon
|
||||
return response.CreateNormalResponse(headers, resp.PrometheusMetrics, http.StatusOK)
|
||||
}
|
||||
|
||||
// GetPluginAssets returns public plugin assets (images, JS, etc.)
|
||||
// getPluginAssets returns public plugin assets (images, JS, etc.)
|
||||
//
|
||||
// /public/plugins/:pluginId/*
|
||||
func (hs *HTTPServer) GetPluginAssets(c *models.ReqContext) {
|
||||
func (hs *HTTPServer) getPluginAssets(c *models.ReqContext) {
|
||||
pluginID := c.Params("pluginId")
|
||||
plugin := hs.PluginManager.GetPlugin(pluginID)
|
||||
if plugin == nil {
|
||||
@@ -276,6 +266,11 @@ func (hs *HTTPServer) GetPluginAssets(c *models.ReqContext) {
|
||||
requestedFile := filepath.Clean(c.Params("*"))
|
||||
pluginFilePath := filepath.Join(plugin.PluginDir, requestedFile)
|
||||
|
||||
if !plugin.IncludedInSignature(requestedFile) {
|
||||
hs.log.Warn("Access to requested plugin file will be forbidden in upcoming Grafana versions as the file "+
|
||||
"is not included in the plugin signature", "file", requestedFile)
|
||||
}
|
||||
|
||||
// It's safe to ignore gosec warning G304 since we already clean the requested file path and subsequently
|
||||
// use this with a prefix of the plugin's directory, which is set during plugin loading
|
||||
// nolint:gosec
|
||||
@@ -300,12 +295,6 @@ func (hs *HTTPServer) GetPluginAssets(c *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if accessForbidden(fi.Name()) {
|
||||
c.JsonApiErr(403, "Plugin file access forbidden",
|
||||
fmt.Errorf("access is forbidden to plugin file %s", pluginFilePath))
|
||||
return
|
||||
}
|
||||
|
||||
if hs.Cfg.Env == setting.Dev {
|
||||
c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache")
|
||||
} else {
|
||||
@@ -448,14 +437,3 @@ func translatePluginRequestErrorToAPIError(err error) response.Response {
|
||||
|
||||
return response.Error(500, "Plugin request failed", err)
|
||||
}
|
||||
|
||||
func accessForbidden(pluginFilename string) bool {
|
||||
ext := filepath.Ext(pluginFilename)
|
||||
|
||||
for _, permittedExt := range permittedFileExts {
|
||||
if strings.EqualFold(permittedExt, ext) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+187
-75
@@ -1,89 +1,201 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func Test_accessForbidden(t *testing.T) {
|
||||
type testCase struct {
|
||||
filename string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t testCase
|
||||
accessForbidden bool
|
||||
}{
|
||||
{
|
||||
name: ".exe files are forbidden",
|
||||
t: testCase{
|
||||
filename: "test.exe",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: ".sh files are forbidden",
|
||||
t: testCase{
|
||||
filename: "test.sh",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "js is not forbidden",
|
||||
t: testCase{
|
||||
func Test_GetPluginAssets(t *testing.T) {
|
||||
pluginID := "test-plugin"
|
||||
pluginDir := "."
|
||||
tmpFile, err := ioutil.TempFile(pluginDir, "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll(tmpFile.Name())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
expectedBody := "Plugin test"
|
||||
_, err = tmpFile.WriteString(expectedBody)
|
||||
assert.NoError(t, err)
|
||||
|
||||
filename: "module.js",
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "logos are not forbidden",
|
||||
t: testCase{
|
||||
requestedFile := filepath.Clean(tmpFile.Name())
|
||||
|
||||
filename: "logo.svg",
|
||||
t.Run("Given a request for an existing plugin file that is listed as a signature covered file", func(t *testing.T) {
|
||||
p := &plugins.PluginBase{
|
||||
Id: pluginID,
|
||||
PluginDir: pluginDir,
|
||||
SignedFiles: map[string]struct{}{
|
||||
requestedFile: {},
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "JPGs are not forbidden",
|
||||
t: testCase{
|
||||
filename: "img/test.jpg",
|
||||
}
|
||||
service := &pluginManager{
|
||||
plugins: map[string]*plugins.PluginBase{
|
||||
pluginID: p,
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "JPEGs are not forbidden",
|
||||
t: testCase{
|
||||
filename: "img/test.jpeg",
|
||||
}
|
||||
l := &logger{}
|
||||
|
||||
url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile)
|
||||
pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l,
|
||||
func(sc *scenarioContext) {
|
||||
callGetPluginAsset(sc)
|
||||
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, expectedBody, sc.resp.Body.String())
|
||||
assert.Empty(t, l.warnings)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a request for an existing plugin file that is not listed as a signature covered file", func(t *testing.T) {
|
||||
p := &plugins.PluginBase{
|
||||
Id: pluginID,
|
||||
PluginDir: pluginDir,
|
||||
}
|
||||
service := &pluginManager{
|
||||
plugins: map[string]*plugins.PluginBase{
|
||||
pluginID: p,
|
||||
},
|
||||
accessForbidden: false,
|
||||
},
|
||||
{
|
||||
name: "ext case is ignored",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis.SH",
|
||||
}
|
||||
l := &logger{}
|
||||
|
||||
url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile)
|
||||
pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l,
|
||||
func(sc *scenarioContext) {
|
||||
callGetPluginAsset(sc)
|
||||
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, expectedBody, sc.resp.Body.String())
|
||||
assert.Empty(t, l.warnings)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a request for an non-existing plugin file", func(t *testing.T) {
|
||||
p := &plugins.PluginBase{
|
||||
Id: pluginID,
|
||||
PluginDir: pluginDir,
|
||||
}
|
||||
service := &pluginManager{
|
||||
plugins: map[string]*plugins.PluginBase{
|
||||
pluginID: p,
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "no file ext is forbidden",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis",
|
||||
}
|
||||
l := &logger{}
|
||||
|
||||
requestedFile := "nonExistent"
|
||||
url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile)
|
||||
pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l,
|
||||
func(sc *scenarioContext) {
|
||||
callGetPluginAsset(sc)
|
||||
|
||||
var respJson map[string]interface{}
|
||||
err := json.NewDecoder(sc.resp.Body).Decode(&respJson)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 404, sc.resp.Code)
|
||||
assert.Equal(t, "Plugin file not found", respJson["message"])
|
||||
assert.Empty(t, l.warnings)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a request for an non-existing plugin", func(t *testing.T) {
|
||||
service := &pluginManager{
|
||||
plugins: map[string]*plugins.PluginBase{},
|
||||
}
|
||||
l := &logger{}
|
||||
|
||||
requestedFile := "nonExistent"
|
||||
url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile)
|
||||
pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l,
|
||||
func(sc *scenarioContext) {
|
||||
callGetPluginAsset(sc)
|
||||
|
||||
var respJson map[string]interface{}
|
||||
err := json.NewDecoder(sc.resp.Body).Decode(&respJson)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
assert.Equal(t, "Plugin not found", respJson["message"])
|
||||
assert.Empty(t, l.warnings)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a request for a core plugin's file", func(t *testing.T) {
|
||||
service := &pluginManager{
|
||||
plugins: map[string]*plugins.PluginBase{
|
||||
pluginID: {
|
||||
IsCorePlugin: true,
|
||||
},
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "empty file ext is forbidden",
|
||||
t: testCase{
|
||||
filename: "scripts/runThis.",
|
||||
},
|
||||
accessForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := accessForbidden(tt.t.filename); got != tt.accessForbidden {
|
||||
t.Errorf("accessForbidden() = %v, accessForbidden %v", got, tt.accessForbidden)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
l := &logger{}
|
||||
|
||||
url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile)
|
||||
pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l,
|
||||
func(sc *scenarioContext) {
|
||||
callGetPluginAsset(sc)
|
||||
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, expectedBody, sc.resp.Body.String())
|
||||
assert.Empty(t, l.warnings)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func callGetPluginAsset(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func pluginAssetScenario(t *testing.T, desc string, url string, urlPattern string, pluginManager plugins.Manager,
|
||||
logger log.Logger, fn scenarioFunc) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
hs := HTTPServer{
|
||||
Cfg: setting.NewCfg(),
|
||||
PluginManager: pluginManager,
|
||||
log: logger,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
sc.defaultHandler = func(c *models.ReqContext) {
|
||||
sc.context = c
|
||||
hs.getPluginAssets(c)
|
||||
}
|
||||
|
||||
sc.m.Get(urlPattern, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
type pluginManager struct {
|
||||
manager.PluginManager
|
||||
|
||||
plugins map[string]*plugins.PluginBase
|
||||
}
|
||||
|
||||
func (pm *pluginManager) GetPlugin(id string) *plugins.PluginBase {
|
||||
return pm.plugins[id]
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
log.Logger
|
||||
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func (l *logger) Warn(msg string, ctx ...interface{}) {
|
||||
l.warnings = append(l.warnings, msg)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
GoOSWindows = "windows"
|
||||
GoOSLinux = "linux"
|
||||
|
||||
ServerBinary = "grafana-server"
|
||||
CLIBinary = "grafana-cli"
|
||||
)
|
||||
|
||||
var binaries = []string{ServerBinary, CLIBinary}
|
||||
|
||||
func logError(message string, err error) int {
|
||||
log.Println(message, err)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// RunCmd runs the build command and returns the exit code
|
||||
func RunCmd() int {
|
||||
opts := BuildOptsFromFlags()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return logError("Error getting working directory", err)
|
||||
}
|
||||
|
||||
packageJSON, err := OpenPackageJSON(wd)
|
||||
if err != nil {
|
||||
return logError("Error opening package json", err)
|
||||
}
|
||||
|
||||
opts.version = packageJSON.Version
|
||||
|
||||
version, iteration := LinuxPackageVersion(packageJSON.Version, opts.buildID)
|
||||
|
||||
if opts.printGenVersion {
|
||||
fmt.Print(genPackageVersion(version, iteration))
|
||||
return 0
|
||||
}
|
||||
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, version, iteration)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
log.Println("Usage: go run build.go build")
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, cmd := range flag.Args() {
|
||||
switch cmd {
|
||||
case "setup":
|
||||
setup(opts.goos)
|
||||
|
||||
case "build-srv", "build-server":
|
||||
if !opts.isDev {
|
||||
clean(opts)
|
||||
}
|
||||
|
||||
if err := doBuild("grafana-server", "./pkg/cmd/grafana-server", opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
case "build-cli":
|
||||
clean(opts)
|
||||
if err := doBuild("grafana-cli", "./pkg/cmd/grafana-cli", opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
case "build":
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
log.Println("building binaries", cmd)
|
||||
// Can't use filepath.Join here because filepath.Join calls filepath.Clean, which removes the `./` from this path, which upsets `go build`
|
||||
if err := doBuild(binary, fmt.Sprintf("./pkg/cmd/%s", binary), opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
yarn("build")
|
||||
|
||||
case "sha-dist":
|
||||
if err := shaDir("dist"); err != nil {
|
||||
return logError("error packaging dist directory", err)
|
||||
}
|
||||
|
||||
case "latest":
|
||||
makeLatestDistCopies()
|
||||
|
||||
case "clean":
|
||||
clean(opts)
|
||||
|
||||
default:
|
||||
log.Println("Unknown command", cmd)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func makeLatestDistCopies() {
|
||||
files, err := ioutil.ReadDir("dist")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create latest copies. Cannot read from /dist")
|
||||
}
|
||||
|
||||
latestMapping := map[string]string{
|
||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||
".linux-amd64-musl.tar.gz": "dist/grafana-latest.linux-x64-musl.tar.gz",
|
||||
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
|
||||
".linux-armv7-musl.tar.gz": "dist/grafana-latest.linux-armv7-musl.tar.gz",
|
||||
".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
|
||||
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
|
||||
".linux-arm64-musl.tar.gz": "dist/grafana-latest.linux-arm64-musl.tar.gz",
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
for extension, fullName := range latestMapping {
|
||||
if strings.HasSuffix(file.Name(), extension) {
|
||||
if _, err := runError("cp", path.Join("dist", file.Name()), fullName); err != nil {
|
||||
log.Println("error running cp command:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func yarn(params ...string) {
|
||||
runPrint(`yarn run`, params...)
|
||||
}
|
||||
|
||||
func genPackageVersion(version string, iteration string) string {
|
||||
if iteration != "" {
|
||||
return fmt.Sprintf("%v-%v", version, iteration)
|
||||
} else {
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
func setup(goos string) {
|
||||
args := []string{"install", "-v"}
|
||||
if goos == GoOSWindows {
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
args = append(args, "./pkg/cmd/grafana-server")
|
||||
runPrint("go", args...)
|
||||
}
|
||||
|
||||
func doBuild(binaryName, pkg string, opts BuildOpts) error {
|
||||
log.Println("building", binaryName, pkg)
|
||||
libcPart := ""
|
||||
if opts.libc != "" {
|
||||
libcPart = fmt.Sprintf("-%s", opts.libc)
|
||||
}
|
||||
binary := fmt.Sprintf("./bin/%s", binaryName)
|
||||
|
||||
//don't include os/arch/libc in output path in dev environment
|
||||
if !opts.isDev {
|
||||
binary = fmt.Sprintf("./bin/%s-%s%s/%s", opts.goos, opts.goarch, libcPart, binaryName)
|
||||
}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
binary += ".exe"
|
||||
}
|
||||
|
||||
if !opts.isDev {
|
||||
rmr(binary, binary+".md5")
|
||||
}
|
||||
|
||||
lf, err := ldflags(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := []string{"build", "-ldflags", lf}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
// Work around a linking error on Windows: "export ordinal too large"
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
|
||||
if len(opts.buildTags) > 0 {
|
||||
args = append(args, "-tags", strings.Join(opts.buildTags, ","))
|
||||
}
|
||||
|
||||
if opts.race {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
|
||||
args = append(args, "-o", binary)
|
||||
args = append(args, pkg)
|
||||
|
||||
runPrint("go", args...)
|
||||
|
||||
if opts.isDev {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := setBuildEnv(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
runPrint("go", "version")
|
||||
libcPart = ""
|
||||
if opts.libc != "" {
|
||||
libcPart = fmt.Sprintf("/%s", opts.libc)
|
||||
}
|
||||
fmt.Printf("Targeting %s/%s%s\n", opts.goos, opts.goarch, libcPart)
|
||||
|
||||
// Create an md5 checksum of the binary, to be included in the archive for
|
||||
// automatic upgrades.
|
||||
return md5File(binary)
|
||||
}
|
||||
|
||||
func ldflags(opts BuildOpts) (string, error) {
|
||||
buildStamp, err := buildStamp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
b.WriteString("-w")
|
||||
b.WriteString(fmt.Sprintf(" -X main.version=%s", opts.version))
|
||||
b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildBranch=%s", getGitBranch()))
|
||||
if v := os.Getenv("LDFLAGS"); v != "" {
|
||||
b.WriteString(fmt.Sprintf(" -extldflags \"%s\"", v))
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func setBuildEnv(opts BuildOpts) error {
|
||||
if err := os.Setenv("GOOS", opts.goos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
// require windows >=7
|
||||
if err := os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.goarch != "amd64" || opts.goos != GoOSLinux {
|
||||
// needed for all other archs
|
||||
opts.cgo = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(opts.goarch, "armv") {
|
||||
if err := os.Setenv("GOARCH", "arm"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Setenv("GOARM", opts.goarch[4:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Setenv("GOARCH", opts.goarch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.cgo {
|
||||
if err := os.Setenv("CGO_ENABLED", "1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.gocc == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Setenv("CC", opts.gocc)
|
||||
}
|
||||
|
||||
func buildStamp() (int64, error) {
|
||||
// use SOURCE_DATE_EPOCH if set.
|
||||
if v, ok := os.LookupEnv("SOURCE_DATE_EPOCH"); ok {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
bs, err := runError("git", "show", "-s", "--format=%ct")
|
||||
if err != nil {
|
||||
return time.Now().Unix(), nil
|
||||
}
|
||||
|
||||
return strconv.ParseInt(string(bs), 10, 64)
|
||||
}
|
||||
|
||||
func clean(opts BuildOpts) {
|
||||
rmr("dist")
|
||||
rmr("tmp")
|
||||
rmr(filepath.Join(build.Default.GOPATH, fmt.Sprintf("pkg/%s_%s/github.com/grafana", opts.goos, opts.goarch)))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package build contains the command / functions for the Grafana build process used when running the "build" target in the makefile
|
||||
package build
|
||||
@@ -0,0 +1,34 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func runError(cmd string, args ...string) ([]byte, error) {
|
||||
// Can ignore gosec G204 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
bs, err := ecmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.TrimSpace(bs), nil
|
||||
}
|
||||
|
||||
func runPrint(cmd string, args ...string) {
|
||||
log.Println(cmd, strings.Join(args, " "))
|
||||
// Can ignore gosec G204 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
ecmd.Stdout = os.Stdout
|
||||
ecmd.Stderr = os.Stderr
|
||||
err := ecmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func logAndClose(c io.Closer) {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Println("error closing:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func shaDir(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(path, ".sha256") {
|
||||
return nil
|
||||
}
|
||||
if err := shaFile(path); err != nil {
|
||||
log.Printf("Failed to create sha file. error: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func shaFile(file string) error {
|
||||
// Can ignore gosec G304 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
r, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logAndClose(r)
|
||||
|
||||
h := sha256.New()
|
||||
_, err = io.Copy(h, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".sha256")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func md5File(file string) error {
|
||||
// Can ignore gosec G304 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer logAndClose(fd)
|
||||
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".md5")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
// basically `rm -r`s the list of files provided
|
||||
func rmr(paths ...string) {
|
||||
for _, path := range paths {
|
||||
log.Println("rm -r", path)
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
log.Println("error deleting folder", path, "error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user