Browse Source

feat(data source variable): progress on data source as variable

Torkel Ödegaard 9 years ago
parent
commit
7349427189
100 changed files with 1726 additions and 1131 deletions
  1. 25 0
      CHANGELOG.md
  2. 17 0
      Makefile
  3. 1 3
      README.md
  4. 1 1
      build.go
  5. 2 2
      circle.yml
  6. 1 1
      docs/sources/http_api/dashboard.md
  7. 1 1
      docs/sources/plugins/index.md
  8. 2 1
      package.json
  9. 2 2
      pkg/api/cloudwatch/metrics.go
  10. 4 0
      pkg/api/dashboard_snapshot.go
  11. 0 1
      pkg/api/dataproxy.go
  12. 1 1
      pkg/api/dtos/plugins.go
  13. 8 1
      pkg/api/index.go
  14. 6 5
      pkg/api/plugins.go
  15. 2 1
      pkg/cmd/grafana-cli/commands/commands.go
  16. 12 7
      pkg/cmd/grafana-cli/commands/install_command.go
  17. 3 8
      pkg/cmd/grafana-cli/commands/remove_command.go
  18. 5 5
      pkg/cmd/grafana-cli/main.go
  19. 1 1
      pkg/models/dashboard_snapshot.go
  20. 22 0
      pkg/models/org_user.go
  21. 7 5
      pkg/plugins/dashboard_importer.go
  22. 15 9
      pkg/plugins/models.go
  23. 1 1
      pkg/services/sqlstore/dashboard.go
  24. 0 1
      pkg/services/sqlstore/plugin_setting.go
  25. 2 2
      public/app/core/directives/dropdown_typeahead.js
  26. 7 4
      public/app/core/directives/metric_segment.js
  27. 8 2
      public/app/core/directives/plugin_component.ts
  28. 0 2
      public/app/core/services/context_srv.ts
  29. 2 0
      public/app/core/table_model.ts
  30. 1 1
      public/app/core/utils/emitter.ts
  31. 1 1
      public/app/features/dashboard/viewStateSrv.js
  32. 1 0
      public/app/features/panel/all.js
  33. 110 0
      public/app/features/panel/metrics_ds_selector.ts
  34. 0 18
      public/app/features/panel/metrics_panel_ctrl.ts
  35. 4 4
      public/app/features/panel/panel_ctrl.ts
  36. 60 1
      public/app/features/panel/partials/query_editor_row.html
  37. 0 34
      public/app/features/panel/query_ctrl.ts
  38. 100 2
      public/app/features/panel/query_editor_row.ts
  39. 4 4
      public/app/features/plugins/ds_edit_ctrl.ts
  40. 4 4
      public/app/features/plugins/import_list/import_list.html
  41. 2 2
      public/app/features/plugins/import_list/import_list.ts
  42. 1 1
      public/app/features/plugins/partials/ds_edit.html
  43. 2 2
      public/app/features/plugins/partials/update_instructions.html
  44. 5 1
      public/app/features/templating/templateSrv.js
  45. 1 8
      public/app/features/templating/templateValuesSrv.js
  46. 14 51
      public/app/partials/metrics.html
  47. 2 2
      public/app/plugins/datasource/cloudwatch/partials/query.editor.html
  48. 51 50
      public/app/plugins/datasource/cloudwatch/partials/query.parameter.html
  49. 11 0
      public/app/plugins/datasource/elasticsearch/bucket_agg.js
  50. BIN
      public/app/plugins/datasource/elasticsearch/img/logo_large.png
  51. 69 104
      public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html
  52. 61 117
      public/app/plugins/datasource/elasticsearch/partials/metric_agg.html
  53. 28 29
      public/app/plugins/datasource/elasticsearch/partials/query.editor.html
  54. 4 0
      public/app/plugins/datasource/elasticsearch/query_builder.js
  55. 43 0
      public/app/plugins/datasource/elasticsearch/query_ctrl.ts
  56. 3 2
      public/app/plugins/datasource/elasticsearch/query_def.js
  57. 6 4
      public/app/plugins/datasource/grafana/partials/query.editor.html
  58. 2 2
      public/app/plugins/datasource/graphite/add_graphite_func.js
  59. 7 0
      public/app/plugins/datasource/graphite/gfunc.js
  60. 21 15
      public/app/plugins/datasource/graphite/partials/query.editor.html
  61. 2 2
      public/app/plugins/datasource/influxdb/README.md
  62. 8 8
      public/app/plugins/datasource/influxdb/influx_query.ts
  63. 86 63
      public/app/plugins/datasource/influxdb/partials/query.editor.html
  64. 1 1
      public/app/plugins/datasource/influxdb/partials/query.options.html
  65. 1 1
      public/app/plugins/datasource/influxdb/partials/query_part.html
  66. 2 2
      public/app/plugins/datasource/influxdb/query_builder.js
  67. 10 1
      public/app/plugins/datasource/influxdb/query_ctrl.ts
  68. 21 9
      public/app/plugins/datasource/influxdb/response_parser.ts
  69. 17 9
      public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts
  70. 250 220
      public/app/plugins/datasource/opentsdb/partials/query.editor.html
  71. 48 74
      public/app/plugins/datasource/prometheus/partials/query.editor.html
  72. 11 12
      public/app/plugins/panel/dashlist/module.ts
  73. 1 1
      public/app/plugins/panel/graph/graph.js
  74. 83 83
      public/app/plugins/panel/graph/module.ts
  75. 1 1
      public/app/plugins/panel/pluginlist/module.html
  76. 5 5
      public/app/plugins/panel/pluginlist/module.ts
  77. 49 0
      public/app/plugins/panel/singlestat/editor.html
  78. 152 35
      public/app/plugins/panel/singlestat/module.ts
  79. 33 28
      public/app/plugins/panel/table/module.ts
  80. 1 1
      public/app/plugins/panel/table/renderer.ts
  81. 6 8
      public/app/plugins/panel/text/module.ts
  82. 2 1
      public/app/system.conf.js
  83. 1 0
      public/sass/_grafana.scss
  84. 3 3
      public/sass/_variables.dark.scss
  85. 1 1
      public/sass/_variables.light.scss
  86. 2 4
      public/sass/base/_code.scss
  87. 6 2
      public/sass/base/_type.scss
  88. 1 1
      public/sass/components/_drop.scss
  89. 14 12
      public/sass/components/_dropdown.scss
  90. 40 8
      public/sass/components/_gf-form.scss
  91. 2 1
      public/sass/components/_panel_graph.scss
  92. 4 0
      public/sass/components/_panel_singlestat.scss
  93. 2 0
      public/sass/components/_panel_text.scss
  94. 57 4
      public/sass/components/_query_editor.scss
  95. 11 0
      public/sass/components/_query_part.scss
  96. 1 1
      public/sass/mixins/_drop_element.scss
  97. 2 1
      public/sass/utils/_utils.scss
  98. 6 0
      public/sass/utils/_widths.scss
  99. 6 1
      public/test/specs/templateSrv-specs.js
  100. 1 1
      public/test/specs/templateValuesSrv-specs.js

+ 25 - 0
CHANGELOG.md

@@ -1,3 +1,28 @@
+# 3.0.0-beta6 (unreleased)
+
+### Enhancements
+* **Singlestat**: Support for gauges in singlestat panel. closes [#3688](https://github.com/grafana/grafana/pull/3688)
+
+### Bug fixes
+* **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement,  fixes [#4726](https://github.com/grafana/grafana/issues/4726)
+* **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755)
+* **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736)
+* **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768)
+* **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760)
+* **Table panel**: Fixed issue table panel formating string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791)
+* **grafana-cli**: Improve error message when failing to install plugins due to corrupt response, fixes [#4651](https://github.com/grafana/grafana/issues/4651)
+* **Singlestat**: Fixes prefix an postfix for gauges, fixes [#4812](https://github.com/grafana/grafana/issues/4812)
+* **Singlestat**: Fixes auto-refresh on change for some options, fixes [#4809](https://github.com/grafana/grafana/issues/4809)
+
+### Breaking changes
+**Data Source Query Editors**: Issue [#3900](https://github.com/grafana/grafana/issues/3900)
+
+Query editors have been updated to use the new form styles. External data source plugins needs to be
+updated to work. Sorry to introduce breaking change this late in beta phase. We wanted to get this change
+in before 3.0 stable is released so we don't have to break data sources in next release (3.1). If you are
+a data source plugin author and want help for how the new form styles work please ask for help in
+slack channel (link to slack channel in readme).
+
 # 3.0.0-beta5 (2016-04-15)
 
 ### Bug fixes

+ 17 - 0
Makefile

@@ -0,0 +1,17 @@
+all: deps build
+
+deps:
+	go run build.go setup
+	godep restore
+	npm install
+
+build:
+	go run build.go build
+	npm run build
+
+test:
+	godep go test -v ./pkg/...
+	npm test
+
+run:
+	./bin/grafana-server

+ 1 - 3
README.md

@@ -103,8 +103,7 @@ npm (v2.5.0) and grunt (v0.4.5). Run the following:
 
 ```bash
 npm install
-npm install -g grunt-cli
-grunt
+npm run build
 ```
 
 ### Recompile backend on source change
@@ -145,4 +144,3 @@ please [sign the CLA](http://docs.grafana.org/project/cla/)
 
 Grafana is distributed under Apache 2.0 License.
 Work in progress Grafana 2.0 (with included Grafana backend)
-

+ 1 - 1
build.go

@@ -306,7 +306,7 @@ func ChangeWorkingDir(dir string) {
 }
 
 func grunt(params ...string) {
-	runPrint("./node_modules/grunt-cli/bin/grunt", params...)
+	runPrint("./node_modules/.bin/grunt", params...)
 }
 
 func setup() {

+ 2 - 2
circle.yml

@@ -25,12 +25,12 @@ test:
      # Go test
      - godep go test -v ./pkg/...
      # js tests
-     - ./node_modules/grunt-cli/bin/grunt test
+     - npm test
      - npm run coveralls
 
 deployment:
   master:
     branch: master
     owner: grafana
-    commands: 
+    commands:
       - ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}

+ 1 - 1
docs/sources/http_api/dashboard.md

@@ -191,7 +191,7 @@ Will return the home dashboard.
 
 `GET /api/dashboards/tags`
 
-Get all tabs of dashboards
+Get all tags of dashboards
 
 **Example Request**:
 

+ 1 - 1
docs/sources/plugins/index.md

@@ -15,7 +15,7 @@ Grafana already have a strong community of contributors and plugin developers.
 By making it easier to develop and install plugins we hope that the community
 can grow even stronger and develop new plugins that we would never think about.
 
-You can discover available plugins on [Grafana.net](http://grafana.net)
+You can discover available plugins on [Grafana.net](https://grafana.net)
 
 
 

+ 2 - 1
package.json

@@ -54,7 +54,7 @@
     "phantomjs-prebuilt": "^2.1.3",
     "reflect-metadata": "0.1.2",
     "rxjs": "5.0.0-beta.4",
-    "sass-lint": "^1.5.0",
+    "sass-lint": "^1.6.0",
     "systemjs": "0.19.24"
   },
   "engines": {
@@ -62,6 +62,7 @@
     "npm": "2.14.x"
   },
   "scripts": {
+    "build": "grunt",
     "test": "grunt test",
     "coveralls": "grunt karma:coveralls && rm -rf ./coverage"
   },

+ 2 - 2
pkg/api/cloudwatch/metrics.go

@@ -56,7 +56,7 @@ func init() {
 			"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
 		"AWS/ES":       {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueLength", "ReadIOPS", "WriteIOPS"},
 		"AWS/Events":   {"Invocations", "FailedInvocations", "TriggeredRules", "MatchedEvents", "ThrottledRules"},
-		"AWS/Kinesis":  {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"},
+		"AWS/Kinesis":  {"GetRecords.Bytes", "GetRecords.IteratorAge", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Records", "GetRecords.Success", "IncomingBytes", "IncomingRecords", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "ReadProvisionedThroughputExceeded", "WriteProvisionedThroughputExceeded", "IteratorAgeMilliseconds", "OutgoingBytes", "OutgoingRecords"},
 		"AWS/Lambda":   {"Invocations", "Errors", "Duration", "Throttles"},
 		"AWS/Logs":     {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
 		"AWS/ML":       {"PredictCount", "PredictFailureCount"},
@@ -88,7 +88,7 @@ func init() {
 		"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
 		"AWS/ES":               {},
 		"AWS/Events":           {"RuleName"},
-		"AWS/Kinesis":          {"StreamName"},
+		"AWS/Kinesis":          {"StreamName", "ShardID"},
 		"AWS/Lambda":           {"FunctionName"},
 		"AWS/Logs":             {"LogGroupName", "DestinationType", "FilterName"},
 		"AWS/ML":               {"MLModelId", "RequestMode"},

+ 4 - 0
pkg/api/dashboard_snapshot.go

@@ -21,6 +21,10 @@ func GetSharingOptions(c *middleware.Context) {
 }
 
 func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
+	if cmd.Name == "" {
+		cmd.Name = "Unnamed snapshot"
+	}
+
 	if cmd.External {
 		// external snapshot ref requires key and delete key
 		if cmd.Key == "" || cmd.DeleteKey == "" {

+ 0 - 1
pkg/api/dataproxy.go

@@ -41,7 +41,6 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
 			req.URL.RawQuery = reqQueryVals.Encode()
 		} else if ds.Type == m.DS_INFLUXDB {
 			req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
-			reqQueryVals.Add("db", ds.Database)
 			req.URL.RawQuery = reqQueryVals.Encode()
 			if !ds.BasicAuth {
 				req.Header.Del("Authorization")

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

@@ -48,6 +48,6 @@ func (slice PluginList) Swap(i, j int) {
 type ImportDashboardCommand struct {
 	PluginId  string                         `json:"pluginId"`
 	Path      string                         `json:"path"`
-	Reinstall bool                           `json:"reinstall"`
+	Overwrite bool                           `json:"overwrite"`
 	Inputs    []plugins.ImportDashboardInput `json:"inputs"`
 }

+ 8 - 1
pkg/api/index.go

@@ -103,6 +103,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 			}
 
 			for _, include := range plugin.Includes {
+				if !c.HasUserRole(include.Role) {
+					continue
+				}
+
 				if include.Type == "page" && include.AddToNav {
 					link := &dtos.NavLink{
 						Url:  setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug,
@@ -110,6 +114,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 					}
 					appLink.Children = append(appLink.Children, link)
 				}
+
 				if include.Type == "dashboard" && include.AddToNav {
 					link := &dtos.NavLink{
 						Url:  setting.AppSubUrl + "/dashboard/db/" + include.Slug,
@@ -124,7 +129,9 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 				appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
 			}
 
-			data.MainNavLinks = append(data.MainNavLinks, appLink)
+			if len(appLink.Children) > 0 {
+				data.MainNavLinks = append(data.MainNavLinks, appLink)
+			}
 		}
 	}
 

+ 6 - 5
pkg/api/plugins.go

@@ -156,11 +156,12 @@ func GetPluginReadme(c *middleware.Context) Response {
 func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
 
 	cmd := plugins.ImportDashboardCommand{
-		OrgId:    c.OrgId,
-		UserId:   c.UserId,
-		PluginId: apiCmd.PluginId,
-		Path:     apiCmd.Path,
-		Inputs:   apiCmd.Inputs,
+		OrgId:     c.OrgId,
+		UserId:    c.UserId,
+		PluginId:  apiCmd.PluginId,
+		Path:      apiCmd.Path,
+		Inputs:    apiCmd.Inputs,
+		Overwrite: apiCmd.Overwrite,
 	}
 
 	if err := bus.Dispatch(&cmd); err != nil {

+ 2 - 1
pkg/cmd/grafana-cli/commands/commands.go

@@ -4,6 +4,7 @@ import (
 	"os"
 
 	"github.com/codegangsta/cli"
+	"github.com/fatih/color"
 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
 )
 
@@ -12,7 +13,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
 
 		cmd := &contextCommandLine{context}
 		if err := command(cmd); err != nil {
-			log.Error("\nError: ")
+			log.Errorf("\n%s: ", color.RedString("Error"))
 			log.Errorf("%s\n\n", err)
 
 			cmd.ShowHelp()

+ 12 - 7
pkg/cmd/grafana-cli/commands/install_command.go

@@ -126,11 +126,16 @@ func downloadFile(pluginName, filePath, url string) (err error) {
 	defer func() {
 		if r := recover(); r != nil {
 			retryCount++
-			if retryCount == 1 {
-				log.Debug("\nFailed downloading. Will retry once.\n")
-				downloadFile(pluginName, filePath, url)
+			if retryCount < 3 {
+				fmt.Println("Failed downloading. Will retry once.")
+				err = downloadFile(pluginName, filePath, url)
 			} else {
-				panic(r)
+				failure := fmt.Sprintf("%v", r)
+				if failure == "runtime error: makeslice: len out of range" {
+					err = fmt.Errorf("Corrupt http response from source. Please try again.\n")
+				} else {
+					panic(r)
+				}
 			}
 		}
 	}()
@@ -164,14 +169,14 @@ func downloadFile(pluginName, filePath, url string) (err error) {
 				return fmt.Errorf(permissionsDeniedMessage, newFile)
 			}
 
-			defer dst.Close()
 			src, err := zf.Open()
 			if err != nil {
-				log.Errorf("%v", err)
+				log.Errorf("Failed to extract file: %v", err)
 			}
-			defer src.Close()
 
 			io.Copy(dst, src)
+			dst.Close()
+			src.Close()
 		}
 	}
 

+ 3 - 8
pkg/cmd/grafana-cli/commands/remove_command.go

@@ -3,7 +3,7 @@ package commands
 import (
 	"errors"
 
-	"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
+	"fmt"
 	m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
 	services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
 )
@@ -15,22 +15,17 @@ func removeCommand(c CommandLine) error {
 	pluginPath := c.GlobalString("pluginsDir")
 	localPlugins := getPluginss(pluginPath)
 
-	log.Info("remove!\n")
-
 	plugin := c.Args().First()
-	log.Info("plugin: " + plugin + "\n")
 	if plugin == "" {
 		return errors.New("Missing plugin parameter")
 	}
 
-	log.Infof("plugins : \n%v\n", localPlugins)
-
 	for _, p := range localPlugins {
 		if p.Id == c.Args().First() {
-			log.Infof("removing plugin %s", p.Id)
 			removePlugin(pluginPath, p.Id)
+			return nil
 		}
 	}
 
-	return nil
+	return fmt.Errorf("Could not find plugin named %s", c.Args().First())
 }

+ 5 - 5
pkg/cmd/grafana-cli/main.go

@@ -8,7 +8,6 @@ import (
 	"github.com/codegangsta/cli"
 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
-	"strings"
 )
 
 var version = "master"
@@ -18,7 +17,7 @@ func getGrafanaPluginDir() string {
 	defaultNix := "/var/lib/grafana/plugins"
 
 	if currentOS == "windows" {
-		return "C:\\opt\\grafana\\plugins"
+		return "../data/plugins"
 	}
 
 	pwd, err := os.Getwd()
@@ -29,16 +28,17 @@ func getGrafanaPluginDir() string {
 	}
 
 	if isDevenvironment(pwd) {
-		return "../../../data/plugins"
+		return "../data/plugins"
 	}
 
 	return defaultNix
 }
 
 func isDevenvironment(pwd string) bool {
-	// if grafana-cli is executed from the cmd folder we can assume
+	// if ../conf/defaults.ini exists, grafana is not installed as package
 	// that its in development environment.
-	return strings.HasSuffix(pwd, "/pkg/cmd/grafana-cli")
+	_, err := os.Stat("../conf/defaults.ini")
+	return err == nil
 }
 
 func main() {

+ 1 - 1
pkg/models/dashboard_snapshot.go

@@ -45,7 +45,7 @@ type DashboardSnapshotDTO struct {
 
 type CreateDashboardSnapshotCommand struct {
 	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
-	Name      string           `json:"name" binding:"Required"`
+	Name      string           `json:"name"`
 	Expires   int64            `json:"expires"`
 
 	// these are passed when storing an external snapshot ref

+ 22 - 0
pkg/models/org_user.go

@@ -1,7 +1,9 @@
 package models
 
 import (
+	"encoding/json"
 	"errors"
+	"fmt"
 	"time"
 )
 
@@ -37,6 +39,26 @@ func (r RoleType) Includes(other RoleType) bool {
 	return r == other
 }
 
+func (r *RoleType) UnmarshalJSON(data []byte) error {
+	var str string
+	err := json.Unmarshal(data, &str)
+	if err != nil {
+		return err
+	}
+
+	*r = RoleType(str)
+
+	if (*r).IsValid() == false {
+		if (*r) != "" {
+			return errors.New(fmt.Sprintf("JSON validation error: invalid role value: %s", *r))
+		}
+
+		*r = ROLE_VIEWER
+	}
+
+	return nil
+}
+
 type OrgUser struct {
 	Id      int64
 	OrgId   int64

+ 7 - 5
pkg/plugins/dashboard_importer.go

@@ -11,12 +11,13 @@ import (
 )
 
 type ImportDashboardCommand struct {
-	Path   string                 `json:"string"`
-	Inputs []ImportDashboardInput `json:"inputs"`
+	Path      string
+	Inputs    []ImportDashboardInput
+	Overwrite bool
 
-	OrgId    int64  `json:"-"`
-	UserId   int64  `json:"-"`
-	PluginId string `json:"-"`
+	OrgId    int64
+	UserId   int64
+	PluginId string
 	Result   *PluginDashboardInfoDTO
 }
 
@@ -67,6 +68,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
 		Dashboard: generatedDash,
 		OrgId:     cmd.OrgId,
 		UserId:    cmd.UserId,
+		Overwrite: cmd.Overwrite,
 	}
 
 	if err := bus.Dispatch(&saveCmd); err != nil {

+ 15 - 9
pkg/plugins/models.go

@@ -7,7 +7,7 @@ import (
 	"strings"
 
 	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/models"
+	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 )
 
@@ -69,6 +69,12 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error {
 		pb.Dependencies.GrafanaVersion = "*"
 	}
 
+	for _, include := range pb.Includes {
+		if include.Role == "" {
+			include.Role = m.RoleType(m.ROLE_VIEWER)
+		}
+	}
+
 	pb.PluginDir = pluginDir
 	Plugins[pb.Id] = pb
 	return nil
@@ -80,14 +86,14 @@ type PluginDependencies struct {
 }
 
 type PluginInclude struct {
-	Name       string          `json:"name"`
-	Path       string          `json:"path"`
-	Type       string          `json:"type"`
-	Component  string          `json:"component"`
-	Role       models.RoleType `json:"role"`
-	AddToNav   bool            `json:"addToNav"`
-	DefaultNav bool            `json:"defaultNav"`
-	Slug       string          `json:"slug"`
+	Name       string     `json:"name"`
+	Path       string     `json:"path"`
+	Type       string     `json:"type"`
+	Component  string     `json:"component"`
+	Role       m.RoleType `json:"role"`
+	AddToNav   bool       `json:"addToNav"`
+	DefaultNav bool       `json:"defaultNav"`
+	Slug       string     `json:"slug"`
 
 	Id string `json:"-"`
 }

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

@@ -216,7 +216,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
 func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
 	return inTransaction2(func(sess *session) error {
 		dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
-		has, err := x.Get(&dashboard)
+		has, err := sess.Get(&dashboard)
 		if err != nil {
 			return err
 		} else if has == false {

+ 0 - 1
pkg/services/sqlstore/plugin_setting.go

@@ -61,7 +61,6 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
 			for key, data := range cmd.SecureJsonData {
 				pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
 			}
-			pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
 			pluginSetting.Updated = time.Now()
 			pluginSetting.Enabled = cmd.Enabled
 			pluginSetting.JsonData = cmd.JsonData

+ 2 - 2
public/app/core/directives/dropdown_typeahead.js

@@ -9,10 +9,10 @@ function (_, $, coreModule) {
   coreModule.default.directive('dropdownTypeahead', function($compile) {
 
     var inputTemplate = '<input type="text"'+
-      ' class="tight-form-input input-medium tight-form-input"' +
+      ' class="gf-form-input input-medium tight-form-input"' +
       ' spellcheck="false" style="display:none"></input>';
 
-    var buttonTemplate = '<a  class="tight-form-item tight-form-func dropdown-toggle"' +
+    var buttonTemplate = '<a  class="gf-form-label tight-form-func dropdown-toggle"' +
       ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
       ' data-placement="top"><i class="fa fa-plus"></i></a>';
 

+ 7 - 4
public/app/core/directives/metric_segment.js

@@ -8,10 +8,13 @@ function (_, $, coreModule) {
 
   coreModule.default.directive('metricSegment', function($compile, $sce) {
     var inputTemplate = '<input type="text" data-provide="typeahead" ' +
-      ' class="tight-form-clear-input input-medium"' +
+      ' class="gf-form-input input-medium"' +
       ' spellcheck="false" style="display:none"></input>';
 
-    var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
+    var linkTemplate = '<a class="gf-form-label" ng-class="segment.cssClass" ' +
+      'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
+
+    var selectTemplate = '<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
       'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
 
     return {
@@ -20,9 +23,9 @@ function (_, $, coreModule) {
         getOptions: "&",
         onChange: "&",
       },
-      link: function($scope, elem) {
+      link: function($scope, elem, attrs) {
         var $input = $(inputTemplate);
-        var $button = $(buttonTemplate);
+        var $button = $(attrs.styleMode === 'select' ? selectTemplate : linkTemplate);
         var segment = $scope.segment;
         var options = null;
         var cancelBlur = null;

+ 8 - 2
public/app/core/directives/plugin_component.ts

@@ -206,9 +206,15 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
     });
 
     $compile(child)(scope);
-
     elem.empty();
-    elem.append(child);
+
+    // let a binding digest cycle complete before adding to dom
+    setTimeout(function() {
+      elem.append(child);
+      scope.$apply(function() {
+        scope.$broadcast('refresh');
+      });
+    });
   }
 
   function registerPluginComponent(scope, elem, attrs, componentInfo) {

+ 0 - 2
public/app/core/services/context_srv.ts

@@ -25,7 +25,6 @@ export class ContextSrv {
   isGrafanaAdmin: any;
   isEditor: any;
   sidemenu: any;
-  lightTheme: any;
 
   constructor() {
     this.pinned = store.getBool('grafana.sidemenu.pinned', false);
@@ -41,7 +40,6 @@ export class ContextSrv {
     }
 
     this.version = config.buildInfo.version;
-    this.lightTheme = false;
     this.user = new User();
     this.isSignedIn = this.user.isSignedIn;
     this.isGrafanaAdmin = this.user.isGrafanaAdmin;

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

@@ -32,6 +32,8 @@ export default class TableModel {
     if (options.desc) {
       this.rows.reverse();
       this.columns[options.col].desc = true;
+    } else {
+      this.columns[options.col].desc = false;
     }
   }
 }

+ 1 - 1
public/app/core/utils/emitter.ts

@@ -23,7 +23,7 @@ export class Emitter {
     this.emitter.on(name, handler);
 
     if (scope) {
-      scope.$on('$destroy', function() {
+      scope.$on('$destroy', () => {
         this.emitter.off(name, handler);
       });
     }

+ 1 - 1
public/app/features/dashboard/viewStateSrv.js

@@ -36,7 +36,7 @@ function (angular, _, $) {
         self.update(payload);
       });
 
-      $scope.onAppEvent('panel-instantiated', function(evt, payload) {
+      $scope.onAppEvent('panel-initialized', function(evt, payload) {
         self.registerPanel(payload.scope);
       });
 

+ 1 - 0
public/app/features/panel/all.js

@@ -5,4 +5,5 @@ define([
   './query_ctrl',
   './panel_editor_tab',
   './query_editor_row',
+  './metrics_ds_selector',
 ], function () {});

+ 110 - 0
public/app/features/panel/metrics_ds_selector.ts

@@ -0,0 +1,110 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+
+var module = angular.module('grafana.directives');
+
+var template = `
+<div class="gf-form-group">
+  <div class="gf-form-inline">
+    <div class="gf-form">
+      <label class="gf-form-label">
+        <i class="icon-gf icon-gf-datasource"></i>
+      </label>
+      <label class="gf-form-label">
+        Panel data source
+      </label>
+
+      <metric-segment segment="ctrl.dsSegment" style-mode="select"
+                      get-options="ctrl.getOptions()"
+                      on-change="ctrl.datasourceChanged()"></metric-segment>
+    </div>
+
+    <div class="gf-form gf-form--offset-1">
+      <button class="btn btn-inverse gf-form-btn" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.current.meta.mixed">
+        <i class="fa fa-plus"></i>&nbsp;
+        Add query
+      </button>
+
+      <div class="dropdown" ng-if="ctrl.current.meta.mixed">
+        <button class="btn btn-inverse dropdown-toggle gf-form-btn" data-toggle="dropdown">
+          Add Query&nbsp;<span class="fa fa-caret-down"></span>
+        </button>
+
+        <ul class="dropdown-menu" role="menu">
+          <li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
+            <a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+
+export class MetricsDsSelectorCtrl {
+  dsSegment: any;
+  dsName: string;
+  panelCtrl: any;
+  datasources: any[];
+  current: any;
+
+  /** @ngInject */
+  constructor(private uiSegmentSrv, datasourceSrv) {
+    this.datasources = datasourceSrv.getMetricSources();
+
+    var dsValue = this.panelCtrl.panel.datasource || null;
+
+    for (let ds of this.datasources) {
+      if (ds.value === dsValue) {
+        this.current = ds;
+      }
+    }
+
+    if (!this.current) {
+      this.current = {name: dsValue + ' not found', value: null};
+    }
+
+    this.dsSegment = uiSegmentSrv.newSegment(this.current.name);
+  }
+
+  getOptions() {
+    return Promise.resolve(this.datasources.map(value => {
+      return this.uiSegmentSrv.newSegment(value.name);
+    }));
+  }
+
+  datasourceChanged() {
+    var ds = _.findWhere(this.datasources, {name: this.dsSegment.value});
+    if (ds) {
+      this.current = ds;
+      this.panelCtrl.setDatasource(ds);
+    }
+  }
+
+  addDataQuery(datasource) {
+    var target: any = {isNew: true};
+
+    if (datasource) {
+      target.datasource = datasource.name;
+    }
+
+    this.panelCtrl.panel.targets.push(target);
+  }
+}
+
+module.directive('metricsDsSelector', function() {
+  return {
+    restrict: 'E',
+    template: template,
+    controller: MetricsDsSelectorCtrl,
+    bindToController: true,
+    controllerAs: 'ctrl',
+    transclude: true,
+    scope: {
+      panelCtrl: "="
+    }
+  };
+});

+ 0 - 18
public/app/features/panel/metrics_panel_ctrl.ts

@@ -28,7 +28,6 @@ class MetricsPanelCtrl extends PanelCtrl {
   resolution: any;
   timeInfo: any;
   skipDataOnInit: boolean;
-  datasources: any[];
   dataStream: any;
   dataSubscription: any;
 
@@ -53,13 +52,6 @@ class MetricsPanelCtrl extends PanelCtrl {
   private onInitMetricsPanelEditMode() {
     this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
     this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
-    this.datasources = this.datasourceSrv.getMetricSources();
-
-    // find current
-    var current = _.findWhere(this.datasources, {value: this.panel.datasource});
-    if (current) {
-      this.datasourceName = current.name;
-    }
   }
 
   private onMetricsPanelRefresh() {
@@ -257,16 +249,6 @@ class MetricsPanelCtrl extends PanelCtrl {
     this.datasource = null;
     this.refresh();
   }
-
-  addDataQuery(datasource) {
-    var target: any = {};
-
-    if (datasource) {
-      target.datasource = datasource.name;
-    }
-
-    this.panel.targets.push(target);
-  }
 }
 
 export {MetricsPanelCtrl};

+ 4 - 4
public/app/features/panel/panel_ctrl.ts

@@ -44,15 +44,15 @@ export class PanelCtrl {
       this.pluginName = plugin.name;
     }
 
-    $scope.$on("refresh", () => this.refresh());
-    $scope.$on("render", () => this.render());
+    $scope.$on("refresh", this.refresh.bind(this));
+    $scope.$on("render", this.render.bind(this));
     $scope.$on("$destroy", () => this.events.emit('panel-teardown'));
   }
 
   init() {
-    this.publishAppEvent('panel-instantiated', {scope: this.$scope});
     this.calculatePanelHeight();
-    this.refresh();
+    this.publishAppEvent('panel-initialized', {scope: this.$scope});
+    this.events.emit('panel-initialized');
   }
 
   renderingCompleted() {

+ 60 - 1
public/app/features/panel/partials/query_editor_row.html

@@ -1,4 +1,63 @@
-<div class="tight-form">
+
+<div class="gf-form-query">
+	<div class="gf-form">
+    <label class="gf-form-label gf-form-query-letter-cell">
+      <a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
+        <span  ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
+          <i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
+          <i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
+        </span>
+        <span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
+        <em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
+      </a>
+		</label>
+  </div>
+
+	<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
+		<div class="gf-form">
+			<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
+				{{ctrl.collapsedText}}
+			</label>
+		</div>
+	</div>
+
+	<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
+	</div>
+
+	<div class="gf-form">
+		<label class="gf-form-label dropdown">
+			<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
+				<i class="fa fa-bars"></i>
+			</a>
+			<ul class="dropdown-menu pull-right" role="menu">
+				<li role="menuitem" ng-if="ctrl.hasTextEditMode">
+					<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
+				</li>
+				<li role="menuitem">
+					<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
+				</li>
+				<li role="menuitem">
+					<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
+				</li>
+				<li role="menuitem">
+					<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
+				</li>
+			</ul>
+		</label>
+    <label class="gf-form-label">
+			<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</label>
+		<label class="gf-form-label">
+			<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
+				<i class="fa fa-trash"></i>
+			</a>
+		</label>
+	</div>
+</div>
+
+<div class="tight-form" ng-if="false">
 	<ul class="tight-form-list pull-right">
 		<li ng-show="ctrl.error" class="tight-form-item">
 			<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">

+ 0 - 34
public/app/features/panel/query_ctrl.ts

@@ -13,45 +13,11 @@ export class QueryCtrl {
 
   constructor(public $scope, private $injector) {
     this.panel = this.panelCtrl.panel;
-
-    if (!this.target.refId) {
-      this.target.refId = this.getNextQueryLetter();
-    }
-  }
-
-  getNextQueryLetter() {
-    var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
-
-    return _.find(letters, refId => {
-      return _.every(this.panel.targets, function(other) {
-        return other.refId !== refId;
-      });
-    });
-  }
-
-  removeQuery() {
-    this.panel.targets = _.without(this.panel.targets, this.target);
-    this.panelCtrl.refresh();
-  };
-
-  duplicateQuery() {
-    var clone = angular.copy(this.target);
-    clone.refId = this.getNextQueryLetter();
-    this.panel.targets.push(clone);
-  }
-
-  moveQuery(direction) {
-    var index = _.indexOf(this.panel.targets, this.target);
-    _.move(this.panel.targets, index, index + direction);
   }
 
   refresh() {
     this.panelCtrl.refresh();
   }
 
-  toggleHideQuery() {
-    this.target.hide = !this.target.hide;
-    this.panelCtrl.refresh();
-  }
 }
 

+ 100 - 2
public/app/features/panel/query_editor_row.ts

@@ -1,17 +1,115 @@
 ///<reference path="../../headers/common.d.ts" />
 
 import angular from 'angular';
-import $ from 'jquery';
+import _ from 'lodash';
 
 var module = angular.module('grafana.directives');
 
+export class QueryRowCtrl {
+  collapsedText: string;
+  canCollapse: boolean;
+  getCollapsedText: any;
+  target: any;
+  queryCtrl: any;
+  panelCtrl: any;
+  panel: any;
+  collapsed: any;
+
+  constructor() {
+    this.panelCtrl = this.queryCtrl.panelCtrl;
+    this.target = this.queryCtrl.target;
+    this.panel = this.panelCtrl.panel;
+
+    if (!this.target.refId) {
+      this.target.refId = this.getNextQueryLetter();
+    }
+
+    this.toggleCollapse(true);
+    if (this.target.isNew) {
+      delete this.target.isNew;
+      this.toggleCollapse(false);
+    }
+  }
+
+  toggleHideQuery() {
+    this.target.hide = !this.target.hide;
+    this.panelCtrl.refresh();
+  }
+
+  getNextQueryLetter() {
+    var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+    return _.find(letters, refId => {
+      return _.every(this.panel.targets, function(other) {
+        return other.refId !== refId;
+      });
+    });
+  }
+
+  toggleCollapse(init) {
+    if (!this.canCollapse) {
+      return;
+    }
+
+    if (!this.panelCtrl.__collapsedQueryCache) {
+      this.panelCtrl.__collapsedQueryCache = {};
+    }
+
+    if (init) {
+      this.collapsed = this.panelCtrl.__collapsedQueryCache[this.target.refId] !== false;
+    } else {
+      this.collapsed = !this.collapsed;
+      this.panelCtrl.__collapsedQueryCache[this.target.refId] = this.collapsed;
+    }
+
+    try {
+      this.collapsedText = this.queryCtrl.getCollapsedText();
+    } catch (e) {
+      var err = e.message || e.toString();
+      this.collapsedText = 'Error: ' + err;
+    }
+  }
+
+  toggleEditorMode() {
+    if (this.canCollapse && this.collapsed) {
+      this.collapsed = false;
+    }
+
+    this.queryCtrl.toggleEditorMode();
+  }
+
+  removeQuery() {
+    delete this.panelCtrl.__collapsedQueryCache[this.target.refId];
+    this.panel.targets = _.without(this.panel.targets, this.target);
+    this.panelCtrl.refresh();
+  }
+
+  duplicateQuery() {
+    var clone = angular.copy(this.target);
+    clone.refId = this.getNextQueryLetter();
+    this.panel.targets.push(clone);
+  }
+
+  moveQuery(direction) {
+    var index = _.indexOf(this.panel.targets, this.target);
+    _.move(this.panel.targets, index, index + direction);
+  }
+}
+
 /** @ngInject **/
 function queryEditorRowDirective() {
   return {
     restrict: 'E',
+    controller: QueryRowCtrl,
+    bindToController: true,
+    controllerAs: "ctrl",
     templateUrl: 'public/app/features/panel/partials/query_editor_row.html',
     transclude: true,
-    scope: {ctrl: "="},
+    scope: {
+      queryCtrl: "=",
+      canCollapse: "=",
+      hasTextEditMode: "=",
+    },
   };
 }
 

+ 4 - 4
public/app/features/plugins/ds_edit_ctrl.ts

@@ -98,9 +98,7 @@ export class DataSourceEditCtrl {
 
       this.datasourceSrv.get(this.current.name).then(datasource => {
         if (!datasource.testDatasource) {
-          this.testing.message = 'Data source does not support test connection feature.';
-          this.testing.status = 'warning';
-          this.testing.title = 'Unknown';
+          delete this.testing;
           return;
         }
 
@@ -118,7 +116,9 @@ export class DataSourceEditCtrl {
           }
         });
       }).finally(() => {
-        this.testing.done = true;
+        if (this.testing) {
+          this.testing.done = true;
+        }
       });
     }
 

+ 4 - 4
public/app/features/plugins/import_list/import_list.html

@@ -15,16 +15,16 @@
 				</td>
 				<td>
 					v{{dash.revision}}
-				</td>
-				<td ng-if="dash.installed">
-					Imported v{{dash.installedRevision}}
+					<span ng-if="dash.installed">
+						&nbsp;(Imported v{{dash.installedRevision}})
+					<span>
 				</td>
 				<td style="text-align: right">
 					<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
 						Import
 					</button>
 					<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
-						Re-Import
+						Update
 					</button>
 					<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
 						Delete

+ 2 - 2
public/app/features/plugins/import_list/import_list.ts

@@ -43,11 +43,11 @@ export class DashImportListCtrl {
     });
   }
 
-  import(dash, reinstall) {
+  import(dash, overwrite) {
     var installCmd = {
       pluginId: this.plugin.id,
       path: dash.path,
-      reinstall: reinstall,
+      overwrite: overwrite,
       inputs: []
     };
 

+ 1 - 1
public/app/features/plugins/partials/ds_edit.html

@@ -53,7 +53,7 @@
 			</plugin-component>
 			</rebuild-on-change>
 
-			<div ng-if="ctrl.testing" style="margin-top: 25px">
+			<div ng-if="ctrl.testing" class="gf-form-group">
 				<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
 				<div class="alert-{{ctrl.testing.status}} alert">
 					<div class="alert-title">{{ctrl.testing.title}}</div>

+ 2 - 2
public/app/features/plugins/partials/update_instructions.html

@@ -14,8 +14,8 @@
 		<div class="gf-form-group">
 			<p>Type the following on the command line to update {{plugin.name}}.</p>
 			<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
-			<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
+			<span class="small">Check out {{plugin.name}} on <a href="https://grafana.net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
 		</div>
-		<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
+		<p class="pluginlist-none-installed"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
 	</div>
 </div>

+ 5 - 1
public/app/features/templating/templateSrv.js

@@ -57,7 +57,7 @@ function (angular, _) {
           }
 
           var escapedValues = _.map(value, regexEscape);
-          return escapedValues.join('|');
+          return '(' + escapedValues.join('|') + ')';
         }
         case "lucene": {
           if (typeof value === 'string') {
@@ -152,6 +152,10 @@ function (angular, _) {
         value = variable.current.value;
         if (self.isAllValue(value)) {
           value = self.getAllValue(variable);
+          // skip formating of custom all values
+          if (variable.allValue) {
+            return value;
+          }
         }
 
         var res = self.formatValue(value, format, variable);

+ 1 - 8
public/app/features/templating/templateValuesSrv.js

@@ -323,17 +323,10 @@ function (angular, _, kbn) {
         options[value] = {text: text, value: value};
       }
 
-      return _.map(_.keys(options).sort(), function(key) {
-        return options[key];
-      });
+      return _.sortBy(options, 'text');
     };
 
     this.addAllOption = function(variable) {
-      if (variable.allValue) {
-        variable.options.unshift({text: 'All', value: variable.allValue});
-        return;
-      }
-
       variable.options.unshift({text: 'All', value: "$__all"});
     };
 

+ 14 - 51
public/app/partials/metrics.html

@@ -1,56 +1,19 @@
-<div class="editor-row">
-
-	<div class="tight-form-container">
-		<div ng-repeat="target in ctrl.panel.targets" ng-class="{'tight-form-disabled': target.hide}">
-			<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
-				<plugin-component type="query-ctrl">
-				</plugin-component>
-			</rebuild-on-change>
-		</div>
-	</div>
-
-	<div style="margin: 20px 0 0 0">
-		<button class="btn btn-inverse" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.datasource.meta.mixed">
-			<i class="fa fa-plus"></i>&nbsp;
-			Query
-		</button>
-
-		<div class="dropdown" ng-if="ctrl.datasource.meta.mixed">
-			<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
-				<i class="fa fa-plus"></i>&nbsp;
-				Query &nbsp; <span class="caret"></span>
-			</button>
-
-			<ul class="dropdown-menu" role="menu">
-				<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
-					<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
-				</li>
-			</ul>
-		</div>
-
-	</div>
-
-	<rebuild-on-change property="ctrl.panel.datasource" show-null="true">
-		<plugin-component type="query-options-ctrl">
-		</plugin-component>
-	</rebuild-on-change>
 
+<div class="query-editor-rows gf-form-group">
+  <div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
+    <rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
+      <plugin-component type="query-ctrl">
+      </plugin-component>
+    </rebuild-on-change>
+  </div>
 </div>
 
-<div class="editor-row">
-
-	<div class="pull-right dropdown" style="margin-right: 10px;">
-		<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
-			<i class="fa fa-database"></i>&nbsp;
-			{{ctrl.datasourceName}} &nbsp; <span class="fa fa-caret-down"></span>
-		</button>
+<metrics-ds-selector panel-ctrl="ctrl"></metrics-ds-selector>
 
-		<ul class="dropdown-menu" role="menu">
-			<li ng-repeat="datasource in ctrl.datasources" role="menuitem">
-				<a ng-click="ctrl.setDatasource(datasource);">{{datasource.name}}</a>
-			</li>
-		</ul>
-	</div>
-
-	<div class="clearfix"></div>
+<div class="gf-form-group">
+  <rebuild-on-change property="ctrl.panel.datasource" show-null="true">
+    <plugin-component type="query-options-ctrl">
+    </plugin-component>
+  </rebuild-on-change>
 </div>
+

+ 2 - 2
public/app/plugins/datasource/cloudwatch/partials/query.editor.html

@@ -1,4 +1,4 @@
-<query-editor-row ctrl="ctrl">
+<query-editor-row query-ctrl="ctrl" can-collapse="false">
+	<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>
 </query-editor-row>
 
-<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>

+ 51 - 50
public/app/plugins/datasource/cloudwatch/partials/query.parameter.html

@@ -1,58 +1,59 @@
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
-			Metric
-		</li>
-		<li>
-			<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
-		</li>
-		<li>
-			<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
-		</li>
-		<li>
-			<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
-		</li>
-		<li class="tight-form-item query-keyword">
-			Stats
-		</li>
-		<li ng-repeat="segment in statSegments">
-			<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
-		</li>
-	</ul>
+<div class="gf-form-inline">
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword width-7">Metric</label>
 
-	<div class="clearfix"></div>
+		<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
+		<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
+		<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
+	</div>
+
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword">Stats</label>
+	</div>
+
+	<div class="gf-form" ng-repeat="segment in statSegments">
+		<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
+	</div>
+
+	<div class="gf-form gf-form--grow">
+		<div class="gf-form-label gf-form-label--grow"></div>
+	</div>
 </div>
 
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
-			Dimensions
-		</li>
-		<li ng-repeat="segment in dimSegments">
-			<metric-segment segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
-		</li>
-	</ul>
+<div class="gf-form-inline">
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword width-7">Dimensions</label>
+		<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
+	</div>
 
-	<div class="clearfix"></div>
+	<div class="gf-form gf-form--grow">
+		<div class="gf-form-label gf-form-label--grow"></div>
+	</div>
 </div>
 
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 100px">
+<div class="gf-form-inline">
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword width-7">
 			Period
-			<tip>Interval between points in seconds</tip>
-		</li>
-		<li>
-			<input type="text" class="input-mini tight-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
-		</li>
-		<li class="tight-form-item query-keyword">
-			Alias
-			<tip>{{metric}} {{stat}} {{namespace}} {{region}} {{DIMENSION_NAME}}</tip>
-		</li>
-		<li>
-			<input type="text" class="input-xlarge tight-form-input"  ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()">
-		</li>
-	</ul>
-	<div class="clearfix"></div>
+			<info-popover mode="right-normal">Interval between points in seconds</info-popover>
+		</label>
+		<input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
+	</div>
+	<div class="gf-form max-width-30">
+		<label class="gf-form-label query-keyword width-7">Alias</label>
+		<input type="text" class="gf-form-input"  ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()">
+		<info-popover mode="right-absolute">
+			Alias replacement variables:
+			<ul ng-non-bindable>
+				<li>{{metric}}</li>
+				<li>{{stat}}</li>
+				<li>{{namespace}}</li>
+				<li>{{region}}</li>
+				<li>{{DIMENSION_NAME}}</li>
+			</ul>
+		</info-popover>
+	</div>
+	<div class="gf-form gf-form--grow">
+		<div class="gf-form-label gf-form-label--grow"></div>
+	</div>
 </div>
-

+ 11 - 0
public/app/plugins/datasource/elasticsearch/bucket_agg.js

@@ -60,6 +60,10 @@ function (angular, _, queryDef) {
           $scope.agg.query = '*';
           break;
         }
+        case 'geohash_grid': {
+          $scope.agg.settings.precision = 3;
+          break;
+        }
       }
 
       $scope.validateModel();
@@ -121,6 +125,13 @@ function (angular, _, queryDef) {
           if (settings.trimEdges && settings.trimEdges > 0) {
             settingsLinkText += ', Trim edges: ' + settings.trimEdges;
           }
+          break;
+        }
+        case 'geohash_grid': {
+          // limit precision to 7
+          settings.precision = Math.max(Math.min(settings.precision, 7), 1);
+          settingsLinkText = 'Precision: ' + settings.precision;
+          break;
         }
       }
 

BIN
public/app/plugins/datasource/elasticsearch/img/logo_large.png


+ 69 - 104
public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html

@@ -1,131 +1,96 @@
-<div class="tight-form">
-	<ul class="tight-form-list">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
+<div class="gf-form-inline">
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword width-7">
 			<span ng-show="isFirst">Group by</span>
 			<span ng-hide="isFirst">Then by</span>
-		</li>
-		<li>
-			<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
-		</li>
-		<li ng-if="agg.field">
-			<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
-		</li>
-		<li ng-if="!agg.field">
-			<span class="tight-form-item tight-form-item-xxlarge">&nbsp;</span>
-		</li>
-		<li class="tight-form-item last" ng-if="settingsLinkText">
+		</label>
+
+		<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="width-10"></metric-segment-model>
+		<metric-segment-model ng-if="agg.field" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment>
+	</div>
+
+	<div class="gf-form gf-form--grow">
+		<label class="gf-form-label gf-form-label--grow">
 			<a ng-click="toggleOptions()">
 				<i class="fa fa-caret-down" ng-show="showOptions"></i>
 				<i class="fa fa-caret-right" ng-hide="showOptions"></i>
 				{{settingsLinkText}}
 			</a>
-		</li>
-	</ul>
+		</label>
+	</div>
 
-	<ul class="tight-form-list pull-right">
-		<li class="tight-form-item last" ng-if="isFirst">
+	<div class="gf-form">
+		<label class="gf-form-label" ng-if="isFirst">
 			<a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a>
-		</li>
-		<li class="tight-form-item last">
+		</label>
+		<label class="gf-form-label">
 			<a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a>
-		</li>
-	</ul>
-	<div class="clearfix"></div>
+		</label>
+	</div>
 </div>
 
-<div class="tight-form" ng-if="showOptions">
-	<div class="tight-form-inner-box" ng-if="agg.type === 'date_histogram'">
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 170px">
-					Interval
-				</li>
-				<li>
-					<metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="last" custom="true"></metric-segment-model>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+<div class="gf-form-group" ng-if="showOptions">
+	<div ng-if="agg.type === 'date_histogram'">
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Interval</label>
+			<metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="width-12" custom="true"></metric-segment-model>
 		</div>
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 170px">
-					Min Doc Count
-				</li>
-				<li>
-					<input type="number" class="tight-form-input" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Min Doc Count</label>
+			<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
 		</div>
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 170px">
-					Trim edges points
-				</li>
-				<li>
-					<input class="tight-form-input" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
-				</li>
-				<li class="tight-form-item last">
-					<i class="fa fa-question-circle" bs-tooltip="'Trim the edges on the timeseries x datapoints'" data-placement="right"></i>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">
+				Trim edges
+				<info-popover mode="right-normal">
+					Trim the edges on the timeseries datapoints
+				</info-popover>
+			</label>
+			<input class="gf-form-input max-width-12" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
 		</div>
 	</div>
-	<div class="tight-form-inner-box" ng-if="agg.type === 'terms'">
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 60px">
-					Order
-				</li>
-				<li>
-					<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+
+	<div ng-if="agg.type === 'terms'">
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label">Order</label>
+			<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
 		</div>
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 60px">
-					Size
-				</li>
-				<li>
-					<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Size</label>
+			<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
 		</div>
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 60px">
-					Order By
-				</li>
-				<li>
-					<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Order By</label>
+			<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
 		</div>
 	</div>
-	<div class="tight-form-inner-box" ng-if="agg.type === 'filters'">
-		<div class="tight-form" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 100px">
-					Query {{$index + 1}}
-				</li>
-				<li>
-					<input type="text" class="tight-form-input input-large" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
-				</li>
-				<li class="tight-form-item last" ng-if="$first">
+
+	<div ng-if="agg.type === 'filters'">
+		<div class="gf-form-inline" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
+			<div class="gf-form">
+				<label class="gf-form-item width-10">Query {{$index + 1}}</label>
+				<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
+			</div>
+			<div class="gf-form">
+				<label class="gf-form-label" ng-if="$first">
 					<a class="pointer" ng-click="addFiltersQuery()"><i class="fa fa-plus"></i></a>
-				</li>
-				<li class="tight-form-item last" ng-if="!$first">
+				</label>
+				<label class="gf-form-label" ng-if="!$first">
 					<a class="pointer" ng-click="removeFiltersQuery(filter)"><i class="fa fa-minus"></i></a>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+				</label>
+			</div>
 		</div>
+	</div>
 
+	<div ng-if="agg.type === 'geohash_grid'">
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label">Precision</label>
+			<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
+		</div>
 	</div>
 
 </div>

+ 61 - 117
public/app/plugins/datasource/elasticsearch/partials/metric_agg.html

@@ -1,138 +1,82 @@
-<div class="tight-form" ng-class="{'tight-form-disabled': agg.hide}">
-	<ul class="tight-form-list">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
+<div class="gf-form-inline" ng-class="{'gf-form-disabled': agg.hide}">
+	<div class="gf-form">
+		<label class="gf-form-label query-keyword width-7">
 			Metric
 			&nbsp;
 			<a ng-click="toggleShowMetric()" bs-tooltip="'Click to toggle show / hide metric'">
 				<i class="fa fa-eye" ng-hide="agg.hide"></i>
 				<i class="fa fa-eye-slash" ng-show="agg.hide"></i>
 			</a>
-		</li>
-		<li>
-			<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
-		</li>
-		<li ng-if="aggDef.requiresField">
-			<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment-model>
-		</li>
-		<li ng-if="aggDef.isPipelineAgg">
-			<metric-segment-model property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="tight-form-item-xxlarge"></metric-segment-model>
-		</li>
-		<li class="tight-form-item last" ng-if="settingsLinkText">
-			<a ng-click="toggleOptions()">
+		</label>
+	</div>
+
+	<div class="gf-form">
+		<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="width-10"></metric-segment-model>
+		<metric-segment-model ng-if="aggDef.requiresField" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment-model>
+		<metric-segment-model ng-if="aggDef.isPipelineAgg" property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="width-12"></metric-segment-model>
+	</div>
+
+	<div class="gf-form gf-form--grow">
+		<label class="gf-form-label gf-form-label--grow">
+			<a ng-click="toggleOptions()" ng-if="settingsLinkText">
 				<i class="fa fa-caret-down" ng-show="showOptions"></i>
 				<i class="fa fa-caret-right" ng-hide="showOptions"></i>
-				{{settingsLinkText}}
+					{{settingsLinkText}}
 			</a>
-		</li>
-	</ul>
+		</label>
+	</div>
 
-	<ul class="tight-form-list pull-right">
-		<li class="tight-form-item last" ng-if="isFirst">
+	<div class="gf-form">
+		<label class="gf-form-label" ng-if="isFirst">
 			<a class="pointer" ng-click="addMetricAgg()"><i class="fa fa-plus"></i></a>
-		</li>
-		<li class="tight-form-item last" ng-if="!isSingle">
+		</label>
+		<label class="gf-form-label" ng-if="!isSingle">
 			<a class="pointer" ng-click="removeMetricAgg()"><i class="fa fa-minus"></i></a>
-		</li>
-	</ul>
-	<div class="clearfix"></div>
+		</label>
+	</div>
 </div>
 
-<div class="tight-form" ng-if="showOptions">
-	<div class="tight-form-inner-box tight-form-container">
-		<div class="tight-form" ng-if="agg.type === 'derivative'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 75px;">
-					Unit
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input last" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
+<div class="gf-form-group" ng-if="showOptions">
 
-		<div class="tight-form" ng-if="agg.type === 'moving_avg'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 75px;">
-					Window
-				</li>
-				<li>
-					<input type="number" class="input-medium tight-form-input last" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form" ng-if="agg.type === 'moving_avg'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 75px;">
-					Model
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input last" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form last" ng-if="agg.type === 'percentiles'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item">
-					Percentiles
-				</li>
-				<li>
-					<input type="text" class="input-xlarge tight-form-input last" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div ng-if="agg.type === 'extended_stats'">
-			<div class="tight-form" ng-repeat="stat in extendedStats">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						{{stat.text}}
-					</li>
-					<li class="tight-form-item last">
-						<editor-checkbox text="" model="agg.meta.{{stat.value}}" change="onChange()"></editor-checkbox>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-		</div>
-		<div class="tight-form" ng-if="agg.type === 'extended_stats'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 100px">
-					Sigma
-				</li>
-				<li>
-					<input type="number" class="input-mini tight-form-input last" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
+	<div class="gf-form offset-width-7" ng-if="agg.type === 'derivative'">
+		<label class="gf-form-label width-10">Unit</label>
+		<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
+	</div>
 
-		<div class="tight-form" ng-if="aggDef.supportsInlineScript">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 100px;">
-					Script
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input last" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
+	<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
+		<label class="gf-form-label width-10">Window</label>
+		<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
+	</div>
 
-		<div class="tight-form" ng-if="aggDef.supportsMissing">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 100px;">
-					Missing
-					<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
-				</li>
-				<li>
-					<input type="number" class="input-medium tight-form-input last" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+	<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
+		<label class="gf-form-label width-10">Model</label>
+		<input type="text" class="gf-form-input max-width-12" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
+	</div>
+
+	<div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
+		<label class="gf-form-label width-10">Percentiles</label>
+		<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
+	</div>
+
+	<div ng-if="agg.type === 'extended_stats'">
+		<gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
+
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Sigma</label>
+			<input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
 		</div>
+	</div>
+
+	<div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
+		<label class="gf-form-label width-10">Script</label>
+		<input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
+	</div>
 
+	<div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
+		<label class="gf-form-label width-10">
+			Missing
+			<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
+		</label>
+		<input type="number" class="gf-form-input max-width-12" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
 	</div>
 </div>

+ 28 - 29
public/app/plugins/datasource/elasticsearch/partials/query.editor.html

@@ -1,32 +1,31 @@
-<query-editor-row ctrl="ctrl">
-	<li class="tight-form-item query-keyword" style="width: 75px">
-		Query
-	</li>
-	<li>
-		<input type="text" class="tight-form-input" style="width: 345px;" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()">
-	</li>
-	<li class="tight-form-item query-keyword">
-		Alias
-	</li>
-	<li>
-		<input type="text" class="tight-form-input" style="width: 200px;" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="ctrl.refresh()">
-	</li>
-</query-editor-row>
+<query-editor-row query-ctrl="ctrl" can-collapse="true">
+
+	<div class="gf-form-inline">
+		<div class="gf-form gf-form--grow">
+			<label class="gf-form-label query-keyword width-7">Query</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()">
+		</div>
+		<div class="gf-form max-width-15">
+			<label class="gf-form-label query-keyword">Alias</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns" ng-blur="ctrl.refresh()">
+		</div>
+	</div>
 
-<div ng-repeat="agg in ctrl.target.metrics">
-	<elastic-metric-agg
-		target="ctrl.target" index="$index"
-		get-fields="ctrl.getFields($fieldType)"
-		on-change="ctrl.queryUpdated()"
-		es-version="ctrl.esVersion">
-	</elastic-metric-agg>
-</div>
+	<div ng-repeat="agg in ctrl.target.metrics">
+		<elastic-metric-agg
+			target="ctrl.target" index="$index"
+			get-fields="ctrl.getFields($fieldType)"
+			on-change="ctrl.queryUpdated()"
+			es-version="ctrl.esVersion">
+		</elastic-metric-agg>
+	</div>
 
-<div ng-repeat="agg in ctrl.target.bucketAggs">
-	<elastic-bucket-agg
-		target="ctrl.target" index="$index"
-		get-fields="ctrl.getFields($fieldType)"
-		on-change="ctrl.queryUpdated()">
-	</elastic-bucket-agg>
-</div>
+	<div ng-repeat="agg in ctrl.target.bucketAggs">
+		<elastic-bucket-agg
+			target="ctrl.target" index="$index"
+			get-fields="ctrl.getFields($fieldType)"
+			on-change="ctrl.queryUpdated()">
+		</elastic-bucket-agg>
+	</div>
 
+</query-editor-row>

+ 4 - 0
public/app/plugins/datasource/elasticsearch/query_builder.js

@@ -153,6 +153,10 @@ function (queryDef) {
           this.buildTermsAgg(aggDef, esAgg, target);
           break;
         }
+        case 'geohash_grid': {
+          esAgg['geohash_grid'] = {field: aggDef.field, precision: aggDef.settings.precision};
+          break;
+        }
       }
 
       nestedAggs.aggs = nestedAggs.aggs || {};

+ 43 - 0
public/app/plugins/datasource/elasticsearch/query_ctrl.ts

@@ -5,6 +5,7 @@ import './metric_agg';
 
 import angular from 'angular';
 import _ from 'lodash';
+import queryDef from './query_def';
 import {QueryCtrl} from 'app/plugins/sdk';
 
 export class ElasticQueryCtrl extends QueryCtrl {
@@ -38,6 +39,48 @@ export class ElasticQueryCtrl extends QueryCtrl {
     this.$rootScope.appEvent('elastic-query-updated');
   }
 
+  getCollapsedText() {
+    var metricAggs = this.target.metrics;
+    var bucketAggs = this.target.bucketAggs;
+    var metricAggTypes = queryDef.getMetricAggTypes(this.esVersion);
+    var bucketAggTypes = queryDef.bucketAggTypes;
+    var text = '';
+
+    if (this.target.query) {
+      text += 'Query: ' + this.target.query + ', ';
+    }
+
+    text += 'Metrics: ';
+
+    _.each(metricAggs, (metric, index) => {
+      var aggDef = _.findWhere(metricAggTypes, {value: metric.type});
+      text += aggDef.text + '(';
+      if (aggDef.requiresField) {
+        text += metric.field;
+      }
+      text += '), ';
+    });
+
+    _.each(bucketAggs, (bucketAgg, index) => {
+      if (index === 0) {
+        text += ' Group by: ';
+      }
+
+      var aggDef = _.findWhere(bucketAggTypes, {value: bucketAgg.type});
+      text += aggDef.text + '(';
+      if (aggDef.requiresField) {
+        text += bucketAgg.field;
+      }
+      text += '), ';
+    });
+
+    if (this.target.alias) {
+      text += 'Alias: ' + this.target.alias;
+    }
+
+    return text;
+  }
+
   handleQueryError(err) {
     this.error = err.message || 'Failed to issue metric query';
     return [];

+ 3 - 2
public/app/plugins/datasource/elasticsearch/query_def.js

@@ -20,9 +20,10 @@ function (_) {
     ],
 
     bucketAggTypes: [
-      {text: "Terms",           value: 'terms' },
+      {text: "Terms",           value: 'terms', requiresField: true},
       {text: "Filters",         value: 'filters' },
-      {text: "Date Histogram",  value: 'date_histogram' },
+      {text: "Geo Hash Grid",        value: 'geohash_grid', requiresField: true},
+      {text: "Date Histogram",  value: 'date_histogram', requiresField: true},
     ],
 
     orderByOptions: [

+ 6 - 4
public/app/plugins/datasource/grafana/partials/query.editor.html

@@ -1,5 +1,7 @@
-<query-editor-row ctrl="ctrl">
-	<li class="tight-form-item">
-		Test metric (fake data source)
-	</li>
+<query-editor-row query-ctrl="ctrl" can-collapse="false">
+	<div class="gf-form-inline">
+		<div class="gf-form">
+			<label class="gf-form-label">Test metric (fake data source)</label>
+		</div>
+	</div>
 </query-editor-row>

+ 2 - 2
public/app/plugins/datasource/graphite/add_graphite_func.js

@@ -11,10 +11,10 @@ function (angular, _, $, gfunc) {
     .module('grafana.directives')
     .directive('graphiteAddFunc', function($compile) {
       var inputTemplate = '<input type="text"'+
-                            ' class="tight-form-input input-medium tight-form-input"' +
+                            ' class="gf-form-input"' +
                             ' spellcheck="false" style="display:none"></input>';
 
-      var buttonTemplate = '<a  class="tight-form-item tight-form-func dropdown-toggle"' +
+      var buttonTemplate = '<a  class="gf-form-label query-part dropdown-toggle"' +
                               ' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
                               '<i class="fa fa-plus"></i></a>';
 

+ 7 - 0
public/app/plugins/datasource/graphite/gfunc.js

@@ -80,6 +80,13 @@ function (_, $) {
     category: categories.Calculate,
   });
 
+  addFuncDef({
+    name: 'stddevSeries',
+    params: optionalSeriesRefArgs,
+    defaultParams: [''],
+    category: categories.Calculate,
+  });
+
   addFuncDef({
     name: 'divideSeries',
     params: optionalSeriesRefArgs,

+ 21 - 15
public/app/plugins/datasource/graphite/partials/query.editor.html

@@ -1,21 +1,27 @@
-<query-editor-row ctrl="ctrl">
+<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
 
-	<li class="tight-form-flex-wrapper" ng-show="ctrl.target.textEditor">
-		<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="ctrl.target.target" give-focus="ctrl.target.textEditor" spellcheck='false' ng-model-onblur ng-change="ctrl.targetTextChanged()"></input>
-	</li>
+	<div class="gf-form" ng-show="ctrl.target.textEditor">
+		<input type="text" class="gf-form-input" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.refresh()"></input>
+	</div>
 
-	<li ng-hide-start="ctrl.target.textEditor"></li>
+  <div ng-hide="ctrl.target.textEditor">
+		<div class="gf-form-inline">
+      <div ng-repeat="segment in ctrl.segments" role="menuitem" class="gf-form">
+        <metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
+      </div>
 
-	<li ng-repeat="segment in ctrl.segments" role="menuitem">
-		<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
-	</li>
-	<li ng-repeat="func in ctrl.functions">
-		<span graphite-func-editor class="tight-form-item tight-form-func">
-		</span>
-	</li>
-	<li class="dropdown" graphite-add-func>
-	</li>
+      <div ng-repeat="func in ctrl.functions" class="gf-form">
+        <span graphite-func-editor class="gf-form-label query-part"></span>
+      </div>
 
-	<li ng-hide-end></li>
+      <div class="gf-form dropdown">
+        <span graphite-add-func></span>
+      </div>
+
+      <div class="gf-form gf-form--grow">
+				<div class="gf-form-label gf-form-label--grow"></div>
+			</div>
+    </div>
+  </div>
 
 </query-editor-row>

+ 2 - 2
public/app/plugins/datasource/influxdb/README.md

@@ -6,8 +6,8 @@ There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0
 
 This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API. 
 
-InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://www.grafana.net/plugins/grafana-influxdb-08-datasource).
+InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://grafana.net/plugins/grafana-influxdb-08-datasource).
 
 Read more about InfluxDB here:
 
-[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
+[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)

+ 8 - 8
public/app/plugins/datasource/influxdb/influx_query.ts

@@ -152,7 +152,9 @@ export default class InfluxQuery {
       if (interpolate) {
         value = this.templateSrv.replace(value, this.scopedVars);
       }
-      value = "'" + value.replace('\\', '\\\\') + "'";
+      if (isNaN(+value)) {
+        value = "'" + value.replace('\\', '\\\\') + "'";
+      }
     } else if (interpolate){
       value = this.templateSrv.replace(value, this.scopedVars, 'regex');
     }
@@ -160,12 +162,14 @@ export default class InfluxQuery {
     return str + '"' + tag.key + '" ' + operator + ' ' + value;
   }
 
-  getMeasurementAndPolicy() {
+  getMeasurementAndPolicy(interpolate) {
     var policy = this.target.policy;
-    var measurement = this.target.measurement;
+    var measurement = this.target.measurement || 'measurement';
 
     if (!measurement.match('^/.*/')) {
       measurement = '"' + measurement+ '"';
+    } else if (interpolate) {
+      measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
     }
 
     if (policy !== 'default') {
@@ -188,10 +192,6 @@ export default class InfluxQuery {
       }
     }
 
-    if (!target.measurement) {
-      throw {message: "Metric measurement is missing"};
-    }
-
     var query = 'SELECT ';
     var i, y;
     for (i = 0; i < this.selectModels.length; i++) {
@@ -208,7 +208,7 @@ export default class InfluxQuery {
       query += selectText;
     }
 
-    query += ' FROM ' + this.getMeasurementAndPolicy() + ' WHERE ';
+    query += ' FROM ' + this.getMeasurementAndPolicy(interpolate) + ' WHERE ';
     var conditions = _.map(target.tags, (tag, index) => {
       return this.renderTagCondition(tag, index, interpolate);
     });

+ 86 - 63
public/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -1,73 +1,96 @@
-<query-editor-row ctrl="ctrl">
-		<ul class="tight-form-list" ng-hide="ctrl.target.rawQuery">
-			<li class="tight-form-item query-keyword" style="width: 75px">
-				FROM
-			</li>
-			<li>
+<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
+
+	<div class="gf-form" ng-if="ctrl.target.rawQuery">
+		<input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" ng-blur="ctrl.refresh()"></input>
+	</div>
+
+	<div ng-if="!ctrl.target.rawQuery">
+
+		<div class="gf-form-inline">
+			<div class="gf-form">
+				<label class="gf-form-label query-keyword width-7">FROM</label>
+
 				<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
-			</li>
-			<li>
 				<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements()" on-change="ctrl.measurementChanged()"></metric-segment>
-			</li>
-			<li class="tight-form-item query-keyword" style="padding-left: 15px; padding-right: 15px;">
-				WHERE
-			</li>
-			<li ng-repeat="segment in ctrl.tagSegments">
+			</div>
+
+			<div class="gf-form">
+				<label class="gf-form-label query-keyword">WHERE</label>
+			</div>
+
+			<div class="gf-form" ng-repeat="segment in ctrl.tagSegments">
 				<metric-segment segment="segment" get-options="ctrl.getTagsOrValues(segment, $index)" on-change="ctrl.tagSegmentUpdated(segment, $index)"></metric-segment>
-			</li>
-		</ul>
+			</div>
 
-		<div class="tight-form-flex-wrapper" ng-show="ctrl.target.rawQuery">
-			<input type="text" class="tight-form-clear-input" ng-model="ctrl.target.query" spellcheck="false" style="width: 100%;" ng-blur="ctrl.refresh()"></input>
+			<div class="gf-form gf-form--grow">
+				<div class="gf-form-label gf-form-label--grow"></div>
+			</div>
+		</div>
+
+		<div class="gf-form-inline" ng-repeat="selectParts in ctrl.queryModel.selectModels">
+			<div class="gf-form">
+				<label class="gf-form-label query-keyword width-7">
+					<span ng-show="$index === 0">SELECT</span>
+				</label>
+			</div>
+
+			<div class="gf-form" ng-repeat="part in selectParts">
+				<influx-query-part-editor
+														class="gf-form-label query-part"
+														part="part"
+														remove-action="ctrl.removeSelectPart(selectParts, part)"
+														part-updated="ctrl.selectPartUpdated(selectParts, part)"
+														get-options="ctrl.getPartOptions(part)">
+				</influx-query-part-editor>
+			</div>
+
+			<div class="gf-form">
+				<label class="dropdown"
+								dropdown-typeahead="ctrl.selectMenu"
+								dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
+				</label>
+			</div>
+
+			<div class="gf-form gf-form--grow">
+				<div class="gf-form-label gf-form-label--grow"></div>
+			</div>
+		</div>
+
+		<div class="gf-form-inline">
+			<div class="gf-form">
+				<label class="gf-form-label query-keyword width-7">
+					<span>GROUP BY</span>
+				</label>
+
+				<influx-query-part-editor
+								ng-repeat="part in ctrl.queryModel.groupByParts"
+								part="part"
+								class="gf-form-label query-part"
+								remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)">
+				</influx-query-part-editor>
+			</div>
+
+			<div class="gf-form gf-form--grow">
+				<div class="gf-form-label gf-form-label--grow"></div>
+			</div>
 		</div>
-</query-editor-row>
 
-<div ng-hide="ctrl.target.rawQuery">
-	<div class="tight-form" ng-repeat="selectParts in ctrl.queryModel.selectModels">
-		<ul class="tight-form-list">
-			<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
-				<span ng-show="$index === 0">SELECT</span>
-			</li>
-			<li ng-repeat="part in selectParts">
-				<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeSelectPart(selectParts, part)" part-updated="ctrl.selectPartUpdated(selectParts, part)" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
-			</li>
-			<li class="dropdown" dropdown-typeahead="ctrl.selectMenu" dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
-			</li>
-		</ul>
-		<div class="clearfix"></div>
 	</div>
 
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
-				<span>GROUP BY</span>
-			</li>
-			<li ng-repeat="part in ctrl.queryModel.groupByParts">
-				<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
-			</li>
-			<li>
-				<metric-segment segment="ctrl.groupBySegment" get-options="ctrl.getGroupByOptions()" on-change="ctrl.groupByAction(part, $index)"></metric-segment>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-30">
+			<label class="gf-form-label query-keyword width-7">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">
+			<label class="gf-form-label">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 gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
 	</div>
-</div>
-
-<div class="tight-form">
-	<ul class="tight-form-list">
-		<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
-			ALIAS BY
-		</li>
-		<li>
-			<input type="text" class="tight-form-clear-input input-xlarge" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
-		</li>
-		<li class="tight-form-item">
-			Format as
-		</li>
-		<li>
-			<select class="input-small tight-form-input" style="width: 104px" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
-		</li>
-	</ul>
-	<div class="clearfix"></div>
-</div>
 
+</query-editor-row>

+ 1 - 1
public/app/plugins/datasource/influxdb/partials/query.options.html

@@ -38,7 +38,7 @@
 </section>
 
 <div class="editor-row">
-	<div class="pull-left" style="margin-top: 30px;">
+	<div class="pull-left">
 
 		<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
 			<h5>Alias patterns</h5>

+ 1 - 1
public/app/plugins/datasource/influxdb/partials/query_part.html

@@ -2,4 +2,4 @@
 	<span class="pointer fa fa-remove" ng-click="removeActionInternal()" ></span>
 </div>
 
-<a ng-click="toggleControls()">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>
+<a ng-click="toggleControls()" class="query-part-name">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>

+ 2 - 2
public/app/plugins/datasource/influxdb/query_builder.js

@@ -25,8 +25,8 @@ function (_) {
       }
     }
 
-    // quote value unless regex
-    if (operator !== '=~' && operator !== '!~') {
+    // quote value unless regex or number
+    if (operator !== '=~' && operator !== '!~' && isNaN(+value)) {
       value = "'" + value + "'";
     }
 

+ 10 - 1
public/app/plugins/datasource/influxdb/query_ctrl.ts

@@ -23,6 +23,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
   measurementSegment: any;
   removeTagFilterSegment: any;
 
+
   /** @ngInject **/
   constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
     super($scope, $injector);
@@ -154,7 +155,11 @@ export class InfluxQueryCtrl extends QueryCtrl {
   }
 
   toggleEditorMode() {
-    this.target.query = this.queryModel.render(false);
+    try {
+      this.target.query = this.queryModel.render(false);
+    } catch (err) {
+      console.log('query render error');
+    }
     this.target.rawQuery = !this.target.rawQuery;
   }
 
@@ -316,5 +321,9 @@ export class InfluxQueryCtrl extends QueryCtrl {
       return '=';
     }
   }
+
+  getCollapsedText() {
+    return this.queryModel.render(false);
+  }
 }
 

+ 21 - 9
public/app/plugins/datasource/influxdb/response_parser.ts

@@ -12,17 +12,29 @@ export default class ResponseParser {
       return [];
     }
 
-    var series = influxResults.series[0];
-    return _.map(series.values, (value) => {
-      if (_.isArray(value)) {
-        if (query.toLowerCase().indexOf('show tag values') >= 0) {
-          return { text: (value[1] || value[0]) };
+    var influxdb11format = query.toLowerCase().indexOf('show tag values') >= 0;
+
+    var res = {};
+    _.each(influxResults.series, serie => {
+      _.each(serie.values, value => {
+        if (_.isArray(value)) {
+          if (influxdb11format) {
+            addUnique(res, value[1] || value[0]);
+          } else {
+            addUnique(res, value[0]);
+          }
         } else {
-          return { text: value[0] };
+          addUnique(res, value);
         }
-      } else {
-        return { text: value };
-      }
+      });
+    });
+
+    return _.map(res, value => {
+      return { text: value};
     });
   }
 }
+
+function addUnique(arr, value) {
+  arr[value] = value;
+}

+ 17 - 9
public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts

@@ -38,7 +38,7 @@ describe("influxdb response parser", () => {
               {
                 "name": "hostnameTagValues",
                 "columns": ["hostname"],
-                "values": [ ["server1"], ["server2"] ]
+                "values": [ ["server1"], ["server2"], ["server2"] ]
               }
             ]
           }
@@ -54,7 +54,7 @@ describe("influxdb response parser", () => {
       });
     });
 
-    describe("response from 0.11.0", () => {
+    describe("response from 0.12.0", () => {
       var response = {
         "results": [
            {
@@ -62,8 +62,19 @@ describe("influxdb response parser", () => {
                {
                  "name": "cpu",
                  "columns": [ "key", "value"],
-                 "values": [ [ "source", "site" ], [ "source", "api" ] ]
-               }
+                 "values": [
+                   [ "source", "site" ],
+                   [ "source", "api" ]
+                 ]
+               },
+               {
+                 "name": "logins",
+                 "columns": [ "key", "value"],
+                 "values": [
+                   [ "source", "site" ],
+                   [ "source", "webapi"]
+                 ]
+               },
              ]
            }
         ]
@@ -72,15 +83,12 @@ describe("influxdb response parser", () => {
       var result = this.parser.parse(query, response);
 
       it("should get two responses", () => {
-        expect(_.size(result)).to.be(2);
+        expect(_.size(result)).to.be(3);
         expect(result[0].text).to.be('site');
         expect(result[1].text).to.be('api');
+        expect(result[2].text).to.be('webapi');
       });
     });
-
-
-
-
   });
 
   describe("SHOW FIELD response", () => {

+ 250 - 220
public/app/plugins/datasource/opentsdb/partials/query.editor.html

@@ -1,223 +1,253 @@
-<query-editor-row ctrl="ctrl">
-	<li class="tight-form-item query-keyword" style="width: 100px">
-		Metric
-	</li>
-	<li>
-		<input type="text" class="input-large tight-form-input" ng-model="ctrl.target.metric"
-		spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
-		ng-blur="ctrl.targetBlur()">
-		</input>
-		<a bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
-			<i class="fa fa-warning"></i>
-		</a>
-	</li>
-	<li class="tight-form-item query-keyword">
-		Aggregator
-	</li>
-	<li>
-		<select ng-model="ctrl.target.aggregator" class="tight-form-input input-small"
-			ng-options="agg for agg in ctrl.aggregators"
-			ng-change="ctrl.targetBlur()">
-		</select>
-		<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
-			<i class="fa fa-warning"></i>
-		</a>
-	</li>
-
-	<li class="tight-form-item query-keyword">
-		Alias:
-		<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
-	</li>
-	<li>
-		<input type="text" class="tight-form-input input-large"
-		ng-model="ctrl.target.alias"
-		spellcheck='false'
-		placeholder="series alias"
-		data-min-length=0 data-items=100
-		ng-blur="ctrl.targetBlur()"></input>
-	</li>
-</query-editor-row>
+<query-editor-row query-ctrl="ctrl" can-collapse="false">
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-25">
+			<label class="gf-form-label query-keyword width-8">
+				Metric
+				<label class="gf-form-label" bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
+					<i class="fa fa-warning"></i>
+				</label>
+			</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.metric"
+						 spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
+						ng-blur="ctrl.targetBlur()">
+			</input>
+		</div>
+		<div class="gf-form">
+			<label class="gf-form-label query-keyword">
+				Aggregator
+				<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
+					<i class="fa fa-warning"></i>
+				</a>
+			</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select ng-model="ctrl.target.aggregator" class="gf-form-input"
+								ng-options="agg for agg in ctrl.aggregators"
+								ng-change="ctrl.targetBlur()">
+		 	 </select>
+			</div>
+		</div>
+			<div class="gf-form max-width-20">
+				<label class="gf-form-label query-keyword width-6">
+					Alias:
+					<info-popover mode="right-normal">
+						Use patterns like $tag_tagname to replace part of the alias for a tag value
+					</info-popover>
+				</label>
+				<input  type="text" class="gf-form-input"
+		   					ng-model="ctrl.target.alias"
+								spellcheck='false'
+								placeholder="series alias"
+								data-min-length=0 data-items=100
+								ng-blur="ctrl.targetBlur()"></input>
+			</div>
+
+			<div class="gf-form gf-form--grow">
+				<div class="gf-form-label gf-form-label--grow"></div>
+			</div>
+	</div>
+
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-25">
+			<label class="gf-form-label query-keyword width-8">Down sample</label>
+			<input type="text" class="gf-form-input"
+						 ng-model="ctrl.target.downsampleInterval"
+						 ng-model-onblur
+			       ng-change="ctrl.targetBlur()"
+			       placeholder="interval"></input>
+			<info-popover mode="right-absolute">
+				blank for auto, or for example <code>1m</code>
+			</info-popover>
+		</div>
+
+		<div class="gf-form">
+			<label class="gf-form-label query-keyword">Aggregator</label>
+			<div class="gf-form-select-wrapper">
+				<select ng-model="ctrl.target.downsampleAggregator" class="gf-form-input"
+								ng-options="agg for agg in ctrl.aggregators"
+				        ng-change="ctrl.targetBlur()">
+				</select>
+			</div>
+		</div>
+
+		<div class="gf-form" ng-if="ctrl.tsdbVersion == 2">
+			<label class="gf-form-label query-keyword width-6">Fill</label>
+			<div class="gf-form-select-wrapper">
+				<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
+								ng-options="agg for agg in ctrl.fillPolicies"
+								ng-change="ctrl.targetBlur()">
+				</select>
+			</div>
+		</div>
+
+		<gf-form-switch class="gf-form"
+										label="Disable downsampling"
+										checked="ctrl.target.disableDownsampling"
+										on-change="ctrl.targetBlur()">
+		</gf-form-switch>
+
+		<div class="gf-form gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
+	</div>
+
+	<div class="gf-form-inline" ng-if="ctrl.tsdbVersion == 2">
+		<div class="gf-form">
+
+			<label class="gf-form-label query-keyword width-8">
+				Filters
+				<info-popover mode="right-normal">
+					Filters does not work with tags, either of the two will work but not both.
+				</info-popover>
+			</label>
+
+			<div ng-repeat="fil in ctrl.target.filters track by $index" class="gf-form-label">
+				{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
+				<a ng-click="ctrl.editFilter(fil, $index)">
+					<i class="fa fa-pencil"></i>
+				</a>
+				<a ng-click="ctrl.removeFilter($index)">
+					<i class="fa fa-remove"></i>
+				</a>
+			</div>
+			<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
+				<a ng-click="ctrl.addFilter()">
+					<i class="fa fa-plus"></i>
+				</a>
+			</label>
+ 		</div>
+
+		<div class="gf-form-inline" ng-show="ctrl.addFilterMode">
+			<div class="gf-form">
+				<input type="text" class="gf-form-input" spellcheck='false'
+						 bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
+             ng-model="ctrl.target.currentFilterKey" placeholder="key">
+				</input>
+			</div>
+
+			<div class="gf-form">
+				<label class="gf-form-label">Type</label>
+				<div class="gf-form-select-wrapper">
+					<select ng-model="ctrl.target.currentFilterType" class="gf-form-input" ng-options="filType for filType in ctrl.filterTypes">
+					</select>
+				</div>
+			</div>
+
+			<div class="gf-form">
+				<input type="text" class="gf-form-input" spellcheck='false' bs-typeahead="ctrl.suggestTagValues" data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
+				</input>
+			</div>
+
+			<gf-form-switch class="gf-form" label="Group by" checked="ctrl.target.currentFilterGroupBy" on-change="ctrl.targetBlur()">
+			</gf-form-switch>
+
+			<div class="gf-form" ng-show="ctrl.addFilterMode">
+				<label class="gf-form-label" ng-show="ctrl.errors.filters">
+					<a bs-tooltip="ctrl.errors.filters" style="color: rgb(229, 189, 28)" >
+						<i class="fa fa-warning"></i>
+					</a>
+				</label>
+
+				<label class="gf-form-label">
+					<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
+					<a ng-click="ctrl.closeAddFilterMode()">
+						<i class="fa fa-remove"></i>
+					</a>
+				</label>
+			</div>
+
+		</div>
+
+		<div class="gf-form gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
+	</div>
+
+	<div class="gf-form-inline">
+		<div class="gf-form">
+			<label class="gf-form-label query-keyword width-8">
+				Tags
+				<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion == 2">
+					Please use filters, tags are deprecated in opentsdb 2.2
+				</info-popover>
+			</label>
+		</div>
+
+		<div class="gf-form" ng-repeat="(key, value) in ctrl.target.tags track by $index" class="gf-form">
+			<label class="gf-form-label">
+				{{key}}&nbsp;=&nbsp;{{value}}
+				<a ng-click="ctrl.editTag(key, value)">
+					<i class="fa fa-pencil"></i>
+				</a>
+				<a ng-click="ctrl.removeTag(key)">
+					<i class="fa fa-remove"></i>
+				</a>
+			</label>
+		</div>
+
+		<div class="gf-form" ng-hide="ctrl.addTagMode">
+			<label class="gf-form-label query-keyword">
+				<a ng-click="ctrl.addTag()"><i class="fa fa-plus"></i></a>
+			</label>
+		</div>
+
+		<div class="gf-form" ng-show="ctrl.addTagMode">
+			<input type="text"
+						 class="gf-form-input" spellcheck='false'
+						 bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
+					   ng-model="ctrl.target.currentTagKey" placeholder="key">
+			</input>
+
+			<input type="text" class="gf-form-input"
+						 spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
+						 data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
+			</input>
 
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
-			Down sample
-		</li>
-
-		<li>
-			<input type="text" class="input-large tight-form-input"
-			ng-model="ctrl.target.downsampleInterval"
-			ng-model-onblur
-			ng-change="ctrl.targetBlur()"
-			placeholder="interval (empty = auto)"></input>
-		</li>
-
-		<li class="tight-form-item query-keyword">
-			Aggregator
-		</li>
-
-		<li>
-			<select ng-model="ctrl.target.downsampleAggregator" class="tight-form-input input-small"
-				ng-options="agg for agg in ctrl.aggregators"
-				ng-change="ctrl.targetBlur()">
-			</select>
-		</li>
-
-		<li class="tight-form-item query-keyword" style="width: 59px" ng-if="ctrl.tsdbVersion == 2">
-			Fill
-		</li>
-
-		<li ng-if="ctrl.tsdbVersion == 2">
-			<select ng-model="ctrl.target.downsampleFillPolicy" class="tight-form-input input-small"
-				ng-options="agg for agg in ctrl.fillPolicies"
-				ng-change="ctrl.targetBlur()">
-			</select>
-		</li>
-
-		<li class="tight-form-item query-keyword">
-			Disable downsampling <editor-checkbox text="" model="ctrl.target.disableDownsampling" change="ctrl.targetBlur()"></editor-checkbox>
-		</li>
-
-	</ul>
-	<div class="clearfix"></div>
-</div>
-
-<div class="tight-form" ng-if="ctrl.tsdbVersion == 2">
-  <ul class="tight-form-list" role="menu">
-    <li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
-      Filters
-      <tip ng-if="ctrl.tsdbVersion == 2">Filters does not work with tags, either of the two will work but not both.</tip>
-    </li>
-    <li ng-repeat="fil in ctrl.target.filters track by $index" class="tight-form-item">
-      {{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
-      <a ng-click="ctrl.editFilter(fil, $index)">
-        <i class="fa fa-pencil"></i>
-      </a>
-      <a ng-click="ctrl.removeFilter($index)">
-        <i class="fa fa-remove"></i>
-      </a>
-    </li>
-    <li class="tight-form-item query-keyword" ng-hide="ctrl.addFilterMode">
-      <a ng-click="ctrl.addFilter()">
-        <i class="fa fa-plus"></i>
-      </a>
-    </li>
-
-    <li class="query-keyword" ng-show="ctrl.addFilterMode">
-      <input type="text" class="input-small tight-form-input" spellcheck='false'
-      bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
-      ng-model="ctrl.target.currentFilterKey" placeholder="key"></input>
-
-      Type <select ng-model="ctrl.target.currentFilterType"
-      class="tight-form-input input-small"
-      ng-options="filType for filType in ctrl.filterTypes">
-      </select>
- 
-      <input type="text" class="input-small tight-form-input"
-      spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
-      data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
-      </input>
-
-      groupBy <editor-checkbox text="" model="ctrl.target.currentFilterGroupBy"></editor-checkbox>
-
-      <a bs-tooltip="ctrl.errors.filters"
-        style="color: rgb(229, 189, 28)"
-        ng-show="ctrl.errors.filters">
-        <i class="fa fa-warning"></i>
-      </a>
- 
-      <a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">
-        add filter
-      </a>
-      <a ng-click="ctrl.closeAddFilterMode()">
-        <i class="fa fa-remove"></i>
-      </a>
-
-    </li>
-  </ul>
-  <div class="clearfix"></div>
-</div>
-
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
-			Tags
-      <tip ng-if="ctrl.tsdbVersion == 2">Please use filters, tags are deprecated in opentsdb 2.2</tip>
-		</li>
-		<li ng-repeat="(key, value) in ctrl.target.tags track by $index" class="tight-form-item">
-			{{key}}&nbsp;=&nbsp;{{value}}
-			<a ng-click="ctrl.editTag(key, value)">
-				<i class="fa fa-pencil"></i>
-			</a>
-			<a ng-click="ctrl.removeTag(key)">
-				<i class="fa fa-remove"></i>
-			</a>
-		</li>
-
-		<li class="tight-form-item query-keyword" ng-hide="ctrl.addTagMode">
-			<a ng-click="ctrl.addTag()">
-				<i class="fa fa-plus"></i>
-			</a>
-		</li>
-
-		<li ng-show="ctrl.addTagMode">
-			<input type="text" class="input-small tight-form-input" spellcheck='false'
-			bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
-			ng-model="ctrl.target.currentTagKey" placeholder="key"></input>
-
-			<input type="text" class="input-small tight-form-input"
-			spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
-			data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
+			<label class="gf-form-label" ng-show="ctrl.errors.tags">
+				<a bs-tooltip="ctrl.errors.tags" style="color: rgb(229, 189, 28)" >
+					<i class="fa fa-warning"></i>
+				</a>
+			</label>
+			<label class="gf-form-label" >
+				<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
+				<a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
+			</label>
+		</div>
+
+		<div class="gf-form gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
+	</div>
+
+	<div class="gf-form-inline">
+		<gf-form-switch class="gf-form" label="Rate" label-class="width-8 query-keyword" checked="ctrl.target.shouldComputeRate" on-change="ctrl.targetBlur()">
+		</gf-form-switch>
+
+		<gf-form-switch ng-hide="!ctrl.target.shouldComputeRate"
+										class="gf-form" label="Counter" checked="ctrl.target.isCounter" on-change="ctrl.targetBlur()">
+		</gf-form-switch>
+
+
+		<div class="gf-form" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
+			<label class="gf-form-label">Counter Max</label>
+			<input type="text" class="gf-form-input"
+					 	 ng-disabled="!ctrl.target.shouldComputeRate"
+						 ng-model="ctrl.target.counterMax" spellcheck='false'
+						 placeholder="max value" ng-model-onblur
+						 ng-blur="ctrl.targetBlur()">
 			</input>
 
-      <a bs-tooltip="ctrl.errors.tags"
-        style="color: rgb(229, 189, 28)"
-        ng-show="ctrl.errors.tags">
-        <i class="fa fa-warning"></i>
-      </a>
-
-			<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">
-				add tag
-			</a>
-      <a ng-click="ctrl.closeAddTagMode()">
-        <i class="fa fa-remove"></i>
-      </a>
-		
-    </li>
-	</ul>
-	<div class="clearfix"></div>
-</div>
-
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
-			Rate <editor-checkbox text="" model="ctrl.target.shouldComputeRate" change="ctrl.targetBlur()"></editor-checkbox>
-		</li>
-
-		<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.shouldComputeRate">
-			Counter <editor-checkbox text="" model="ctrl.target.isCounter" change="ctrl.targetBlur()"></editor-checkbox>
-		</li>
-
-		<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
-			Counter Max:
-		</li>
-
-		<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
-			<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
-			ng-model="ctrl.target.counterMax" spellcheck='false'
-			placeholder="max value" ng-model-onblur
-			ng-blur="ctrl.targetBlur()"></input>
-		</li>
-		<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
-			Reset Value:
-		</li>
-		<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
-			<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
-			ng-model="ctrl.target.counterResetValue" spellcheck='false'
-			placeholder="reset value" ng-model-onblur
-			ng-blur="ctrl.targetBlur()"></input>
-		</li>
-	</ul>
-
-	<div class="clearfix"></div>
-</div>
+			<label class="gf-form-label">Reset Value</label>
+			<input type="text" class="tight-form-input input-small"
+					   ng-disabled="!ctrl.target.shouldComputeRate"
+						 ng-model="ctrl.target.counterResetValue" spellcheck='false'
+						 placeholder="reset value" ng-model-onblur
+						 ng-blur="ctrl.targetBlur()">
+			</input>
+		</div>
+
+		<div class="gf-form gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
+	</div>
+</query-editor-row>
+

+ 48 - 74
public/app/plugins/datasource/prometheus/partials/query.editor.html

@@ -1,80 +1,54 @@
-<query-editor-row ctrl="ctrl">
-	<li class="tight-form-item" style="width: 94px">
-		Query
-	</li>
-	<li>
-		<input type="text"
-		class="input-xxlarge tight-form-input"
-		ng-model="ctrl.target.expr"
-		spellcheck='false'
-		placeholder="query expression"
-		data-min-length=0 data-items=100
-		ng-model-onblur
-		ng-change="ctrl.refreshMetricData()">
-	</li>
-	<li class="tight-form-item">
-		Metric
-	</li>
-	<li>
-		<input type="text"
-		class="input-medium tight-form-input"
-		ng-model="ctrl.target.metric"
-		spellcheck='false'
-		bs-typeahead="ctrl.suggestMetrics"
-		placeholder="metric name"
-		data-min-length=0 data-items=100>
-	</li>
-</query-editor-row>
+<query-editor-row query-ctrl="ctrl" can-collapse="false">
+	<div class="gf-form-inline">
+		<div class="gf-form gf-form--grow">
+			<label class="gf-form-label width-8">Query</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck='false' placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()">
+		</div>
+		<div class="gf-form max-width-22">
+			<label class="gf-form-label">Metric lookup</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
+		</div>
+	</div>
 
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item tight-form-align" style="width: 94px">
-			Legend format
-		</li>
-		<li>
-			<input type="text" class="tight-form-input input-xxlarge" ng-model="ctrl.target.legendFormat"
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-26">
+			<label class="gf-form-label width-8">Legend format</label>
+			<input type="text" class="gf-form-input" ng-model="ctrl.target.legendFormat"
 			spellcheck='false' placeholder="legend format" data-min-length=0 data-items=1000
 			ng-model-onblur ng-change="ctrl.refreshMetricData()">
 			</input>
-		</li>
-	</ul>
-
-	<div class="clearfix"></div>
-</div>
+		</div>
+		<div class="gf-form">
+			<label class="gf-form-label width-5">Step</label>
+			<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
+					   data-placement="right"
+			       spellcheck='false'
+			       placeholder="{{ctrl.panelCtrl.interval}}"
+			       data-min-length=0 data-items=100
+			       ng-model-onblur
+			       ng-change="ctrl.refreshMetricData()"/>
+			<info-popover mode="right-absolute">
+				Leave blank for auto handling based on time range and panel width
+			</info-popover>
+		</div>
+		<div class="gf-form">
+			<label class="gf-form-label">Resolution</label>
+			<div class="gf-form-select-wrapper max-width-15">
+				<select ng-model="ctrl.target.intervalFactor" class="gf-form-input"
+					ng-options="r.factor as r.label for r in ctrl.resolutions"
+					ng-change="ctrl.refreshMetricData()">
+				</select>
+			</div>
+			<label class="gf-form-label">
+				<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
+					<i class="fa fa-share-square-o"></i>
+				</a>
+			</label>
+		</div>
 
-<div class="tight-form">
-	<ul class="tight-form-list" role="menu">
-		<li class="tight-form-item tight-form-align" style="width: 94px">
-			Step
-		</li>
-		<li>
-			<input type="text" class="input-mini tight-form-input" ng-model="ctrl.target.interval"
-			bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-			data-placement="right"
-			spellcheck='false'
-			placeholder="{{ctrl.panelCtrl.interval}}"
-			data-min-length=0 data-items=100
-			ng-model-onblur
-			ng-change="ctrl.refreshMetricData()"
-			/>
-			</input>
-		</li>
+		<div class="gf-form gf-form--grow">
+			<div class="gf-form-label gf-form-label--grow"></div>
+		</div>
+	</div>
 
-		<li class="tight-form-item">
-			Resolution
-		</li>
-		<li>
-			<select ng-model="ctrl.target.intervalFactor" class="tight-form-input input-mini"
-				ng-options="r.factor as r.label for r in ctrl.resolutions"
-				ng-change="ctrl.refreshMetricData()">
-			</select>
-		</li>
-		<li class="tight-form-item">
-			<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
-				<i class="fa fa-share-square-o"></i>
-			</a>
-		</li>
-	</ul>
-
-	<div class="clearfix"></div>
-</div>
+</query-editor-row>

+ 11 - 12
public/app/plugins/panel/dashlist/module.ts

@@ -5,27 +5,26 @@ import config from 'app/core/config';
 import {PanelCtrl} from 'app/plugins/sdk';
 import {impressions} from 'app/features/dashboard/impression_store';
 
- // Set and populate defaults
-var panelDefaults = {
-  query: '',
-  limit: 10,
-  tags: [],
-  recent: false,
-  search: false,
-  starred: true,
-  headings: true,
-};
-
 class DashListCtrl extends PanelCtrl {
   static templateUrl = 'module.html';
 
   groups: any[];
   modes: any[];
 
+  panelDefaults = {
+    query: '',
+    limit: 10,
+    tags: [],
+    recent: false,
+    search: false,
+    starred: true,
+    headings: true,
+  };
+
   /** @ngInject */
   constructor($scope, $injector, private backendSrv) {
     super($scope, $injector);
-    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel, this.panelDefaults);
 
     if (this.panel.tag) {
       this.panel.tags = [this.panel.tag];

+ 1 - 1
public/app/plugins/panel/graph/graph.js

@@ -73,7 +73,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
             var legendSeries = _.filter(data, function(series) {
               return series.hideFromLegend(panel.legend) === false;
             });
-            var total = 23 + (22 * legendSeries.length);
+            var total = 23 + (21 * legendSeries.length);
             return Math.min(total, Math.floor(panelHeight/2));
           } else {
             return 26;

+ 83 - 83
public/app/plugins/panel/graph/module.ts

@@ -13,85 +13,6 @@ import TimeSeries from 'app/core/time_series2';
 import * as fileExport from 'app/core/utils/file_export';
 import {MetricsPanelCtrl} from 'app/plugins/sdk';
 
-var panelDefaults = {
-  // datasource name, null = default datasource
-  datasource: null,
-  // sets client side (flot) or native graphite png renderer (png)
-  renderer: 'flot',
-  yaxes: [
-    {
-      label: null,
-      show: true,
-      logBase: 1,
-      min: null,
-      max: null,
-      format: 'short'
-    },
-    {
-      label: null,
-      show: true,
-      logBase: 1,
-      min: null,
-      max: null,
-      format: 'short'
-    }
-  ],
-  xaxis: {
-    show: true
-  },
-  grid          : {
-    threshold1: null,
-    threshold2: null,
-    threshold1Color: 'rgba(216, 200, 27, 0.27)',
-    threshold2Color: 'rgba(234, 112, 112, 0.22)'
-  },
-  // show/hide lines
-  lines         : true,
-  // fill factor
-  fill          : 1,
-  // line width in pixels
-  linewidth     : 2,
-  // show hide points
-  points        : false,
-  // point radius in pixels
-  pointradius   : 5,
-  // show hide bars
-  bars          : false,
-  // enable/disable stacking
-  stack         : false,
-  // stack percentage mode
-  percentage    : false,
-  // legend options
-  legend: {
-    show: true, // disable/enable legend
-    values: false, // disable/enable legend values
-    min: false,
-    max: false,
-    current: false,
-    total: false,
-    avg: false
-  },
-  // how null points should be handled
-  nullPointMode : 'connected',
-  // staircase line mode
-  steppedLine: false,
-  // tooltip options
-  tooltip       : {
-    value_type: 'cumulative',
-    shared: true,
-    msResolution: false,
-  },
-  // time overrides
-  timeFrom: null,
-  timeShift: null,
-  // metric queries
-  targets: [{}],
-  // series color overrides
-  aliasColors: {},
-  // other style overrides
-  seriesOverrides: [],
-};
-
 class GraphCtrl extends MetricsPanelCtrl {
   static template = template;
 
@@ -105,14 +26,93 @@ class GraphCtrl extends MetricsPanelCtrl {
   datapointsWarning: boolean;
   colors: any = [];
 
+  panelDefaults = {
+    // datasource name, null = default datasource
+    datasource: null,
+    // sets client side (flot) or native graphite png renderer (png)
+    renderer: 'flot',
+    yaxes: [
+      {
+        label: null,
+        show: true,
+        logBase: 1,
+        min: null,
+        max: null,
+        format: 'short'
+      },
+      {
+        label: null,
+        show: true,
+        logBase: 1,
+        min: null,
+        max: null,
+        format: 'short'
+      }
+    ],
+    xaxis: {
+      show: true
+    },
+    grid          : {
+      threshold1: null,
+      threshold2: null,
+      threshold1Color: 'rgba(216, 200, 27, 0.27)',
+      threshold2Color: 'rgba(234, 112, 112, 0.22)'
+    },
+    // show/hide lines
+    lines         : true,
+    // fill factor
+    fill          : 1,
+    // line width in pixels
+    linewidth     : 2,
+    // show hide points
+    points        : false,
+    // point radius in pixels
+    pointradius   : 5,
+    // show hide bars
+    bars          : false,
+    // enable/disable stacking
+    stack         : false,
+    // stack percentage mode
+    percentage    : false,
+    // legend options
+    legend: {
+      show: true, // disable/enable legend
+      values: false, // disable/enable legend values
+      min: false,
+      max: false,
+      current: false,
+      total: false,
+      avg: false
+    },
+    // how null points should be handled
+    nullPointMode : 'connected',
+    // staircase line mode
+    steppedLine: false,
+    // tooltip options
+    tooltip       : {
+      value_type: 'cumulative',
+      shared: true,
+      msResolution: false,
+    },
+    // time overrides
+    timeFrom: null,
+    timeShift: null,
+    // metric queries
+    targets: [{}],
+    // series color overrides
+    aliasColors: {},
+    // other style overrides
+    seriesOverrides: [],
+  };
+
   /** @ngInject */
   constructor($scope, $injector, private annotationsSrv) {
     super($scope, $injector);
 
-    _.defaults(this.panel, angular.copy(panelDefaults));
-    _.defaults(this.panel.tooltip, panelDefaults.tooltip);
-    _.defaults(this.panel.grid, panelDefaults.grid);
-    _.defaults(this.panel.legend, panelDefaults.legend);
+    _.defaults(this.panel, this.panelDefaults);
+    _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
+    _.defaults(this.panel.grid, this.panelDefaults.grid);
+    _.defaults(this.panel.legend, this.panelDefaults.legend);
 
     this.colors = $scope.$root.colors;
 

+ 1 - 1
public/app/plugins/panel/pluginlist/module.html

@@ -22,7 +22,7 @@
       </a>
     </div>
     <div class="pluginlist-item" ng-show="category.list.length === 0">
-      <a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana.net/plugins/">
+      <a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="https://grafana.net/plugins">
         <span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span>
       </a>
     </div>

+ 5 - 5
public/app/plugins/panel/pluginlist/module.ts

@@ -4,20 +4,20 @@ import _ from 'lodash';
 import config from 'app/core/config';
 import {PanelCtrl} from '../../../features/panel/panel_ctrl';
 
-// Set and populate defaults
-var panelDefaults = {
-};
-
 class PluginListCtrl extends PanelCtrl {
   static templateUrl = 'module.html';
 
   pluginList: any[];
   viewModel: any;
 
+  // Set and populate defaults
+  panelDefaults = {
+  };
+
   /** @ngInject */
   constructor($scope, $injector, private backendSrv, private $location) {
     super($scope, $injector);
-    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel, this.panelDefaults);
 
     this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
     this.pluginList = [];

+ 49 - 0
public/app/plugins/panel/singlestat/editor.html

@@ -156,6 +156,55 @@
 	</div>
 </div>
 
+<div class="editor-row">
+	<div class="section" style="margin-bottom: 20px">
+		<div class="tight-form">
+			<ul class="tight-form-list">
+				<li class="tight-form-item" style="width: 80px">
+					<strong>Gauge</strong>
+				</li>
+				<li class="tight-form-item">
+					Show&nbsp;
+					<input class="cr1" id="panel.gauge.show" type="checkbox"
+					ng-model="ctrl.panel.gauge.show" ng-checked="ctrl.panel.gauge.show" ng-change="ctrl.render()">
+					<label for="panel.gauge.show" class="cr1"></label>
+				</li>
+				<li class="tight-form-item">
+					Min
+				</li>
+				<li>
+					<input type="number" class="input-small tight-form-input" ng-model="ctrl.panel.gauge.minValue" ng-blur="ctrl.render()" placeholder="0"></input>
+				</li>
+				<li class="tight-form-item last">
+					Max
+				</li>
+				<li>
+					<input type="number" class="input-small tight-form-input last" ng-model="ctrl.panel.gauge.maxValue" ng-blur="ctrl.render()" placeholder="100"></input>
+					<span class="alert-state-critical" ng-show="ctrl.invalidGaugeRange">
+						&nbsp;
+						<i class="fa fa-warning"></i>
+						Min value is bigger than max.
+					</span>
+				</li>
+			</ul>
+			<div class="clearfix"></div>
+		</div>
+		<div class="tight-form last">
+			<li class="tight-form-item">
+				Threshold labels&nbsp;
+				<input class="cr1" id="panel.gauge.thresholdLabels" type="checkbox" ng-model="ctrl.panel.gauge.thresholdLabels" ng-checked="ctrl.panel.gauge.thresholdLabels" ng-change="ctrl.render()">
+				<label for="panel.gauge.thresholdLabels" class="cr1"></label>
+			</li>
+			<li class="tight-form-item">
+				Threshold markers&nbsp;
+				<input class="cr1" id="panel.gauge.thresholdMarkers" type="checkbox" ng-model="ctrl.panel.gauge.thresholdMarkers" ng-checked="ctrl.panel.gauge.thresholdMarkers" ng-change="ctrl.render()">
+				<label for="panel.gauge.thresholdMarkers" class="cr1"></label>
+			</li>
+			<div class="clearfix"></div>
+		</div>
+	</div>
+</div>
+
 <div class="editor-row">
 	<div class="section" style="margin-bottom: 20px">
 		<div class="tight-form last">

+ 152 - 35
public/app/plugins/panel/singlestat/module.ts

@@ -4,43 +4,13 @@ import angular from 'angular';
 import _ from 'lodash';
 import $ from 'jquery';
 import 'jquery.flot';
+import 'jquery.flot.gauge';
 
 import kbn from 'app/core/utils/kbn';
+import config from 'app/core/config';
 import TimeSeries from 'app/core/time_series2';
 import {MetricsPanelCtrl} from 'app/plugins/sdk';
 
-// Set and populate defaults
-var panelDefaults = {
-  links: [],
-  datasource: null,
-  maxDataPoints: 100,
-  interval: null,
-  targets: [{}],
-  cacheTimeout: null,
-  format: 'none',
-  prefix: '',
-  postfix: '',
-  nullText: null,
-  valueMaps: [
-    { value: 'null', op: '=', text: 'N/A' }
-  ],
-  nullPointMode: 'connected',
-  valueName: 'avg',
-  prefixFontSize: '50%',
-  valueFontSize: '80%',
-  postfixFontSize: '50%',
-  thresholds: '',
-  colorBackground: false,
-  colorValue: false,
-  colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
-  sparkline: {
-    show: false,
-    full: false,
-    lineColor: 'rgb(31, 120, 193)',
-    fillColor: 'rgba(31, 118, 189, 0.18)',
-  }
-};
-
 class SingleStatCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
 
@@ -48,11 +18,51 @@ class SingleStatCtrl extends MetricsPanelCtrl {
   data: any;
   fontSizes: any[];
   unitFormats: any[];
+  invalidGaugeRange: boolean;
+
+  // Set and populate defaults
+  panelDefaults = {
+    links: [],
+    datasource: null,
+    maxDataPoints: 100,
+    interval: null,
+    targets: [{}],
+    cacheTimeout: null,
+    format: 'none',
+    prefix: '',
+    postfix: '',
+    nullText: null,
+    valueMaps: [
+      { value: 'null', op: '=', text: 'N/A' }
+    ],
+    nullPointMode: 'connected',
+    valueName: 'avg',
+    prefixFontSize: '50%',
+    valueFontSize: '80%',
+    postfixFontSize: '50%',
+    thresholds: '',
+    colorBackground: false,
+    colorValue: false,
+    colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
+    sparkline: {
+      show: false,
+      full: false,
+      lineColor: 'rgb(31, 120, 193)',
+      fillColor: 'rgba(31, 118, 189, 0.18)',
+    },
+    gauge: {
+      show: false,
+      minValue: 0,
+      maxValue: 100,
+      thresholdMarkers: true,
+      thresholdLabels: false
+    }
+  };
 
   /** @ngInject */
   constructor($scope, $injector, private $location, private linkSrv) {
     super($scope, $injector);
-    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel, this.panelDefaults);
 
     this.events.on('data-received', this.onDataReceived.bind(this));
     this.events.on('data-error', this.onDataError.bind(this));
@@ -270,6 +280,109 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       return body;
     }
 
+    function getValueText() {
+      var result = panel.prefix ? panel.prefix : '';
+      result += data.valueFormated;
+      result += panel.postfix ? panel.postfix : '';
+
+      return result;
+    }
+
+    function addGauge() {
+      ctrl.invalidGaugeRange = false;
+      if (panel.gauge.minValue > panel.gauge.maxValue) {
+        ctrl.invalidGaugeRange = true;
+        return;
+      }
+
+      var plotCanvas = $('<div></div>');
+      var width = elem.width();
+      var height = elem.height();
+      var plotCss = {
+        top: '10px',
+        margin: 'auto',
+        position: 'relative',
+        height: (height * 0.9) + 'px',
+        width:  width + 'px'
+      };
+
+      plotCanvas.css(plotCss);
+
+      var thresholds = [];
+      for (var i = 0; i < data.thresholds.length; i++) {
+        thresholds.push({
+          value: data.thresholds[i],
+          color: data.colorMap[i]
+        });
+      }
+      thresholds.push({
+        value: panel.gauge.maxValue,
+        color: data.colorMap[data.colorMap.length  - 1]
+      });
+
+      var bgColor = config.bootData.user.lightTheme
+        ? 'rgb(230,230,230)'
+        : 'rgb(38,38,38)';
+
+
+      var dimension = Math.min(width, height);
+      var fontSize = Math.min(dimension/4, 100);
+      var gaugeWidth = Math.min(dimension/6, 60);
+      var thresholdMarkersWidth = gaugeWidth/5;
+
+      var options = {
+        series: {
+          gauges: {
+            gauge: {
+              min: panel.gauge.minValue,
+              max: panel.gauge.maxValue,
+              background: { color: bgColor },
+              border: { color: null },
+              shadow: { show: false },
+              width: gaugeWidth,
+            },
+            frame: { show: false },
+            label: { show: false },
+            layout: { margin: 0, thresholdWidth: 0 },
+            cell: { border: { width: 0 } },
+            threshold: {
+              values: thresholds,
+              label: {
+                show: panel.gauge.thresholdLabels,
+                margin: 8,
+                font: { size: 18 }
+              },
+              show: panel.gauge.thresholdMarkers,
+              width: thresholdMarkersWidth,
+            },
+            value: {
+              color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null,
+              formatter: function() { return getValueText(); },
+              font: { size: fontSize, family: 'Helvetica Neue", Helvetica, Arial, sans-serif' }
+            },
+            show: true
+          }
+        }
+      };
+
+      elem.append(plotCanvas);
+
+      var plotSeries = {
+        data: [[0, data.valueRounded]]
+      };
+
+      $.plot(plotCanvas, [plotSeries], options);
+    }
+
+    function getGaugeFontSize() {
+      if (panel.valueFontSize) {
+        var num = parseInt(panel.valueFontSize.substring(0, panel.valueFontSize.length - 1));
+        return (30 * (num / 100)) + 15;
+      } else {
+        return 30;
+      }
+    }
+
     function addSparkline() {
       var width = elem.width() + 20;
       if (width < 30) {
@@ -331,11 +444,11 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
     function render() {
       if (!ctrl.data) { return; }
-
+      ctrl.setValues(ctrl.data);
       data = ctrl.data;
       setElementHeight();
 
-      var body = getBigValueHtml();
+      var body = panel.gauge.show ? '' : getBigValueHtml();
 
       if (panel.colorBackground && !isNaN(data.valueRounded)) {
         var color = getColorForValue(data, data.valueRounded);
@@ -358,6 +471,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
         addSparkline();
       }
 
+      if (panel.gauge.show) {
+        addGauge();
+      }
+
       elem.toggleClass('pointer', panel.links.length > 0);
 
       if (panel.links.length > 0) {

+ 33 - 28
public/app/plugins/panel/table/module.ts

@@ -10,33 +10,6 @@ import {transformDataToTable} from './transformers';
 import {tablePanelEditor} from './editor';
 import {TableRenderer} from './renderer';
 
-var panelDefaults = {
-  targets: [{}],
-  transform: 'timeseries_to_columns',
-  pageSize: null,
-  showHeader: true,
-  styles: [
-    {
-      type: 'date',
-      pattern: 'Time',
-      dateFormat: 'YYYY-MM-DD HH:mm:ss',
-    },
-    {
-      unit: 'short',
-      type: 'number',
-      decimals: 2,
-      colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
-      colorMode: null,
-      pattern: '/.*/',
-      thresholds: [],
-    }
-  ],
-  columns: [],
-  scroll: true,
-  fontSize: '100%',
-  sort: {col: 0, desc: true},
-};
-
 class TablePanelCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
 
@@ -44,6 +17,33 @@ class TablePanelCtrl extends MetricsPanelCtrl {
   dataRaw: any;
   table: any;
 
+  panelDefaults = {
+    targets: [{}],
+    transform: 'timeseries_to_columns',
+    pageSize: null,
+    showHeader: true,
+    styles: [
+      {
+        type: 'date',
+        pattern: 'Time',
+        dateFormat: 'YYYY-MM-DD HH:mm:ss',
+      },
+      {
+        unit: 'short',
+        type: 'number',
+        decimals: 2,
+        colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
+        colorMode: null,
+        pattern: '/.*/',
+        thresholds: [],
+      }
+    ],
+    columns: [],
+    scroll: true,
+    fontSize: '100%',
+    sort: {col: 0, desc: true},
+  };
+
   /** @ngInject */
   constructor($scope, $injector, private annotationsSrv) {
     super($scope, $injector);
@@ -56,7 +56,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
       delete this.panel.fields;
     }
 
-    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel, this.panelDefaults);
 
     this.events.on('data-received', this.onDataReceived.bind(this));
     this.events.on('data-error', this.onDataError.bind(this));
@@ -120,6 +120,11 @@ class TablePanelCtrl extends MetricsPanelCtrl {
   }
 
   toggleColumnSort(col, colIndex) {
+    // remove sort flag from current column
+    if (this.table.columns[this.panel.sort.col]) {
+      this.table.columns[this.panel.sort.col].sort = false;
+    }
+
     if (this.panel.sort.col === colIndex) {
       if (this.panel.sort.desc) {
         this.panel.sort.desc = false;

+ 1 - 1
public/app/plugins/panel/table/renderer.ts

@@ -30,7 +30,7 @@ export class TableRenderer {
     }
 
     if (_.isArray(v)) {
-      v = v.join(',&nbsp;');
+      v = v.join(', ');
     }
 
     return v;

+ 6 - 8
public/app/plugins/panel/text/module.ts

@@ -3,23 +3,21 @@
 import _ from 'lodash';
 import {PanelCtrl} from 'app/plugins/sdk';
 
- // Set and populate defaults
-var panelDefaults = {
-  mode    : "markdown", // 'html', 'markdown', 'text'
-  content : "# title",
-};
-
 export class TextPanelCtrl extends PanelCtrl {
   static templateUrl = `public/app/plugins/panel/text/module.html`;
 
   remarkable: any;
   content: string;
-
+  // Set and populate defaults
+  panelDefaults = {
+    mode    : "markdown", // 'html', 'markdown', 'text'
+    content : "# title",
+  };
   /** @ngInject */
   constructor($scope, $injector, private templateSrv, private $sce) {
     super($scope, $injector);
 
-    _.defaults(this.panel, panelDefaults);
+    _.defaults(this.panel, this.panelDefaults);
 
     this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
     this.events.on('refresh', this.onRender.bind(this));

+ 2 - 1
public/app/system.conf.js

@@ -27,7 +27,8 @@ System.config({
     "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",
     "jquery.flot.time": "vendor/flot/jquery.flot.time",
     "jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair",
-    "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow"
+    "jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
+    "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge"
   },
 
   packages: {

+ 1 - 0
public/sass/_grafana.scss

@@ -70,6 +70,7 @@
 @import "components/drop";
 @import "components/query_editor";
 @import "components/tabbed_view";
+@import "components/query_part";
 
 // PAGES
 @import "pages/login";

+ 3 - 3
public/sass/_variables.dark.scss

@@ -67,8 +67,8 @@ $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98%
 
 // Links
 // -------------------------
-$link-color:              darken($white,11%);
-$link-color-disabled:     darken($link-color,30%);
+$link-color:              darken($white, 11%);
+$link-color-disabled:     darken($link-color, 30%);
 $link-hover-color:        $white;
 $external-link-color:     $blue;
 
@@ -76,7 +76,7 @@ $external-link-color:     $blue;
 // -------------------------
 $headings-color:       darken($white,11%);
 $abbr-border-color: 	 $gray-3 !default;
-$text-muted: 			     darken($link-color,30%);
+$text-muted: 			     $text-color-weak;
 
 $blockquote-small-color:  $gray-3 !default;
 $blockquote-border-color: $gray-4 !default;

+ 1 - 1
public/sass/_variables.light.scss

@@ -82,7 +82,7 @@ $external-link-color:    $blue;
 // -------------------------
 $headings-color:       $text-color;
 $abbr-border-color: 	 $gray-2 !default;
-$text-muted: 			     darken($link-color,30%);
+$text-muted: 			     $text-color-weak;
 
 $blockquote-small-color:  $gray-2 !default;
 $blockquote-border-color: $gray-3 !default;

+ 2 - 4
public/sass/base/_code.scss

@@ -18,6 +18,8 @@ pre {
 code {
   color: $text-color;
   white-space: nowrap;
+  padding: 2px 5px;
+  margin: 0 2px;
 }
 
 code.code--small {
@@ -26,10 +28,6 @@ code.code--small {
   margin: 0 2px;
 }
 
-p.code--line {
-  line-height: 1.8;
-}
-
 // Blocks of code
 pre {
   display: block;

+ 6 - 2
public/sass/base/_type.scss

@@ -27,9 +27,9 @@ em      { font-style: italic; color: $headings-color; }
 cite    { font-style: normal; }
 
 // Utility classes
-.muted               { color: $gray-2; }
+.muted               { color: $text-muted; }
 a.muted:hover,
-a.muted:focus        { color: darken($gray-2, 10%); }
+a.muted:focus        { color: darken($text-muted, 10%); }
 
 .text-warning        { color: $state-warning-text; }
 a.text-warning:hover,
@@ -118,6 +118,10 @@ small,
   font-weight: normal;
 }
 
+.small-xs {
+  font-size: $font-size-xs;
+}
+
 mark,
 .mark {
   padding: .2em;

+ 1 - 1
public/sass/components/_drop.scss

@@ -1,4 +1,4 @@
-$popover-arrow-size: 1rem;
+$popover-arrow-size: 0.7rem;
 $color: inherit;
 $backgroundColor: $btn-secondary-bg;
 $color: $text-color;

+ 14 - 12
public/sass/components/_dropdown.scss

@@ -67,18 +67,20 @@
   }
 
   // Links within the dropdown menu
-  > li > a {
-    display: block;
-    padding: 3px 20px 3px 15px;
-    clear: both;
-    font-weight: normal;
-    line-height: $line-height-base;
-    color: $dropdownLinkColor;
-    white-space: nowrap;
-
-    i {
-      padding-right: 5px;
-      color: $link-color-disabled;
+  > li {
+      > a {
+      display: block;
+      padding: 3px 20px 3px 15px;
+      clear: both;
+      font-weight: normal;
+      line-height: $line-height-base;
+      color: $dropdownLinkColor;
+      white-space: nowrap;
+
+      i {
+        padding-right: 5px;
+        color: $link-color-disabled;
+      }
     }
   }
 }

+ 40 - 8
public/sass/components/_gf-form.scss

@@ -8,8 +8,22 @@ $gf-form-margin: 0.25rem;
   text-align: left;
   position: relative;
 
-  .cr1 {
-    margin-left: 8px;
+  &--offset-1 {
+    margin-left: $spacer;
+  }
+
+  &--grow {
+    flex-grow: 1;
+  }
+}
+
+.gf-form-disabled {
+  color: $text-color-weak;
+
+  .query-keyword,
+  a,
+  .gf-form-input {
+    color: $text-color-weak;
   }
 }
 
@@ -22,10 +36,6 @@ $gf-form-margin: 0.25rem;
   flex-direction: row;
   flex-wrap: wrap;
   align-content: flex-start;
-
-  .gf-form-flex {
-    flex-grow: 1;
-  }
 }
 
 .gf-form-button-row {
@@ -48,6 +58,12 @@ $gf-form-margin: 0.25rem;
 
   border: $input-btn-border-width solid transparent;
   @include border-radius($label-border-radius-sm);
+
+
+  &--grow {
+    flex-grow: 1;
+    min-height: 2.70rem;
+  }
 }
 
 .gf-form-checkbox {
@@ -107,6 +123,21 @@ $gf-form-margin: 0.25rem;
   }
 
   &.gf-size-auto { width: auto; }
+
+  &--dropdown {
+    padding-right: $input-padding-x*2;
+
+    &:after {
+      position: absolute;
+      top: 35%;
+      right: $input-padding-x/2;
+      background-color: transparent;
+      color: $input-color;
+      font: normal normal normal $font-size-sm/1 FontAwesome;
+      content: '\f0d7';
+      pointer-events: none;
+    }
+  }
 }
 
 .gf-form-select-wrapper {
@@ -152,9 +183,11 @@ $gf-form-margin: 0.25rem;
 }
 
 .gf-form-btn {
-  margin-right: $gf-form-margin;
   padding: $input-padding-y $input-padding-x;
+  margin-right: $gf-form-margin;
   line-height: $input-line-height;
+  font-size: $font-size-sm;
+
   flex-shrink: 0;
   flex-grow: 0;
 }
@@ -209,4 +242,3 @@ $gf-form-margin: 0.25rem;
 select.gf-form-input ~ .gf-form-help-icon {
   right: 10px;
 }
-

+ 2 - 1
public/sass/components/_panel_graph.scss

@@ -85,7 +85,8 @@
 }
 
 .graph-legend-table {
-  overflow-y: scroll;
+  overflow-y: auto;
+  overflow-x: hidden;
 
   .graph-legend-series {
     display: table-row;

+ 4 - 0
public/sass/components/_panel_singlestat.scss

@@ -48,4 +48,8 @@
   }
 }
 
+#flotGagueValue0 {
+  font-weight: bold; //please dont hurt me for this!
+}
+
 

+ 2 - 0
public/sass/components/_panel_text.scss

@@ -3,4 +3,6 @@
   ul {
     margin: 0 0 $spacer $spacer * 1.5;
   }
+  li {line-height: 2;}
+  a { color: $external-link-color; }
 }

+ 57 - 4
public/sass/components/_query_editor.scss

@@ -4,13 +4,66 @@
 }
 
 .query-segment-key {
-  border-right: none;
-  padding-right: 1px;
+  //border-right: none;
+  //padding-right: 1px;
 }
 
 .query-segment-operator {
-  padding-right: 1px;
-  border-right: none;
+  //padding-right: 1px;
+  //border-right: none;
   color: $orange;
 }
 
+.gf-form-query {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: nowrap;
+  align-content: flex-start;
+  align-items: flex-start;
+
+  .gf-form,
+  .gf-form-filler {
+    margin-bottom: 2px;
+  }
+
+  .gf-form-switch,
+  .gf-form-switch label,
+  .gf-form-input,
+  .gf-form-select-wrapper,
+  .gf-form-filler,
+  .gf-form-label {
+    margin-right: 2px;
+  }
+}
+
+.gf-form-query-content {
+  flex-grow: 2;
+
+  &--collapsed {
+    overflow: hidden;
+
+    .gf-form-label {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      width: 100%;
+      white-space: nowrap;
+    }
+  }
+}
+
+.gf-form-query-letter-cell {
+  .gf-form-query-letter-cell-carret {
+    display: inline-block;
+    width: 0.7rem;
+    position: relative;
+    left: -2px;
+  }
+  .gf-form-query-letter-cell-letter {
+    font-weight: bold;
+    color: $blue;
+  }
+  .gf-form-query-letter-cell-ds {
+    color: $text-color-weak;
+  }
+}
+

+ 11 - 0
public/sass/components/_query_part.scss

@@ -0,0 +1,11 @@
+
+.query-part {
+  background-color: $input-bg !important;
+
+  &.show-function-controls {
+    padding-top: 5px;
+    min-width: 100px;
+    text-align: center;
+  }
+}
+

+ 1 - 1
public/sass/mixins/_drop_element.scss

@@ -10,7 +10,7 @@
       font-family: inherit;
       background: $theme-bg;
       color: $theme-color;
-      padding: $spacer;
+      padding: 0.65rem;
       font-size: $font-size-sm;
       max-width: 20rem;
 

+ 2 - 1
public/sass/utils/_utils.scss

@@ -61,13 +61,14 @@ button.close {
 .hide {
   display: none;
 }
+
 .show {
   display: block;
 }
 
 // Visibility
 .invisible {
-  visibility: hidden;
+  visibility: hidden !important;
 }
 
 // For Affix plugin

+ 6 - 0
public/sass/utils/_widths.scss

@@ -17,3 +17,9 @@
   }
 }
 
+@for $i from 1 through 30 {
+  .offset-width-#{$i} {
+    margin-left: ($spacer * $i) !important;
+  }
+}
+

+ 6 - 1
public/test/specs/templateSrv-specs.js

@@ -99,6 +99,11 @@ define([
         var target = _templateSrv.replace('this.$test.filters', {}, 'glob');
         expect(target).to.be('this.*.filters');
       });
+
+      it('should not escape custom all value', function() {
+        var target = _templateSrv.replace('this.$test', {}, 'regex');
+        expect(target).to.be('this.*');
+      });
     });
 
     describe('lucene format', function() {
@@ -127,7 +132,7 @@ define([
 
       it('multi value and regex format should render regex string', function() {
         var result = _templateSrv.formatValue(['test.','test2'], 'regex');
-        expect(result).to.be('test\\.|test2');
+        expect(result).to.be('(test\\.|test2)');
       });
 
       it('multi value and pipe should render pipe string', function() {

+ 1 - 1
public/test/specs/templateValuesSrv-specs.js

@@ -280,7 +280,7 @@ define([
       });
 
       it('should add All option with custom value', function() {
-        expect(scenario.variable.options[0].value).to.be('*');
+        expect(scenario.variable.options[0].value).to.be('$__all');
       });
     });
 

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