Просмотр исходного кода

Merge remote-tracking branch 'upstream/master' into postgres-query-builder

Sven Klemm 7 лет назад
Родитель
Сommit
a05d694d31
65 измененных файлов с 1664 добавлено и 189 удалено
  1. 9 0
      CHANGELOG.md
  2. 2 2
      Gopkg.lock
  3. 1 1
      Gopkg.toml
  4. 1 1
      devenv/dashboards/bulk-testing/bulkdash.jsonnet
  5. 2 2
      devenv/setup.sh
  6. 1 0
      docs/sources/administration/provisioning.md
  7. 70 0
      docs/sources/guides/whats-new-in-v5-2.md
  8. 21 13
      docs/sources/installation/configuration.md
  9. 14 0
      docs/sources/installation/docker.md
  10. 1 0
      package.json
  11. 1 1
      pkg/api/alerting.go
  12. 3 0
      pkg/api/api.go
  13. 1 1
      pkg/api/dtos/models.go
  14. 11 0
      pkg/api/frontendsettings.go
  15. 17 3
      pkg/api/index.go
  16. 1 0
      pkg/models/dashboards.go
  17. 1 0
      pkg/models/datasource.go
  18. 4 0
      pkg/services/provisioning/dashboards/config_reader.go
  19. 7 3
      pkg/services/provisioning/dashboards/config_reader_test.go
  20. 53 14
      pkg/services/provisioning/dashboards/file_reader.go
  21. 39 0
      pkg/services/provisioning/dashboards/file_reader_linux_test.go
  22. 7 4
      pkg/services/provisioning/dashboards/file_reader_test.go
  23. 1 0
      pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml
  24. 1 0
      pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml
  25. 1 0
      pkg/services/provisioning/dashboards/testdata/test-dashboards/symlink
  26. 40 35
      pkg/services/provisioning/dashboards/types.go
  27. 1 1
      pkg/services/sqlstore/alert.go
  28. 3 3
      pkg/services/sqlstore/alert_test.go
  29. 4 0
      pkg/services/sqlstore/migrations/dashboard_mig.go
  30. 10 3
      pkg/tsdb/elasticsearch/response_parser.go
  31. 26 0
      pkg/util/md5.go
  32. 17 0
      pkg/util/md5_test.go
  33. 59 7
      public/app/core/controllers/login_ctrl.ts
  34. 18 15
      public/app/core/services/keybindingSrv.ts
  35. 4 0
      public/app/core/table_model.ts
  36. 12 6
      public/app/features/dashboard/save_modal.ts
  37. 41 3
      public/app/features/dashboard/specs/save_modal.jest.ts
  38. 5 3
      public/app/features/panel/metrics_panel_ctrl.ts
  39. 2 1
      public/app/features/panel/specs/metrics_panel_ctrl.jest.ts
  40. 2 0
      public/app/features/plugins/built_in_plugins.ts
  41. 8 0
      public/app/features/plugins/ds_edit_ctrl.ts
  42. 3 3
      public/app/features/plugins/partials/ds_http_settings.html
  43. 82 51
      public/app/partials/login.html
  44. 26 0
      public/app/plugins/datasource/influxdb-ifql/README.md
  45. 233 0
      public/app/plugins/datasource/influxdb-ifql/datasource.ts
  46. 26 0
      public/app/plugins/datasource/influxdb-ifql/img/influxdb_logo.svg
  47. 17 0
      public/app/plugins/datasource/influxdb-ifql/module.ts
  48. 24 0
      public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html
  49. 24 0
      public/app/plugins/datasource/influxdb-ifql/partials/config.html
  50. 24 0
      public/app/plugins/datasource/influxdb-ifql/partials/query.editor.html
  51. 24 0
      public/app/plugins/datasource/influxdb-ifql/plugin.json
  52. 27 0
      public/app/plugins/datasource/influxdb-ifql/query_ctrl.ts
  53. 88 0
      public/app/plugins/datasource/influxdb-ifql/response_parser.ts
  54. 53 0
      public/app/plugins/datasource/influxdb-ifql/specs/datasource.jest.ts
  55. 63 0
      public/app/plugins/datasource/influxdb-ifql/specs/response_parser.jest.ts
  56. 349 0
      public/app/plugins/datasource/influxdb-ifql/specs/sample_response_csv.ts
  57. 4 3
      public/app/plugins/panel/dashlist/editor.html
  58. 3 2
      public/app/plugins/panel/dashlist/module.ts
  59. 18 2
      public/app/routes/ReactContainer.tsx
  60. 1 0
      public/app/routes/routes.ts
  61. 4 0
      public/sass/components/_gf-form.scss
  62. 5 6
      public/sass/components/_panel_singlestat.scss
  63. 38 0
      public/sass/pages/_login.scss
  64. 2 0
      public/test/specs/helpers.ts
  65. 4 0
      yarn.lock

+ 9 - 0
CHANGELOG.md

@@ -1,8 +1,11 @@
 # 5.2.0 (unreleased)
 
+# 5.2.0-beta1 (2018-06-05)
+
 ### New Features
 
 * **Elasticsearch**: Alerting support [#5893](https://github.com/grafana/grafana/issues/5893), thx [@WPH95](https://github.com/WPH95)
+* **Login**: Change admin password after first login [#11882](https://github.com/grafana/grafana/issues/11882)
 * **Alert list panel**: Updated to support filtering alerts by name, dashboard title, folder, tags [#11500](https://github.com/grafana/grafana/issues/11500), [#8168](https://github.com/grafana/grafana/issues/8168), [#6541](https://github.com/grafana/grafana/issues/6541)
 
 ### Minor
@@ -30,6 +33,12 @@
 * **Singlestat**: Fix singlestat threshold tooltip [#11971](https://github.com/grafana/grafana/issues/11971)
 * **Dashboard**: Hide grid controls in fullscreen/low-activity views [#11771](https://github.com/grafana/grafana/issues/11771)
 * **Dashboard**: Validate uid when importing dashboards [#11515](https://github.com/grafana/grafana/issues/11515)
+* **Docker**: Support for env variables ending with _FILE [grafana-docker #166](https://github.com/grafana/grafana-docker/pull/166), thx [@efrecon](https://github.com/efrecon)
+* **Alert list panel**: Show alerts for user with viewer role [#11167](https://github.com/grafana/grafana/issues/11167)
+* **Provisioning**: Verify checksum of dashboards before updating to reduce load on database [#11670](https://github.com/grafana/grafana/issues/11670)
+* **Provisioning**: Support symlinked files in dashboard provisioning config files [#11958](https://github.com/grafana/grafana/issues/11958)
+* **Dashboard list panel**: Search dashboards by folder [#11525](https://github.com/grafana/grafana/issues/11525)
+* **Sidenav**: Always show server admin link in sidenav if grafana admin [#11657](https://github.com/grafana/grafana/issues/11657)
 
 # 5.1.3 (2018-05-16)
 

+ 2 - 2
Gopkg.lock

@@ -226,7 +226,7 @@
   version = "v1.1.1"
 
 [[projects]]
-  branch = "renderer"
+  branch = "master"
   name = "github.com/grafana/grafana-plugin-model"
   packages = [
     "go/datasource",
@@ -670,6 +670,6 @@
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "6c7ae4bcbe7fa4430d3bdbf204df1b7c59cba88151fbcefa167ce15e6351b6d3"
+  inputs-digest = "08b97771990365d506af4788acb33cdf283ce89856669262ecb84860ad45bfcb"
   solver-name = "gps-cdcl"
   solver-version = 1

+ 1 - 1
Gopkg.toml

@@ -100,7 +100,7 @@ ignored = [
   version = "1.1.1"
 
 [[constraint]]
-  branch = "renderer"
+  branch = "master"
   name = "github.com/grafana/grafana-plugin-model"
 
 [[constraint]]

+ 1 - 1
devenv/dashboards/bulk-testing/bulkdash.jsonnet

@@ -1137,4 +1137,4 @@
   "title": "Big Dashboard",
   "uid": "000000003",
   "version": 16
-}
+}

+ 2 - 2
devenv/setup.sh

@@ -8,7 +8,7 @@ bulkDashboard() {
     MAX=400
     while [  $COUNTER -lt $MAX ]; do
         jsonnet -o "dashboards/bulk-testing/dashboard${COUNTER}.json" -e "local bulkDash = import 'dashboards/bulk-testing/bulkdash.jsonnet'; bulkDash + {  uid: 'uid-${COUNTER}',  title: 'title-${COUNTER}' }"
-        let COUNTER=COUNTER+1 
+        let COUNTER=COUNTER+1
     done
 
     ln -s -f -r ./dashboards/bulk-testing/bulk-dashboards.yaml ../conf/provisioning/dashboards/custom.yaml
@@ -58,4 +58,4 @@ main() {
 	fi
 }
 
-main "$@"
+main "$@"

+ 1 - 0
docs/sources/administration/provisioning.md

@@ -197,6 +197,7 @@ providers:
   folder: ''
   type: file
   disableDeletion: false
+  updateIntervalSeconds: 3 #how often Grafana will scan for changed dashboards
   options:
     path: /var/lib/grafana/dashboards
 ```

+ 70 - 0
docs/sources/guides/whats-new-in-v5-2.md

@@ -0,0 +1,70 @@
++++
+title = "What's New in Grafana v5.2"
+description = "Feature & improvement highlights for Grafana v5.2"
+keywords = ["grafana", "new", "documentation", "5.2"]
+type = "docs"
+[menu.docs]
+name = "Version 5.2"
+identifier = "v5.2"
+parent = "whatsnew"
+weight = -8
++++
+
+# What's New in Grafana v5.2
+
+Grafana v5.2 brings new features, many enhancements and bug fixes. This article will detail the major new features and enhancements.
+
+* [Elasticsearch alerting]({{< relref "#elasticsearch-alerting" >}}) it's finally here!
+* [Cross platform build support]({{< relref "#cross-platform-build-support" >}}) enables native builds of Grafana for many more platforms!
+* [Improved Docker image]({{< relref "#improved-docker-image" >}}) with support for docker secrets
+* [Prometheus]({{< relref "#prometheus" >}}) with alignment enhancements
+* [Alerting]({{< relref "#alerting" >}}) with alert notification channel type for Discord
+* [Dashboards & Panels]({{< relref "#dashboards-panels" >}})
+
+## Elasticsearch alerting
+
+{{< docs-imagebox img="/img/docs/v52/elasticsearch_alerting.png" max-width="800px" class="docs-image--right" >}}
+
+Grafana v5.2 ships with an updated Elasticsearch datasource with support for alerting. Alerting support for Elasticsearch has been one of
+the most requested features by our community and now it's finally here. Please try it out and let us know what you think.
+
+<div class="clearfix"></div>
+
+## Cross platform build support
+
+Grafana v5.2 brings an improved build pipeline with cross platform support. This enables native builds of Grafana for ARMv7 (x32), ARM64 (x64),
+MacOS/Darwin (x64) and Windows (x64) in both stable and nightly builds.
+
+We've been longing for native ARM build support for a long time. With the help from our amazing community this is now finally available.
+
+## Improved Docker image
+
+The Grafana docker image now includes support for Docker secrets which enables you to supply Grafana with configuration through files. More
+information in the [Installing using Docker documentation](/installation/docker/#reading-secrets-from-files-support-for-docker-secrets).
+
+## Prometheus
+
+The Prometheus datasource now aligns the start/end of the query sent to Prometheus with the step, which ensures PromQL expressions with *rate*
+functions get consistent results, and thus avoid graphs jumping around on reload.
+
+## Alerting
+
+By popular demand Grafana now includes support for an alert notification channel type for [Discord](https://discordapp.com/).
+
+## Dashboards & Panels
+
+### Modified time range and variables are no longer saved by default
+
+{{< docs-imagebox img="/img/docs/v52/dashboard_save_modal.png" max-width="800px" class="docs-image--right" >}}
+
+Starting from Grafana v5.2 a modified time range or variable are no longer saved by default. To save a modified
+time range or variable you'll need to actively select that when saving a dashboard, see screenshot.
+This should hopefully make it easier to have sane defaults of time and variables in dashboards and make it more explicit
+when you actually want to overwrite those settings.
+
+<div class="clearfix"></div>
+
+## Changelog
+
+Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list
+of new features, changes, and bug fixes.

+ 21 - 13
docs/sources/installation/configuration.md

@@ -419,25 +419,33 @@ allowed_organizations = github google
 
 ## [auth.google]
 
-You need to create a Google project. You can do this in the [Google
-Developer Console](https://console.developers.google.com/project).  When
-you create the project you will need to specify a callback URL. Specify
-this as callback:
+First, you need to create a Google OAuth Client:
 
-```bash
-http://<my_grafana_server_name_or_ip>:<grafana_server_port>/login/google
-```
+1. Go to https://console.developers.google.com/apis/credentials
 
-This callback URL must match the full HTTP address that you use in your
-browser to access Grafana, but with the prefix path of `/login/google`.
-When the Google project is created you will get a Client ID and a Client
-Secret. Specify these in the Grafana configuration file. For example:
+2. Click the 'Create Credentials' button, then click 'OAuth Client ID' in the
+menu that drops down
+
+3. Enter the following:
+
+   - Application Type: Web Application
+   - Name: Grafana
+   - Authorized Javascript Origins: https://grafana.mycompany.com
+   - Authorized Redirect URLs: https://grafana.mycompany.com/login/google
+
+   Replace https://grafana.mycompany.com with the URL of your Grafana instance.
+
+4. Click Create
+
+5. Copy the Client ID and Client Secret from the 'OAuth Client' modal
+
+Specify the Client ID and Secret in the Grafana configuration file. For example:
 
 ```bash
 [auth.google]
 enabled = true
-client_id = YOUR_GOOGLE_APP_CLIENT_ID
-client_secret = YOUR_GOOGLE_APP_CLIENT_SECRET
+client_id = CLIENT_ID
+client_secret = CLIENT_SECRET
 scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
 auth_url = https://accounts.google.com/o/oauth2/auth
 token_url = https://accounts.google.com/o/oauth2/token

+ 14 - 0
docs/sources/installation/docker.md

@@ -130,6 +130,20 @@ ID=$(id -u) # saves your user id in the ID variable
 docker run -d --user $ID --volume "$PWD/data:/var/lib/grafana" -p 3000:3000 grafana/grafana:5.1.0
 ```
 
+## Reading secrets from files (support for Docker Secrets)
+
+> Available in v5.2.0 and later
+
+It's possible to supply Grafana with configuration through files. This works well with [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) as the secrets by default gets mapped into `/run/secrets/<name of secret>` of the container.
+
+You can do this with any of the configuration options in conf/grafana.ini by setting `GF_<SectionName>_<KeyName>_FILE` to the path of the file holding the secret.
+
+Let's say you want to set the admin password this way.
+
+- Admin password secret: `/run/secrets/admin_password`
+- Environment variable: `GF_SECURITY_ADMIN_PASSWORD_FILE=/run/secrets/admin_password`
+
+
 ## Migration from a previous version of the docker container to 5.1 or later
 
 The docker container for Grafana has seen a major rewrite for 5.1.

+ 1 - 0
package.json

@@ -157,6 +157,7 @@
     "moment": "^2.18.1",
     "mousetrap": "^1.6.0",
     "mousetrap-global-bind": "^1.1.0",
+    "papaparse": "^4.4.0",
     "prismjs": "^1.6.0",
     "prop-types": "^15.6.0",
     "react": "^16.2.0",

+ 1 - 1
pkg/api/alerting.go

@@ -79,7 +79,7 @@ func GetAlerts(c *m.ReqContext) Response {
 			DashboardIds: dashboardIDs,
 			Type:         string(search.DashHitDB),
 			FolderIds:    folderIDs,
-			Permission:   m.PERMISSION_EDIT,
+			Permission:   m.PERMISSION_VIEW,
 		}
 
 		err := bus.Dispatch(&searchQuery)

+ 3 - 0
pkg/api/api.go

@@ -77,6 +77,9 @@ func (hs *HTTPServer) registerRoutes() {
 	r.Get("/dashboards/", reqSignedIn, Index)
 	r.Get("/dashboards/*", reqSignedIn, Index)
 
+	r.Get("/explore/", reqEditorRole, Index)
+	r.Get("/explore/*", reqEditorRole, Index)
+
 	r.Get("/playlists/", reqSignedIn, Index)
 	r.Get("/playlists/*", reqSignedIn, Index)
 	r.Get("/alerting/", reqSignedIn, Index)

+ 1 - 1
pkg/api/dtos/models.go

@@ -52,7 +52,7 @@ type UserStars struct {
 
 func GetGravatarUrl(text string) string {
 	if setting.DisableGravatar {
-		return "/public/img/user_profile.png"
+		return setting.AppSubUrl + "/public/img/user_profile.png"
 	}
 
 	if text == "" {

+ 11 - 0
pkg/api/frontendsettings.go

@@ -85,6 +85,13 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
 				dsMap["database"] = ds.Database
 				dsMap["url"] = url
 			}
+
+			if ds.Type == m.DS_INFLUXDB_IFQL {
+				dsMap["username"] = ds.User
+				dsMap["password"] = ds.Password
+				dsMap["database"] = ds.Database
+				dsMap["url"] = url
+			}
 		}
 
 		if ds.Type == m.DS_ES {
@@ -95,6 +102,10 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
 			dsMap["database"] = ds.Database
 		}
 
+		if ds.Type == m.DS_INFLUXDB_IFQL {
+			dsMap["database"] = ds.Database
+		}
+
 		if ds.Type == m.DS_PROMETHEUS {
 			// add unproxied server URL for link to Prometheus web UI
 			dsMap["directUrl"] = ds.Url

+ 17 - 3
pkg/api/index.go

@@ -128,7 +128,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 		Children: dashboardChildNavs,
 	})
 
-	if setting.ExploreEnabled {
+	if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
 		data.NavTree = append(data.NavTree, &dtos.NavLink{
 			Text:     "Explore",
 			Id:       "explore",
@@ -233,7 +233,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 		}
 	}
 
-	if c.OrgRole == m.ROLE_ADMIN {
+	if c.IsGrafanaAdmin || c.OrgRole == m.ROLE_ADMIN {
 		cfgNode := &dtos.NavLink{
 			Id:       "cfg",
 			Text:     "Configuration",
@@ -287,10 +287,24 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 			},
 		}
 
-		if c.IsGrafanaAdmin {
+		if c.OrgRole != m.ROLE_ADMIN {
+			cfgNode = &dtos.NavLink{
+				Id:       "cfg",
+				Text:     "Configuration",
+				SubTitle: "Organization: " + c.OrgName,
+				Icon:     "gicon gicon-cog",
+				Url:      setting.AppSubUrl + "/admin/users",
+				Children: make([]*dtos.NavLink, 0),
+			}
+		}
+
+		if c.OrgRole == m.ROLE_ADMIN && c.IsGrafanaAdmin {
 			cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
 				Divider: true, HideFromTabs: true, Id: "admin-divider", Text: "Text",
 			})
+		}
+
+		if c.IsGrafanaAdmin {
 			cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
 				Text:         "Server Admin",
 				HideFromTabs: true,

+ 1 - 0
pkg/models/dashboards.go

@@ -254,6 +254,7 @@ type DashboardProvisioning struct {
 	DashboardId int64
 	Name        string
 	ExternalId  string
+	CheckSum    string
 	Updated     int64
 }
 

+ 1 - 0
pkg/models/datasource.go

@@ -12,6 +12,7 @@ const (
 	DS_GRAPHITE      = "graphite"
 	DS_INFLUXDB      = "influxdb"
 	DS_INFLUXDB_08   = "influxdb_08"
+	DS_INFLUXDB_IFQL = "influxdb-ifql"
 	DS_ES            = "elasticsearch"
 	DS_OPENTSDB      = "opentsdb"
 	DS_CLOUDWATCH    = "cloudwatch"

+ 4 - 0
pkg/services/provisioning/dashboards/config_reader.go

@@ -81,6 +81,10 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
 		if dashboards[i].OrgId == 0 {
 			dashboards[i].OrgId = 1
 		}
+
+		if dashboards[i].UpdateIntervalSeconds == 0 {
+			dashboards[i].UpdateIntervalSeconds = 3
+		}
 	}
 
 	return dashboards, nil

+ 7 - 3
pkg/services/provisioning/dashboards/config_reader_test.go

@@ -22,7 +22,7 @@ func TestDashboardsAsConfig(t *testing.T) {
 			cfg, err := cfgProvider.readConfig()
 			So(err, ShouldBeNil)
 
-			validateDashboardAsConfig(cfg)
+			validateDashboardAsConfig(t, cfg)
 		})
 
 		Convey("Can read config file in version 0 format", func() {
@@ -30,7 +30,7 @@ func TestDashboardsAsConfig(t *testing.T) {
 			cfg, err := cfgProvider.readConfig()
 			So(err, ShouldBeNil)
 
-			validateDashboardAsConfig(cfg)
+			validateDashboardAsConfig(t, cfg)
 		})
 
 		Convey("Should skip invalid path", func() {
@@ -56,7 +56,9 @@ func TestDashboardsAsConfig(t *testing.T) {
 		})
 	})
 }
-func validateDashboardAsConfig(cfg []*DashboardsAsConfig) {
+func validateDashboardAsConfig(t *testing.T, cfg []*DashboardsAsConfig) {
+	t.Helper()
+
 	So(len(cfg), ShouldEqual, 2)
 
 	ds := cfg[0]
@@ -68,6 +70,7 @@ func validateDashboardAsConfig(cfg []*DashboardsAsConfig) {
 	So(len(ds.Options), ShouldEqual, 1)
 	So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
 	So(ds.DisableDeletion, ShouldBeTrue)
+	So(ds.UpdateIntervalSeconds, ShouldEqual, 10)
 
 	ds2 := cfg[1]
 	So(ds2.Name, ShouldEqual, "default")
@@ -78,4 +81,5 @@ func validateDashboardAsConfig(cfg []*DashboardsAsConfig) {
 	So(len(ds2.Options), ShouldEqual, 1)
 	So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
 	So(ds2.DisableDeletion, ShouldBeFalse)
+	So(ds2.UpdateIntervalSeconds, ShouldEqual, 3)
 }

+ 53 - 14
pkg/services/provisioning/dashboards/file_reader.go

@@ -4,12 +4,14 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
 	"time"
 
 	"github.com/grafana/grafana/pkg/services/dashboards"
+	"github.com/grafana/grafana/pkg/util"
 
 	"github.com/grafana/grafana/pkg/bus"
 
@@ -19,8 +21,6 @@ import (
 )
 
 var (
-	checkDiskForChangesInterval = time.Second * 3
-
 	ErrFolderNameMissing = errors.New("Folder name missing")
 )
 
@@ -47,15 +47,25 @@ func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReade
 		log.Error("Cannot read directory", "error", err)
 	}
 
-	absPath, err := filepath.Abs(path)
+	copy := path
+	path, err := filepath.Abs(path)
 	if err != nil {
 		log.Error("Could not create absolute path ", "path", path)
-		absPath = path //if .Abs return an error we fallback to path
+	}
+
+	path, err = filepath.EvalSymlinks(path)
+	if err != nil {
+		log.Error("Failed to read content of symlinked path: %s", path)
+	}
+
+	if path == "" {
+		path = copy
+		log.Info("falling back to original path due to EvalSymlink/Abs failure")
 	}
 
 	return &fileReader{
 		Cfg:              cfg,
-		Path:             absPath,
+		Path:             path,
 		log:              log,
 		dashboardService: dashboards.NewProvisioningService(),
 	}, nil
@@ -66,7 +76,7 @@ func (fr *fileReader) ReadAndListen(ctx context.Context) error {
 		fr.log.Error("failed to search for dashboards", "error", err)
 	}
 
-	ticker := time.NewTicker(checkDiskForChangesInterval)
+	ticker := time.NewTicker(time.Duration(int64(time.Second) * fr.Cfg.UpdateIntervalSeconds))
 
 	running := false
 
@@ -159,15 +169,20 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
 	}
 
 	provisionedData, alreadyProvisioned := provisionedDashboardRefs[path]
-	upToDate := alreadyProvisioned && provisionedData.Updated == resolvedFileInfo.ModTime().Unix()
+	upToDate := alreadyProvisioned && provisionedData.Updated >= resolvedFileInfo.ModTime().Unix()
 
-	dash, err := fr.readDashboardFromFile(path, resolvedFileInfo.ModTime(), folderId)
+	jsonFile, err := fr.readDashboardFromFile(path, resolvedFileInfo.ModTime(), folderId)
 	if err != nil {
 		fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
 		return provisioningMetadata, nil
 	}
 
+	if provisionedData != nil && jsonFile.checkSum == provisionedData.CheckSum {
+		upToDate = true
+	}
+
 	// keeps track of what uid's and title's we have already provisioned
+	dash := jsonFile.dashboard
 	provisioningMetadata.uid = dash.Dashboard.Uid
 	provisioningMetadata.title = dash.Dashboard.Title
 
@@ -185,7 +200,13 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
 	}
 
 	fr.log.Debug("saving new dashboard", "file", path)
-	dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime().Unix()}
+	dp := &models.DashboardProvisioning{
+		ExternalId: path,
+		Name:       fr.Cfg.Name,
+		Updated:    resolvedFileInfo.ModTime().Unix(),
+		CheckSum:   jsonFile.checkSum,
+	}
+
 	_, err = fr.dashboardService.SaveProvisionedDashboard(dash, dp)
 	return provisioningMetadata, err
 }
@@ -283,14 +304,30 @@ func validateWalkablePath(fileInfo os.FileInfo) (bool, error) {
 	return true, nil
 }
 
-func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time, folderId int64) (*dashboards.SaveDashboardDTO, error) {
+type dashboardJsonFile struct {
+	dashboard    *dashboards.SaveDashboardDTO
+	checkSum     string
+	lastModified time.Time
+}
+
+func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time, folderId int64) (*dashboardJsonFile, error) {
 	reader, err := os.Open(path)
 	if err != nil {
 		return nil, err
 	}
 	defer reader.Close()
 
-	data, err := simplejson.NewFromReader(reader)
+	all, err := ioutil.ReadAll(reader)
+	if err != nil {
+		return nil, err
+	}
+
+	checkSum, err := util.Md5SumString(string(all))
+	if err != nil {
+		return nil, err
+	}
+
+	data, err := simplejson.NewJson(all)
 	if err != nil {
 		return nil, err
 	}
@@ -300,7 +337,11 @@ func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time,
 		return nil, err
 	}
 
-	return dash, nil
+	return &dashboardJsonFile{
+		dashboard:    dash,
+		checkSum:     checkSum,
+		lastModified: lastModified,
+	}, nil
 }
 
 type provisioningMetadata struct {
@@ -328,7 +369,6 @@ func (checker provisioningSanityChecker) track(pm provisioningMetadata) {
 	if len(pm.title) > 0 {
 		checker.titleUsage[pm.title] += 1
 	}
-
 }
 
 func (checker provisioningSanityChecker) logWarnings(log log.Logger) {
@@ -343,5 +383,4 @@ func (checker provisioningSanityChecker) logWarnings(log log.Logger) {
 			log.Error("the same 'title' is used more than once", "title", title, "provider", checker.provisioningProvider)
 		}
 	}
-
 }

+ 39 - 0
pkg/services/provisioning/dashboards/file_reader_linux_test.go

@@ -0,0 +1,39 @@
+// +build linux
+
+package dashboards
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/grafana/grafana/pkg/log"
+)
+
+var (
+	symlinkedFolder = "testdata/test-dashboards/symlink"
+)
+
+func TestProvsionedSymlinkedFolder(t *testing.T) {
+	cfg := &DashboardsAsConfig{
+		Name:    "Default",
+		Type:    "file",
+		OrgId:   1,
+		Folder:  "",
+		Options: map[string]interface{}{"path": symlinkedFolder},
+	}
+
+	reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
+	if err != nil {
+		t.Error("expected err to be nil")
+	}
+
+	want, err := filepath.Abs(containingId)
+
+	if err != nil {
+		t.Errorf("expected err to be nill")
+	}
+
+	if reader.Path != want {
+		t.Errorf("got %s want %s", reader.Path, want)
+	}
+}

+ 7 - 4
pkg/services/provisioning/dashboards/file_reader_test.go

@@ -49,13 +49,16 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
 		})
 
 		Convey("using full path", func() {
-			cfg.Options["folder"] = "/var/lib/grafana/dashboards"
+			fullPath := "/var/lib/grafana/dashboards"
+			if runtime.GOOS == "windows" {
+				fullPath = `c:\var\lib\grafana`
+			}
+
+			cfg.Options["folder"] = fullPath
 			reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
 			So(err, ShouldBeNil)
 
-			if runtime.GOOS != "windows" {
-				So(reader.Path, ShouldEqual, "/var/lib/grafana/dashboards")
-			}
+			So(reader.Path, ShouldEqual, fullPath)
 			So(filepath.IsAbs(reader.Path), ShouldBeTrue)
 		})
 

+ 1 - 0
pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml

@@ -6,6 +6,7 @@ providers:
   folder: 'developers'
   editable: true
   disableDeletion: true
+  updateIntervalSeconds: 10
   type: file
   options:
     path: /var/lib/grafana/dashboards

+ 1 - 0
pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml

@@ -3,6 +3,7 @@
   folder: 'developers'
   editable: true
   disableDeletion: true
+  updateIntervalSeconds: 10
   type: file
   options:
     path: /var/lib/grafana/dashboards

+ 1 - 0
pkg/services/provisioning/dashboards/testdata/test-dashboards/symlink

@@ -0,0 +1 @@
+containing-id/

+ 40 - 35
pkg/services/provisioning/dashboards/types.go

@@ -10,23 +10,25 @@ import (
 )
 
 type DashboardsAsConfig struct {
-	Name            string
-	Type            string
-	OrgId           int64
-	Folder          string
-	Editable        bool
-	Options         map[string]interface{}
-	DisableDeletion bool
+	Name                  string
+	Type                  string
+	OrgId                 int64
+	Folder                string
+	Editable              bool
+	Options               map[string]interface{}
+	DisableDeletion       bool
+	UpdateIntervalSeconds int64
 }
 
 type DashboardsAsConfigV0 struct {
-	Name            string                 `json:"name" yaml:"name"`
-	Type            string                 `json:"type" yaml:"type"`
-	OrgId           int64                  `json:"org_id" yaml:"org_id"`
-	Folder          string                 `json:"folder" yaml:"folder"`
-	Editable        bool                   `json:"editable" yaml:"editable"`
-	Options         map[string]interface{} `json:"options" yaml:"options"`
-	DisableDeletion bool                   `json:"disableDeletion" yaml:"disableDeletion"`
+	Name                  string                 `json:"name" yaml:"name"`
+	Type                  string                 `json:"type" yaml:"type"`
+	OrgId                 int64                  `json:"org_id" yaml:"org_id"`
+	Folder                string                 `json:"folder" yaml:"folder"`
+	Editable              bool                   `json:"editable" yaml:"editable"`
+	Options               map[string]interface{} `json:"options" yaml:"options"`
+	DisableDeletion       bool                   `json:"disableDeletion" yaml:"disableDeletion"`
+	UpdateIntervalSeconds int64                  `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"`
 }
 
 type ConfigVersion struct {
@@ -38,13 +40,14 @@ type DashboardAsConfigV1 struct {
 }
 
 type DashboardProviderConfigs struct {
-	Name            string                 `json:"name" yaml:"name"`
-	Type            string                 `json:"type" yaml:"type"`
-	OrgId           int64                  `json:"orgId" yaml:"orgId"`
-	Folder          string                 `json:"folder" yaml:"folder"`
-	Editable        bool                   `json:"editable" yaml:"editable"`
-	Options         map[string]interface{} `json:"options" yaml:"options"`
-	DisableDeletion bool                   `json:"disableDeletion" yaml:"disableDeletion"`
+	Name                  string                 `json:"name" yaml:"name"`
+	Type                  string                 `json:"type" yaml:"type"`
+	OrgId                 int64                  `json:"orgId" yaml:"orgId"`
+	Folder                string                 `json:"folder" yaml:"folder"`
+	Editable              bool                   `json:"editable" yaml:"editable"`
+	Options               map[string]interface{} `json:"options" yaml:"options"`
+	DisableDeletion       bool                   `json:"disableDeletion" yaml:"disableDeletion"`
+	UpdateIntervalSeconds int64                  `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"`
 }
 
 func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) {
@@ -68,13 +71,14 @@ func mapV0ToDashboardAsConfig(v0 []*DashboardsAsConfigV0) []*DashboardsAsConfig
 
 	for _, v := range v0 {
 		r = append(r, &DashboardsAsConfig{
-			Name:            v.Name,
-			Type:            v.Type,
-			OrgId:           v.OrgId,
-			Folder:          v.Folder,
-			Editable:        v.Editable,
-			Options:         v.Options,
-			DisableDeletion: v.DisableDeletion,
+			Name:                  v.Name,
+			Type:                  v.Type,
+			OrgId:                 v.OrgId,
+			Folder:                v.Folder,
+			Editable:              v.Editable,
+			Options:               v.Options,
+			DisableDeletion:       v.DisableDeletion,
+			UpdateIntervalSeconds: v.UpdateIntervalSeconds,
 		})
 	}
 
@@ -86,13 +90,14 @@ func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig {
 
 	for _, v := range dc.Providers {
 		r = append(r, &DashboardsAsConfig{
-			Name:            v.Name,
-			Type:            v.Type,
-			OrgId:           v.OrgId,
-			Folder:          v.Folder,
-			Editable:        v.Editable,
-			Options:         v.Options,
-			DisableDeletion: v.DisableDeletion,
+			Name:                  v.Name,
+			Type:                  v.Type,
+			OrgId:                 v.OrgId,
+			Folder:                v.Folder,
+			Editable:              v.Editable,
+			Options:               v.Options,
+			DisableDeletion:       v.DisableDeletion,
+			UpdateIntervalSeconds: v.UpdateIntervalSeconds,
 		})
 	}
 

+ 1 - 1
pkg/services/sqlstore/alert.go

@@ -116,7 +116,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
 	}
 
 	if query.User.OrgRole != m.ROLE_ADMIN {
-		builder.writeDashboardPermissionFilter(query.User, m.PERMISSION_EDIT)
+		builder.writeDashboardPermissionFilter(query.User, m.PERMISSION_VIEW)
 	}
 
 	builder.Write(" ORDER BY name ASC")

+ 3 - 3
pkg/services/sqlstore/alert_test.go

@@ -2,7 +2,6 @@ package sqlstore
 
 import (
 	"testing"
-
 	"time"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -110,11 +109,12 @@ func TestAlertingDataAccess(t *testing.T) {
 		})
 
 		Convey("Viewer cannot read alerts", func() {
-			alertQuery := m.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_VIEWER}}
+			viewerUser := &m.SignedInUser{OrgRole: m.ROLE_VIEWER, OrgId: 1}
+			alertQuery := m.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: viewerUser}
 			err2 := HandleAlertsQuery(&alertQuery)
 
 			So(err2, ShouldBeNil)
-			So(alertQuery.Result, ShouldHaveLength, 0)
+			So(alertQuery.Result, ShouldHaveLength, 1)
 		})
 
 		Convey("Alerts with same dashboard id and panel id should update", func() {

+ 4 - 0
pkg/services/sqlstore/migrations/dashboard_mig.go

@@ -211,4 +211,8 @@ func addDashboardMigration(mg *Migrator) {
 		"name":         "name",
 		"external_id":  "external_id",
 	})
+
+	mg.AddMigration("Add check_sum column", NewAddColumnMigration(dashboardExtrasTableV2, &Column{
+		Name: "check_sum", Type: DB_NVarchar, Length: 32, Nullable: true,
+	}))
 }

+ 10 - 3
pkg/tsdb/elasticsearch/response_parser.go

@@ -113,15 +113,22 @@ func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Qu
 				}
 			}
 
-			for k, v := range esAgg.Get("buckets").MustMap() {
-				bucket := simplejson.NewFromAny(v)
+			buckets := esAgg.Get("buckets").MustMap()
+			bucketKeys := make([]string, 0)
+			for k := range buckets {
+				bucketKeys = append(bucketKeys, k)
+			}
+			sort.Strings(bucketKeys)
+
+			for _, bucketKey := range bucketKeys {
+				bucket := simplejson.NewFromAny(buckets[bucketKey])
 				newProps := make(map[string]string, 0)
 
 				for k, v := range props {
 					newProps[k] = v
 				}
 
-				newProps["filter"] = k
+				newProps["filter"] = bucketKey
 
 				err = rp.processBuckets(bucket.MustMap(), target, series, table, newProps, depth+1)
 				if err != nil {

+ 26 - 0
pkg/util/md5.go

@@ -0,0 +1,26 @@
+package util
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"io"
+	"strings"
+)
+
+// Md5Sum calculates the md5sum of a stream
+func Md5Sum(reader io.Reader) (string, error) {
+	var returnMD5String string
+	hash := md5.New()
+	if _, err := io.Copy(hash, reader); err != nil {
+		return returnMD5String, err
+	}
+	hashInBytes := hash.Sum(nil)[:16]
+	returnMD5String = hex.EncodeToString(hashInBytes)
+	return returnMD5String, nil
+}
+
+// Md5Sum calculates the md5sum of a string
+func Md5SumString(input string) (string, error) {
+	buffer := strings.NewReader(input)
+	return Md5Sum(buffer)
+}

+ 17 - 0
pkg/util/md5_test.go

@@ -0,0 +1,17 @@
+package util
+
+import "testing"
+
+func TestMd5Sum(t *testing.T) {
+	input := "dont hash passwords with md5"
+
+	have, err := Md5SumString(input)
+	if err != nil {
+		t.Fatal("expected err to be nil")
+	}
+
+	want := "2d6a56c82d09d374643b926d3417afba"
+	if have != want {
+		t.Fatalf("expected: %s got: %s", want, have)
+	}
+}

+ 59 - 7
public/app/core/controllers/login_ctrl.ts

@@ -11,10 +11,15 @@ export class LoginCtrl {
       password: '',
     };
 
+    $scope.command = {};
+    $scope.result = '';
+
     contextSrv.sidemenu = false;
 
     $scope.oauth = config.oauth;
     $scope.oauthEnabled = _.keys(config.oauth).length > 0;
+    $scope.ldapEnabled = config.ldapEnabled;
+    $scope.authProxyEnabled = config.authProxyEnabled;
 
     $scope.disableLoginForm = config.disableLoginForm;
     $scope.disableUserSignUp = config.disableUserSignUp;
@@ -39,6 +44,43 @@ export class LoginCtrl {
       }
     };
 
+    $scope.changeView = function() {
+      let loginView = document.querySelector('#login-view');
+      let changePasswordView = document.querySelector('#change-password-view');
+
+      loginView.className += ' add';
+      setTimeout(() => {
+        loginView.className += ' hidden';
+      }, 250);
+      setTimeout(() => {
+        changePasswordView.classList.remove('hidden');
+      }, 251);
+      setTimeout(() => {
+        changePasswordView.classList.remove('remove');
+      }, 301);
+
+      setTimeout(() => {
+        document.getElementById('newPassword').focus();
+      }, 400);
+    };
+
+    $scope.changePassword = function() {
+      $scope.command.oldPassword = 'admin';
+
+      if ($scope.command.newPassword !== $scope.command.confirmNew) {
+        $scope.appEvent('alert-warning', ['New passwords do not match', '']);
+        return;
+      }
+
+      backendSrv.put('/api/user/password', $scope.command).then(function() {
+        $scope.toGrafana();
+      });
+    };
+
+    $scope.skip = function() {
+      $scope.toGrafana();
+    };
+
     $scope.loginModeChanged = function(newValue) {
       $scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
     };
@@ -65,18 +107,28 @@ export class LoginCtrl {
       }
 
       backendSrv.post('/login', $scope.formModel).then(function(result) {
-        var params = $location.search();
+        $scope.result = result;
 
-        if (params.redirect && params.redirect[0] === '/') {
-          window.location.href = config.appSubUrl + params.redirect;
-        } else if (result.redirectUrl) {
-          window.location.href = result.redirectUrl;
-        } else {
-          window.location.href = config.appSubUrl + '/';
+        if ($scope.formModel.password !== 'admin' || $scope.ldapEnabled || $scope.authProxyEnabled) {
+          $scope.toGrafana();
+          return;
         }
+        $scope.changeView();
       });
     };
 
+    $scope.toGrafana = function() {
+      var params = $location.search();
+
+      if (params.redirect && params.redirect[0] === '/') {
+        window.location.href = config.appSubUrl + params.redirect;
+      } else if ($scope.result.redirectUrl) {
+        window.location.href = $scope.result.redirectUrl;
+      } else {
+        window.location.href = config.appSubUrl + '/';
+      }
+    };
+
     $scope.init();
   }
 }

+ 18 - 15
public/app/core/services/keybindingSrv.ts

@@ -14,7 +14,7 @@ export class KeybindingSrv {
   timepickerOpen = false;
 
   /** @ngInject */
-  constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv) {
+  constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv, private contextSrv) {
     // clear out all shortcuts on route change
     $rootScope.$on('$routeChangeSuccess', () => {
       Mousetrap.reset();
@@ -177,21 +177,24 @@ export class KeybindingSrv {
       }
     });
 
-    this.bind('x', async () => {
-      if (dashboard.meta.focusPanelId) {
-        const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);
-        const datasource = await this.datasourceSrv.get(panel.datasource);
-        if (datasource && datasource.supportsExplore) {
-          const range = this.timeSrv.timeRangeForUrl();
-          const state = {
-            ...datasource.getExploreState(panel),
-            range,
-          };
-          const exploreState = encodePathComponent(JSON.stringify(state));
-          this.$location.url(`/explore/${exploreState}`);
+    // jump to explore if permissions allow
+    if (this.contextSrv.isEditor) {
+      this.bind('x', async () => {
+        if (dashboard.meta.focusPanelId) {
+          const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);
+          const datasource = await this.datasourceSrv.get(panel.datasource);
+          if (datasource && datasource.supportsExplore) {
+            const range = this.timeSrv.timeRangeForUrl();
+            const state = {
+              ...datasource.getExploreState(panel),
+              range,
+            };
+            const exploreState = encodePathComponent(JSON.stringify(state));
+            this.$location.url(`/explore/${exploreState}`);
+          }
         }
-      }
-    });
+      });
+    }
 
     // delete panel
     this.bind('p r', () => {

+ 4 - 0
public/app/core/table_model.ts

@@ -44,4 +44,8 @@ export default class TableModel {
       this.columnMap[col.text] = col;
     }
   }
+
+  addRow(row) {
+    this.rows.push(row);
+  }
 }

+ 12 - 6
public/app/features/dashboard/save_modal.ts

@@ -16,13 +16,13 @@ const template = `
 
   <form name="ctrl.saveForm" ng-submit="ctrl.save()" class="modal-content" novalidate>
     <div class="p-t-1">
-      <div class="gf-form-group" ng-if="ctrl.timeChange || ctrl.variableChange">
+      <div class="gf-form-group" ng-if="ctrl.timeChange || ctrl.variableValueChange">
 		    <gf-form-switch class="gf-form"
 			    label="Save current time range" ng-if="ctrl.timeChange" label-class="width-12" switch-class="max-width-6"
 			    checked="ctrl.saveTimerange" on-change="buildUrl()">
 		    </gf-form-switch>
 		    <gf-form-switch class="gf-form"
-			    label="Save current variables" ng-if="ctrl.variableChange" label-class="width-12" switch-class="max-width-6"
+			    label="Save current variables" ng-if="ctrl.variableValueChange" label-class="width-12" switch-class="max-width-6"
 			    checked="ctrl.saveVariables" on-change="buildUrl()">
 		    </gf-form-switch>
 	    </div>
@@ -70,7 +70,7 @@ export class SaveDashboardModalCtrl {
   saveForm: any;
   dismiss: () => void;
   timeChange = false;
-  variableChange = false;
+  variableValueChange = false;
 
   /** @ngInject */
   constructor(private dashboardSrv) {
@@ -91,18 +91,24 @@ export class SaveDashboardModalCtrl {
   }
 
   compareTemplating() {
+    //checks if variables has been added or removed, if so variables will be saved automatically
+    if (this.dashboardSrv.dash.originalTemplating.length !== this.dashboardSrv.dash.templating.list.length) {
+      return (this.variableValueChange = false);
+    }
+
+    //checks if variable value has changed
     if (this.dashboardSrv.dash.templating.list.length > 0) {
       for (let i = 0; i < this.dashboardSrv.dash.templating.list.length; i++) {
         if (
           this.dashboardSrv.dash.templating.list[i].current.text !==
           this.dashboardSrv.dash.originalTemplating[i].current.text
         ) {
-          return (this.variableChange = true);
+          return (this.variableValueChange = true);
         }
       }
-      return (this.variableChange = false);
+      return (this.variableValueChange = false);
     } else {
-      return (this.variableChange = false);
+      return (this.variableValueChange = false);
     }
   }
 

+ 41 - 3
public/app/features/dashboard/specs/save_modal.jest.ts

@@ -43,7 +43,7 @@ describe('SaveDashboardModal', () => {
       let modal = new SaveDashboardModalCtrl(fakeDashboardSrv);
 
       expect(modal.timeChange).toBe(true);
-      expect(modal.variableChange).toBe(true);
+      expect(modal.variableValueChange).toBe(true);
     });
 
     it('should hide checkboxes', () => {
@@ -54,7 +54,6 @@ describe('SaveDashboardModal', () => {
               {
                 current: {
                   selected: true,
-                  //tags: Array(0),
                   text: 'server_002',
                   value: 'server_002',
                 },
@@ -84,7 +83,46 @@ describe('SaveDashboardModal', () => {
       };
       let modal = new SaveDashboardModalCtrl(fakeDashboardSrv);
       expect(modal.timeChange).toBe(false);
-      expect(modal.variableChange).toBe(false);
+      expect(modal.variableValueChange).toBe(false);
+    });
+
+    it('should hide variable checkboxes', () => {
+      let fakeDashboardSrv = {
+        dash: {
+          templating: {
+            list: [
+              {
+                current: {
+                  selected: true,
+                  text: 'server_002',
+                  value: 'server_002',
+                },
+                name: 'Server',
+              },
+              {
+                current: {
+                  selected: true,
+                  text: 'web_002',
+                  value: 'web_002',
+                },
+                name: 'Web',
+              },
+            ],
+          },
+          originalTemplating: [
+            {
+              current: {
+                selected: true,
+                text: 'server_002',
+                value: 'server_002',
+              },
+              name: 'Server',
+            },
+          ],
+        },
+      };
+      let modal = new SaveDashboardModalCtrl(fakeDashboardSrv);
+      expect(modal.variableValueChange).toBe(false);
     });
   });
 });

+ 5 - 3
public/app/features/panel/metrics_panel_ctrl.ts

@@ -1,9 +1,9 @@
-import config from 'app/core/config';
 import $ from 'jquery';
 import _ from 'lodash';
+
+import config from 'app/core/config';
 import kbn from 'app/core/utils/kbn';
 import { PanelCtrl } from 'app/features/panel/panel_ctrl';
-
 import * as rangeUtil from 'app/core/utils/rangeutil';
 import * as dateMath from 'app/core/utils/datemath';
 import { encodePathComponent } from 'app/core/utils/location_util';
@@ -16,6 +16,7 @@ class MetricsPanelCtrl extends PanelCtrl {
   datasourceName: any;
   $q: any;
   $timeout: any;
+  contextSrv: any;
   datasourceSrv: any;
   timeSrv: any;
   templateSrv: any;
@@ -37,6 +38,7 @@ class MetricsPanelCtrl extends PanelCtrl {
     // make metrics tab the default
     this.editorTabIndex = 1;
     this.$q = $injector.get('$q');
+    this.contextSrv = $injector.get('contextSrv');
     this.datasourceSrv = $injector.get('datasourceSrv');
     this.timeSrv = $injector.get('timeSrv');
     this.templateSrv = $injector.get('templateSrv');
@@ -312,7 +314,7 @@ class MetricsPanelCtrl extends PanelCtrl {
 
   getAdditionalMenuItems() {
     const items = [];
-    if (this.datasource && this.datasource.supportsExplore) {
+    if (this.contextSrv.isEditor && this.datasource && this.datasource.supportsExplore) {
       items.push({
         text: 'Explore',
         click: 'ctrl.explore();',

+ 2 - 1
public/app/features/panel/specs/metrics_panel_ctrl.jest.ts

@@ -24,8 +24,9 @@ describe('MetricsPanelCtrl', () => {
       });
     });
 
-    describe('and has datasource set that supports explore', () => {
+    describe('and has datasource set that supports explore and user has powers', () => {
       beforeEach(() => {
+        ctrl.contextSrv = { isEditor: true };
         ctrl.datasource = { supportsExplore: true };
         additionalItems = ctrl.getAdditionalMenuItems();
       });

+ 2 - 0
public/app/features/plugins/built_in_plugins.ts

@@ -4,6 +4,7 @@ import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/modul
 import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module';
 import * as grafanaPlugin from 'app/plugins/datasource/grafana/module';
 import * as influxdbPlugin from 'app/plugins/datasource/influxdb/module';
+import * as influxdbIfqlPlugin from 'app/plugins/datasource/influxdb-ifql/module';
 import * as mixedPlugin from 'app/plugins/datasource/mixed/module';
 import * as mysqlPlugin from 'app/plugins/datasource/mysql/module';
 import * as postgresPlugin from 'app/plugins/datasource/postgres/module';
@@ -30,6 +31,7 @@ const builtInPlugins = {
   'app/plugins/datasource/opentsdb/module': opentsdbPlugin,
   'app/plugins/datasource/grafana/module': grafanaPlugin,
   'app/plugins/datasource/influxdb/module': influxdbPlugin,
+  'app/plugins/datasource/influxdb-ifql/module': influxdbIfqlPlugin,
   'app/plugins/datasource/mixed/module': mixedPlugin,
   'app/plugins/datasource/mysql/module': mysqlPlugin,
   'app/plugins/datasource/postgres/module': postgresPlugin,

+ 8 - 0
public/app/features/plugins/ds_edit_ctrl.ts

@@ -204,10 +204,18 @@ coreModule.directive('datasourceHttpSettings', function() {
     scope: {
       current: '=',
       suggestUrl: '@',
+      noDirectAccess: '@',
     },
     templateUrl: 'public/app/features/plugins/partials/ds_http_settings.html',
     link: {
       pre: function($scope, elem, attrs) {
+        // do not show access option if direct access is disabled
+        $scope.showAccessOption = $scope.noDirectAccess !== 'true';
+        $scope.showAccessHelp = false;
+        $scope.toggleAccessHelp = function() {
+          $scope.showAccessHelp = !$scope.showAccessHelp;
+        };
+
         $scope.getSuggestUrls = function() {
           return [$scope.suggestUrl];
         };

+ 3 - 3
public/app/features/plugins/partials/ds_http_settings.html

@@ -22,7 +22,7 @@
       </div>
     </div>
 
-    <div class="gf-form-inline">
+    <div class="gf-form-inline" ng-if="showAccessOption">
       <div class="gf-form max-width-30">
         <span class="gf-form-label width-7">Access</span>
         <div class="gf-form-select-wrapper max-width-24">
@@ -30,7 +30,7 @@
         </div>
       </div>
       <div class="gf-form">
-        <label class="gf-form-label query-keyword pointer" ng-click="ctrl.showAccessHelp = !ctrl.showAccessHelp">
+        <label class="gf-form-label query-keyword pointer" ng-click="toggleAccessHelp()">
           Help&nbsp;
           <i class="fa fa-caret-down" ng-show="ctrl.showAccessHelp"></i>
           <i class="fa fa-caret-right" ng-hide="ctrl.showAccessHelp">&nbsp;</i>
@@ -38,7 +38,7 @@
       </div>
     </div>
 
-    <div class="alert alert-info" ng-show="ctrl.showAccessHelp">
+    <div class="alert alert-info" ng-show="showAccessHelp">
       <div class="alert-body">
         <p>
           Access mode controls how requests to the data source will be handled.

+ 82 - 51
public/app/partials/login.html

@@ -4,70 +4,101 @@
       <img class="logo-icon" src="public/img/grafana_icon.svg" alt="Grafana" />
       <i class="icon-gf icon-gf-grafana_wordmark"></i>
     </div>
-    <div class="login-inner-box">
-      <form name="loginForm" class="login-form-group gf-form-group" ng-hide="disableLoginForm">
-        <div class="login-form">
-          <input type="text" name="username" class="gf-form-input login-form-input" required ng-model='formModel.user' placeholder={{loginHint}}
-            autofocus>
-        </div>
-        <div class="login-form">
-          <input type="password" name="password" class="gf-form-input login-form-input" required ng-model="formModel.password" id="inputPassword"
-            placeholder="password">
-        </div>
-        <div class="login-button-group">
-          <button type="submit" class="btn btn-large p-x-2" ng-click="submit();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
-            Log In
-          </button>
+    <div class="login-outer-box">
+      <div class="login-inner-box" id="login-view">
+        <form name="loginForm" class="login-form-group gf-form-group" ng-hide="disableLoginForm">
+          <div class="login-form">
+            <input type="text" name="username" class="gf-form-input login-form-input" required ng-model='formModel.user' placeholder={{loginHint}}
+              autofocus>
+          </div>
+          <div class="login-form">
+            <input type="password" name="password" class="gf-form-input login-form-input" required ng-model="formModel.password" id="inputPassword"
+              placeholder="password">
+          </div>
+          <div class="login-button-group">
+            <button type="submit" class="btn btn-large p-x-2" ng-click="submit();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
+              Log In
+            </button>
             <div class="small login-button-forgot-password">
               <a href="user/password/send-reset-email">
                 Forgot your password?
               </a>
+            </div>
           </div>
-        </div>
-      </form>
-      <div class="text-center login-divider" ng-show="oauthEnabled">
-        <div>
-          <div class="login-divider-line">
+        </form>
+        <div class="text-center login-divider" ng-show="oauthEnabled">
+          <div>
+            <div class="login-divider-line">
+            </div>
+          </div>
+          <div>
+            <span class="login-divider-text">
+              <span ng-hide="disableLoginForm">or</span>
+            </span>
+          </div>
+          <div>
+            <div class="login-divider-line">
+            </div>
           </div>
         </div>
-        <div>
-          <span class="login-divider-text">
-            <span ng-hide="disableLoginForm">or</span>
-          </span>
+        <div class="clearfix"></div>
+        <div class="login-oauth text-center" ng-show="oauthEnabled">
+          <a class="btn btn-medium btn-service btn-service--google login-btn" href="login/google" target="_self" ng-if="oauth.google">
+            <i class="btn-service-icon fa fa-google"></i>
+            Sign in with Google
+          </a>
+          <a class="btn btn-medium btn-service btn-service--github login-btn" href="login/github" target="_self" ng-if="oauth.github">
+            <i class="btn-service-icon fa fa-github"></i>
+            Sign in with GitHub
+          </a>
+          <a class="btn btn-medium btn-inverse btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self"
+            ng-if="oauth.grafana_com">
+            <i class="btn-service-icon"></i>
+            Sign in with Grafana.com
+          </a>
+          <a class="btn btn-medium btn-inverse btn-service btn-service--oauth login-btn" href="login/generic_oauth" target="_self"
+            ng-if="oauth.generic_oauth">
+            <i class="btn-service-icon fa fa-sign-in"></i>
+            Sign in with {{oauth.generic_oauth.name}}
+          </a>
         </div>
-        <div>
-          <div class="login-divider-line">
+        <div class="login-signup-box" ng-show="!disableUserSignUp">
+          <div class="login-signup-title p-r-1">
+            New to Grafana?
           </div>
+          <a href="signup" class="btn btn-medium btn-signup btn-p-x-2">
+            Sign Up
+          </a>
         </div>
       </div>
-      <div class="clearfix"></div>
-      <div class="login-oauth text-center" ng-show="oauthEnabled">
-        <a class="btn btn-medium btn-service btn-service--google login-btn" href="login/google" target="_self" ng-if="oauth.google">
-          <i class="btn-service-icon fa fa-google"></i>
-          Sign in with Google
-        </a>
-        <a class="btn btn-medium btn-service btn-service--github login-btn" href="login/github" target="_self" ng-if="oauth.github">
-          <i class="btn-service-icon fa fa-github"></i>
-          Sign in with GitHub
-        </a>
-        <a class="btn btn-medium btn-inverse btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self" ng-if="oauth.grafana_com">
-          <i class="btn-service-icon"></i>
-          Sign in with Grafana.com
-        </a>
-        <a class="btn btn-medium btn-inverse btn-service btn-service--oauth login-btn" href="login/generic_oauth" target="_self" ng-if="oauth.generic_oauth">
-          <i class="btn-service-icon fa fa-sign-in"></i>
-          Sign in with {{oauth.generic_oauth.name}}
-        </a>
-      </div>
-      <div class="login-signup-box" ng-show="!disableUserSignUp">
-        <div class="login-signup-title p-r-1">
-          New to Grafana?
+      <div class="login-inner-box remove hidden" id="change-password-view">
+        <div class="text-left login-change-password-info">
+          <h5>Change Password</h5>
+          Before you can get started with awesome dashboards we need you to make your account more secure by changing your password.
+          <br />You can change your password again later.
         </div>
-        <a href="signup" class="btn btn-medium btn-signup btn-p-x-2">
-          Sign Up
-        </a>
+        <form class="login-form-group gf-form-group">
+          <div class="login-form">
+            <input type="password" id="newPassword" name="newPassword" class="gf-form-input login-form-input" required ng-model='command.newPassword'
+              placeholder="New password">
+          </div>
+          <div class="login-form">
+            <input type="password" name="confirmNew" class="gf-form-input login-form-input" required ng-model="command.confirmNew" placeholder="Confirm new password">
+          </div>
+          <div class="login-button-group login-button-group--right text-right">
+            <a class="btn btn-link" ng-click="skip();">
+              Skip
+              <info-popover mode="no-padding">
+                If you skip you will be promted to change password next time you login.
+              </info-popover>
+            </a>
+            <button type="submit" class="btn btn-large p-x-2" ng-click="changePassword();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-success': loginForm.$valid}">
+              Save
+            </button>
+          </div>
+        </form>
       </div>
+      <div class="clearfix"></div>
     </div>
-    <div class="clearfix"></div>
   </div>
 </div>

+ 26 - 0
public/app/plugins/datasource/influxdb-ifql/README.md

@@ -0,0 +1,26 @@
+# InfluxDB (IFQL) Datasource [BETA] -  Native Plugin
+
+Grafana ships with **built in** support for InfluxDB (>= 1.4.1).
+
+Use this datasource if you want to use IFQL to query your InfluxDB.
+Feel free to run this datasource side-by-side with the non-IFQL datasource.
+If you point both datasources to the same InfluxDB instance, you can switch query mode by switching the datasources.
+
+Read more about IFQL here:
+
+[https://github.com/influxdata/ifql](https://github.com/influxdata/ifql)
+
+Read more about InfluxDB here:
+
+[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
+
+## Roadmap
+
+- Sync Grafana time ranges with `range()`
+- Template variable expansion
+- Syntax highlighting
+- Tab completion (functions, values)
+- Result helpers (result counts, table previews)
+- Annotations support
+- Alerting integration
+- Explore UI integration

+ 233 - 0
public/app/plugins/datasource/influxdb-ifql/datasource.ts

@@ -0,0 +1,233 @@
+import _ from 'lodash';
+
+import * as dateMath from 'app/core/utils/datemath';
+
+import { getTableModelFromResult, getTimeSeriesFromResult, parseResults } from './response_parser';
+
+function serializeParams(params) {
+  if (!params) {
+    return '';
+  }
+
+  return _.reduce(
+    params,
+    (memo, value, key) => {
+      if (value === null || value === undefined) {
+        return memo;
+      }
+      memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+      return memo;
+    },
+    []
+  ).join('&');
+}
+
+const MAX_SERIES = 20;
+export default class InfluxDatasource {
+  type: string;
+  url: string;
+  username: string;
+  password: string;
+  name: string;
+  orgName: string;
+  database: any;
+  basicAuth: any;
+  withCredentials: any;
+  interval: any;
+  supportAnnotations: boolean;
+  supportMetrics: boolean;
+
+  /** @ngInject */
+  constructor(instanceSettings, private backendSrv, private templateSrv) {
+    this.type = 'influxdb-ifql';
+    this.url = instanceSettings.url.trim();
+
+    this.username = instanceSettings.username;
+    this.password = instanceSettings.password;
+    this.name = instanceSettings.name;
+    this.orgName = instanceSettings.orgName || 'defaultorgname';
+    this.database = instanceSettings.database;
+    this.basicAuth = instanceSettings.basicAuth;
+    this.withCredentials = instanceSettings.withCredentials;
+    this.interval = (instanceSettings.jsonData || {}).timeInterval;
+    this.supportAnnotations = true;
+    this.supportMetrics = true;
+  }
+
+  prepareQueries(options) {
+    const targets = _.cloneDeep(options.targets);
+    const timeFilter = this.getTimeFilter(options);
+    options.scopedVars.range = { value: timeFilter };
+
+    // Filter empty queries and replace grafana variables
+    const queryTargets = targets.filter(t => t.query).map(t => {
+      const interpolated = this.templateSrv.replace(t.query, options.scopedVars);
+      return {
+        ...t,
+        query: interpolated,
+      };
+    });
+
+    return queryTargets;
+  }
+
+  query(options) {
+    const queryTargets = this.prepareQueries(options);
+    if (queryTargets.length === 0) {
+      return Promise.resolve({ data: [] });
+    }
+
+    const queries = queryTargets.map(target => {
+      const { query, resultFormat } = target;
+
+      if (resultFormat === 'table') {
+        return (
+          this._seriesQuery(query, options)
+            .then(response => parseResults(response.data))
+            // Keep only first result from each request
+            .then(results => results[0])
+            .then(getTableModelFromResult)
+        );
+      } else {
+        return this._seriesQuery(query, options)
+          .then(response => parseResults(response.data))
+          .then(results => results.map(getTimeSeriesFromResult));
+      }
+    });
+
+    return Promise.all(queries).then((series: any) => {
+      let seriesList = _.flattenDeep(series).slice(0, MAX_SERIES);
+      return { data: seriesList };
+    });
+  }
+
+  annotationQuery(options) {
+    if (!options.annotation.query) {
+      return Promise.reject({
+        message: 'Query missing in annotation definition',
+      });
+    }
+
+    var timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw });
+    var query = options.annotation.query.replace('$timeFilter', timeFilter);
+    query = this.templateSrv.replace(query, null, 'regex');
+
+    return {};
+  }
+
+  metricFindQuery(query: string, options?: any) {
+    // TODO not implemented
+    var interpolated = this.templateSrv.replace(query, null, 'regex');
+
+    return this._seriesQuery(interpolated, options).then(_.curry(parseResults)(query));
+  }
+
+  _seriesQuery(query: string, options?: any) {
+    if (!query) {
+      return Promise.resolve({ data: '' });
+    }
+    return this._influxRequest('POST', '/v1/query', { q: query }, options);
+  }
+
+  testDatasource() {
+    const query = `from(db:"${this.database}") |> last()`;
+
+    return this._influxRequest('POST', '/v1/query', { q: query })
+      .then(res => {
+        if (res && res.trim()) {
+          return { status: 'success', message: 'Data source connected and database found.' };
+        }
+        return {
+          status: 'error',
+          message:
+            'Data source connected, but has no data. Verify the "Database" field and make sure the database has data.',
+        };
+      })
+      .catch(err => {
+        return { status: 'error', message: err.message };
+      });
+  }
+
+  _influxRequest(method: string, url: string, data: any, options?: any) {
+    let params: any = {
+      orgName: this.orgName,
+    };
+
+    if (this.username) {
+      params.u = this.username;
+      params.p = this.password;
+    }
+
+    // data sent as GET param
+    _.extend(params, data);
+    data = null;
+
+    let req: any = {
+      method: method,
+      url: this.url + url,
+      params: params,
+      data: data,
+      precision: 'ms',
+      inspect: { type: this.type },
+      paramSerializer: serializeParams,
+    };
+
+    req.headers = req.headers || {};
+    if (this.basicAuth || this.withCredentials) {
+      req.withCredentials = true;
+    }
+    if (this.basicAuth) {
+      req.headers.Authorization = this.basicAuth;
+    }
+
+    return this.backendSrv.datasourceRequest(req).then(
+      result => {
+        return result;
+      },
+      function(err) {
+        if (err.status !== 0 || err.status >= 300) {
+          if (err.data && err.data.error) {
+            throw {
+              message: 'InfluxDB Error: ' + err.data.error,
+              data: err.data,
+              config: err.config,
+            };
+          } else {
+            throw {
+              message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
+              data: err.data,
+              config: err.config,
+            };
+          }
+        }
+      }
+    );
+  }
+
+  getTimeFilter(options) {
+    const from = this.getInfluxTime(options.rangeRaw.from, false);
+    const to = this.getInfluxTime(options.rangeRaw.to, true);
+    if (to === 'now') {
+      return `start: ${from}`;
+    }
+    return `start: ${from}, stop: ${to}`;
+  }
+
+  getInfluxTime(date, roundUp) {
+    if (_.isString(date)) {
+      if (date === 'now') {
+        return date;
+      }
+
+      const parts = /^now\s*-\s*(\d+)([d|h|m|s])$/.exec(date);
+      if (parts) {
+        const amount = parseInt(parts[1]);
+        const unit = parts[2];
+        return '-' + amount + unit;
+      }
+      date = dateMath.parse(date, roundUp);
+    }
+
+    return date.toISOString();
+  }
+}

+ 26 - 0
public/app/plugins/datasource/influxdb-ifql/img/influxdb_logo.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 78.8 79.9" style="enable-background:new 0 0 78.8 79.9;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#symbol_1_);}
+</style>
+<g id="influxdb_logo">
+
+		<linearGradient id="symbol_1_" gradientUnits="userSpaceOnUse" x1="41.5273" y1="41.85" x2="319.2919" y2="41.85" gradientTransform="matrix(1 0 0 -1 0 81.8)">
+		<stop  offset="0" style="stop-color:#4591ED"/>
+		<stop  offset="1" style="stop-color:#00C9FF"/>
+	</linearGradient>
+	<path id="symbol_2_" class="st0" d="M78.7,48.2L71.1,15c-0.4-1.8-2.1-3.6-3.9-4.1L32.3,0.2c-0.5-0.1-1-0.2-1.5-0.2
+		c-1.5,0-3.1,0.6-4,1.5l-25,23.2c-1.3,1.2-2.1,3.6-1.7,5.4l8.1,35.5c0.4,1.8,2.1,3.6,3.9,4.1l32.6,10c0.5,0.1,1,0.2,1.5,0.2
+		c1.5,0,3.1-0.6,4-1.5l26.7-24.8C78.4,52.4,79.1,50,78.7,48.2z M35.9,8l23.9,7.3c0.9,0.3,0.9,0.7,0,0.9l-12.6,2.9
+		c-1,0.2-2.3-0.2-2.9-0.9l-8.8-9.5C34.8,8.1,35,7.8,35.9,8z M50.8,50.9c0.2,1-0.4,1.5-1.3,1.2l-25.8-7.9c-0.9-0.3-1.1-1.1-0.4-1.7
+		l19.8-18.4c0.7-0.7,1.5-0.4,1.7,0.5L50.8,50.9z M8.3,27.5L29.3,8c0.7-0.7,1.8-0.6,2.5,0.1l10.5,11.3c0.7,0.7,0.6,1.8-0.1,2.5
+		l-21,19.5c-0.7,0.7-1.8,0.6-2.5-0.1L8.2,30C7.6,29.3,7.6,28.2,8.3,27.5z M13.4,58.5L7.8,34.2c-0.2-1,0.1-1.1,0.8-0.4l8.8,9.5
+		c0.7,0.7,1,2.1,0.7,3l-3.8,12.3C14.1,59.4,13.6,59.4,13.4,58.5z M44.1,72.6l-27.3-8.4c-0.9-0.3-1.5-1.3-1.2-2.2l4.5-14.8
+		c0.3-0.9,1.3-1.5,2.2-1.2l27.3,8.4c0.9,0.3,1.5,1.3,1.2,2.2l-4.5,14.8C46,72.4,45,72.9,44.1,72.6z M68.4,52.7l-18.3,17
+		c-0.7,0.7-1.1,0.4-0.8-0.5l3.8-12.3c0.3-0.9,1.3-1.9,2.3-2.1L68,51.9C68.9,51.7,69.1,52.1,68.4,52.7z M70.4,49.1l-15.1,3.4
+		c-1,0.2-1.9-0.4-2.1-1.3l-6.4-27.9c-0.2-1,0.4-1.9,1.3-2.1l15.1-3.4c1-0.2,1.9,0.4,2.1,1.3L71.7,47C71.9,47.9,71.3,48.9,70.4,49.1z
+		"/>
+</g>
+</svg>

+ 17 - 0
public/app/plugins/datasource/influxdb-ifql/module.ts

@@ -0,0 +1,17 @@
+import InfluxDatasource from './datasource';
+import { InfluxIfqlQueryCtrl } from './query_ctrl';
+
+class InfluxConfigCtrl {
+  static templateUrl = 'partials/config.html';
+}
+
+class InfluxAnnotationsQueryCtrl {
+  static templateUrl = 'partials/annotations.editor.html';
+}
+
+export {
+  InfluxDatasource as Datasource,
+  InfluxIfqlQueryCtrl as QueryCtrl,
+  InfluxConfigCtrl as ConfigCtrl,
+  InfluxAnnotationsQueryCtrl as AnnotationsQueryCtrl,
+};

+ 24 - 0
public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html

@@ -0,0 +1,24 @@
+
+<div class="gf-form-group">
+	<div class="gf-form">
+		<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="select text from events where $timeFilter limit 1000"></input>
+	</div>
+</div>
+
+<h5 class="section-heading">Field mappings <tip>If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
+<div class="gf-form-group">
+	<div class="gf-form-inline">
+		<div class="gf-form">
+			<span class="gf-form-label width-4">Text</span>
+			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.textColumn' placeholder=""></input>
+		</div>
+		<div class="gf-form">
+			<span class="gf-form-label width-4">Tags</span>
+			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.tagsColumn' placeholder=""></input>
+		</div>
+		<div class="gf-form" ng-show="ctrl.annotation.titleColumn">
+			<span class="gf-form-label width-4">Title <em class="muted">(deprecated)</em></span>
+			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.titleColumn' placeholder=""></input>
+		</div>
+	</div>
+</div>

+ 24 - 0
public/app/plugins/datasource/influxdb-ifql/partials/config.html

@@ -0,0 +1,24 @@
+<datasource-http-settings current="ctrl.current" no-direct-access="true" suggest-url="http://localhost:8093">
+</datasource-http-settings>
+
+<h3 class="page-heading">InfluxDB Details</h3>
+
+<div class="gf-form-group">
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-30">
+			<span class="gf-form-label width-7">Default Database</span>
+			<input type="text" class="gf-form-input" ng-model='ctrl.current.database' placeholder="" required></input>
+		</div>
+	</div>
+
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-15">
+			<span class="gf-form-label width-7">User</span>
+			<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
+		</div>
+		<div class="gf-form max-width-15">
+			<span class="gf-form-label width-7">Password</span>
+			<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
+		</div>
+	</div>
+</div>

+ 24 - 0
public/app/plugins/datasource/influxdb-ifql/partials/query.editor.html

@@ -0,0 +1,24 @@
+<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
+
+  <div class="gf-form">
+    <textarea rows="3" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" placeholder="IFQL Query" ng-model-onblur
+      ng-change="ctrl.refresh()"></textarea>
+  </div>
+  <div class="gf-form-inline">
+    <div class="gf-form">
+      <label class="gf-form-label query-keyword">FORMAT AS</label>
+      <div class="gf-form-select-wrapper">
+        <select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats"
+          ng-change="ctrl.refresh()"></select>
+      </div>
+    </div>
+    <div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'">
+      <label class="gf-form-label query-keyword">ALIAS BY</label>
+      <input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
+    </div>
+    <div class="gf-form gf-form--grow">
+      <div class="gf-form-label gf-form-label--grow"></div>
+    </div>
+  </div>
+
+</query-editor-row>

+ 24 - 0
public/app/plugins/datasource/influxdb-ifql/plugin.json

@@ -0,0 +1,24 @@
+{
+  "type": "datasource",
+  "name": "InfluxDB (IFQL) [BETA]",
+  "id": "influxdb-ifql",
+  "defaultMatchFormat": "regex values",
+  "metrics": true,
+  "annotations": false,
+  "alerting": false,
+  "queryOptions": {
+    "minInterval": true
+  },
+  "info": {
+    "description": "InfluxDB Data Source for IFQL Queries for Grafana",
+    "author": {
+      "name": "Grafana Project",
+      "url": "https://grafana.com"
+    },
+    "logos": {
+      "small": "img/influxdb_logo.svg",
+      "large": "img/influxdb_logo.svg"
+    },
+    "version": "5.1.0"
+  }
+}

+ 27 - 0
public/app/plugins/datasource/influxdb-ifql/query_ctrl.ts

@@ -0,0 +1,27 @@
+import { QueryCtrl } from 'app/plugins/sdk';
+
+function makeDefaultQuery(database) {
+  return `from(db: "${database}")
+  |> range($range)
+  |> limit(n:1000)
+`;
+}
+export class InfluxIfqlQueryCtrl extends QueryCtrl {
+  static templateUrl = 'partials/query.editor.html';
+
+  resultFormats: any[];
+
+  /** @ngInject **/
+  constructor($scope, $injector) {
+    super($scope, $injector);
+
+    if (this.target.query === undefined) {
+      this.target.query = makeDefaultQuery(this.datasource.database);
+    }
+    this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }];
+  }
+
+  getCollapsedText() {
+    return this.target.query;
+  }
+}

+ 88 - 0
public/app/plugins/datasource/influxdb-ifql/response_parser.ts

@@ -0,0 +1,88 @@
+import Papa from 'papaparse';
+import groupBy from 'lodash/groupBy';
+
+import TableModel from 'app/core/table_model';
+
+const filterColumnKeys = key => key && key[0] !== '_' && key !== 'result' && key !== 'table';
+
+const IGNORE_FIELDS_FOR_NAME = ['result', '', 'table'];
+export const getNameFromRecord = record => {
+  // Measurement and field
+  const metric = [record._measurement, record._field];
+
+  // Add tags
+  const tags = Object.keys(record)
+    .filter(key => key[0] !== '_')
+    .filter(key => IGNORE_FIELDS_FOR_NAME.indexOf(key) === -1)
+    .map(key => `${key}=${record[key]}`);
+
+  return [...metric, ...tags].join(' ');
+};
+
+const parseCSV = (input: string) =>
+  Papa.parse(input, {
+    header: true,
+    comments: '#',
+  }).data;
+
+export const parseValue = (input: string) => {
+  const value = parseFloat(input);
+  return isNaN(value) ? null : value;
+};
+
+export const parseTime = (input: string) => Date.parse(input);
+
+export function parseResults(response: string): any[] {
+  return response.trim().split(/\n\s*\s/);
+}
+
+export function getTableModelFromResult(result: string) {
+  const data = parseCSV(result);
+
+  const table = new TableModel();
+  if (data.length > 0) {
+    // First columns are fixed
+    const firstColumns = [
+      { text: 'Time', id: '_time' },
+      { text: 'Measurement', id: '_measurement' },
+      { text: 'Field', id: '_field' },
+    ];
+
+    // Dynamically add columns for tags
+    const firstRecord = data[0];
+    const tags = Object.keys(firstRecord)
+      .filter(filterColumnKeys)
+      .map(key => ({ id: key, text: key }));
+
+    const valueColumn = { id: '_value', text: 'Value' };
+    const columns = [...firstColumns, ...tags, valueColumn];
+    columns.forEach(c => table.addColumn(c));
+
+    // Add rows
+    data.forEach(record => {
+      const row = columns.map(c => record[c.id]);
+      table.addRow(row);
+    });
+  }
+
+  return table;
+}
+
+export function getTimeSeriesFromResult(result: string) {
+  const data = parseCSV(result);
+  if (data.length === 0) {
+    return [];
+  }
+
+  // Group results by table ID (assume one table per timeseries for now)
+  const tables = groupBy(data, 'table');
+  const seriesList = Object.keys(tables)
+    .map(id => tables[id])
+    .map(series => {
+      const datapoints = series.map(record => [parseValue(record._value), parseTime(record._time)]);
+      const alias = getNameFromRecord(series[0]);
+      return { datapoints, target: alias };
+    });
+
+  return seriesList;
+}

+ 53 - 0
public/app/plugins/datasource/influxdb-ifql/specs/datasource.jest.ts

@@ -0,0 +1,53 @@
+import moment from 'moment';
+
+import { TemplateSrv } from 'app/features/templating/template_srv';
+
+import Datasource from '../datasource';
+
+describe('InfluxDB (IFQL)', () => {
+  const templateSrv = new TemplateSrv();
+  const ds = new Datasource({ url: '' }, {}, templateSrv);
+  const DEFAULT_OPTIONS = {
+    rangeRaw: { to: 'now', from: 'now - 3h' },
+    scopedVars: {},
+    targets: [],
+  };
+
+  let queries: any[];
+
+  describe('prepareQueries()', () => {
+    it('filters empty queries', () => {
+      queries = ds.prepareQueries(DEFAULT_OPTIONS);
+      expect(queries.length).toBe(0);
+
+      queries = ds.prepareQueries({
+        ...DEFAULT_OPTIONS,
+        targets: [{ query: '' }],
+      });
+      expect(queries.length).toBe(0);
+    });
+
+    it('replaces $range variable', () => {
+      queries = ds.prepareQueries({
+        ...DEFAULT_OPTIONS,
+        targets: [{ query: 'from(db: "test") |> range($range)' }],
+      });
+      expect(queries.length).toBe(1);
+      expect(queries[0].query).toBe('from(db: "test") |> range(start: -3h)');
+    });
+
+    it('replaces $range variable with custom dates', () => {
+      const to = moment();
+      const from = moment().subtract(1, 'hours');
+      queries = ds.prepareQueries({
+        ...DEFAULT_OPTIONS,
+        rangeRaw: { to, from },
+        targets: [{ query: 'from(db: "test") |> range($range)' }],
+      });
+      expect(queries.length).toBe(1);
+      const start = from.toISOString();
+      const stop = to.toISOString();
+      expect(queries[0].query).toBe(`from(db: "test") |> range(start: ${start}, stop: ${stop})`);
+    });
+  });
+});

+ 63 - 0
public/app/plugins/datasource/influxdb-ifql/specs/response_parser.jest.ts

@@ -0,0 +1,63 @@
+import {
+  getNameFromRecord,
+  getTableModelFromResult,
+  getTimeSeriesFromResult,
+  parseResults,
+  parseValue,
+} from '../response_parser';
+import response from './sample_response_csv';
+
+describe('influxdb ifql response parser', () => {
+  describe('parseResults()', () => {
+    it('expects three results', () => {
+      const results = parseResults(response);
+      expect(results.length).toBe(2);
+    });
+  });
+
+  describe('getTableModelFromResult()', () => {
+    it('expects a table model', () => {
+      const results = parseResults(response);
+      const table = getTableModelFromResult(results[0]);
+      expect(table.columns.length).toBe(6);
+      expect(table.rows.length).toBe(300);
+    });
+  });
+
+  describe('getTimeSeriesFromResult()', () => {
+    it('expects time series', () => {
+      const results = parseResults(response);
+      const series = getTimeSeriesFromResult(results[0]);
+      expect(series.length).toBe(50);
+      expect(series[0].datapoints.length).toBe(6);
+    });
+  });
+
+  describe('getNameFromRecord()', () => {
+    it('expects name based on measurements and tags', () => {
+      const record = {
+        '': '',
+        result: '',
+        table: '0',
+        _start: '2018-06-02T06:35:25.651942602Z',
+        _stop: '2018-06-02T07:35:25.651942602Z',
+        _time: '2018-06-02T06:35:31Z',
+        _value: '0',
+        _field: 'usage_guest',
+        _measurement: 'cpu',
+        cpu: 'cpu-total',
+        host: 'kenobi-3.local',
+      };
+      expect(getNameFromRecord(record)).toBe('cpu usage_guest cpu=cpu-total host=kenobi-3.local');
+    });
+  });
+
+  describe('parseValue()', () => {
+    it('parses a number', () => {
+      expect(parseValue('42.3')).toBe(42.3);
+    });
+    it('parses a non-number to null', () => {
+      expect(parseValue('foo')).toBe(null);
+    });
+  });
+});

+ 349 - 0
public/app/plugins/datasource/influxdb-ifql/specs/sample_response_csv.ts

@@ -0,0 +1,349 @@
+const result = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
+#partition,false,false,true,true,false,false,true,true,true,true
+#default,_result,,,,,,,,,
+,result,table,_start,_stop,_time,_value,_field,_measurement,cpu,host
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,81.87046761690422,usage_idle,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,82.03398300849575,usage_idle,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,76.26186906546727,usage_idle,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,79.65465465465465,usage_idle,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,70.72195853110168,usage_idle,cpu,cpu-total,kenobi-3.local
+,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,69.86746686671668,usage_idle,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.25156289072268,usage_system,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,8.045977011494253,usage_system,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,8.79560219890055,usage_system,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,8.408408408408409,usage_system,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,11.64126904821384,usage_system,cpu,cpu-total,kenobi-3.local
+,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,13.078269567391848,usage_system,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,11.877969492373094,usage_user,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9.920039980009996,usage_user,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,14.942528735632184,usage_user,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,11.936936936936936,usage_user,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,17.636772420684487,usage_user,cpu,cpu-total,kenobi-3.local
+,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,17.05426356589147,usage_user,cpu,cpu-total,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,73.1,usage_idle,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,69.03096903096903,usage_idle,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,63.63636363636363,usage_idle,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,67.86786786786787,usage_idle,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,57.4,usage_idle,cpu,cpu0,kenobi-3.local
+,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,57.8,usage_idle,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,9.6,usage_system,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,14.985014985014985,usage_system,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,14.185814185814186,usage_system,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,13.813813813813814,usage_system,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,17.9,usage_system,cpu,cpu0,kenobi-3.local
+,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,20,usage_system,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,17.3,usage_user,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,15.984015984015985,usage_user,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,22.17782217782218,usage_user,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,18.31831831831832,usage_user,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,24.7,usage_user,cpu,cpu0,kenobi-3.local
+,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,22.2,usage_user,cpu,cpu0,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,89.8,usage_idle,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,91.8,usage_idle,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,87.11288711288711,usage_idle,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,89.48948948948949,usage_idle,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,83,usage_idle,cpu,cpu1,kenobi-3.local
+,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,80.1,usage_idle,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,3.5,usage_system,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4,usage_system,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4.895104895104895,usage_system,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4.504504504504505,usage_system,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,6.3,usage_system,cpu,cpu1,kenobi-3.local
+,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,7.9,usage_system,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.7,usage_user,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4.2,usage_user,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,7.992007992007992,usage_user,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,6.006006006006006,usage_user,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,10.7,usage_user,cpu,cpu1,kenobi-3.local
+,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,12,usage_user,cpu,cpu1,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,75.17517517517517,usage_idle,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,74.82517482517483,usage_idle,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,67.9,usage_idle,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,72.47247247247248,usage_idle,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,61.63836163836164,usage_idle,cpu,cpu2,kenobi-3.local
+,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,62,usage_idle,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,8.208208208208209,usage_system,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9.99000999000999,usage_system,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,11.2,usage_system,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,10.81081081081081,usage_system,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,14.785214785214785,usage_system,cpu,cpu2,kenobi-3.local
+,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,16.2,usage_system,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,16.616616616616618,usage_user,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,15.184815184815184,usage_user,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,20.9,usage_user,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,16.716716716716718,usage_user,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,23.576423576423576,usage_user,cpu,cpu2,kenobi-3.local
+,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,21.8,usage_user,cpu,cpu2,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,89.4,usage_idle,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,92.5,usage_idle,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,86.4,usage_idle,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,88.78878878878879,usage_idle,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,80.83832335329342,usage_idle,cpu,cpu3,kenobi-3.local
+,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,79.57957957957957,usage_idle,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,3.7,usage_system,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,3.2,usage_system,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4.9,usage_system,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4.504504504504505,usage_system,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,7.584830339321357,usage_system,cpu,cpu3,kenobi-3.local
+,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,8.208208208208209,usage_system,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.9,usage_user,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4.3,usage_user,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,8.7,usage_user,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,6.706706706706707,usage_user,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,11.57684630738523,usage_user,cpu,cpu3,kenobi-3.local
+,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,12.212212212212211,usage_user,cpu,cpu3,kenobi-3.local
+
+#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string,string,string,string,string
+#partition,false,false,true,true,false,false,true,true,true,true,true,true,true
+#default,_result,,,,,,,,,,,,
+,result,table,_start,_stop,_time,_value,_field,_measurement,device,fstype,host,mode,path
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,9024180224,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9025056768,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,9024774144,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,9024638976,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,9024299008,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,9024036864,free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4290025659,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4290025659,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4290025657,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4941620,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4941620,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4941622,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,240518561792,used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,240517685248,used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,240517967872,used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,240518103040,used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,240518443008,used,disk,disk1,hfs,kenobi-3.local,rw,/
+,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,240518705152,used,disk,disk1,hfs,kenobi-3.local,rw,/
+
+`;
+
+export default result;

+ 4 - 3
public/app/plugins/panel/dashlist/editor.html

@@ -23,10 +23,11 @@
     </div>
 
     <div class="gf-form">
-      <folder-picker  root-name="All"
-                      initial-folder-id="ctrl.panel.folderId"
+      <folder-picker  initial-folder-id="ctrl.panel.folderId"
 											on-change="ctrl.onFolderChange($folder)"
-											label-class="width-6">
+                      label-class="width-6"
+                      initial-title="'All'"
+                      enable-reset="true">
 			</folder-picker>
     </div>
 

+ 3 - 2
public/app/plugins/panel/dashlist/module.ts

@@ -17,7 +17,7 @@ class DashListCtrl extends PanelCtrl {
     search: false,
     starred: true,
     headings: true,
-    folderId: 0,
+    folderId: null,
   };
 
   /** @ngInject */
@@ -85,7 +85,8 @@ class DashListCtrl extends PanelCtrl {
       limit: this.panel.limit,
       query: this.panel.query,
       tag: this.panel.tags,
-      folderId: this.panel.folderId,
+      folderIds: this.panel.folderId,
+      type: 'dash-db',
     };
 
     return this.backendSrv.search(params).then(result => {

+ 18 - 2
public/app/routes/ReactContainer.tsx

@@ -6,6 +6,7 @@ import coreModule from 'app/core/core_module';
 import { store } from 'app/stores/store';
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
+import { ContextSrv } from 'app/core/services/context_srv';
 
 function WrapInProvider(store, Component, props) {
   return (
@@ -16,16 +17,31 @@ function WrapInProvider(store, Component, props) {
 }
 
 /** @ngInject */
-export function reactContainer($route, $location, backendSrv: BackendSrv, datasourceSrv: DatasourceSrv) {
+export function reactContainer(
+  $route,
+  $location,
+  backendSrv: BackendSrv,
+  datasourceSrv: DatasourceSrv,
+  contextSrv: ContextSrv
+) {
   return {
     restrict: 'E',
     template: '',
     link(scope, elem) {
-      let component = $route.current.locals.component;
+      // Check permissions for this component
+      const { roles } = $route.current.locals;
+      if (roles && roles.length) {
+        if (!roles.some(r => contextSrv.hasRole(r))) {
+          $location.url('/');
+        }
+      }
+
+      let { component } = $route.current.locals;
       // Dynamic imports return whole module, need to extract default export
       if (component.default) {
         component = component.default;
       }
+
       const props = {
         backendSrv: backendSrv,
         datasourceSrv: datasourceSrv,

+ 1 - 0
public/app/routes/routes.ts

@@ -113,6 +113,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
     .when('/explore/:initial?', {
       template: '<react-container />',
       resolve: {
+        roles: () => ['Editor', 'Admin'],
         component: () => import(/* webpackChunkName: "explore" */ 'app/containers/Explore/Wrapper'),
       },
     })

+ 4 - 0
public/sass/components/_gf-form.scss

@@ -384,6 +384,10 @@ $input-border: 1px solid $input-border-color;
   &--header {
     margin-bottom: $gf-form-margin;
   }
+
+  &--no-padding {
+    padding-left: 0;
+  }
 }
 
 select.gf-form-input ~ .gf-form-help-icon {

+ 5 - 6
public/sass/components/_panel_singlestat.scss

@@ -7,14 +7,13 @@
 
 .singlestat-panel-value-container {
   line-height: 1;
-  position: absolute;
+  display: table-cell;
+  vertical-align: middle;
+  text-align: center;
+  position: relative;
   z-index: 1;
   font-size: 3em;
-  font-weight: bold;
-  margin: 0;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
+  font-weight: $font-weight-semi-bold;
 }
 
 .singlestat-panel-prefix {

+ 38 - 0
public/sass/pages/_login.scss

@@ -59,6 +59,14 @@ select:-webkit-autofill:focus {
   justify-content: space-between;
   width: 100%;
   margin-top: 0.5rem;
+
+  &--right {
+    justify-content: flex-end;
+
+    & .btn {
+      margin-left: 1rem;
+    }
+  }
 }
 
 .login-button-forgot-password {
@@ -75,7 +83,9 @@ select:-webkit-autofill:focus {
   align-items: stretch;
   flex-direction: column;
   position: relative;
+  justify-content: center;
   z-index: 1;
+  height: 320px;
 }
 
 .login-branding {
@@ -100,6 +110,11 @@ select:-webkit-autofill:focus {
   }
 }
 
+.login-outer-box {
+  display: flex;
+  overflow-y: hidden;
+}
+
 .login-inner-box {
   text-align: center;
   padding: 2rem 4rem;
@@ -109,6 +124,22 @@ select:-webkit-autofill:focus {
   justify-content: center;
   flex-grow: 1;
   max-width: 415px;
+  transform: tranlate(0px, 0px);
+  transition: 0.25s ease;
+
+  &.add {
+    transform: translate(0px, -320px);
+    &.hidden {
+      display: none;
+    }
+  }
+
+  &.remove {
+    transform: translate(0px, 320px);
+    &.hidden {
+      display: none;
+    }
+  }
 }
 
 .login-tab-header {
@@ -117,6 +148,13 @@ select:-webkit-autofill:focus {
   margin-bottom: 3rem;
 }
 
+.login-change-password-info {
+  padding-bottom: 1.5rem;
+
+  & h5 {
+    text-align: left;
+  }
+}
 .btn-signup {
   color: $white;
   border: 1px solid $login-border;

+ 2 - 0
public/test/specs/helpers.ts

@@ -11,6 +11,7 @@ export function ControllerTestContext() {
   this.$element = {};
   this.$sanitize = {};
   this.annotationsSrv = {};
+  this.contextSrv = {};
   this.timeSrv = new TimeSrvStub();
   this.templateSrv = new TemplateSrvStub();
   this.datasourceSrv = {
@@ -27,6 +28,7 @@ export function ControllerTestContext() {
 
   this.providePhase = function(mocks) {
     return angularMocks.module(function($provide) {
+      $provide.value('contextSrv', self.contextSrv);
       $provide.value('datasourceSrv', self.datasourceSrv);
       $provide.value('annotationsSrv', self.annotationsSrv);
       $provide.value('timeSrv', self.timeSrv);

+ 4 - 0
yarn.lock

@@ -7928,6 +7928,10 @@ pako@~1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
 
+papaparse@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.4.0.tgz#6bcdbda80873e00cfb0bdcd7a4571c72a9a40168"
+
 parallel-transform@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"