Provisioning: Use folders structure from the file system to create desired folders in dashboard provisioning. (#23117)
Fixes #12016
This commit is contained in:
@@ -269,6 +269,8 @@ providers:
|
|||||||
options:
|
options:
|
||||||
# <string, required> path to dashboard files on disk. Required when using the 'file' type
|
# <string, required> path to dashboard files on disk. Required when using the 'file' type
|
||||||
path: /var/lib/grafana/dashboards
|
path: /var/lib/grafana/dashboards
|
||||||
|
# <bool> use folder names from filesystem to create folders in Grafana
|
||||||
|
foldersFromFilesStructure: true
|
||||||
```
|
```
|
||||||
|
|
||||||
When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path every **updateIntervalSeconds** and look for updated json files and update/insert those into the database.
|
When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path every **updateIntervalSeconds** and look for updated json files and update/insert those into the database.
|
||||||
@@ -302,6 +304,35 @@ By default Grafana will delete dashboards in the database if the file is removed
|
|||||||
> Be careful not to re-use the same `title` multiple times within a folder
|
> Be careful not to re-use the same `title` multiple times within a folder
|
||||||
> or `uid` within the same installation as this will cause weird behaviors.
|
> or `uid` within the same installation as this will cause weird behaviors.
|
||||||
|
|
||||||
|
### Provision folders structure from filesystem to Grafana
|
||||||
|
If you already store your dashboards using folders in a git repo or on a filesystem, and also you want to have the same folder names in the Grafana menu, you can use `foldersFromFilesStructure` option.
|
||||||
|
|
||||||
|
For example, to replicate these dashboards structure from the filesystem to Grafana,
|
||||||
|
```
|
||||||
|
/etc/dashboards
|
||||||
|
├── /server
|
||||||
|
│ ├── /common_dashboard.json
|
||||||
|
│ └── /network_dashboard.json
|
||||||
|
└── /application
|
||||||
|
├── /requests_dashboard.json
|
||||||
|
└── /resources_dashboard.json
|
||||||
|
```
|
||||||
|
you need to specify just this short provision configuration file.
|
||||||
|
```yaml
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: dashboards
|
||||||
|
type: file
|
||||||
|
updateIntervalSeconds: 30
|
||||||
|
options:
|
||||||
|
path: /etc/dashboards
|
||||||
|
foldersFromFileStructure: true
|
||||||
|
```
|
||||||
|
`server` and `application` will become new folders in Grafana menu.
|
||||||
|
|
||||||
|
> **Note.** `folder` and `folderUid` options should be empty or missing to make `foldersFromFileStructure` works.
|
||||||
|
|
||||||
## Alert Notification Channels
|
## Alert Notification Channels
|
||||||
|
|
||||||
Alert Notification Channels can be provisioned by adding one or more yaml config files in the [`provisioning/notifiers`](/administration/configuration/#provisioning) directory.
|
Alert Notification Channels can be provisioned by adding one or more yaml config files in the [`provisioning/notifiers`](/administration/configuration/#provisioning) directory.
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type FileReader struct {
|
|||||||
Path string
|
Path string
|
||||||
log log.Logger
|
log log.Logger
|
||||||
dashboardProvisioningService dashboards.DashboardProvisioningService
|
dashboardProvisioningService dashboards.DashboardProvisioningService
|
||||||
|
FoldersFromFilesStructure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDashboardFileReader returns a new filereader based on `config`
|
// NewDashboardFileReader returns a new filereader based on `config`
|
||||||
@@ -48,11 +49,17 @@ func NewDashboardFileReader(cfg *config, log log.Logger) (*FileReader, error) {
|
|||||||
log.Warn("[Deprecated] The folder property is deprecated. Please use path instead.")
|
log.Warn("[Deprecated] The folder property is deprecated. Please use path instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foldersFromFilesStructure, _ := cfg.Options["foldersFromFilesStructure"].(bool)
|
||||||
|
if foldersFromFilesStructure && cfg.Folder != "" && cfg.FolderUID != "" {
|
||||||
|
return nil, fmt.Errorf("'folder' and 'folderUID' should be empty using 'foldersFromFilesStructure' option")
|
||||||
|
}
|
||||||
|
|
||||||
return &FileReader{
|
return &FileReader{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
Path: path,
|
Path: path,
|
||||||
log: log,
|
log: log,
|
||||||
dashboardProvisioningService: dashboards.NewProvisioningService(),
|
dashboardProvisioningService: dashboards.NewProvisioningService(),
|
||||||
|
FoldersFromFilesStructure: foldersFromFilesStructure,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +88,6 @@ func (fr *FileReader) startWalkingDisk() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
folderID, err := getOrCreateFolderID(fr.Cfg, fr.dashboardProvisioningService)
|
|
||||||
if err != nil && err != ErrFolderNameMissing {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardProvisioningService, fr.Cfg.Name)
|
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardProvisioningService, fr.Cfg.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -101,16 +103,61 @@ func (fr *FileReader) startWalkingDisk() error {
|
|||||||
|
|
||||||
sanityChecker := newProvisioningSanityChecker(fr.Cfg.Name)
|
sanityChecker := newProvisioningSanityChecker(fr.Cfg.Name)
|
||||||
|
|
||||||
|
if fr.FoldersFromFilesStructure {
|
||||||
|
err = fr.storeDashboardsInFoldersFromFileStructure(filesFoundOnDisk, provisionedDashboardRefs, resolvedPath, &sanityChecker)
|
||||||
|
} else {
|
||||||
|
err = fr.storeDashboardsInFolder(filesFoundOnDisk, provisionedDashboardRefs, &sanityChecker)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sanityChecker.logWarnings(fr.log)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeDashboardsInFolder saves dashboards from the filesystem on disk to the folder from config
|
||||||
|
func (fr *FileReader) storeDashboardsInFolder(filesFoundOnDisk map[string]os.FileInfo, dashboardRefs map[string]*models.DashboardProvisioning, sanityChecker *provisioningSanityChecker) error {
|
||||||
|
folderID, err := getOrCreateFolderID(fr.Cfg, fr.dashboardProvisioningService, fr.Cfg.Folder)
|
||||||
|
|
||||||
|
if err != nil && err != ErrFolderNameMissing {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// save dashboards based on json files
|
// save dashboards based on json files
|
||||||
for path, fileInfo := range filesFoundOnDisk {
|
for path, fileInfo := range filesFoundOnDisk {
|
||||||
provisioningMetadata, err := fr.saveDashboard(path, folderID, fileInfo, provisionedDashboardRefs)
|
provisioningMetadata, err := fr.saveDashboard(path, folderID, fileInfo, dashboardRefs)
|
||||||
sanityChecker.track(provisioningMetadata)
|
sanityChecker.track(provisioningMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fr.log.Error("failed to save dashboard", "error", err)
|
fr.log.Error("failed to save dashboard", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sanityChecker.logWarnings(fr.log)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeDashboardsInFoldersFromFilesystemStructure saves dashboards from the filesystem on disk to the same folder
|
||||||
|
// in grafana as they are in on the filesystem
|
||||||
|
func (fr *FileReader) storeDashboardsInFoldersFromFileStructure(filesFoundOnDisk map[string]os.FileInfo, dashboardRefs map[string]*models.DashboardProvisioning, resolvedPath string, sanityChecker *provisioningSanityChecker) error {
|
||||||
|
for path, fileInfo := range filesFoundOnDisk {
|
||||||
|
folderName := ""
|
||||||
|
|
||||||
|
dashboardsFolder := filepath.Dir(path)
|
||||||
|
if dashboardsFolder != resolvedPath {
|
||||||
|
folderName = filepath.Base(dashboardsFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
folderID, err := getOrCreateFolderID(fr.Cfg, fr.dashboardProvisioningService, folderName)
|
||||||
|
if err != nil && err != ErrFolderNameMissing {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioningMetadata, err := fr.saveDashboard(path, folderID, fileInfo, dashboardRefs)
|
||||||
|
sanityChecker.track(provisioningMetadata)
|
||||||
|
if err != nil {
|
||||||
|
fr.log.Error("failed to save dashboard", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +218,7 @@ func (fr *FileReader) saveDashboard(path string, folderID int64, fileInfo os.Fil
|
|||||||
// keeps track of what uid's and title's we have already provisioned
|
// keeps track of what uid's and title's we have already provisioned
|
||||||
dash := jsonFile.dashboard
|
dash := jsonFile.dashboard
|
||||||
provisioningMetadata.uid = dash.Dashboard.Uid
|
provisioningMetadata.uid = dash.Dashboard.Uid
|
||||||
provisioningMetadata.title = dash.Dashboard.Title
|
provisioningMetadata.identity = dashboardIdentity{title: dash.Dashboard.Title, folderID: dash.Dashboard.FolderId}
|
||||||
|
|
||||||
if upToDate {
|
if upToDate {
|
||||||
return provisioningMetadata, nil
|
return provisioningMetadata, nil
|
||||||
@@ -212,12 +259,12 @@ func getProvisionedDashboardByPath(service dashboards.DashboardProvisioningServi
|
|||||||
return byPath, nil
|
return byPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrCreateFolderID(cfg *config, service dashboards.DashboardProvisioningService) (int64, error) {
|
func getOrCreateFolderID(cfg *config, service dashboards.DashboardProvisioningService, folderName string) (int64, error) {
|
||||||
if cfg.Folder == "" {
|
if folderName == "" {
|
||||||
return 0, ErrFolderNameMissing
|
return 0, ErrFolderNameMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &models.GetDashboardQuery{Slug: models.SlugifyTitle(cfg.Folder), OrgId: cfg.OrgID}
|
cmd := &models.GetDashboardQuery{Slug: models.SlugifyTitle(folderName), OrgId: cfg.OrgID}
|
||||||
err := bus.Dispatch(cmd)
|
err := bus.Dispatch(cmd)
|
||||||
|
|
||||||
if err != nil && err != models.ErrDashboardNotFound {
|
if err != nil && err != models.ErrDashboardNotFound {
|
||||||
@@ -227,7 +274,7 @@ func getOrCreateFolderID(cfg *config, service dashboards.DashboardProvisioningSe
|
|||||||
// dashboard folder not found. create one.
|
// dashboard folder not found. create one.
|
||||||
if err == models.ErrDashboardNotFound {
|
if err == models.ErrDashboardNotFound {
|
||||||
dash := &dashboards.SaveDashboardDTO{}
|
dash := &dashboards.SaveDashboardDTO{}
|
||||||
dash.Dashboard = models.NewDashboardFolder(cfg.Folder)
|
dash.Dashboard = models.NewDashboardFolder(folderName)
|
||||||
dash.Dashboard.IsFolder = true
|
dash.Dashboard.IsFolder = true
|
||||||
dash.Overwrite = true
|
dash.Overwrite = true
|
||||||
dash.OrgId = cfg.OrgID
|
dash.OrgId = cfg.OrgID
|
||||||
@@ -356,29 +403,39 @@ func (fr *FileReader) resolvedPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type provisioningMetadata struct {
|
type provisioningMetadata struct {
|
||||||
uid string
|
uid string
|
||||||
title string
|
identity dashboardIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardIdentity struct {
|
||||||
|
folderID int64
|
||||||
|
title string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboardIdentity) Exists() bool {
|
||||||
|
return len(d.title) > 0 && d.folderID > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProvisioningSanityChecker(provisioningProvider string) provisioningSanityChecker {
|
func newProvisioningSanityChecker(provisioningProvider string) provisioningSanityChecker {
|
||||||
return provisioningSanityChecker{
|
return provisioningSanityChecker{
|
||||||
provisioningProvider: provisioningProvider,
|
provisioningProvider: provisioningProvider,
|
||||||
uidUsage: map[string]uint8{},
|
uidUsage: map[string]uint8{},
|
||||||
titleUsage: map[string]uint8{}}
|
titleUsage: map[dashboardIdentity]uint8{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type provisioningSanityChecker struct {
|
type provisioningSanityChecker struct {
|
||||||
provisioningProvider string
|
provisioningProvider string
|
||||||
uidUsage map[string]uint8
|
uidUsage map[string]uint8
|
||||||
titleUsage map[string]uint8
|
titleUsage map[dashboardIdentity]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (checker provisioningSanityChecker) track(pm provisioningMetadata) {
|
func (checker provisioningSanityChecker) track(pm provisioningMetadata) {
|
||||||
if len(pm.uid) > 0 {
|
if len(pm.uid) > 0 {
|
||||||
checker.uidUsage[pm.uid]++
|
checker.uidUsage[pm.uid]++
|
||||||
}
|
}
|
||||||
if len(pm.title) > 0 {
|
if pm.identity.Exists() {
|
||||||
checker.titleUsage[pm.title]++
|
checker.titleUsage[pm.identity]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,9 +446,9 @@ func (checker provisioningSanityChecker) logWarnings(log log.Logger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for title, times := range checker.titleUsage {
|
for identity, times := range checker.titleUsage {
|
||||||
if times > 1 {
|
if times > 1 {
|
||||||
log.Error("the same 'title' is used more than once", "title", title, "provider", checker.provisioningProvider)
|
log.Error("the same 'title' is used more than once", "title", identity.title, "provider", checker.provisioningProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dashboards
|
package dashboards
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -8,22 +9,22 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultDashboards = "testdata/test-dashboards/folder-one"
|
defaultDashboards = "testdata/test-dashboards/folder-one"
|
||||||
brokenDashboards = "testdata/test-dashboards/broken-dashboards"
|
brokenDashboards = "testdata/test-dashboards/broken-dashboards"
|
||||||
oneDashboard = "testdata/test-dashboards/one-dashboard"
|
oneDashboard = "testdata/test-dashboards/one-dashboard"
|
||||||
containingID = "testdata/test-dashboards/containing-id"
|
containingID = "testdata/test-dashboards/containing-id"
|
||||||
unprovision = "testdata/test-dashboards/unprovision"
|
unprovision = "testdata/test-dashboards/unprovision"
|
||||||
|
foldersFromFilesStructure = "testdata/test-dashboards/folders-from-files-structure"
|
||||||
|
|
||||||
fakeService *fakeDashboardProvisioningService
|
fakeService *fakeDashboardProvisioningService
|
||||||
)
|
)
|
||||||
@@ -52,6 +53,14 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
|
|||||||
So(reader.Path, ShouldNotEqual, "")
|
So(reader.Path, ShouldNotEqual, "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("using foldersFromFilesStructure as options", func() {
|
||||||
|
cfg.Options["path"] = foldersFromFilesStructure
|
||||||
|
cfg.Options["foldersFromFilesStructure"] = true
|
||||||
|
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(reader.Path, ShouldNotEqual, "")
|
||||||
|
})
|
||||||
|
|
||||||
Convey("using full path", func() {
|
Convey("using full path", func() {
|
||||||
fullPath := "/var/lib/grafana/dashboards"
|
fullPath := "/var/lib/grafana/dashboards"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@@ -152,6 +161,46 @@ func TestDashboardFileReader(t *testing.T) {
|
|||||||
So(len(fakeService.inserted), ShouldEqual, 1)
|
So(len(fakeService.inserted), ShouldEqual, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Get folder from files structure", func() {
|
||||||
|
cfg.Options["path"] = foldersFromFilesStructure
|
||||||
|
cfg.Options["foldersFromFilesStructure"] = true
|
||||||
|
|
||||||
|
reader, err := NewDashboardFileReader(cfg, logger)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = reader.startWalkingDisk()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(fakeService.inserted), ShouldEqual, 5)
|
||||||
|
|
||||||
|
foldersCount := 0
|
||||||
|
for _, d := range fakeService.inserted {
|
||||||
|
if d.Dashboard.IsFolder {
|
||||||
|
foldersCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
So(foldersCount, ShouldEqual, 2)
|
||||||
|
|
||||||
|
foldersAndDashboards := make(map[string]struct{}, 5)
|
||||||
|
for _, d := range fakeService.inserted {
|
||||||
|
title := d.Dashboard.Title
|
||||||
|
if _, ok := foldersAndDashboards[title]; ok {
|
||||||
|
So(fmt.Errorf("dashboard title %q already exists", title), ShouldBeNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch title {
|
||||||
|
case "folderOne", "folderTwo":
|
||||||
|
So(d.Dashboard.IsFolder, ShouldBeTrue)
|
||||||
|
case "Grafana1", "Grafana2", "RootDashboard":
|
||||||
|
So(d.Dashboard.IsFolder, ShouldBeFalse)
|
||||||
|
default:
|
||||||
|
So(fmt.Errorf("unknown dashboard title %q", title), ShouldBeNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
foldersAndDashboards[title] = struct{}{}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Invalid configuration should return error", func() {
|
Convey("Invalid configuration should return error", func() {
|
||||||
cfg := &config{
|
cfg := &config{
|
||||||
Name: "Default",
|
Name: "Default",
|
||||||
@@ -213,7 +262,7 @@ func TestDashboardFileReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := getOrCreateFolderID(cfg, fakeService)
|
_, err := getOrCreateFolderID(cfg, fakeService, cfg.Folder)
|
||||||
So(err, ShouldEqual, ErrFolderNameMissing)
|
So(err, ShouldEqual, ErrFolderNameMissing)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -228,7 +277,7 @@ func TestDashboardFileReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
folderID, err := getOrCreateFolderID(cfg, fakeService)
|
folderID, err := getOrCreateFolderID(cfg, fakeService, cfg.Folder)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
inserted := false
|
inserted := false
|
||||||
for _, d := range fakeService.inserted {
|
for _, d := range fakeService.inserted {
|
||||||
|
|||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"title": "Grafana1",
|
||||||
|
"tags": [],
|
||||||
|
"style": "dark",
|
||||||
|
"timezone": "browser",
|
||||||
|
"editable": true,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"title": "New row",
|
||||||
|
"height": "150px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"span": 12,
|
||||||
|
"editable": true,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Welcome to"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Welcome to Grafana",
|
||||||
|
"height": "210px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Documentation Links"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
|
||||||
|
"style": {},
|
||||||
|
"title": "Tips & Shortcuts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"height": "250px",
|
||||||
|
"editable": true,
|
||||||
|
"collapse": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"span": 12,
|
||||||
|
"type": "graph",
|
||||||
|
"x-axis": true,
|
||||||
|
"y-axis": true,
|
||||||
|
"scale": 1,
|
||||||
|
"y_formats": [
|
||||||
|
"short",
|
||||||
|
"short"
|
||||||
|
],
|
||||||
|
"grid": {
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"leftMax": null,
|
||||||
|
"rightMax": null,
|
||||||
|
"leftMin": null,
|
||||||
|
"rightMin": null,
|
||||||
|
"threshold1": null,
|
||||||
|
"threshold2": null,
|
||||||
|
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||||
|
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||||
|
},
|
||||||
|
"resolution": 100,
|
||||||
|
"lines": true,
|
||||||
|
"fill": 1,
|
||||||
|
"linewidth": 2,
|
||||||
|
"dashes": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"spaceLength": 10,
|
||||||
|
"points": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"bars": false,
|
||||||
|
"stack": true,
|
||||||
|
"spyable": true,
|
||||||
|
"options": false,
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"values": false,
|
||||||
|
"min": false,
|
||||||
|
"max": false,
|
||||||
|
"current": false,
|
||||||
|
"total": false,
|
||||||
|
"avg": false
|
||||||
|
},
|
||||||
|
"interactive": true,
|
||||||
|
"legend_counts": true,
|
||||||
|
"timezone": "browser",
|
||||||
|
"percentage": false,
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"steppedLine": false,
|
||||||
|
"tooltip": {
|
||||||
|
"value_type": "cumulative",
|
||||||
|
"query_as_alias": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "randomWalk('random walk')",
|
||||||
|
"function": "mean",
|
||||||
|
"column": "value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliasColors": {},
|
||||||
|
"aliasYAxis": {},
|
||||||
|
"title": "First Graph (click title to edit)",
|
||||||
|
"datasource": "graphite",
|
||||||
|
"renderer": "flot",
|
||||||
|
"annotate": {
|
||||||
|
"enable": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nav": [
|
||||||
|
{
|
||||||
|
"type": "timepicker",
|
||||||
|
"collapse": false,
|
||||||
|
"enable": true,
|
||||||
|
"status": "Stable",
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
],
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"now": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"version": 5
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"title": "Grafana2",
|
||||||
|
"tags": [],
|
||||||
|
"style": "dark",
|
||||||
|
"timezone": "browser",
|
||||||
|
"editable": true,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"title": "New row",
|
||||||
|
"height": "150px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"span": 12,
|
||||||
|
"editable": true,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Welcome to"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Welcome to Grafana",
|
||||||
|
"height": "210px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Documentation Links"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
|
||||||
|
"style": {},
|
||||||
|
"title": "Tips & Shortcuts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"height": "250px",
|
||||||
|
"editable": true,
|
||||||
|
"collapse": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"span": 12,
|
||||||
|
"type": "graph",
|
||||||
|
"x-axis": true,
|
||||||
|
"y-axis": true,
|
||||||
|
"scale": 1,
|
||||||
|
"y_formats": [
|
||||||
|
"short",
|
||||||
|
"short"
|
||||||
|
],
|
||||||
|
"grid": {
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"leftMax": null,
|
||||||
|
"rightMax": null,
|
||||||
|
"leftMin": null,
|
||||||
|
"rightMin": null,
|
||||||
|
"threshold1": null,
|
||||||
|
"threshold2": null,
|
||||||
|
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||||
|
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||||
|
},
|
||||||
|
"resolution": 100,
|
||||||
|
"lines": true,
|
||||||
|
"fill": 1,
|
||||||
|
"linewidth": 2,
|
||||||
|
"dashes": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"spaceLength": 10,
|
||||||
|
"points": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"bars": false,
|
||||||
|
"stack": true,
|
||||||
|
"spyable": true,
|
||||||
|
"options": false,
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"values": false,
|
||||||
|
"min": false,
|
||||||
|
"max": false,
|
||||||
|
"current": false,
|
||||||
|
"total": false,
|
||||||
|
"avg": false
|
||||||
|
},
|
||||||
|
"interactive": true,
|
||||||
|
"legend_counts": true,
|
||||||
|
"timezone": "browser",
|
||||||
|
"percentage": false,
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"steppedLine": false,
|
||||||
|
"tooltip": {
|
||||||
|
"value_type": "cumulative",
|
||||||
|
"query_as_alias": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "randomWalk('random walk')",
|
||||||
|
"function": "mean",
|
||||||
|
"column": "value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliasColors": {},
|
||||||
|
"aliasYAxis": {},
|
||||||
|
"title": "First Graph (click title to edit)",
|
||||||
|
"datasource": "graphite",
|
||||||
|
"renderer": "flot",
|
||||||
|
"annotate": {
|
||||||
|
"enable": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nav": [
|
||||||
|
{
|
||||||
|
"type": "timepicker",
|
||||||
|
"collapse": false,
|
||||||
|
"enable": true,
|
||||||
|
"status": "Stable",
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
],
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"now": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"version": 5
|
||||||
|
}
|
||||||
pkg/services/provisioning/dashboards/testdata/test-dashboards/folders-from-files-structure/root.json
Vendored
+172
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"title": "RootDashboard",
|
||||||
|
"tags": [],
|
||||||
|
"style": "dark",
|
||||||
|
"timezone": "browser",
|
||||||
|
"editable": true,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"title": "New row",
|
||||||
|
"height": "150px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"span": 12,
|
||||||
|
"editable": true,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Welcome to"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Welcome to Grafana",
|
||||||
|
"height": "210px",
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
|
||||||
|
"style": {},
|
||||||
|
"title": "Documentation Links"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"span": 6,
|
||||||
|
"type": "text",
|
||||||
|
"mode": "html",
|
||||||
|
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
|
||||||
|
"style": {},
|
||||||
|
"title": "Tips & Shortcuts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"height": "250px",
|
||||||
|
"editable": true,
|
||||||
|
"collapse": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"span": 12,
|
||||||
|
"type": "graph",
|
||||||
|
"x-axis": true,
|
||||||
|
"y-axis": true,
|
||||||
|
"scale": 1,
|
||||||
|
"y_formats": [
|
||||||
|
"short",
|
||||||
|
"short"
|
||||||
|
],
|
||||||
|
"grid": {
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"leftMax": null,
|
||||||
|
"rightMax": null,
|
||||||
|
"leftMin": null,
|
||||||
|
"rightMin": null,
|
||||||
|
"threshold1": null,
|
||||||
|
"threshold2": null,
|
||||||
|
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||||
|
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||||
|
},
|
||||||
|
"resolution": 100,
|
||||||
|
"lines": true,
|
||||||
|
"fill": 1,
|
||||||
|
"linewidth": 2,
|
||||||
|
"dashes": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"spaceLength": 10,
|
||||||
|
"points": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"bars": false,
|
||||||
|
"stack": true,
|
||||||
|
"spyable": true,
|
||||||
|
"options": false,
|
||||||
|
"legend": {
|
||||||
|
"show": true,
|
||||||
|
"values": false,
|
||||||
|
"min": false,
|
||||||
|
"max": false,
|
||||||
|
"current": false,
|
||||||
|
"total": false,
|
||||||
|
"avg": false
|
||||||
|
},
|
||||||
|
"interactive": true,
|
||||||
|
"legend_counts": true,
|
||||||
|
"timezone": "browser",
|
||||||
|
"percentage": false,
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"steppedLine": false,
|
||||||
|
"tooltip": {
|
||||||
|
"value_type": "cumulative",
|
||||||
|
"query_as_alias": true
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target": "randomWalk('random walk')",
|
||||||
|
"function": "mean",
|
||||||
|
"column": "value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliasColors": {},
|
||||||
|
"aliasYAxis": {},
|
||||||
|
"title": "First Graph (click title to edit)",
|
||||||
|
"datasource": "graphite",
|
||||||
|
"renderer": "flot",
|
||||||
|
"annotate": {
|
||||||
|
"enable": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nav": [
|
||||||
|
{
|
||||||
|
"type": "timepicker",
|
||||||
|
"collapse": false,
|
||||||
|
"enable": true,
|
||||||
|
"status": "Stable",
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
],
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"now": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"version": 5
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user