mirror of
https://github.com/grafana/grafana.git
synced 2025-09-21 17:43:23 +08:00
Previews: datasource permissions (#52747)
* Previews: datasource permissions * lint * simplify - force non-null `ds_uids` * add `canBeDisabled` to search service * add `IncludeThumbnailsWithEmptyDsUids` * remove force refresh migration * refactor main preview service * add safeguard * revert ticker interval * update testdata * fix test * add mock search service * add datasources lookup test * update migration * extract ds lookup to its own package to avoid cyclic imports * lint * fix dashbaord extract, use the real datasource lookup in tests. IS IT BULLETPROOF YET?! * fix dashbaord extract, use the real datasource lookup in tests. IS IT BULLETPROOF YET?! * remove stale log * consistent casing * pass context to `createServiceAccount` * filter out the special grafana ds
This commit is contained in:
@ -86,6 +86,7 @@ type DashboardThumbnail struct {
|
||||
Image []byte `json:"image"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Updated time.Time `json:"updated"`
|
||||
DsUIDs string `json:"-" xorm:"ds_uids"`
|
||||
}
|
||||
|
||||
//
|
||||
@ -123,6 +124,7 @@ type FindDashboardThumbnailCountCommand struct {
|
||||
|
||||
type FindDashboardsWithStaleThumbnailsCommand struct {
|
||||
IncludeManuallyUploadedThumbnails bool
|
||||
IncludeThumbnailsWithEmptyDsUIDs bool
|
||||
Theme Theme
|
||||
Kind ThumbnailKind
|
||||
Result []*DashboardWithStaleThumbnail
|
||||
@ -133,6 +135,7 @@ type SaveDashboardThumbnailCommand struct {
|
||||
DashboardVersion int
|
||||
Image []byte
|
||||
MimeType string
|
||||
DatasourceUIDs []string
|
||||
|
||||
Result *DashboardThumbnail
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana/pkg/infra/filestorage"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/extract"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
@ -26,7 +26,7 @@ func exportDashboards(helper *commitHelper, job *gitExportJob) error {
|
||||
folders[0] = job.cfg.GeneralFolderPath // "general"
|
||||
}
|
||||
|
||||
lookup, err := searchV2.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql)
|
||||
lookup, err := dslookup.LoadDatasourceLookup(helper.ctx, helper.orgID, job.sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
@ -232,9 +233,9 @@ func getEntityReferences(resp *backend.DataResponse) ([]entityReferences, error)
|
||||
return nil, errors.New("invalid value in dash_uid field")
|
||||
}
|
||||
|
||||
rawDsUids, ok := dsUidField.At(i).(*json.RawMessage)
|
||||
rawDsUids, ok := dsUidField.At(i).(json.RawMessage)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid value in ds_uid field")
|
||||
return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uidField, dsUidField.At(i))
|
||||
}
|
||||
|
||||
var uids []string
|
||||
|
@ -496,7 +496,7 @@ func doSearchQuery(
|
||||
fURL := data.NewFieldFromFieldType(data.FieldTypeString, 0)
|
||||
fLocation := data.NewFieldFromFieldType(data.FieldTypeString, 0)
|
||||
fTags := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0)
|
||||
fDSUIDs := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0)
|
||||
fDSUIDs := data.NewFieldFromFieldType(data.FieldTypeJSON, 0)
|
||||
fExplain := data.NewFieldFromFieldType(data.FieldTypeNullableJSON, 0)
|
||||
|
||||
fScore.Name = "score"
|
||||
@ -592,14 +592,14 @@ func doSearchQuery(
|
||||
fTags.Append(nil)
|
||||
}
|
||||
|
||||
if len(dsUIDs) > 0 {
|
||||
js, _ := json.Marshal(dsUIDs)
|
||||
jsb := json.RawMessage(js)
|
||||
fDSUIDs.Append(&jsb)
|
||||
} else {
|
||||
fDSUIDs.Append(nil)
|
||||
if len(dsUIDs) == 0 {
|
||||
dsUIDs = []string{}
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(dsUIDs)
|
||||
jsb := json.RawMessage(js)
|
||||
fDSUIDs.Append(jsb)
|
||||
|
||||
if q.Explain {
|
||||
if isMatchAllQuery {
|
||||
fScore.Append(float64(fieldLen + q.From))
|
||||
|
136
pkg/services/searchV2/dslookup/ds_lookup.go
Normal file
136
pkg/services/searchV2/dslookup/ds_lookup.go
Normal file
@ -0,0 +1,136 @@
|
||||
package dslookup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type DataSourceRef struct {
|
||||
UID string `json:"uid,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type DatasourceLookup interface {
|
||||
// ByRef will return the default DS given empty reference (nil ref, or empty ref.uid and ref.type)
|
||||
ByRef(ref *DataSourceRef) *DataSourceRef
|
||||
ByType(dsType string) []DataSourceRef
|
||||
}
|
||||
|
||||
type DatasourceQueryResult struct {
|
||||
UID string `xorm:"uid"`
|
||||
Type string `xorm:"type"`
|
||||
Name string `xorm:"name"`
|
||||
IsDefault bool `xorm:"is_default"`
|
||||
}
|
||||
|
||||
func CreateDatasourceLookup(rows []*DatasourceQueryResult) DatasourceLookup {
|
||||
byUID := make(map[string]*DataSourceRef, 50)
|
||||
byName := make(map[string]*DataSourceRef, 50)
|
||||
byType := make(map[string][]DataSourceRef, 50)
|
||||
var defaultDS *DataSourceRef
|
||||
|
||||
for _, row := range rows {
|
||||
ref := &DataSourceRef{
|
||||
UID: row.UID,
|
||||
Type: row.Type,
|
||||
}
|
||||
byUID[row.UID] = ref
|
||||
byName[row.Name] = ref
|
||||
if row.IsDefault {
|
||||
defaultDS = ref
|
||||
}
|
||||
|
||||
if _, ok := byType[row.Type]; !ok {
|
||||
byType[row.Type] = make([]DataSourceRef, 0)
|
||||
}
|
||||
byType[row.Type] = append(byType[row.Type], *ref)
|
||||
}
|
||||
|
||||
grafanaDs := &DataSourceRef{
|
||||
UID: "grafana",
|
||||
Type: "datasource",
|
||||
}
|
||||
if defaultDS == nil {
|
||||
// fallback replicated from /pkg/api/frontendsettings.go
|
||||
// https://github.com/grafana/grafana/blob/7ef21662f9ad74b80d832b9f2aa9db2fb4192741/pkg/api/frontendsettings.go#L51-L56
|
||||
defaultDS = grafanaDs
|
||||
}
|
||||
|
||||
if _, ok := byUID[grafanaDs.UID]; !ok {
|
||||
byUID[grafanaDs.UID] = grafanaDs
|
||||
}
|
||||
|
||||
grafanaDsName := "-- Grafana --"
|
||||
if _, ok := byName[grafanaDsName]; !ok {
|
||||
byName[grafanaDsName] = grafanaDs
|
||||
}
|
||||
|
||||
return &DsLookup{
|
||||
byName: byName,
|
||||
byUID: byUID,
|
||||
byType: byType,
|
||||
defaultDS: defaultDS,
|
||||
}
|
||||
}
|
||||
|
||||
type DsLookup struct {
|
||||
byName map[string]*DataSourceRef
|
||||
byUID map[string]*DataSourceRef
|
||||
byType map[string][]DataSourceRef
|
||||
defaultDS *DataSourceRef
|
||||
}
|
||||
|
||||
func (d *DsLookup) ByRef(ref *DataSourceRef) *DataSourceRef {
|
||||
if ref == nil {
|
||||
return d.defaultDS
|
||||
}
|
||||
|
||||
key := ""
|
||||
if ref.UID != "" {
|
||||
ds, ok := d.byUID[ref.UID]
|
||||
if ok {
|
||||
return ds
|
||||
}
|
||||
key = ref.UID
|
||||
}
|
||||
if key == "" {
|
||||
return d.defaultDS
|
||||
}
|
||||
ds, ok := d.byUID[key]
|
||||
if ok {
|
||||
return ds
|
||||
}
|
||||
|
||||
return d.byName[key]
|
||||
}
|
||||
|
||||
func (d *DsLookup) ByType(dsType string) []DataSourceRef {
|
||||
ds, ok := d.byType[dsType]
|
||||
if !ok {
|
||||
return make([]DataSourceRef, 0)
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
func LoadDatasourceLookup(ctx context.Context, orgID int64, sql *sqlstore.SQLStore) (DatasourceLookup, error) {
|
||||
rows := make([]*DatasourceQueryResult, 0)
|
||||
|
||||
if err := sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
sess.Table("data_source").
|
||||
Where("org_id = ?", orgID).
|
||||
Cols("uid", "name", "type", "is_default")
|
||||
|
||||
err := sess.Find(&rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return CreateDatasourceLookup(rows), nil
|
||||
}
|
@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
)
|
||||
|
||||
func logf(format string, a ...interface{}) {
|
||||
@ -22,43 +24,46 @@ type templateVariable struct {
|
||||
}
|
||||
|
||||
type datasourceVariableLookup struct {
|
||||
variableNameToRefs map[string][]DataSourceRef
|
||||
dsLookup DatasourceLookup
|
||||
variableNameToRefs map[string][]dslookup.DataSourceRef
|
||||
dsLookup dslookup.DatasourceLookup
|
||||
}
|
||||
|
||||
func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []DataSourceRef {
|
||||
func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []dslookup.DataSourceRef {
|
||||
switch value {
|
||||
case "default":
|
||||
// can be the default DS, or a DS with UID="default"
|
||||
candidateDs := d.dsLookup.ByRef(&DataSourceRef{UID: value})
|
||||
candidateDs := d.dsLookup.ByRef(&dslookup.DataSourceRef{UID: value})
|
||||
if candidateDs == nil {
|
||||
// get the actual default DS
|
||||
candidateDs = d.dsLookup.ByRef(nil)
|
||||
}
|
||||
|
||||
if candidateDs != nil {
|
||||
return []DataSourceRef{*candidateDs}
|
||||
return []dslookup.DataSourceRef{*candidateDs}
|
||||
}
|
||||
return []DataSourceRef{}
|
||||
return []dslookup.DataSourceRef{}
|
||||
case "$__all":
|
||||
// TODO: filter datasources by template variable's regex
|
||||
return d.dsLookup.ByType(datasourceType)
|
||||
case "":
|
||||
return []DataSourceRef{}
|
||||
return []dslookup.DataSourceRef{}
|
||||
case "No data sources found":
|
||||
return []DataSourceRef{}
|
||||
return []dslookup.DataSourceRef{}
|
||||
default:
|
||||
return []DataSourceRef{
|
||||
{
|
||||
UID: value,
|
||||
Type: datasourceType,
|
||||
},
|
||||
// some variables use `ds.name` rather `ds.uid`
|
||||
if ref := d.dsLookup.ByRef(&dslookup.DataSourceRef{
|
||||
UID: value,
|
||||
}); ref != nil {
|
||||
return []dslookup.DataSourceRef{*ref}
|
||||
}
|
||||
|
||||
// discard variable
|
||||
return []dslookup.DataSourceRef{}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *datasourceVariableLookup) add(templateVariable templateVariable) {
|
||||
var refs []DataSourceRef
|
||||
var refs []dslookup.DataSourceRef
|
||||
|
||||
datasourceType, isDataSourceTypeValid := templateVariable.query.(string)
|
||||
if !isDataSourceTypeValid {
|
||||
@ -81,8 +86,8 @@ func (d *datasourceVariableLookup) add(templateVariable templateVariable) {
|
||||
d.variableNameToRefs[templateVariable.name] = unique(refs)
|
||||
}
|
||||
|
||||
func unique(refs []DataSourceRef) []DataSourceRef {
|
||||
var uniqueRefs []DataSourceRef
|
||||
func unique(refs []dslookup.DataSourceRef) []dslookup.DataSourceRef {
|
||||
var uniqueRefs []dslookup.DataSourceRef
|
||||
uidPresence := make(map[string]bool)
|
||||
for _, ref := range refs {
|
||||
if !uidPresence[ref.UID] {
|
||||
@ -93,25 +98,25 @@ func unique(refs []DataSourceRef) []DataSourceRef {
|
||||
return uniqueRefs
|
||||
}
|
||||
|
||||
func (d *datasourceVariableLookup) getDatasourceRefs(name string) []DataSourceRef {
|
||||
func (d *datasourceVariableLookup) getDatasourceRefs(name string) []dslookup.DataSourceRef {
|
||||
refs, ok := d.variableNameToRefs[name]
|
||||
if ok {
|
||||
return refs
|
||||
}
|
||||
|
||||
return []DataSourceRef{}
|
||||
return []dslookup.DataSourceRef{}
|
||||
}
|
||||
|
||||
func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableLookup {
|
||||
func newDatasourceVariableLookup(dsLookup dslookup.DatasourceLookup) *datasourceVariableLookup {
|
||||
return &datasourceVariableLookup{
|
||||
variableNameToRefs: make(map[string][]DataSourceRef),
|
||||
variableNameToRefs: make(map[string][]dslookup.DataSourceRef),
|
||||
dsLookup: dsLookup,
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
// ReadDashboard will take a byte stream and return dashboard info
|
||||
func ReadDashboard(stream io.Reader, lookup DatasourceLookup) (*DashboardInfo, error) {
|
||||
func ReadDashboard(stream io.Reader, lookup dslookup.DatasourceLookup) (*DashboardInfo, error) {
|
||||
dash := &DashboardInfo{}
|
||||
|
||||
iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024)
|
||||
@ -277,7 +282,7 @@ func panelRequiresDatasource(panel PanelInfo) bool {
|
||||
return panel.Type != "row"
|
||||
}
|
||||
|
||||
func fillDefaultDatasources(dash *DashboardInfo, lookup DatasourceLookup) {
|
||||
func fillDefaultDatasources(dash *DashboardInfo, lookup dslookup.DatasourceLookup) {
|
||||
for i, panel := range dash.Panels {
|
||||
if len(panel.Datasource) != 0 || !panelRequiresDatasource(panel) {
|
||||
continue
|
||||
@ -285,14 +290,14 @@ func fillDefaultDatasources(dash *DashboardInfo, lookup DatasourceLookup) {
|
||||
|
||||
defaultDs := lookup.ByRef(nil)
|
||||
if defaultDs != nil {
|
||||
dash.Panels[i].Datasource = []DataSourceRef{*defaultDs}
|
||||
dash.Panels[i].Datasource = []dslookup.DataSourceRef{*defaultDs}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterOutSpecialDatasources(dash *DashboardInfo) {
|
||||
for i, panel := range dash.Panels {
|
||||
var dsRefs []DataSourceRef
|
||||
var dsRefs []dslookup.DataSourceRef
|
||||
|
||||
// partition into actual datasource references and variables
|
||||
for _, ds := range panel.Datasource {
|
||||
@ -314,24 +319,33 @@ func filterOutSpecialDatasources(dash *DashboardInfo) {
|
||||
|
||||
func replaceDatasourceVariables(dash *DashboardInfo, datasourceVariablesLookup *datasourceVariableLookup) {
|
||||
for i, panel := range dash.Panels {
|
||||
var dsVariableRefs []DataSourceRef
|
||||
var dsRefs []DataSourceRef
|
||||
var dsVariableRefs []dslookup.DataSourceRef
|
||||
var dsRefs []dslookup.DataSourceRef
|
||||
|
||||
// partition into actual datasource references and variables
|
||||
for i := range panel.Datasource {
|
||||
isVariableRef := strings.HasPrefix(panel.Datasource[i].UID, "$")
|
||||
if isVariableRef {
|
||||
uid := panel.Datasource[i].UID
|
||||
if isVariableRef(uid) {
|
||||
dsVariableRefs = append(dsVariableRefs, panel.Datasource[i])
|
||||
} else {
|
||||
dsRefs = append(dsRefs, panel.Datasource[i])
|
||||
}
|
||||
}
|
||||
|
||||
dash.Panels[i].Datasource = append(dsRefs, findDatasourceRefsForVariables(dsVariableRefs, datasourceVariablesLookup)...)
|
||||
variables := findDatasourceRefsForVariables(dsVariableRefs, datasourceVariablesLookup)
|
||||
dash.Panels[i].Datasource = append(dsRefs, variables...)
|
||||
}
|
||||
}
|
||||
|
||||
func getDataSourceVariableName(dsVariableRef DataSourceRef) string {
|
||||
func isSpecialDatasource(uid string) bool {
|
||||
return uid == "-- Mixed --" || uid == "-- Dashboard --"
|
||||
}
|
||||
|
||||
func isVariableRef(uid string) bool {
|
||||
return strings.HasPrefix(uid, "$")
|
||||
}
|
||||
|
||||
func getDataSourceVariableName(dsVariableRef dslookup.DataSourceRef) string {
|
||||
if strings.HasPrefix(dsVariableRef.UID, "${") {
|
||||
return strings.TrimPrefix(strings.TrimSuffix(dsVariableRef.UID, "}"), "${")
|
||||
}
|
||||
@ -339,8 +353,8 @@ func getDataSourceVariableName(dsVariableRef DataSourceRef) string {
|
||||
return strings.TrimPrefix(dsVariableRef.UID, "$")
|
||||
}
|
||||
|
||||
func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []DataSourceRef {
|
||||
var referencedDs []DataSourceRef
|
||||
func findDatasourceRefsForVariables(dsVariableRefs []dslookup.DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []dslookup.DataSourceRef {
|
||||
var referencedDs []dslookup.DataSourceRef
|
||||
for _, dsVariableRef := range dsVariableRefs {
|
||||
variableName := getDataSourceVariableName(dsVariableRef)
|
||||
refs := datasourceVariablesLookup.getDatasourceRefs(variableName)
|
||||
@ -350,7 +364,7 @@ func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVa
|
||||
}
|
||||
|
||||
// will always return strings for now
|
||||
func readPanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelInfo {
|
||||
func readPanelInfo(iter *jsoniter.Iterator, lookup dslookup.DatasourceLookup) PanelInfo {
|
||||
panel := PanelInfo{}
|
||||
|
||||
targets := newTargetInfo(lookup)
|
||||
|
@ -8,41 +8,50 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type dsLookup struct {
|
||||
}
|
||||
|
||||
func (d *dsLookup) ByRef(ref *DataSourceRef) *DataSourceRef {
|
||||
if ref == nil || ref.UID == "" {
|
||||
return &DataSourceRef{
|
||||
UID: "default.uid",
|
||||
Type: "default.type",
|
||||
}
|
||||
}
|
||||
|
||||
if ref.UID == "default" {
|
||||
return nil
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
func (d *dsLookup) ByType(dsType string) []DataSourceRef {
|
||||
if dsType == "sqlite-datasource" {
|
||||
return []DataSourceRef{
|
||||
{
|
||||
UID: "sqlite-1",
|
||||
Type: "sqlite-datasource",
|
||||
},
|
||||
{
|
||||
UID: "sqlite-2",
|
||||
Type: "sqlite-datasource",
|
||||
},
|
||||
}
|
||||
}
|
||||
return make([]DataSourceRef, 0)
|
||||
func dsLookup() dslookup.DatasourceLookup {
|
||||
return dslookup.CreateDatasourceLookup([]*dslookup.DatasourceQueryResult{
|
||||
{
|
||||
UID: "P8045C56BDA891CB2",
|
||||
Type: "cloudwatch",
|
||||
Name: "cloudwatch-name",
|
||||
IsDefault: false,
|
||||
},
|
||||
{
|
||||
UID: "PD8C576611E62080A",
|
||||
Type: "testdata",
|
||||
Name: "gdev-testdata",
|
||||
IsDefault: false,
|
||||
},
|
||||
{
|
||||
UID: "dgd92lq7k",
|
||||
Type: "frser-sqlite-datasource",
|
||||
Name: "frser-sqlite-datasource-name",
|
||||
IsDefault: false,
|
||||
},
|
||||
{
|
||||
UID: "sqlite-1",
|
||||
Type: "sqlite-datasource",
|
||||
Name: "SQLite Grafana",
|
||||
IsDefault: false,
|
||||
},
|
||||
{
|
||||
UID: "sqlite-2",
|
||||
Type: "sqlite-datasource",
|
||||
Name: "SQLite Grafana2",
|
||||
IsDefault: false,
|
||||
},
|
||||
{
|
||||
UID: "default.uid",
|
||||
Type: "default.type",
|
||||
Name: "default.name",
|
||||
IsDefault: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadDashboard(t *testing.T) {
|
||||
@ -80,7 +89,7 @@ func TestReadDashboard(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
dash, err := ReadDashboard(f, &dsLookup{})
|
||||
dash, err := ReadDashboard(f, dsLookup())
|
||||
sortDatasources(dash)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
@ -2,22 +2,24 @@ package extract
|
||||
|
||||
import (
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
)
|
||||
|
||||
type targetInfo struct {
|
||||
lookup DatasourceLookup
|
||||
uids map[string]*DataSourceRef
|
||||
lookup dslookup.DatasourceLookup
|
||||
uids map[string]*dslookup.DataSourceRef
|
||||
}
|
||||
|
||||
func newTargetInfo(lookup DatasourceLookup) targetInfo {
|
||||
func newTargetInfo(lookup dslookup.DatasourceLookup) targetInfo {
|
||||
return targetInfo{
|
||||
lookup: lookup,
|
||||
uids: make(map[string]*DataSourceRef),
|
||||
uids: make(map[string]*dslookup.DataSourceRef),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *targetInfo) GetDatasourceInfo() []DataSourceRef {
|
||||
keys := make([]DataSourceRef, len(s.uids))
|
||||
func (s *targetInfo) GetDatasourceInfo() []dslookup.DataSourceRef {
|
||||
keys := make([]dslookup.DataSourceRef, len(s.uids))
|
||||
i := 0
|
||||
for _, v := range s.uids {
|
||||
keys[i] = *v
|
||||
@ -31,18 +33,28 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.StringValue:
|
||||
key := iter.ReadString()
|
||||
ds := s.lookup.ByRef(&DataSourceRef{UID: key})
|
||||
s.addRef(ds)
|
||||
|
||||
dsRef := &dslookup.DataSourceRef{UID: key}
|
||||
if !isVariableRef(dsRef.UID) && !isSpecialDatasource(dsRef.UID) {
|
||||
ds := s.lookup.ByRef(dsRef)
|
||||
s.addRef(ds)
|
||||
} else {
|
||||
s.addRef(dsRef)
|
||||
}
|
||||
|
||||
case jsoniter.NilValue:
|
||||
s.addRef(s.lookup.ByRef(nil))
|
||||
iter.Skip()
|
||||
|
||||
case jsoniter.ObjectValue:
|
||||
ref := &DataSourceRef{}
|
||||
ref := &dslookup.DataSourceRef{}
|
||||
iter.ReadVal(ref)
|
||||
ds := s.lookup.ByRef(ref)
|
||||
s.addRef(ds)
|
||||
|
||||
if !isVariableRef(ref.UID) && !isSpecialDatasource(ref.UID) {
|
||||
s.addRef(s.lookup.ByRef(ref))
|
||||
} else {
|
||||
s.addRef(ref)
|
||||
}
|
||||
|
||||
default:
|
||||
v := iter.Read()
|
||||
@ -50,7 +62,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *targetInfo) addRef(ref *DataSourceRef) {
|
||||
func (s *targetInfo) addRef(ref *dslookup.DataSourceRef) {
|
||||
if ref != nil && ref.UID != "" {
|
||||
s.uids[ref.UID] = ref
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
"tags": null,
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "-- Grafana --"
|
||||
"uid": "grafana",
|
||||
"type": "datasource"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
@ -15,7 +16,8 @@
|
||||
"pluginVersion": "7.5.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "-- Grafana --"
|
||||
"uid": "grafana",
|
||||
"type": "datasource"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
],
|
||||
@ -19,7 +19,7 @@
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
]
|
||||
|
@ -7,7 +7,7 @@
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
],
|
||||
@ -19,7 +19,7 @@
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
]
|
||||
|
@ -6,14 +6,14 @@
|
||||
"dsVariable"
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "gdev-testdata",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
},
|
||||
{
|
||||
"uid": "PD8C576611E62080A",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "P8045C56BDA891CB2",
|
||||
"type": "cloudwatch"
|
||||
@ -26,14 +26,14 @@
|
||||
"type": "table",
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "gdev-testdata",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
},
|
||||
{
|
||||
"uid": "PD8C576611E62080A",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "P8045C56BDA891CB2",
|
||||
"type": "cloudwatch"
|
||||
|
@ -7,11 +7,11 @@
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana2",
|
||||
"uid": "sqlite-2",
|
||||
"type": "sqlite-datasource"
|
||||
},
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
],
|
||||
@ -23,11 +23,11 @@
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana2",
|
||||
"uid": "sqlite-2",
|
||||
"type": "sqlite-datasource"
|
||||
},
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
]
|
||||
|
@ -6,13 +6,13 @@
|
||||
"dsVariable"
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "gdev-testdata",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
},
|
||||
{
|
||||
"uid": "PD8C576611E62080A",
|
||||
"type": "testdata"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
@ -22,13 +22,13 @@
|
||||
"type": "table",
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "gdev-testdata",
|
||||
"type": "testdata"
|
||||
},
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
},
|
||||
{
|
||||
"uid": "PD8C576611E62080A",
|
||||
"type": "testdata"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
],
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
],
|
||||
@ -19,7 +19,7 @@
|
||||
"pluginVersion": "9.1.0-pre",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "SQLite Grafana",
|
||||
"uid": "sqlite-1",
|
||||
"type": "sqlite-datasource"
|
||||
}
|
||||
]
|
||||
|
@ -1,43 +1,36 @@
|
||||
package extract
|
||||
|
||||
type DatasourceLookup interface {
|
||||
// ByRef will return the default DS given empty reference (nil ref, or empty ref.uid and ref.type)
|
||||
ByRef(ref *DataSourceRef) *DataSourceRef
|
||||
ByType(dsType string) []DataSourceRef
|
||||
}
|
||||
|
||||
type DataSourceRef struct {
|
||||
UID string `json:"uid,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
)
|
||||
|
||||
type PanelInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Type string `json:"type,omitempty"` // PluginID
|
||||
PluginVersion string `json:"pluginVersion,omitempty"`
|
||||
Datasource []DataSourceRef `json:"datasource,omitempty"` // UIDs
|
||||
Transformer []string `json:"transformer,omitempty"` // ids of the transformation steps
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Type string `json:"type,omitempty"` // PluginID
|
||||
PluginVersion string `json:"pluginVersion,omitempty"`
|
||||
Datasource []dslookup.DataSourceRef `json:"datasource,omitempty"` // UIDs
|
||||
Transformer []string `json:"transformer,omitempty"` // ids of the transformation steps
|
||||
|
||||
// Rows define panels as sub objects
|
||||
Collapsed []PanelInfo `json:"collapsed,omitempty"`
|
||||
}
|
||||
|
||||
type DashboardInfo struct {
|
||||
UID string `json:"uid,omitempty"`
|
||||
ID int64 `json:"id,omitempty"` // internal ID
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
TemplateVars []string `json:"templateVars,omitempty"` // the keys used
|
||||
Datasource []DataSourceRef `json:"datasource,omitempty"` // UIDs
|
||||
Panels []PanelInfo `json:"panels"` // nesed documents
|
||||
SchemaVersion int64 `json:"schemaVersion"`
|
||||
LinkCount int64 `json:"linkCount"`
|
||||
TimeFrom string `json:"timeFrom"`
|
||||
TimeTo string `json:"timeTo"`
|
||||
TimeZone string `json:"timezone"`
|
||||
Refresh string `json:"refresh,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"` // editable = false
|
||||
UID string `json:"uid,omitempty"`
|
||||
ID int64 `json:"id,omitempty"` // internal ID
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
TemplateVars []string `json:"templateVars,omitempty"` // the keys used
|
||||
Datasource []dslookup.DataSourceRef `json:"datasource,omitempty"` // UIDs
|
||||
Panels []PanelInfo `json:"panels"` // nesed documents
|
||||
SchemaVersion int64 `json:"schemaVersion"`
|
||||
LinkCount int64 `json:"linkCount"`
|
||||
TimeFrom string `json:"timeFrom"`
|
||||
TimeTo string `json:"timeTo"`
|
||||
TimeZone string `json:"timezone"`
|
||||
Refresh string `json:"refresh,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"` // editable = false
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/dslookup"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2/extract"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
@ -725,7 +726,7 @@ func (l sqlDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, das
|
||||
}
|
||||
|
||||
// key will allow name or uid
|
||||
lookup, err := LoadDatasourceLookup(ctx, orgID, l.sql)
|
||||
lookup, err := dslookup.LoadDatasourceLookup(ctx, orgID, l.sql)
|
||||
if err != nil {
|
||||
return dashboards, err
|
||||
}
|
||||
@ -813,109 +814,3 @@ type dashboardQueryResult struct {
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type datasourceQueryResult struct {
|
||||
UID string `xorm:"uid"`
|
||||
Type string `xorm:"type"`
|
||||
Name string `xorm:"name"`
|
||||
IsDefault bool `xorm:"is_default"`
|
||||
}
|
||||
|
||||
func createDatasourceLookup(rows []*datasourceQueryResult) extract.DatasourceLookup {
|
||||
byUID := make(map[string]*extract.DataSourceRef, 50)
|
||||
byName := make(map[string]*extract.DataSourceRef, 50)
|
||||
byType := make(map[string][]extract.DataSourceRef, 50)
|
||||
var defaultDS *extract.DataSourceRef
|
||||
|
||||
for _, row := range rows {
|
||||
ref := &extract.DataSourceRef{
|
||||
UID: row.UID,
|
||||
Type: row.Type,
|
||||
}
|
||||
byUID[row.UID] = ref
|
||||
byName[row.Name] = ref
|
||||
if row.IsDefault {
|
||||
defaultDS = ref
|
||||
}
|
||||
|
||||
if _, ok := byType[row.Type]; !ok {
|
||||
byType[row.Type] = make([]extract.DataSourceRef, 5)
|
||||
}
|
||||
byType[row.Type] = append(byType[row.Type], *ref)
|
||||
}
|
||||
|
||||
if defaultDS == nil {
|
||||
// fallback replicated from /pkg/api/frontendsettings.go
|
||||
// https://github.com/grafana/grafana/blob/7ef21662f9ad74b80d832b9f2aa9db2fb4192741/pkg/api/frontendsettings.go#L51-L56
|
||||
defaultDS = &extract.DataSourceRef{
|
||||
UID: "grafana",
|
||||
Type: "datasource",
|
||||
}
|
||||
}
|
||||
|
||||
return &dsLookup{
|
||||
byName: byName,
|
||||
byUID: byUID,
|
||||
byType: byType,
|
||||
defaultDS: defaultDS,
|
||||
}
|
||||
}
|
||||
|
||||
type dsLookup struct {
|
||||
byName map[string]*extract.DataSourceRef
|
||||
byUID map[string]*extract.DataSourceRef
|
||||
byType map[string][]extract.DataSourceRef
|
||||
defaultDS *extract.DataSourceRef
|
||||
}
|
||||
|
||||
func (d *dsLookup) ByRef(ref *extract.DataSourceRef) *extract.DataSourceRef {
|
||||
if ref == nil {
|
||||
return d.defaultDS
|
||||
}
|
||||
key := ""
|
||||
if ref.UID != "" {
|
||||
ds, ok := d.byUID[ref.UID]
|
||||
if ok {
|
||||
return ds
|
||||
}
|
||||
key = ref.UID
|
||||
}
|
||||
if key == "" {
|
||||
return d.defaultDS
|
||||
}
|
||||
ds, ok := d.byUID[key]
|
||||
if ok {
|
||||
return ds
|
||||
}
|
||||
return d.byName[key]
|
||||
}
|
||||
|
||||
func (d *dsLookup) ByType(dsType string) []extract.DataSourceRef {
|
||||
ds, ok := d.byType[dsType]
|
||||
if !ok {
|
||||
return make([]extract.DataSourceRef, 0)
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
func LoadDatasourceLookup(ctx context.Context, orgID int64, sql *sqlstore.SQLStore) (extract.DatasourceLookup, error) {
|
||||
rows := make([]*datasourceQueryResult, 0)
|
||||
|
||||
if err := sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
sess.Table("data_source").
|
||||
Where("org_id = ?", orgID).
|
||||
Cols("uid", "name", "type", "is_default")
|
||||
|
||||
err := sess.Find(&rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createDatasourceLookup(rows), nil
|
||||
}
|
||||
|
70
pkg/services/searchV2/search_service_mock.go
Normal file
70
pkg/services/searchV2/search_service_mock.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Code generated by mockery v2.10.6. DO NOT EDIT.
|
||||
|
||||
package searchV2
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockSearchService is an autogenerated mock type for the SearchService type
|
||||
type MockSearchService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// DoDashboardQuery provides a mock function with given fields: ctx, user, orgId, query
|
||||
func (_m *MockSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse {
|
||||
ret := _m.Called(ctx, user, orgId, query)
|
||||
|
||||
var r0 *backend.DataResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *backend.User, int64, DashboardQuery) *backend.DataResponse); ok {
|
||||
r0 = rf(ctx, user, orgId, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*backend.DataResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// IsDisabled provides a mock function with given fields:
|
||||
func (_m *MockSearchService) IsDisabled() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RegisterDashboardIndexExtender provides a mock function with given fields: ext
|
||||
func (_m *MockSearchService) RegisterDashboardIndexExtender(ext DashboardIndexExtender) {
|
||||
_m.Called(ext)
|
||||
}
|
||||
|
||||
// Run provides a mock function with given fields: ctx
|
||||
func (_m *MockSearchService) Run(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// TriggerReIndex provides a mock function with given fields:
|
||||
func (_m *MockSearchService) TriggerReIndex() {
|
||||
_m.Called()
|
||||
}
|
@ -112,7 +112,7 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen
|
||||
}
|
||||
err := s.sql.GetSignedInUser(ctx, getSignedInUserQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email)
|
||||
s.logger.Error("Error while retrieving user", "error", err, "email", backendUser.Email, "login", getSignedInUserQuery.Login)
|
||||
return nil, errors.New("auth error")
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,10 @@ import (
|
||||
type stubSearchService struct {
|
||||
}
|
||||
|
||||
func (s *stubSearchService) IsDisabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *stubSearchService) TriggerReIndex() {
|
||||
// noop.
|
||||
}
|
||||
|
@ -19,9 +19,9 @@
|
||||
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []json.RawMessage |
|
||||
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders.permissions:read","folders.permissions:write","folders:create","folders:delete","folders:read","folders:write"]}] |
|
||||
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | [] | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders.permissions:read","folders.permissions:write","folders:create","folders:delete","folders:read","folders:write"]}] |
|
||||
// | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards.permissions:read","dashboards.permissions:write","dashboards:create","dashboards:delete","dashboards:read","dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]}] |
|
||||
// | | | | | | "gdev", | "datasource-1" | | |
|
||||
// | | | | | | "alerting" | ] | | |
|
||||
@ -115,8 +115,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -183,7 +182,7 @@
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
[],
|
||||
[
|
||||
"datasource-1"
|
||||
],
|
||||
|
@ -19,9 +19,9 @@
|
||||
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []json.RawMessage |
|
||||
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders:read"]}] |
|
||||
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | [] | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders:read"]}] |
|
||||
// | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":[]}] |
|
||||
// | | | | | | "gdev", | "datasource-1" | | |
|
||||
// | | | | | | "alerting" | ] | | |
|
||||
@ -115,8 +115,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -183,7 +182,7 @@
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
[],
|
||||
[
|
||||
"datasource-1"
|
||||
],
|
||||
|
@ -8,12 +8,12 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 0 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -84,8 +84,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | boom | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | boom | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 3 | created | | /pfix/d/3/ | null | null | general |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 3 | created | | /pfix/d/3/ | null | [] | general |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
"general"
|
||||
|
@ -8,12 +8,12 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 0 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -84,8 +84,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | nginx | | /pfix/d/2/ | null | null | general |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | nginx | | /pfix/d/2/ | null | [] | general |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
"general"
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 4 | One more dash | | /pfix/d/4/ | null | null | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 4 | One more dash | | /pfix/d/4/ | null | [] | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+
|
||||
// | folder | 1 | My folder | | /pfix/dashboards/f/1/ | null | null | |
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+
|
||||
// | folder | 1 | My folder | | /pfix/dashboards/f/1/ | null | [] | |
|
||||
// +----------------+----------------+----------------+------------------+-----------------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | heatTorkel | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | heatTorkel | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,14 +8,14 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 2 Rows
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | null | |
|
||||
// | dashboard | 2 | topology heatmap | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | heat-torkel | | /pfix/d/1/ | null | [] | |
|
||||
// | dashboard | 2 | topology heatmap | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -86,8 +86,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -126,8 +125,8 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
"",
|
||||
|
@ -8,12 +8,12 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 0 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -84,8 +84,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Archer Data System | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | Document Sync repo | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+--------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,13 +8,13 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 1 Rows
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Eyjafjallajökull Eruption data | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Eyjafjallajökull Eruption data | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+--------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -85,8 +85,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -119,7 +118,7 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null
|
||||
[]
|
||||
],
|
||||
[
|
||||
""
|
||||
|
@ -8,14 +8,14 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 2 Rows
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | null | |
|
||||
// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | null | |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | [] | |
|
||||
// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | [] | |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -86,8 +86,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -126,8 +125,8 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
"",
|
||||
|
@ -8,14 +8,14 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 8 Fields by 2 Rows
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | null | |
|
||||
// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | null | |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+--------------------------+----------------+
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
// | dashboard | 1 | Three can keep a secret, if two of them are dead (Benjamin Franklin) | | /pfix/d/1/ | null | [] | |
|
||||
// | dashboard | 2 | A secret is powerful when it is empty (Umberto Eco) | | /pfix/d/2/ | null | [] | |
|
||||
// +----------------+----------------+----------------------------------------------------------------------+------------------+----------------+--------------------------+-------------------------+----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -86,8 +86,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -126,8 +125,8 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
"",
|
||||
|
@ -73,7 +73,7 @@
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -133,7 +133,7 @@
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
[],
|
||||
[
|
||||
"datasource-1"
|
||||
],
|
||||
|
23
pkg/services/searchV2/testdata/sort-asc.jsonc
vendored
23
pkg/services/searchV2/testdata/sort-asc.jsonc
vendored
@ -9,14 +9,14 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 9 Fields by 2 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// | dashboard | 1 | a-test | | /pfix/d/1/ | null | null | | 0 |
|
||||
// | dashboard | 2 | z-test | | /pfix/d/2/ | null | null | | 1 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []float64 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
// | dashboard | 1 | a-test | | /pfix/d/1/ | null | [] | | 0 |
|
||||
// | dashboard | 2 | z-test | | /pfix/d/2/ | null | [] | | 1 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -88,8 +88,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -135,8 +134,8 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
"",
|
||||
|
23
pkg/services/searchV2/testdata/sort-desc.jsonc
vendored
23
pkg/services/searchV2/testdata/sort-desc.jsonc
vendored
@ -9,14 +9,14 @@
|
||||
// }
|
||||
// Name: Query results
|
||||
// Dimensions: 9 Fields by 2 Rows
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// | dashboard | 2 | z-test | | /pfix/d/2/ | null | null | | 3 |
|
||||
// | dashboard | 1 | a-test | | /pfix/d/1/ | null | null | | 2 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []json.RawMessage | Type: []string | Type: []float64 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
// | dashboard | 2 | z-test | | /pfix/d/2/ | null | [] | | 3 |
|
||||
// | dashboard | 1 | a-test | | /pfix/d/1/ | null | [] | | 2 |
|
||||
// +----------------+----------------+----------------+------------------+----------------+--------------------------+-------------------------+----------------+-----------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
@ -88,8 +88,7 @@
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -135,8 +134,8 @@
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
"",
|
||||
|
@ -31,7 +31,9 @@ type DashboardQuery struct {
|
||||
From int `json:"from,omitempty"` // for paging
|
||||
}
|
||||
|
||||
//go:generate mockery --name SearchService --structname MockSearchService --inpackage --filename search_service_mock.go
|
||||
type SearchService interface {
|
||||
registry.CanBeDisabled
|
||||
registry.BackgroundService
|
||||
DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse
|
||||
RegisterDashboardIndexExtender(ext DashboardIndexExtender)
|
||||
|
@ -2,6 +2,7 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@ -22,6 +23,18 @@ func (ss *SQLStore) GetThumbnail(ctx context.Context, query *models.GetDashboard
|
||||
return query.Result, err
|
||||
}
|
||||
|
||||
func marshalDatasourceUids(dsUids []string) (string, error) {
|
||||
if dsUids == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
b, err := json.Marshal(dsUids)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboardThumbnailCommand) (*models.DashboardThumbnail, error) {
|
||||
err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
|
||||
existing, err := findThumbnailByMeta(sess, cmd.DashboardThumbnailMeta)
|
||||
@ -30,11 +43,17 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard
|
||||
return err
|
||||
}
|
||||
|
||||
dsUids, err := marshalDatasourceUids(cmd.DatasourceUIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
existing.Image = cmd.Image
|
||||
existing.MimeType = cmd.MimeType
|
||||
existing.Updated = time.Now()
|
||||
existing.DashboardVersion = cmd.DashboardVersion
|
||||
existing.DsUIDs = dsUids
|
||||
existing.State = models.ThumbnailStateDefault
|
||||
_, err = sess.ID(existing.Id).Update(existing)
|
||||
cmd.Result = existing
|
||||
@ -53,6 +72,7 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard
|
||||
thumb.Theme = cmd.Theme
|
||||
thumb.Kind = cmd.Kind
|
||||
thumb.Image = cmd.Image
|
||||
thumb.DsUIDs = dsUids
|
||||
thumb.MimeType = cmd.MimeType
|
||||
thumb.DashboardId = dash.Id
|
||||
thumb.DashboardVersion = cmd.DashboardVersion
|
||||
@ -101,9 +121,17 @@ func (ss *SQLStore) FindDashboardsWithStaleThumbnails(ctx context.Context, cmd *
|
||||
sess.Table("dashboard")
|
||||
sess.Join("LEFT", "dashboard_thumbnail", "dashboard.id = dashboard_thumbnail.dashboard_id AND dashboard_thumbnail.theme = ? AND dashboard_thumbnail.kind = ?", cmd.Theme, cmd.Kind)
|
||||
sess.Where("dashboard.is_folder = ?", dialect.BooleanStr(false))
|
||||
sess.Where("(dashboard.version != dashboard_thumbnail.dashboard_version "+
|
||||
"OR dashboard_thumbnail.state = ? "+
|
||||
"OR dashboard_thumbnail.id IS NULL)", models.ThumbnailStateStale)
|
||||
|
||||
query := "(dashboard.version != dashboard_thumbnail.dashboard_version " +
|
||||
"OR dashboard_thumbnail.state = ? " +
|
||||
"OR dashboard_thumbnail.id IS NULL"
|
||||
args := []interface{}{models.ThumbnailStateStale}
|
||||
|
||||
if cmd.IncludeThumbnailsWithEmptyDsUIDs {
|
||||
query += " OR dashboard_thumbnail.ds_uids = ?"
|
||||
args = append(args, "")
|
||||
}
|
||||
sess.Where(query+")", args...)
|
||||
|
||||
if !cmd.IncludeManuallyUploadedThumbnails {
|
||||
sess.Where("(dashboard_thumbnail.id is not null AND dashboard_thumbnail.dashboard_version != ?) "+
|
||||
@ -119,13 +147,13 @@ func (ss *SQLStore) FindDashboardsWithStaleThumbnails(ctx context.Context, cmd *
|
||||
"dashboard.version",
|
||||
"dashboard.slug")
|
||||
|
||||
var dashboards = make([]*models.DashboardWithStaleThumbnail, 0)
|
||||
err := sess.Find(&dashboards)
|
||||
var result = make([]*models.DashboardWithStaleThumbnail, 0)
|
||||
err := sess.Find(&result)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Result = dashboards
|
||||
cmd.Result = result
|
||||
return err
|
||||
})
|
||||
|
||||
@ -145,6 +173,7 @@ func findThumbnailByMeta(sess *DBSession, meta models.DashboardThumbnailMeta) (*
|
||||
"dashboard_thumbnail.dashboard_version",
|
||||
"dashboard_thumbnail.state",
|
||||
"dashboard_thumbnail.kind",
|
||||
"dashboard_thumbnail.ds_uids",
|
||||
"dashboard_thumbnail.mime_type",
|
||||
"dashboard_thumbnail.theme",
|
||||
"dashboard_thumbnail.updated")
|
||||
|
@ -69,6 +69,23 @@ func TestIntegrationSqlStorage(t *testing.T) {
|
||||
require.Len(t, res, 0)
|
||||
})
|
||||
|
||||
t.Run("Should return dashboards with thumbnails with empty ds_uids array", func(t *testing.T) {
|
||||
setup()
|
||||
dash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
|
||||
upsertTestDashboardThumbnail(t, sqlStore, dash.Uid, dash.OrgId, dash.Version)
|
||||
|
||||
cmd := models.FindDashboardsWithStaleThumbnailsCommand{
|
||||
Kind: kind,
|
||||
IncludeThumbnailsWithEmptyDsUIDs: true,
|
||||
Theme: theme,
|
||||
}
|
||||
res, err := sqlStore.FindDashboardsWithStaleThumbnails(context.Background(), &cmd)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, dash.Id, res[0].Id)
|
||||
})
|
||||
|
||||
t.Run("Should return dashboards with thumbnails marked as stale", func(t *testing.T) {
|
||||
setup()
|
||||
dash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
|
@ -24,4 +24,8 @@ func addDashboardThumbsMigrations(mg *migrator.Migrator) {
|
||||
|
||||
mg.AddMigration("create dashboard_thumbnail table", migrator.NewAddTableMigration(dashThumbs))
|
||||
mg.AddMigration("add unique indexes for dashboard_thumbnail", migrator.NewAddIndexMigration(dashThumbs, dashThumbs.Indices[0]))
|
||||
mg.AddMigration("Add ds_uids column to dashboard_thumbnail table", migrator.NewAddColumnMigration(dashThumbs,
|
||||
// uids of datasources used in the dashboard when taking preview
|
||||
&migrator.Column{Name: "ds_uids", Type: migrator.DB_Text, Nullable: false, Default: ""},
|
||||
))
|
||||
}
|
||||
|
@ -37,9 +37,10 @@ type simpleCrawler struct {
|
||||
queueMutex sync.Mutex
|
||||
log log.Logger
|
||||
renderingSessionByOrgId map[int64]rendering.Session
|
||||
dsUidsLookup getDatasourceUidsForDashboard
|
||||
}
|
||||
|
||||
func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo, cfg *setting.Cfg, settings setting.DashboardPreviewsSettings) dashRenderer {
|
||||
func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo, cfg *setting.Cfg, settings setting.DashboardPreviewsSettings, dsUidsLookup getDatasourceUidsForDashboard) dashRenderer {
|
||||
threadCount := int(settings.CrawlThreadCount)
|
||||
c := &simpleCrawler{
|
||||
// temporarily increases the concurrentLimit from the 'cfg.RendererConcurrentRequestLimit' to 'cfg.RendererConcurrentRequestLimit + crawlerThreadCount'
|
||||
@ -48,6 +49,7 @@ func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, rep
|
||||
renderService: renderService,
|
||||
threadCount: threadCount,
|
||||
glive: gl,
|
||||
dsUidsLookup: dsUidsLookup,
|
||||
thumbnailRepo: repo,
|
||||
log: log.New("thumbnails_crawler"),
|
||||
status: crawlStatus{
|
||||
@ -287,6 +289,13 @@ func (r *simpleCrawler) walk(ctx context.Context, id int) {
|
||||
url := models.GetKioskModeDashboardUrl(item.Uid, item.Slug, r.opts.Theme)
|
||||
r.log.Info("Getting dashboard thumbnail", "walkerId", id, "dashboardUID", item.Uid, "url", url)
|
||||
|
||||
dsUids, err := r.dsUidsLookup(ctx, item.Uid, item.OrgId)
|
||||
if err != nil {
|
||||
r.log.Warn("Error getting datasource uids", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err)
|
||||
r.newErrorResult()
|
||||
continue
|
||||
}
|
||||
|
||||
res, err := r.renderService.Render(ctx, rendering.Opts{
|
||||
Width: 320,
|
||||
Height: 240,
|
||||
@ -321,7 +330,7 @@ func (r *simpleCrawler) walk(ctx context.Context, id int) {
|
||||
OrgId: item.OrgId,
|
||||
Theme: r.opts.Theme,
|
||||
Kind: r.thumbnailKind,
|
||||
}, item.Version)
|
||||
}, item.Version, dsUids)
|
||||
|
||||
if err != nil {
|
||||
r.log.Warn("Error saving image image", "walkerId", id, "dashboardUID", item.Uid, "url", url, "err", err, "itemTime", time.Since(itemStarted))
|
||||
|
@ -2,40 +2,137 @@ package thumbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type CrawlerAuthSetupService interface {
|
||||
Setup(ctx context.Context) (CrawlerAuth, error)
|
||||
}
|
||||
|
||||
func ProvideCrawlerAuthSetupService() *OSSCrawlerAuthSetupService {
|
||||
return &OSSCrawlerAuthSetupService{}
|
||||
func ProvideCrawlerAuthSetupService(serviceAccounts serviceaccounts.Service, serviceAccountsStore serviceaccounts.Store, sqlStore *sqlstore.SQLStore) *OSSCrawlerAuthSetupService {
|
||||
return &OSSCrawlerAuthSetupService{
|
||||
serviceAccountNamePrefix: "dashboard-previews-crawler-org-",
|
||||
serviceAccounts: serviceAccounts,
|
||||
log: log.New("oss_crawler_account_setup_service"),
|
||||
sqlStore: sqlStore,
|
||||
serviceAccountsStore: serviceAccountsStore,
|
||||
}
|
||||
}
|
||||
|
||||
type OSSCrawlerAuthSetupService struct{}
|
||||
type OSSCrawlerAuthSetupService struct {
|
||||
log log.Logger
|
||||
serviceAccountNamePrefix string
|
||||
serviceAccounts serviceaccounts.Service
|
||||
serviceAccountsStore serviceaccounts.Store
|
||||
sqlStore *sqlstore.SQLStore
|
||||
}
|
||||
|
||||
type CrawlerAuth interface {
|
||||
GetUserId(orgId int64) int64
|
||||
GetLogin(orgId int64) string
|
||||
GetOrgRole() models.RoleType
|
||||
}
|
||||
|
||||
type staticCrawlerAuth struct {
|
||||
userId int64
|
||||
orgRole models.RoleType
|
||||
func (o *OSSCrawlerAuthSetupService) findAllOrgIds(ctx context.Context) ([]int64, error) {
|
||||
searchAllOrgsQuery := &models.SearchOrgsQuery{}
|
||||
if err := o.sqlStore.SearchOrgs(ctx, searchAllOrgsQuery); err != nil {
|
||||
o.log.Error("Error when searching for orgs", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgIds := make([]int64, 0)
|
||||
for i := range searchAllOrgsQuery.Result {
|
||||
orgIds = append(orgIds, searchAllOrgsQuery.Result[i].Id)
|
||||
}
|
||||
|
||||
return orgIds, nil
|
||||
}
|
||||
|
||||
func (o *staticCrawlerAuth) GetOrgRole() models.RoleType {
|
||||
type crawlerAuth struct {
|
||||
accountIdByOrgId map[int64]int64
|
||||
loginByOrgId map[int64]string
|
||||
orgRole models.RoleType
|
||||
}
|
||||
|
||||
func (o *crawlerAuth) GetOrgRole() models.RoleType {
|
||||
return o.orgRole
|
||||
}
|
||||
|
||||
func (o *staticCrawlerAuth) GetUserId(orgId int64) int64 {
|
||||
return o.userId
|
||||
func (o *crawlerAuth) GetUserId(orgId int64) int64 {
|
||||
return o.accountIdByOrgId[orgId]
|
||||
}
|
||||
|
||||
func (o *crawlerAuth) GetLogin(orgId int64) string {
|
||||
return o.loginByOrgId[orgId]
|
||||
}
|
||||
|
||||
func (o *OSSCrawlerAuthSetupService) Setup(ctx context.Context) (CrawlerAuth, error) {
|
||||
orgIds, err := o.findAllOrgIds(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// userId:0 and ROLE_ADMIN grants the crawler process permissions to view all dashboards in all folders & orgs
|
||||
// the process doesn't and shouldn't actually need to edit/modify any resources from the UI
|
||||
return &staticCrawlerAuth{userId: 0, orgRole: models.ROLE_ADMIN}, nil
|
||||
orgRole := models.ROLE_ADMIN
|
||||
|
||||
accountIdByOrgId := make(map[int64]int64)
|
||||
loginByOrgId := make(map[int64]string)
|
||||
for _, orgId := range orgIds {
|
||||
o.log.Info("Creating account for org", "orgId", orgId)
|
||||
|
||||
serviceAccountNameOrg := o.serviceAccountNamePrefix + strconv.FormatInt(orgId, 10)
|
||||
|
||||
saForm := serviceaccounts.CreateServiceAccountForm{
|
||||
Name: serviceAccountNameOrg,
|
||||
Role: &orgRole,
|
||||
}
|
||||
|
||||
serviceAccount, err := o.serviceAccounts.CreateServiceAccount(ctx, orgId, &saForm)
|
||||
accountAlreadyExists := errors.Is(err, database.ErrServiceAccountAlreadyExists)
|
||||
|
||||
if !accountAlreadyExists && err != nil {
|
||||
o.log.Error("Failed to create the service account", "err", err, "accountName", serviceAccountNameOrg, "orgId", orgId)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serviceAccountLogin string
|
||||
var serviceAccountId int64
|
||||
if accountAlreadyExists {
|
||||
id, err := o.serviceAccounts.RetrieveServiceAccountIdByName(ctx, orgId, serviceAccountNameOrg)
|
||||
if err != nil {
|
||||
o.log.Error("Failed to retrieve service account", "err", err, "accountName", serviceAccountNameOrg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update org_role to make sure everything works properly if someone has changed the role since SA's original creation
|
||||
dto, err := o.serviceAccountsStore.UpdateServiceAccount(ctx, orgId, id, &serviceaccounts.UpdateServiceAccountForm{
|
||||
Name: &serviceAccountNameOrg,
|
||||
Role: &orgRole,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
o.log.Error("Failed to update service account's role", "err", err, "accountName", serviceAccountNameOrg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceAccountLogin = dto.Login
|
||||
serviceAccountId = id
|
||||
} else {
|
||||
serviceAccountLogin = serviceAccount.Login
|
||||
serviceAccountId = serviceAccount.Id
|
||||
}
|
||||
|
||||
accountIdByOrgId[orgId] = serviceAccountId
|
||||
loginByOrgId[orgId] = serviceAccountLogin
|
||||
}
|
||||
|
||||
return &crawlerAuth{accountIdByOrgId: accountIdByOrgId, loginByOrgId: loginByOrgId, orgRole: orgRole}, nil
|
||||
}
|
||||
|
91
pkg/services/thumbs/datasources_lookup.go
Normal file
91
pkg/services/thumbs/datasources_lookup.go
Normal file
@ -0,0 +1,91 @@
|
||||
package thumbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
)
|
||||
|
||||
type getDatasourceUidsForDashboard func(ctx context.Context, dashboardUid string, orgId int64) ([]string, error)
|
||||
|
||||
type dsUidsLookup struct {
|
||||
searchService searchV2.SearchService
|
||||
crawlerAuth CrawlerAuth
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func getDatasourceUIDs(resp *backend.DataResponse, uid string) ([]string, error) {
|
||||
if resp == nil {
|
||||
return nil, errors.New("nil response")
|
||||
}
|
||||
|
||||
if resp.Error != nil {
|
||||
return nil, resp.Error
|
||||
}
|
||||
|
||||
if len(resp.Frames) == 0 {
|
||||
return nil, errors.New("empty response")
|
||||
}
|
||||
|
||||
frame := resp.Frames[0]
|
||||
field, idx := frame.FieldByName("ds_uid")
|
||||
|
||||
if field.Len() == 0 || idx == -1 {
|
||||
return nil, fmt.Errorf("no ds_uid field for uid %s", uid)
|
||||
}
|
||||
|
||||
rawValue, ok := field.At(0).(json.RawMessage)
|
||||
if !ok || rawValue == nil {
|
||||
return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uid, field.At(0))
|
||||
}
|
||||
|
||||
jsonValue, err := rawValue.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var uids []string
|
||||
err = json.Unmarshal(jsonValue, &uids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
func filterOutGrafanaDs(uids []string) []string {
|
||||
var filtered []string
|
||||
for _, uid := range uids {
|
||||
if uid != grafanads.DatasourceUID {
|
||||
filtered = append(filtered, uid)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (d *dsUidsLookup) getDatasourceUidsForDashboard(ctx context.Context, dashboardUid string, orgId int64) ([]string, error) {
|
||||
if d.searchService.IsDisabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dashQueryResponse := d.searchService.DoDashboardQuery(ctx, &backend.User{
|
||||
Login: d.crawlerAuth.GetLogin(orgId),
|
||||
Role: string(d.crawlerAuth.GetOrgRole()),
|
||||
}, orgId, searchV2.DashboardQuery{
|
||||
UIDs: []string{dashboardUid},
|
||||
})
|
||||
|
||||
uids, err := getDatasourceUIDs(dashQueryResponse, dashboardUid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return filterOutGrafanaDs(uids), nil
|
||||
}
|
57
pkg/services/thumbs/datasources_lookup_test.go
Normal file
57
pkg/services/thumbs/datasources_lookup_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
package thumbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/search_response_frame.json
|
||||
exampleListFrameJSON string
|
||||
exampleListFrame = &data.Frame{}
|
||||
_ = exampleListFrame.UnmarshalJSON([]byte(exampleListFrameJSON))
|
||||
)
|
||||
|
||||
func TestShouldParseUidFromSearchResponseFrame(t *testing.T) {
|
||||
searchService := &searchV2.MockSearchService{}
|
||||
dsLookup := &dsUidsLookup{
|
||||
searchService: searchService,
|
||||
crawlerAuth: &crawlerAuth{},
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch),
|
||||
}
|
||||
|
||||
dashboardUid := "abc"
|
||||
searchService.On("IsDisabled").Return(false)
|
||||
searchService.On("DoDashboardQuery", mock.Anything, mock.Anything, mock.Anything, searchV2.DashboardQuery{
|
||||
UIDs: []string{dashboardUid},
|
||||
}).Return(&backend.DataResponse{
|
||||
Frames: []*data.Frame{exampleListFrame},
|
||||
})
|
||||
|
||||
uids, err := dsLookup.getDatasourceUidsForDashboard(context.Background(), dashboardUid, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"datasource-2", "datasource-3", "datasource-4"}, uids)
|
||||
}
|
||||
|
||||
func TestShouldReturnNullIfSearchServiceIsDisabled(t *testing.T) {
|
||||
searchService := &searchV2.MockSearchService{}
|
||||
dsLookup := &dsUidsLookup{
|
||||
searchService: searchService,
|
||||
crawlerAuth: &crawlerAuth{},
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch),
|
||||
}
|
||||
|
||||
dashboardUid := "abc"
|
||||
searchService.On("IsDisabled").Return(true)
|
||||
uids, err := dsLookup.getDatasourceUidsForDashboard(context.Background(), dashboardUid, 1)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, uids)
|
||||
}
|
@ -79,8 +79,8 @@ type dashRenderer interface {
|
||||
type thumbnailRepo interface {
|
||||
updateThumbnailState(ctx context.Context, state models.ThumbnailState, meta models.DashboardThumbnailMeta) error
|
||||
doThumbnailsExist(ctx context.Context) (bool, error)
|
||||
saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error)
|
||||
saveFromBytes(ctx context.Context, bytes []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error)
|
||||
saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error)
|
||||
saveFromBytes(ctx context.Context, bytes []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error)
|
||||
getThumbnail(ctx context.Context, meta models.DashboardThumbnailMeta) (*models.DashboardThumbnail, error)
|
||||
findDashboardsWithStaleThumbnails(ctx context.Context, theme models.Theme, thumbnailKind models.ThumbnailKind) ([]*models.DashboardWithStaleThumbnail, error)
|
||||
}
|
||||
|
@ -9,23 +9,26 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
func newThumbnailRepo(store *sqlstore.SQLStore) thumbnailRepo {
|
||||
func newThumbnailRepo(store *sqlstore.SQLStore, search searchV2.SearchService) thumbnailRepo {
|
||||
repo := &sqlThumbnailRepository{
|
||||
store: store,
|
||||
log: log.New("thumbnails_repo"),
|
||||
store: store,
|
||||
search: search,
|
||||
log: log.New("thumbnails_repo"),
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
type sqlThumbnailRepository struct {
|
||||
store *sqlstore.SQLStore
|
||||
log log.Logger
|
||||
store *sqlstore.SQLStore
|
||||
search searchV2.SearchService
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) {
|
||||
func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) {
|
||||
// the filePath variable is never set by the user. it refers to a temporary file created either in
|
||||
// 1. thumbs/service.go, when user uploads a thumbnail
|
||||
// 2. the rendering service, when image-renderer returns a screenshot
|
||||
@ -42,7 +45,7 @@ func (r *sqlThumbnailRepository) saveFromFile(ctx context.Context, filePath stri
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return r.saveFromBytes(ctx, content, getMimeType(filePath), meta, dashboardVersion)
|
||||
return r.saveFromBytes(ctx, content, getMimeType(filePath), meta, dashboardVersion, dsUids)
|
||||
}
|
||||
|
||||
func getMimeType(filePath string) string {
|
||||
@ -53,12 +56,13 @@ func getMimeType(filePath string) string {
|
||||
return "image/png"
|
||||
}
|
||||
|
||||
func (r *sqlThumbnailRepository) saveFromBytes(ctx context.Context, content []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int) (int64, error) {
|
||||
func (r *sqlThumbnailRepository) saveFromBytes(ctx context.Context, content []byte, mimeType string, meta models.DashboardThumbnailMeta, dashboardVersion int, dsUids []string) (int64, error) {
|
||||
cmd := &models.SaveDashboardThumbnailCommand{
|
||||
DashboardThumbnailMeta: meta,
|
||||
Image: content,
|
||||
MimeType: mimeType,
|
||||
DashboardVersion: dashboardVersion,
|
||||
DatasourceUIDs: dsUids,
|
||||
}
|
||||
|
||||
_, err := r.store.SaveThumbnail(ctx, cmd)
|
||||
@ -87,6 +91,7 @@ func (r *sqlThumbnailRepository) getThumbnail(ctx context.Context, meta models.D
|
||||
func (r *sqlThumbnailRepository) findDashboardsWithStaleThumbnails(ctx context.Context, theme models.Theme, kind models.ThumbnailKind) ([]*models.DashboardWithStaleThumbnail, error) {
|
||||
return r.store.FindDashboardsWithStaleThumbnails(ctx, &models.FindDashboardsWithStaleThumbnailsCommand{
|
||||
IncludeManuallyUploadedThumbnails: false,
|
||||
IncludeThumbnailsWithEmptyDsUIDs: !r.search.IsDisabled(),
|
||||
Theme: theme,
|
||||
Kind: kind,
|
||||
})
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources/permissions"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/segmentio/encoding/json"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@ -56,6 +58,10 @@ type thumbService struct {
|
||||
canRunCrawler bool
|
||||
settings setting.DashboardPreviewsSettings
|
||||
dashboardService dashboards.DashboardService
|
||||
dsUidsLookup getDatasourceUidsForDashboard
|
||||
dsPermissionsService permissions.DatasourcePermissionsService
|
||||
licensing models.Licensing
|
||||
searchService searchV2.SearchService
|
||||
}
|
||||
|
||||
type crawlerScheduleOptions struct {
|
||||
@ -71,13 +77,14 @@ type crawlerScheduleOptions struct {
|
||||
func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
lockService *serverlock.ServerLockService, renderService rendering.Service,
|
||||
gl *live.GrafanaLive, store *sqlstore.SQLStore, authSetupService CrawlerAuthSetupService,
|
||||
dashboardService dashboards.DashboardService) Service {
|
||||
dashboardService dashboards.DashboardService, searchService searchV2.SearchService,
|
||||
dsPermissionsService permissions.DatasourcePermissionsService, licensing models.Licensing) Service {
|
||||
if !features.IsEnabled(featuremgmt.FlagDashboardPreviews) {
|
||||
return &dummyService{}
|
||||
}
|
||||
logger := log.New("previews_service")
|
||||
|
||||
thumbnailRepo := newThumbnailRepo(store)
|
||||
thumbnailRepo := newThumbnailRepo(store, searchService)
|
||||
|
||||
canRunCrawler := true
|
||||
|
||||
@ -91,17 +98,27 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
logger.Info("Crawler auth setup complete", "crawlerAuthSetupTime", time.Since(authSetupStarted))
|
||||
}
|
||||
|
||||
dsUidsLookup := &dsUidsLookup{
|
||||
searchService: searchService,
|
||||
crawlerAuth: crawlerAuth,
|
||||
features: features,
|
||||
}
|
||||
|
||||
t := &thumbService{
|
||||
licensing: licensing,
|
||||
renderingService: renderService,
|
||||
renderer: newSimpleCrawler(renderService, gl, thumbnailRepo, cfg, cfg.DashboardPreviews),
|
||||
renderer: newSimpleCrawler(renderService, gl, thumbnailRepo, cfg, cfg.DashboardPreviews, dsUidsLookup.getDatasourceUidsForDashboard),
|
||||
thumbnailRepo: thumbnailRepo,
|
||||
store: store,
|
||||
features: features,
|
||||
lockService: lockService,
|
||||
crawlLockServiceActionName: "dashboard-crawler",
|
||||
searchService: searchService,
|
||||
log: logger,
|
||||
canRunCrawler: canRunCrawler,
|
||||
dsUidsLookup: dsUidsLookup.getDatasourceUidsForDashboard,
|
||||
settings: cfg.DashboardPreviews,
|
||||
dsPermissionsService: dsPermissionsService,
|
||||
scheduleOptions: crawlerScheduleOptions{
|
||||
tickerInterval: 5 * time.Minute,
|
||||
crawlInterval: cfg.DashboardPreviews.SchedulerInterval,
|
||||
@ -233,6 +250,10 @@ func (hs *thumbService) GetImage(c *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !hs.hasAccessToPreview(c, res, req) {
|
||||
return
|
||||
}
|
||||
|
||||
currentEtag := fmt.Sprintf("%d", res.Updated.Unix())
|
||||
c.Resp.Header().Set("ETag", currentEtag)
|
||||
|
||||
@ -248,6 +269,50 @@ func (hs *thumbService) GetImage(c *models.ReqContext) {
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *thumbService) hasAccessToPreview(c *models.ReqContext, res *models.DashboardThumbnail, req *previewRequest) bool {
|
||||
if !hs.licensing.FeatureEnabled("accesscontrol.enforcement") {
|
||||
return true
|
||||
}
|
||||
|
||||
if hs.searchService.IsDisabled() {
|
||||
c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"})
|
||||
return false
|
||||
}
|
||||
|
||||
if res.DsUIDs == "" {
|
||||
hs.log.Debug("dashboard preview is stale; no datasource uids", "dashboardUid", req.UID)
|
||||
c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"})
|
||||
return false
|
||||
}
|
||||
|
||||
var dsUids []string
|
||||
err := json.Unmarshal([]byte(res.DsUIDs), &dsUids)
|
||||
|
||||
if err != nil {
|
||||
hs.log.Error("Error when retrieving datasource uids", "dashboardUid", req.UID, "err", err)
|
||||
c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"})
|
||||
return false
|
||||
}
|
||||
|
||||
accessibleDatasources, err := hs.dsPermissionsService.FilterDatasourceUidsBasedOnQueryPermissions(c.Req.Context(), c.SignedInUser, dsUids)
|
||||
if err != nil && !errors.Is(err, permissions.ErrNotImplemented) {
|
||||
hs.log.Error("Error when filtering datasource uids", "dashboardUid", req.UID, "err", err)
|
||||
c.JSON(500, map[string]string{"dashboardUID": req.UID, "error": "unknown"})
|
||||
return false
|
||||
}
|
||||
|
||||
if !errors.Is(err, permissions.ErrNotImplemented) {
|
||||
canQueryAllDatasources := len(accessibleDatasources) == len(dsUids)
|
||||
if !canQueryAllDatasources {
|
||||
hs.log.Info("Denied access to dashboard preview", "dashboardUid", req.UID, "err", err, "dashboardDatasources", dsUids, "accessibleDatasources", accessibleDatasources)
|
||||
c.JSON(404, map[string]string{"dashboardUID": req.UID, "error": "unknown"})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *models.ReqContext) dashboardPreviewsSetupConfig {
|
||||
return hs.getDashboardPreviewsSetupSettings(c.Req.Context())
|
||||
}
|
||||
@ -330,12 +395,19 @@ func (hs *thumbService) SetImage(c *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
dsUids, err := hs.dsUidsLookup(c.Req.Context(), req.UID, req.OrgID)
|
||||
if err != nil {
|
||||
hs.log.Error("error looking up datasource ids", "err", err, "dashboardUid", req.UID)
|
||||
c.JSON(500, map[string]string{"error": "internal server error"})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = hs.thumbnailRepo.saveFromBytes(c.Req.Context(), fileBytes, getMimeType(handler.Filename), models.DashboardThumbnailMeta{
|
||||
DashboardUID: req.UID,
|
||||
OrgId: req.OrgID,
|
||||
Theme: req.Theme,
|
||||
Kind: req.Kind,
|
||||
}, models.DashboardVersionForManualThumbnailUpload)
|
||||
}, models.DashboardVersionForManualThumbnailUpload, dsUids)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(400, map[string]string{"error": "error saving thumbnail file"})
|
||||
|
123
pkg/services/thumbs/testdata/search_response_frame.json
vendored
Normal file
123
pkg/services/thumbs/testdata/search_response_frame.json
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
{
|
||||
"schema": {
|
||||
"name": "Query results",
|
||||
"refId": "Search",
|
||||
"meta": {
|
||||
"type": "search-results",
|
||||
"custom": {
|
||||
"count": 106,
|
||||
"locationInfo": {
|
||||
"yboVMzb7z": {
|
||||
"name": "gdev dashboards",
|
||||
"kind": "folder",
|
||||
"url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
|
||||
}
|
||||
},
|
||||
"sortBy": "name_sort"
|
||||
}
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"name": "kind",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "uid",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "panel_type",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
},
|
||||
"config": {
|
||||
"links": [
|
||||
{
|
||||
"title": "link",
|
||||
"url": "${__value.text}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ds_uid",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage",
|
||||
"nullable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "location",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"values": [
|
||||
[
|
||||
"dashboard"
|
||||
],
|
||||
[
|
||||
"ujaM1h6nz"
|
||||
],
|
||||
[
|
||||
"abc2"
|
||||
],
|
||||
[
|
||||
""
|
||||
],
|
||||
[
|
||||
"/dashboards/f/ujaM1h6nz/abc2"
|
||||
],
|
||||
[
|
||||
[
|
||||
"gdev"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"datasource-2",
|
||||
"datasource-3",
|
||||
"datasource-4",
|
||||
"grafana"
|
||||
]
|
||||
],
|
||||
[
|
||||
""
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user