diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8239bd437..6b8cd4f7128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 3.0.0-beta6 (unreleased) + +### Enhancements +* **Singlestat**: Support for gauges in singlestat panel. closes [#3688](https://github.com/grafana/grafana/pull/3688) +* **Templating**: Support for data source as variable, closes [#816](https://github.com/grafana/grafana/pull/816) + +### 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 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..6cccaa7f64b --- /dev/null +++ b/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 diff --git a/README.md b/README.md index 931055349cf..d538ba78e1e 100644 --- a/README.md +++ b/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) - diff --git a/build.go b/build.go index 93b9e6db087..0965a405e70 100644 --- a/build.go +++ b/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() { diff --git a/circle.yml b/circle.yml index 22dab8ab893..02f9f91e103 100644 --- a/circle.yml +++ b/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} diff --git a/docs/sources/http_api/dashboard.md b/docs/sources/http_api/dashboard.md index 68f6ab022cc..ead63c722aa 100644 --- a/docs/sources/http_api/dashboard.md +++ b/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**: diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 231b4a601ac..bb870f026f3 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -11,7 +11,7 @@ page_keywords: grafana, installation, debian, ubuntu, guide Description | Download ------------ | ------------- Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb) -Beta .deb for Debian-based Linux | [grafana_3.0.0-beta51460725904_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb) +Beta .deb for Debian-based Linux | [grafana_3.0.0-beta61461918338_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta61461918338_amd64.deb) ## Install Stable @@ -21,9 +21,9 @@ Beta .deb for Debian-based Linux | [grafana_3.0.0-beta51460725904_amd64.deb](h ## Install 3.0 Beta - $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb + $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta61461918338_amd64.deb $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_3.0.0-beta51460725904_amd64.deb + $ sudo dpkg -i grafana_3.0.0-beta61461918338_amd64.deb ## APT Repository diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index d981a5c4b2c..f6a7ebb9c3a 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -11,7 +11,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide Description | Download ------------ | ------------- Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm) -Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta51460725904.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta51460725904§.x86_64.rpm) +Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta61461918338.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta61461918338§.x86_64.rpm) ## Install Stable Release from package file @@ -34,18 +34,18 @@ Or install manually using `rpm`. You can install Grafana using Yum directly. - $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta51460725904.x86_64.rpm + $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta61461918338.x86_64.rpm Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-3.0.0-beta51460725904.x86_64.rpm + $ sudo rpm -Uvh grafana-3.0.0-beta61461918338.x86_64.rpm #### On OpenSuse: - $ sudo rpm -i --nodeps grafana-3.0.0-beta51460725904.x86_64.rpm + $ sudo rpm -i --nodeps grafana-3.0.0-beta61461918338.x86_64.rpm ## Install via YUM Repository diff --git a/docs/sources/plugins/index.md b/docs/sources/plugins/index.md index 08654bfb946..bb5c8062223 100644 --- a/docs/sources/plugins/index.md +++ b/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) diff --git a/package.json b/package.json index f454a42ba7a..e314476f999 100644 --- a/package.json +++ b/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" }, diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index f5e5274a202..5ecdaabf516 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/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"}, diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 3c369ca5b7f..cdcb871314d 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/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 == "" { diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index eb88045cd51..b00ef595161 100644 --- a/pkg/api/dataproxy.go +++ b/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") diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index fbdf8d4e0ea..7d46cc9d348 100644 --- a/pkg/api/dtos/plugins.go +++ b/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"` } diff --git a/pkg/api/index.go b/pkg/api/index.go index 575ea35cfaf..53538fd2775 100644 --- a/pkg/api/index.go +++ b/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) + } } } diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 6446f7fee33..ad81d438978 100644 --- a/pkg/api/plugins.go +++ b/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 { diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index b3821a47844..ec454078f9b 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/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() diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 81e7a15233b..eb5973d07be 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/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() } } diff --git a/pkg/cmd/grafana-cli/commands/remove_command.go b/pkg/cmd/grafana-cli/commands/remove_command.go index e0ecbb2b788..9792ed9d095 100644 --- a/pkg/cmd/grafana-cli/commands/remove_command.go +++ b/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()) } diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go index 5cc3fb6f306..b9549e00c1a 100644 --- a/pkg/cmd/grafana-cli/main.go +++ b/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() { diff --git a/pkg/models/dashboard_snapshot.go b/pkg/models/dashboard_snapshot.go index f920e91f2e4..4a4d1290b6d 100644 --- a/pkg/models/dashboard_snapshot.go +++ b/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 diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index 48d17deb9db..d7a918751ef 100644 --- a/pkg/models/org_user.go +++ b/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 diff --git a/pkg/plugins/dashboard_importer.go b/pkg/plugins/dashboard_importer.go index 834bfabd048..4d2757b9a0e 100644 --- a/pkg/plugins/dashboard_importer.go +++ b/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 { diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 68268239c51..aa381abacc7 100644 --- a/pkg/plugins/models.go +++ b/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:"-"` } diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index a64094cb65e..ef36fd75ab6 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/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 { diff --git a/pkg/services/sqlstore/plugin_setting.go b/pkg/services/sqlstore/plugin_setting.go index b3285e905cf..ec0b9b2e2d7 100644 --- a/pkg/services/sqlstore/plugin_setting.go +++ b/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 diff --git a/public/app/core/components/dashboard_selector.ts b/public/app/core/components/dashboard_selector.ts index 913a43911fa..4209ff3f8dc 100644 --- a/public/app/core/components/dashboard_selector.ts +++ b/public/app/core/components/dashboard_selector.ts @@ -7,6 +7,9 @@ import coreModule from 'app/core/core_module'; var template = ` + + Not finding dashboard you want? Star it first, then it should appear in this select box. + `; export class DashboardSelectorCtrl { diff --git a/public/app/core/components/info_popover.ts b/public/app/core/components/info_popover.ts index cc49ecbc4ab..d370aab9d6d 100644 --- a/public/app/core/components/info_popover.ts +++ b/public/app/core/components/info_popover.ts @@ -8,21 +8,24 @@ import Drop from 'tether-drop'; export function infoPopover() { return { restrict: 'E', + template: '', transclude: true, link: function(scope, elem, attrs, ctrl, transclude) { - var inputElem = elem.prev(); - if (inputElem.length === 0) { - console.log('Failed to find input element for popover'); - return; - } - var offset = attrs.offset || '0 -10px'; var position = attrs.position || 'right middle'; var classes = 'drop-help drop-hide-out-of-bounds'; + var openOn = 'hover'; + + elem.addClass('gf-form-help-icon'); + if (attrs.wide) { classes += ' drop-wide'; } + if (attrs.mode) { + elem.addClass('gf-form-help-icon--' + attrs.mode); + } + transclude(function(clone, newScope) { var content = document.createElement("div"); _.each(clone, (node) => { @@ -30,11 +33,12 @@ export function infoPopover() { }); var drop = new Drop({ - target: inputElem[0], + target: elem[0], content: content, position: position, classes: classes, - openOn: 'click', + openOn: openOn, + hoverOpenDelay: 400, tetherOptions: { offset: offset } diff --git a/public/app/core/components/switch.ts b/public/app/core/components/switch.ts index e2ba60d92e8..2a64ec487f7 100644 --- a/public/app/core/components/switch.ts +++ b/public/app/core/components/switch.ts @@ -7,7 +7,12 @@ import coreModule from 'app/core/core_module'; import Drop from 'tether-drop'; var template = ` - +
@@ -49,22 +54,6 @@ export function switchDirective() { onChange: "&", }, template: template, - link: (scope, elem) => { - if (scope.ctrl.tooltip) { - var drop = new Drop({ - target: elem[0], - content: scope.ctrl.tooltip, - position: "right middle", - classes: 'drop-help', - openOn: 'hover', - hoverOpenDelay: 400, - }); - - scope.$on('$destroy', function() { - drop.destroy(); - }); - } - } }; } diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js index 2dc5111874e..b44e953785e 100644 --- a/public/app/core/directives/dropdown_typeahead.js +++ b/public/app/core/directives/dropdown_typeahead.js @@ -9,10 +9,10 @@ function (_, $, coreModule) { coreModule.default.directive('dropdownTypeahead', function($compile) { var inputTemplate = ''; - var buttonTemplate = ''; diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js index 56dc1fd935a..61415a660b2 100644 --- a/public/app/core/directives/metric_segment.js +++ b/public/app/core/directives/metric_segment.js @@ -8,10 +8,13 @@ function (_, $, coreModule) { coreModule.default.directive('metricSegment', function($compile, $sce) { var inputTemplate = ''; - var buttonTemplate = ''; + + var selectTemplate = ''; 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; diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 3e6383cc5c6..6f693a13d68 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/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) { diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index ad670f68a32..41d99c3198f 100644 --- a/public/app/core/services/context_srv.ts +++ b/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; diff --git a/public/app/core/services/datasource_srv.js b/public/app/core/services/datasource_srv.js index 07e3f004d45..2c3a043ac6c 100644 --- a/public/app/core/services/datasource_srv.js +++ b/public/app/core/services/datasource_srv.js @@ -7,36 +7,11 @@ define([ function (angular, _, coreModule, config) { 'use strict'; - coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope) { + coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope, templateSrv) { var self = this; this.init = function() { this.datasources = {}; - this.metricSources = []; - this.annotationSources = []; - - _.each(config.datasources, function(value, key) { - if (value.meta && value.meta.metrics) { - self.metricSources.push({ - value: key === config.defaultDatasource ? null : key, - name: key, - meta: value.meta, - }); - } - if (value.meta && value.meta.annotations) { - self.annotationSources.push(value); - } - }); - - this.metricSources.sort(function(a, b) { - if (a.meta.builtIn || a.name > b.name) { - return 1; - } - if (a.name < b.name) { - return -1; - } - return 0; - }); }; this.get = function(name) { @@ -44,6 +19,8 @@ function (angular, _, coreModule, config) { return this.get(config.defaultDatasource); } + name = templateSrv.replace(name); + if (this.datasources[name]) { return $q.when(this.datasources[name]); } @@ -89,11 +66,61 @@ function (angular, _, coreModule, config) { }; this.getAnnotationSources = function() { - return this.annotationSources; + return _.reduce(config.datasources, function(memo, key, value) { + + if (value.meta && value.meta.annotations) { + memo.push(value); + } + + return memo; + }, []); }; - this.getMetricSources = function() { - return this.metricSources; + this.getMetricSources = function(options) { + var metricSources = []; + + _.each(config.datasources, function(value, key) { + if (value.meta && value.meta.metrics) { + metricSources.push({ + value: key === config.defaultDatasource ? null : key, + name: key, + meta: value.meta, + }); + } + }); + + if (!options || !options.skipVariables) { + // look for data source variables + for (var i = 0; i < templateSrv.variables.length; i++) { + var variable = templateSrv.variables[i]; + if (variable.type !== 'datasource') { + continue; + } + + var first = variable.current.value; + var ds = config.datasources[first]; + + if (ds) { + metricSources.push({ + name: '$' + variable.name, + value: '$' + variable.name, + meta: ds.meta, + }); + } + } + } + + metricSources.sort(function(a, b) { + if (a.meta.builtIn || a.name > b.name) { + return 1; + } + if (a.name < b.name) { + return -1; + } + return 0; + }); + + return metricSources; }; this.init(); diff --git a/public/app/core/services/segment_srv.js b/public/app/core/services/segment_srv.js index a0b85a8bfb2..d05a2bb011f 100644 --- a/public/app/core/services/segment_srv.js +++ b/public/app/core/services/segment_srv.js @@ -19,7 +19,7 @@ function (angular, _, coreModule) { if (_.isString(options)) { this.value = options; - this.html = $sce.trustAsHtml(this.value); + this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value)); return; } diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index fa0aeccc54b..f3d0b81998f 100644 --- a/public/app/core/table_model.ts +++ b/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; } } } diff --git a/public/app/core/utils/emitter.ts b/public/app/core/utils/emitter.ts index 5f3f61cca2e..4cdc19e7b20 100644 --- a/public/app/core/utils/emitter.ts +++ b/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); }); } diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index a336ea7e6f5..cd05b9a8c52 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -26,7 +26,10 @@
- +
@@ -46,19 +49,19 @@ label="Editable" tooltip="Uncheck, then save and reload to disable all dashboard editing" checked="dashboard.editable" - label-class="width-10"> + label-class="width-11"> + label-class="width-11"> + label-class="width-11"> diff --git a/public/app/features/dashboard/timepicker/timepicker.html b/public/app/features/dashboard/timepicker/timepicker.html index a1b4321cc80..12570b3f2b0 100644 --- a/public/app/features/dashboard/timepicker/timepicker.html +++ b/public/app/features/dashboard/timepicker/timepicker.html @@ -3,7 +3,7 @@
  • -
  • +
  • Zoom Out
  • diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index fbdff8e116a..b22ec162dd6 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -92,7 +92,7 @@ export class TimePickerCtrl { move(direction) { var range = this.timeSrv.timeRange(); - var timespan = (range.to.valueOf() - range.from.valueOf()); + var timespan = (range.to.valueOf() - range.from.valueOf()) / 2; var to, from; if (direction === -1) { to = range.to.valueOf() - timespan; diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 2738105553a..cda5e69f006 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/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); }); diff --git a/public/app/features/org/prefs_control.ts b/public/app/features/org/prefs_control.ts index 53704d5e390..fab51ad02b8 100644 --- a/public/app/features/org/prefs_control.ts +++ b/public/app/features/org/prefs_control.ts @@ -49,7 +49,7 @@ export class PrefsControlCtrl { } var template = ` -
    +

    Preferences

    @@ -61,9 +61,8 @@ var template = `
    Home Dashboard - +
    diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index b3ec16e541f..2f978e65345 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -5,4 +5,5 @@ define([ './query_ctrl', './panel_editor_tab', './query_editor_row', + './metrics_ds_selector', ], function () {}); diff --git a/public/app/features/panel/metrics_ds_selector.ts b/public/app/features/panel/metrics_ds_selector.ts new file mode 100644 index 00000000000..b281c43048b --- /dev/null +++ b/public/app/features/panel/metrics_ds_selector.ts @@ -0,0 +1,110 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; + +var module = angular.module('grafana.directives'); + +var template = ` +
    +
    +
    + + + + +
    + +
    + + + +
    +
    +
    +`; + + +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: "=" + } + }; +}); diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index e8c16fd9ab7..0bccee8ff35 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -15,6 +15,7 @@ class MetricsPanelCtrl extends PanelCtrl { error: boolean; loading: boolean; datasource: any; + datasourceName: any; $q: any; $timeout: any; datasourceSrv: any; @@ -27,7 +28,6 @@ class MetricsPanelCtrl extends PanelCtrl { resolution: any; timeInfo: any; skipDataOnInit: boolean; - datasources: any[]; dataStream: any; dataSubscription: any; @@ -52,7 +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(); } private onMetricsPanelRefresh() { @@ -246,19 +245,10 @@ class MetricsPanelCtrl extends PanelCtrl { } this.panel.datasource = datasource.value; + this.datasourceName = datasource.name; this.datasource = null; this.refresh(); } - - addDataQuery(datasource) { - var target: any = {}; - - if (datasource) { - target.datasource = datasource.name; - } - - this.panel.targets.push(target); - } } export {MetricsPanelCtrl}; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 4f3df7e6e8a..82e19f8681b 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -50,9 +50,9 @@ export class PanelCtrl { } 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() { diff --git a/public/app/features/panel/partials/query_editor_row.html b/public/app/features/panel/partials/query_editor_row.html index 81f7e4f6c98..e8dbe1434e7 100644 --- a/public/app/features/panel/partials/query_editor_row.html +++ b/public/app/features/panel/partials/query_editor_row.html @@ -1,4 +1,63 @@ -
    + +
    + + +
    +
    + +
    +
    + +
    +
    + +
    + + + +
    +
    + +
    Type -
    +
    @@ -53,7 +53,7 @@ -
    +
    Testing....
    {{ctrl.testing.title}}
    diff --git a/public/app/features/plugins/partials/ds_http_settings.html b/public/app/features/plugins/partials/ds_http_settings.html index 404918f90d4..08d2af5744a 100644 --- a/public/app/features/plugins/partials/ds_http_settings.html +++ b/public/app/features/plugins/partials/ds_http_settings.html @@ -3,29 +3,34 @@

    Http settings

    -
    - Url - - - -

    Specify a complete HTTP url (for example http://your_server:8080)

    - - Your access method is Direct, this means the url - needs to be accessable from the browser. - - - Your access method is currently Proxy, this means the url - needs to be accessable from the grafana backend. - -
    +
    +
    + Url + + +

    Specify a complete HTTP url (for example http://your_server:8080)

    + + Your access method is Direct, this means the url + needs to be accessable from the browser. + + + Your access method is currently Proxy, this means the url + needs to be accessable from the grafana backend. + +
    +
    -
    - - Access Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request - -
    - +
    +
    + Access +
    + + + Direct = url is used directly from browser
    + Proxy = Grafana backend will proxy the request +
    +
    @@ -34,12 +39,12 @@
    + label="Basic Auth" + checked="current.basicAuth" switch-class="max-width-6"> + label="With Credentials" + checked="current.withCredentials" switch-class="max-width-6">
    diff --git a/public/app/features/plugins/partials/update_instructions.html b/public/app/features/plugins/partials/update_instructions.html index 80e3a413d1d..9648be53c86 100644 --- a/public/app/features/plugins/partials/update_instructions.html +++ b/public/app/features/plugins/partials/update_instructions.html @@ -14,8 +14,8 @@

    Type the following on the command line to update {{plugin.name}}.

    grafana-cli plugins update {{plugin.id}}
    - Check out {{plugin.name}} on
    Grafana.net for README and changelog. If you do not have access to the command line, ask your Grafana administator. + Check out {{plugin.name}} on Grafana.net for README and changelog. If you do not have access to the command line, ask your Grafana administator.
    -

    Pro tip: To update all plugins at once, type grafana-cli plugins update-all on the command line.

    +

    Pro tip: To update all plugins at once, type grafana-cli plugins update-all on the command line.

    diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index 291915ee99f..43bda1db05f 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -20,6 +20,13 @@ function (angular, _) { multi: false, }; + $scope.variableTypes = [ + {value: "query", text: "Query"}, + {value: "interval", text: "Interval"}, + {value: "datasource", text: "Data source"}, + {value: "custom", text: "Custom"}, + ]; + $scope.refreshOptions = [ {value: 0, text: "Never"}, {value: 1, text: "On Dashboard Load"}, @@ -35,10 +42,16 @@ function (angular, _) { $scope.init = function() { $scope.mode = 'list'; + $scope.datasourceTypes = {}; $scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) { + $scope.datasourceTypes[ds.meta.id] = {text: ds.meta.name, value: ds.meta.id}; return !ds.meta.builtIn; }); + $scope.datasourceTypes = _.map($scope.datasourceTypes, function(value) { + return value; + }); + $scope.variables = templateSrv.variables; $scope.reset(); @@ -132,9 +145,16 @@ function (angular, _) { if ($scope.current.type === 'interval') { $scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d'; } + if ($scope.current.type === 'query') { $scope.current.query = ''; } + + if ($scope.current.type === 'datasource') { + $scope.current.query = $scope.datasourceTypes[0].value; + $scope.current.regex = ''; + $scope.current.refresh = 1; + } }; $scope.removeVariable = function(variable) { diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index d8c74d1eea9..fc387ff44cf 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -75,39 +75,49 @@
    - Name - -
    -
    - Type -
    - -
    + Name +
    - Data source -
    - + + Type + +
    +
    Query
    +
    Variable values are fetched from a metric names query to a data source
    +
    Interval
    +
    Timespan variable type
    +
    Datasource
    +
    Dynamically switch data sources using this type of variable
    +
    Custom
    +
    Define variable values manually
    +
    + Templating docs +
    +
    +
    +
    -
    +
    - Label - + Label +
    -
    - Hide -
    - +
    + Hide +
    +
    -
    Value Options
    +
    Interval Options
    +
    Values @@ -135,6 +145,7 @@
    +
    Custom Options
    Values seperated by comma @@ -142,43 +153,69 @@
    -
    - Query - - - - - - - - - - - - - - - - - - -
    -
    - - Regex - Optional, if you want to extract part of a series name or metric node segment - - -
    -
    - Refresh - - When to update the values of this variable, will slow down dashboard load / time change -
    -
    +
    Query Options
    -
    -
    Selection Options
    +
    +
    + Data source +
    + +
    +
    +
    + + Refresh + + When to update the values of this variable. + + +
    + +
    +
    +
    +
    + Query + +
    +
    + + Regex + + Optional, if you want to extract part of a series name or metric node segment. + + + +
    +
    + +
    +
    Data source options
    + +
    + +
    + +
    +
    + +
    + + +
    +
    + +
    +
    Selection Options
    Preview of values (shows max 20)
    -
    - - {{option.text}} - +
    +
    + {{option.text}} +
    diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index 26dc77d36a9..f60414eac43 100644 --- a/public/app/features/templating/templateSrv.js +++ b/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); diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 799e27583d5..dc878c19c08 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -63,7 +63,9 @@ function (angular, _, kbn) { // determine our dependencies. if (variable.type === "query") { _.forEach(this.variables, function(v) { - if (templateSrv.containsVariable(variable.query, v.name)) { + // both query and datasource can contain variable + if (templateSrv.containsVariable(variable.query, v.name) || + templateSrv.containsVariable(variable.datasource, v.name)) { dependencies.push(self.variableLock[v.name].promise); } }); @@ -149,7 +151,8 @@ function (angular, _, kbn) { if (otherVariable === updatedVariable) { return; } - if (templateSrv.containsVariable(otherVariable.query, updatedVariable.name)) { + if (templateSrv.containsVariable(otherVariable.query, updatedVariable.name) || + templateSrv.containsVariable(otherVariable.datasource, updatedVariable.name)) { return self.updateOptions(otherVariable); } }); @@ -158,6 +161,11 @@ function (angular, _, kbn) { }; this._updateNonQueryVariable = function(variable) { + if (variable.type === 'datasource') { + self.updateDataSourceVariable(variable); + return; + } + // extract options in comma seperated string variable.options = _.map(variable.query.split(/[,]+/), function(text) { return { text: text.trim(), value: text.trim() }; @@ -172,6 +180,36 @@ function (angular, _, kbn) { } }; + this.updateDataSourceVariable = function(variable) { + var options = []; + var sources = datasourceSrv.getMetricSources({skipVariables: true}); + var regex; + + if (variable.regex) { + regex = kbn.stringToJsRegex(templateSrv.replace(variable.regex)); + } + + for (var i = 0; i < sources.length; i++) { + var source = sources[i]; + // must match on type + if (source.meta.id !== variable.query) { + continue; + } + + if (regex && !regex.exec(source.name)) { + continue; + } + + options.push({text: source.name, value: source.name}); + } + + if (options.length === 0) { + options.push({text: 'No datasurces found', value: ''}); + } + + variable.options = options; + }; + this.updateOptions = function(variable) { if (variable.type !== 'query') { self._updateNonQueryVariable(variable); @@ -288,17 +326,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"}); }; diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html index 7297d415c25..1cd6718e27b 100644 --- a/public/app/partials/metrics.html +++ b/public/app/partials/metrics.html @@ -1,56 +1,19 @@ -
    - -
    -
    - - - - -
    -
    - -
    - - - - -
    - - - - - +
    +
    + + + + +
    -
    + - - -
    +
    + + + +
    + diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index 5f999c58a42..ede6baaa525 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -1,22 +1,27 @@

    CloudWatch details

    -
    +
    - - + + + + Credentials profile name, as specified in ~/.aws/credentials, leave blank for default +
    - -
    - + +
    + + + Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region. +
    - - + + + + Namespaces of Custom Metrics +
    diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html index a64635dc397..82b00e9a23b 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html @@ -1,4 +1,4 @@ - + + - diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html index 7b0785e808d..23261d2fef5 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.parameter.html @@ -1,58 +1,59 @@ -
    - +
    +
    + -
    + + + +
    + +
    + +
    + +
    + +
    + +
    +
    +
    -
    - +
    +
    + + +
    -
    +
    +
    +
    -
    - -
    + Interval between points in seconds + + +
    +
    + + + + Alias replacement variables: +
      +
    • {{metric}}
    • +
    • {{stat}}
    • +
    • {{namespace}}
    • +
    • {{region}}
    • +
    • {{DIMENSION_NAME}}
    • +
    +
    +
    +
    +
    +
    - diff --git a/public/app/plugins/datasource/elasticsearch/bucket_agg.js b/public/app/plugins/datasource/elasticsearch/bucket_agg.js index 18e2f3cf3b1..43ef4f91ab7 100644 --- a/public/app/plugins/datasource/elasticsearch/bucket_agg.js +++ b/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; } } diff --git a/public/app/plugins/datasource/elasticsearch/img/logo_large.png b/public/app/plugins/datasource/elasticsearch/img/logo_large.png index 429dedac1a7..b76d01e5d74 100644 Binary files a/public/app/plugins/datasource/elasticsearch/img/logo_large.png and b/public/app/plugins/datasource/elasticsearch/img/logo_large.png differ diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html index 3ddbfed3f80..4f2225e8cce 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html @@ -1,131 +1,96 @@ -
    -
      -
    • +
      +
      +
    • -
    • - -
    • -
    • - -
    • -
    • -   -
    • -
    • + + + + +
    + +
  • - + + - -
    + + -
    -
    -
    -
      -
    • - Interval -
    • -
    • - -
    • -
    -
    +
    +
    +
    + +
    -
    -
      -
    • - Min Doc Count -
    • -
    • - -
    • -
    -
    + +
    + +
    -
    -
      -
    • - Trim edges points -
    • -
    • - -
    • -
    • - -
    • -
    -
    + +
    + +
    -
    -
    -
      -
    • - Order -
    • -
    • - -
    • -
    -
    + +
    +
    + +
    -
    -
      -
    • - Size -
    • -
    • - -
    • -
    -
    + +
    + +
    -
    -
      -
    • - Order By -
    • -
    • - -
    • -
    -
    + +
    + +
    -
    -
    -
      -
    • - Query {{$index + 1}} -
    • -
    • - -
    • -
    • + +
      +
      +
      + + +
      +
      +
    • -
    • + +
    • -
    -
    + +
    +
    +
    +
    + + +
    diff --git a/public/app/plugins/datasource/elasticsearch/partials/metric_agg.html b/public/app/plugins/datasource/elasticsearch/partials/metric_agg.html index 5f3c3072bfa..24582baf8d1 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/metric_agg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/metric_agg.html @@ -1,138 +1,82 @@ -
    -
      -
    • +
      +
      +
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - + +
    + +
    + + + +
    + +
    -
      -
    • +
      +
    • -
    • + +
    • -
    -
    + +
    -
    -
    -
    -
      -
    • - Unit -
    • -
    • - -
    • -
    -
    -
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    -
    -
      -
    • - Window -
    • -
    • - -
    • -
    -
    -
    -
    -
      -
    • - Model -
    • -
    • - -
    • -
    -
    -
    -
    -
      -
    • - Percentiles -
    • -
    • - -
    • -
    -
    -
    -
    -
    -
      -
    • - {{stat.text}} -
    • -
    • - -
    • -
    -
    -
    -
    -
    -
      -
    • - Sigma -
    • -
    • - -
    • -
    -
    -
    +
    + -
    -
      -
    • - Script -
    • -
    • - -
    • -
    -
    +
    + +
    +
    -
    -
      -
    • - Missing - 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 -
    • -
    • - -
    • -
    -
    -
    +
    + + +
    +
    + +
    diff --git a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html index 017f5cf1a42..48fea594ad6 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html @@ -1,32 +1,31 @@ - -
  • - Query -
  • -
  • - -
  • -
  • - Alias -
  • -
  • - -
  • + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    +
    - -
    - - -
    - -
    - - -
    - diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index dd071ba137d..9c8217102aa 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/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 || {}; diff --git a/public/app/plugins/datasource/elasticsearch/query_ctrl.ts b/public/app/plugins/datasource/elasticsearch/query_ctrl.ts index 21bb578f40e..a356b433f57 100644 --- a/public/app/plugins/datasource/elasticsearch/query_ctrl.ts +++ b/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 []; diff --git a/public/app/plugins/datasource/elasticsearch/query_def.js b/public/app/plugins/datasource/elasticsearch/query_def.js index ae44af3e2db..b40fcb73023 100644 --- a/public/app/plugins/datasource/elasticsearch/query_def.js +++ b/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: [ diff --git a/public/app/plugins/datasource/grafana/partials/query.editor.html b/public/app/plugins/datasource/grafana/partials/query.editor.html index 3131b4cc346..880402573d5 100644 --- a/public/app/plugins/datasource/grafana/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana/partials/query.editor.html @@ -1,5 +1,7 @@ - -
  • - Test metric (fake data source) -
  • + +
    +
    + +
    +
    diff --git a/public/app/plugins/datasource/graphite/add_graphite_func.js b/public/app/plugins/datasource/graphite/add_graphite_func.js index 4547526d08f..bb4558b154b 100644 --- a/public/app/plugins/datasource/graphite/add_graphite_func.js +++ b/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 = ''; - var buttonTemplate = '' + ''; diff --git a/public/app/plugins/datasource/graphite/gfunc.js b/public/app/plugins/datasource/graphite/gfunc.js index de482598fc5..a7907186811 100644 --- a/public/app/plugins/datasource/graphite/gfunc.js +++ b/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, diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index 90dfe0794c0..60372fb8ab0 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,21 +1,27 @@ - + -
  • - -
  • +
    + +
    -
  • +
    +
    + -
  • - -
  • -
  • - - -
  • - +
    + +
    -
  • + + +
    +
    +
    +
    +
    diff --git a/public/app/plugins/datasource/influxdb/README.md b/public/app/plugins/datasource/influxdb/README.md index 45eaa51eb0f..d8d98cf5c6b 100644 --- a/public/app/plugins/datasource/influxdb/README.md +++ b/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/) \ No newline at end of file +[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 85457c7a8b3..301d40a2edb 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/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); }); diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 140f298f458..c2efe3093f4 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -1,73 +1,96 @@ - -
      -
    • - FROM -
    • -
    • + + +
      + +
      + +
      + +
      +
      + + -
    • -
    • -
    • -
    • - WHERE -
    • -
    • +
    + +
    + +
    + +
    - - +
    -
    - +
    +
    +
    + +
    +
    + +
    + +
    + + +
    + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + +
    + +
    +
    +
    +
    + +
    + +
    +
    + + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + - -
    -
    -
      -
    • - SELECT -
    • -
    • - -
    • - -
    -
    -
    - -
    -
      -
    • - GROUP BY -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    - -
    -
      -
    • - ALIAS BY -
    • -
    • - -
    • -
    • - Format as -
    • -
    • - -
    • -
    -
    -
    - diff --git a/public/app/plugins/datasource/influxdb/partials/query.options.html b/public/app/plugins/datasource/influxdb/partials/query.options.html index 1e3a08eb556..e2b5e522541 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.options.html +++ b/public/app/plugins/datasource/influxdb/partials/query.options.html @@ -38,7 +38,7 @@
    -
    +
    Alias patterns
    diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html index 0eb0146ec13..478edfe5c29 100644 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -2,4 +2,4 @@
    -{{part.def.type}}() +{{part.def.type}}() diff --git a/public/app/plugins/datasource/influxdb/query_builder.js b/public/app/plugins/datasource/influxdb/query_builder.js index 1921c672ba6..07d920350d7 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.js +++ b/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 + "'"; } diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 041e3c8a882..8d6a03fc4a1 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/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); + } } diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index 2e33b398a88..23173674361 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/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; +} diff --git a/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts b/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts index e58fe32dd1b..f545753d10e 100644 --- a/public/app/plugins/datasource/influxdb/specs/response_parser_specs.ts +++ b/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", () => { diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index def68d19bf9..4683f9c300a 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -1,223 +1,253 @@ - -
  • - Metric -
  • -
  • - - - - - -
  • -
  • - Aggregator -
  • -
  • - - - - -
  • + +
    +
    + + + +
    +
    + +
    + +
    +
    +
    + + +
    -
  • - Alias: - Use patterns like $tag_tagname to replace part of the alias for a tag value -
  • -
  • - -
  • - +
    +
    +
    +
    -
    - -
    -
    +
    + {{fil.tagk}} = {{fil.type}}({{fil.filter}}) , groupBy = {{fil.groupBy}} + + + + + + +
    + +
    -
    - -
    -
    + +
    -
    -
    -
  • - - - -
  • +
    +
    +
    +
    -
  • - +
    +
    + +
    - +
    + +
    + +
    + +
    + +
  • - -
    -
    + + +
    -
    -
    -
  • - Counter -
  • +
    + + -
  • - Counter Max: -
  • + + -
  • - -
  • -
  • - Reset Value: -
  • -
  • - -
  • - -
    -
    +
    + + + + + + + +
    + +
    +
    +
    +
    + + diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index fa04bb33b18..37c27ace58a 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,80 +1,54 @@ - -
  • - Query -
  • -
  • - -
  • -
  • - Metric -
  • -
  • - -
  • -
    + +
    +
    + + +
    +
    + + +
    +
    -
    - +
    +
    + + + + Leave blank for auto handling based on time range and panel width + +
    +
    + +
    + +
    + +
    -
    -
    +
    +
    +
    +
    -
    - - -
    -
    + diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 77029bd7ceb..b6407cf103e 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/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]; diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 9e6633b5551..669fe1fbc91 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/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; diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 1fcd44204f7..a11c537b178 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/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; diff --git a/public/app/plugins/panel/pluginlist/module.html b/public/app/plugins/panel/pluginlist/module.html index c73da35b391..8e74f173a07 100644 --- a/public/app/plugins/panel/pluginlist/module.html +++ b/public/app/plugins/panel/pluginlist/module.html @@ -22,7 +22,7 @@
    - + No additional panels installed. Browse Grafana.net
    diff --git a/public/app/plugins/panel/pluginlist/module.ts b/public/app/plugins/panel/pluginlist/module.ts index 8ec86efd9c2..5ef1fb6fb0c 100644 --- a/public/app/plugins/panel/pluginlist/module.ts +++ b/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 = []; diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index 6b0806133b8..fc3c9f69542 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -156,6 +156,55 @@
    +
    +
    +
    +
      +
    • + Gauge +
    • +
    • + Show  + + +
    • +
    • + Min +
    • +
    • + +
    • +
    • + Max +
    • +
    • + + +   + + Min value is bigger than max. + +
    • +
    +
    +
    +
    +
  • + Threshold labels  + + +
  • +
  • + Threshold markers  + + +
  • +
    +
    +
    +
    +
    diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 8af684c9d68..3c0b9e5342a 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/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 = $('
    '); + 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) { diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 7a2973cc390..473ce4baa48 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/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; diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index c00656071be..6d75f5c97c9 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -30,7 +30,7 @@ export class TableRenderer { } if (_.isArray(v)) { - v = v.join(', '); + v = v.join(', '); } return v; diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 42657788c0a..7228f220d46 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/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)); diff --git a/public/app/system.conf.js b/public/app/system.conf.js index 276988e5c34..40b70c0e30e 100644 --- a/public/app/system.conf.js +++ b/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: { diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 460adebb9da..194d7a5487c 100644 --- a/public/sass/_grafana.scss +++ b/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"; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 7013753c8a9..53e46d2a260 100644 --- a/public/sass/_variables.dark.scss +++ b/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; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 46208330375..f9d1c21a191 100644 --- a/public/sass/_variables.light.scss +++ b/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; diff --git a/public/sass/base/_code.scss b/public/sass/base/_code.scss index eb085c0577c..a97f04bc84b 100644 --- a/public/sass/base/_code.scss +++ b/public/sass/base/_code.scss @@ -11,28 +11,23 @@ pre { background-color: $code-tag-bg; color: $text-color; border: 1px solid $code-tag-border; - padding: 10px; border-radius: 4px; } // Inline code code { color: $text-color; - background-color: $code-tag-bg; - border: 1px solid darken($code-tag-bg, 15%); white-space: nowrap; + padding: 2px 5px; + margin: 0 2px; } code.code--small { font-size: $font-size-xs; - padding: 5px; + padding: 0.2rem; margin: 0 2px; } -p.code--line { - line-height: 1.8; -} - // Blocks of code pre { display: block; @@ -43,6 +38,7 @@ pre { white-space: pre; white-space: pre-wrap; background-color: $code-tag-bg; + padding: 10px; // Make prettyprint styles more spaced out for readability &.prettyprint { diff --git a/public/sass/base/_type.scss b/public/sass/base/_type.scss index 904dfa13b4e..7770be25a68 100644 --- a/public/sass/base/_type.scss +++ b/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; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 4045c1daa4b..570c1862ef0 100644 --- a/public/sass/components/_drop.scss +++ b/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; diff --git a/public/sass/components/_dropdown.scss b/public/sass/components/_dropdown.scss index 339284eb471..c4fbb20f1f3 100644 --- a/public/sass/components/_dropdown.scss +++ b/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; + > 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; + i { + padding-right: 5px; + color: $link-color-disabled; + } } } } diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 12e1fd2f32f..0ae9f7ebb71 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -6,9 +6,24 @@ $gf-form-margin: 0.25rem; flex-direction: row; align-items: center; 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; } } @@ -21,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 { @@ -47,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 { @@ -106,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 { @@ -138,6 +170,12 @@ $gf-form-margin: 0.25rem; content: '\f0d7'; pointer-events: none; } + + &--has-help-icon { + &:after { + right: $input-padding-x*3; + } + } } .gf-form--v-stretch { @@ -145,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; } @@ -182,3 +222,23 @@ $gf-form-margin: 0.25rem; pointer-events: none; } } + +.gf-form-help-icon { + flex-grow: 0; + padding-left: $spacer; + color: $text-color-weak; + + &--right-absolute { + position: absolute; + right: $spacer; + top: 8px; + } + + &--right-normal { + float: right; + } +} + +select.gf-form-input ~ .gf-form-help-icon { + right: 10px; +} diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index ca79034a0bd..b830561f816 100644 --- a/public/sass/components/_panel_graph.scss +++ b/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; diff --git a/public/sass/components/_panel_singlestat.scss b/public/sass/components/_panel_singlestat.scss index 84de79c387e..fac2f716674 100644 --- a/public/sass/components/_panel_singlestat.scss +++ b/public/sass/components/_panel_singlestat.scss @@ -48,4 +48,8 @@ } } +#flotGagueValue0 { + font-weight: bold; //please dont hurt me for this! +} + diff --git a/public/sass/components/_panel_text.scss b/public/sass/components/_panel_text.scss index 06d3495aaa0..6e0301cecc5 100644 --- a/public/sass/components/_panel_text.scss +++ b/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; } } diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss index fe8d3fb157c..4c807ed0e6b 100644 --- a/public/sass/components/_query_editor.scss +++ b/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; + } +} + diff --git a/public/sass/components/_query_part.scss b/public/sass/components/_query_part.scss new file mode 100644 index 00000000000..1e2fb9622c2 --- /dev/null +++ b/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; + } +} + diff --git a/public/sass/mixins/_drop_element.scss b/public/sass/mixins/_drop_element.scss index d08e50e63b8..290e49f4cad 100644 --- a/public/sass/mixins/_drop_element.scss +++ b/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; diff --git a/public/sass/utils/_utils.scss b/public/sass/utils/_utils.scss index 055fe96a213..39e4edfe7ed 100644 --- a/public/sass/utils/_utils.scss +++ b/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 diff --git a/public/sass/utils/_widths.scss b/public/sass/utils/_widths.scss index b4bdbdb6ccc..cf324b35c72 100644 --- a/public/sass/utils/_widths.scss +++ b/public/sass/utils/_widths.scss @@ -17,3 +17,9 @@ } } +@for $i from 1 through 30 { + .offset-width-#{$i} { + margin-left: ($spacer * $i) !important; + } +} + diff --git a/public/test/core/utils/emitter_specs.ts b/public/test/core/utils/emitter_specs.ts index f7076c46719..fec4d02a649 100644 --- a/public/test/core/utils/emitter_specs.ts +++ b/public/test/core/utils/emitter_specs.ts @@ -24,7 +24,7 @@ describe("Emitter", () => { expect(sub2Called).to.be(true); }); - it.only('should handle errors', () => { + it('should handle errors', () => { var events = new Emitter(); var sub1Called = 0; var sub2Called = 0; diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js index 4948e1272ce..3334d7a7bfe 100644 --- a/public/test/specs/templateSrv-specs.js +++ b/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() { diff --git a/public/test/specs/templateValuesSrv-specs.js b/public/test/specs/templateValuesSrv-specs.js index d81ce4fd059..3f4cd96c444 100644 --- a/public/test/specs/templateValuesSrv-specs.js +++ b/public/test/specs/templateValuesSrv-specs.js @@ -92,6 +92,7 @@ define([ var ds = {}; ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult)); ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds)); + ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources); ctx.service.updateOptions(scenario.variable); ctx.$rootScope.$digest(); @@ -137,7 +138,6 @@ define([ }); }); - describeUpdateVariable('interval variable with auto', function(scenario) { scenario.setup(function() { scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test', auto: true, auto_count: 10 }; @@ -237,7 +237,7 @@ define([ }); }); - describeUpdateVariable('regex pattern without slashes', function(scenario) { + describeUpdateVariable('regex pattern without slashes', function(scenario) { scenario.setup(function() { scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; scenario.variable.regex = 'backend_01'; @@ -280,10 +280,27 @@ 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'); + }); + }); + + describeUpdateVariable('datasource variable with regex filter', function(scenario) { + scenario.setup(function() { + scenario.variable = {type: 'datasource', query: 'graphite', name: 'test', current: {}, regex: '/pee$/' }; + scenario.metricSources = [ + {name: 'backend1', meta: {id: 'influx'}}, + {name: 'backend2_pee', meta: {id: 'graphite'}}, + {name: 'backend3', meta: {id: 'graphite'}}, + {name: 'backend4_pee', meta: {id: 'graphite'}}, + ]; + }); + + it('should set only contain graphite ds and filtered using regex', function() { + expect(scenario.variable.options.length).to.be(2); + expect(scenario.variable.options[0].value).to.be('backend2_pee'); + expect(scenario.variable.options[1].value).to.be('backend4_pee'); }); }); }); - }); diff --git a/public/test/test-main.js b/public/test/test-main.js index d40955022fe..dbee086c250 100644 --- a/public/test/test-main.js +++ b/public/test/test-main.js @@ -35,7 +35,8 @@ "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: { diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js new file mode 100644 index 00000000000..d8ed958e990 --- /dev/null +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -0,0 +1,960 @@ +/*! + * jquery.flot.gauge v1.1.0 * + * + * Flot plugin for rendering gauge charts. + * + * Copyright (c) 2015 @toyoty99. + * Licensed under the MIT license. + */ + +/** + * @module flot.gauge + */ +(function($) { + + + /** + * Gauge class + * + * @class Gauge + */ + var Gauge = (function() { + /** + * context of canvas + * + * @property context + * @type Object + */ + var context; + /** + * placeholder of canvas + * + * @property placeholder + * @type Object + */ + var placeholder; + /** + * options of plot + * + * @property options + * @type Object + */ + var options; + /** + * options of gauge + * + * @property gaugeOptions + * @type Object + */ + var gaugeOptions; + /** + * data series + * + * @property series + * @type Array + */ + var series; + /** + * logger + * + * @property logger + * @type Object + */ + var logger; + + /** + * constructor + * + * @class Gauge + * @constructor + * @param {Object} gaugeOptions gauge options + */ + var Gauge = function(plot, ctx) { + context = ctx; + placeholder = plot.getPlaceholder(); + options = plot.getOptions(); + gaugeOptions = options.series.gauges; + series = plot.getData(); + logger = getLogger(gaugeOptions.debug); + } + + /** + * calculate layout + * + * @method calculateLayout + * @return the calculated layout properties + */ + Gauge.prototype.calculateLayout = function() { + + var canvasWidth = placeholder.width(); + var canvasHeight = placeholder.height(); + + + + // calculate cell size + var columns = Math.min(series.length, gaugeOptions.layout.columns); + var rows = Math.ceil(series.length / columns); + + + + var margin = gaugeOptions.layout.margin; + var hMargin = gaugeOptions.layout.hMargin; + var vMargin = gaugeOptions.layout.vMargin; + var cellWidth = (canvasWidth - (margin * 2) - (hMargin * (columns - 1))) / columns; + var cellHeight = (canvasHeight - (margin * 2) - (vMargin * (rows - 1))) / rows; + if (gaugeOptions.layout.square) { + var cell = Math.min(cellWidth, cellHeight); + cellWidth = cell; + cellHeight = cell; + } + + + + // calculate 'auto' values + calculateAutoValues(gaugeOptions, cellWidth); + + // calculate maximum radius + var cellMargin = gaugeOptions.cell.margin; + var labelMargin = 0; + var labelFontSize = 0; + if (gaugeOptions.label.show) { + labelMargin = gaugeOptions.label.margin; + labelFontSize = gaugeOptions.label.font.size; + } + var valueMargin = 0; + var valueFontSize = 0; + if (gaugeOptions.value.show) { + valueMargin = gaugeOptions.value.margin; + valueFontSize = gaugeOptions.value.font.size; + } + var thresholdWidth = 0; + if (gaugeOptions.threshold.show) { + thresholdWidth = gaugeOptions.threshold.width; + } + var thresholdLabelMargin = 0; + var thresholdLabelFontSize = 0; + if (gaugeOptions.threshold.label.show) { + thresholdLabelMargin = gaugeOptions.threshold.label.margin; + thresholdLabelFontSize = gaugeOptions.threshold.label.font.size; + } + + var maxRadiusH = (cellWidth / 2) - cellMargin - thresholdWidth - (thresholdLabelMargin * 2) - thresholdLabelFontSize; + + var startAngle = gaugeOptions.gauge.startAngle; + var endAngle = gaugeOptions.gauge.endAngle; + var dAngle = (endAngle - startAngle) / 100; + var heightRatioV = -1; + for (var a = startAngle; a < endAngle; a += dAngle) { + heightRatioV = Math.max(heightRatioV, Math.sin(toRad(a))); + } + heightRatioV = Math.max(heightRatioV, Math.sin(toRad(endAngle))); + var outerRadiusV = (cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize) / (1 + heightRatioV); + if (outerRadiusV * heightRatioV < valueMargin + (valueFontSize / 2)) { + outerRadiusV = cellHeight - (cellMargin * 2) - (labelMargin * 2) - labelFontSize - valueMargin - (valueFontSize / 2); + } + var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth; + + var radius = Math.min(maxRadiusH, maxRadiusV); + + + var width = gaugeOptions.gauge.width; + if (width >= radius) { + width = Math.max(3, radius / 3); + } + + + var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius; + var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2)); + + return { + canvasWidth: canvasWidth, + canvasHeight: canvasHeight, + margin: margin, + hMargin: hMargin, + vMargin: vMargin, + columns: columns, + rows: rows, + cellWidth: cellWidth, + cellHeight: cellHeight, + cellMargin: cellMargin, + labelMargin: labelMargin, + labelFontSize: labelFontSize, + valueMargin: valueMargin, + valueFontSize: valueFontSize, + width: width, + radius: radius, + thresholdWidth: thresholdWidth, + thresholdLabelMargin: thresholdLabelMargin, + thresholdLabelFontSize: thresholdLabelFontSize, + gaugeOuterHeight: gaugeOuterHeight + }; + } + + /** + * calculate the values which are set as 'auto' + * + * @method calculateAutoValues + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} cellWidth the width of cell + */ + function calculateAutoValues(gaugeOptionsi, cellWidth) { + + if (gaugeOptionsi.gauge.width === "auto") { + gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8); + } + if (gaugeOptionsi.label.margin === "auto") { + gaugeOptionsi.label.margin = Math.max(1, cellWidth / 20); + } + if (gaugeOptionsi.label.font.size === "auto") { + gaugeOptionsi.label.font.size = Math.max(5, cellWidth / 8); + } + if (gaugeOptionsi.value.margin === "auto") { + gaugeOptionsi.value.margin = Math.max(1, cellWidth / 30); + } + if (gaugeOptionsi.value.font.size === "auto") { + gaugeOptionsi.value.font.size = Math.max(5, cellWidth / 9); + } + if (gaugeOptionsi.threshold.width === "auto") { + gaugeOptionsi.threshold.width = Math.max(3, cellWidth / 100); + } + if (gaugeOptionsi.threshold.label.margin === "auto") { + gaugeOptionsi.threshold.label.margin = Math.max(3, cellWidth / 40); + } + if (gaugeOptionsi.threshold.label.font.size === "auto") { + gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15); + } + + } + Gauge.prototype.calculateAutoValues = calculateAutoValues; + + /** + * calculate the layout of the cell inside + * + * @method calculateCellLayout + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} cellWidth the width of cell + * @param {Number} i the index of the series + * @return the calculated cell layout properties + */ + Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) { + + // calculate top, left and center + var c = col(layout.columns, i); + var r = row(layout.columns, i); + var x = layout.margin + (layout.cellWidth + layout.hMargin) * c; + var y = layout.margin + (layout.cellHeight + layout.vMargin) * r; + var cx = x + (layout.cellWidth / 2); + var cy = y + layout.cellMargin + (layout.labelMargin * 2) + layout.labelFontSize + layout.thresholdWidth + + layout.thresholdLabelFontSize + (layout.thresholdLabelMargin * 2) + layout.radius; + var blank = layout.cellHeight - (layout.cellMargin * 2) - (layout.labelMargin * 2) - layout.labelFontSize - layout.gaugeOuterHeight; + var offsetY = 0; + if (gaugeOptionsi.cell.vAlign === "middle") { + offsetY = (blank / 2); + } else if (gaugeOptionsi.cell.vAlign === "bottom") { + offsetY = blank; + } + cy += offsetY; + + return { + col: c, + row: r, + x: x, + y: y, + offsetY: offsetY, + cellWidth: layout.cellWidth, + cellHeight: layout.cellHeight, + cellMargin: layout.cellMargin, + cx: cx, + cy: cy + } + } + + /** + * draw the background of chart + * + * @method drawBackground + * @param {Object} layout the layout properties + */ + Gauge.prototype.drawBackground = function(layout) { + + if (!gaugeOptions.frame.show) { + return; + } + context.save(); + context.strokeStyle = options.grid.borderColor; + context.lineWidth = options.grid.borderWidth; + context.strokeRect(0, 0, layout.canvasWidth, layout.canvasHeight); + if (options.grid.backgroundColor) { + context.fillStyle = options.grid.backgroundColor; + context.fillRect(0, 0, layout.canvasWidth, layout.canvasHeight); + } + context.restore(); + } + + /** + * draw the background of cell + * + * @method drawCellBackground + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} cellLayout the cell layout properties + */ + Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) { + + context.save(); + if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { + context.strokeStyle = gaugeOptionsi.cell.border.color; + context.lineWidth = gaugeOptionsi.cell.border.width; + context.strokeRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); + } + if (gaugeOptionsi.cell.background && gaugeOptionsi.cell.background.color) { + context.fillStyle = gaugeOptionsi.cell.background.color; + context.fillRect(cellLayout.x, cellLayout.y, cellLayout.cellWidth, cellLayout.cellHeight); + } + context.restore(); + } + + /** + * draw the gauge + * + * @method drawGauge + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {String} label the label of data + * @param {Number} data the value of the gauge + */ + Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) { + + + var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0; + + + // draw gauge frame + drawArcWithShadow( + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius, + layout.width, + toRad(gaugeOptionsi.gauge.startAngle), + toRad(gaugeOptionsi.gauge.endAngle), + gaugeOptionsi.gauge.border.color, // line color + gaugeOptionsi.gauge.border.width, // line width + gaugeOptionsi.gauge.background.color, // fill color + blur); + + // draw gauge + var c1 = getColor(gaugeOptionsi, data); + var a2 = calculateAngle(gaugeOptionsi, layout, data); + drawArcWithShadow( + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius - 1, + layout.width - 2, + toRad(gaugeOptionsi.gauge.startAngle), + toRad(a2), + c1, // line color + 1, // line width + c1, // fill color + blur); + } + + /** + * decide the color of the data from the threshold options + * + * @method getColor + * @private + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Number} data the value of the gauge + */ + function getColor(gaugeOptionsi, data) { + var color; + for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { + var threshold = gaugeOptionsi.threshold.values[i]; + color = threshold.color; + if (data <= threshold.value) { + break; + } + } + return color; + } + + /** + * calculate the angle of the data + * + * @method calculateAngle + * @private + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Number} data the value of the gauge + */ + function calculateAngle(gaugeOptionsi, layout, data) { + var a = + gaugeOptionsi.gauge.startAngle + + (gaugeOptionsi.gauge.endAngle - gaugeOptionsi.gauge.startAngle) + * ((data - gaugeOptionsi.gauge.min) / (gaugeOptionsi.gauge.max - gaugeOptionsi.gauge.min)); + + if (a < gaugeOptionsi.gauge.startAngle) { + a = gaugeOptionsi.gauge.startAngle; + } else if (a > gaugeOptionsi.gauge.endAngle) { + a = gaugeOptionsi.gauge.endAngle; + } + return a; + } + + /** + * draw the arc of the threshold + * + * @method drawThreshold + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + */ + Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) { + + var a1 = gaugeOptionsi.gauge.startAngle; + for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { + var threshold = gaugeOptionsi.threshold.values[i]; + c1 = threshold.color; + a2 = calculateAngle(gaugeOptionsi, layout, threshold.value); + drawArc( + context, + cellLayout.cx, // center x + cellLayout.cy, // center y + layout.radius + layout.thresholdWidth, + layout.thresholdWidth - 2, + toRad(a1), + toRad(a2), + c1, // line color + 1, // line width + c1); // fill color + a1 = a2; + } + } + + /** + * draw an arc with a shadow + * + * @method drawArcWithShadow + * @private + * @param {Number} cx the x position of the center + * @param {Number} cy the y position of the center + * @param {Number} r the radius of an arc + * @param {Number} w the width of an arc + * @param {Number} rd1 the start angle of an arc in radians + * @param {Number} rd2 the end angle of an arc in radians + * @param {String} lc the color of a line + * @param {Number} lw the widht of a line + * @param {String} fc the fill color of an arc + * @param {Number} blur the shdow blur + */ + function drawArcWithShadow(cx, cy, r, w, rd1, rd2, lc, lw, fc, blur) { + if (rd1 === rd2) { + return; + } + context.save(); + + drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc); + + if (blur) { + drawArc(context, cx, cy, r, w, rd1, rd2); + context.clip(); + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 10; + context.shadowColor = "gray"; + drawArc(context, cx, cy, r + 1, w + 2, rd1, rd2, lc, 1); + } + context.restore(); + } + + /** + * draw the label of the gauge + * + * @method drawLable + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Object} item the item of the series + */ + Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) { + + drawText( + cellLayout.cx, + cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY, + "flotGagueLabel" + i, + gaugeOptionsi.label.formatter ? gaugeOptionsi.label.formatter(item.label, item.data[0][1]) : text, + gaugeOptionsi.label); + } + + /** + * draw the value of the gauge + * + * @method drawValue + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Object} item the item of the series + */ + Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) { + + drawText( + cellLayout.cx, + cellLayout.cy - (gaugeOptionsi.value.font.size / 2), + "flotGagueValue" + i, + gaugeOptionsi.value.formatter ? gaugeOptionsi.value.formatter(item.label, item.data[0][1]) : text, + gaugeOptionsi.value); + } + + /** + * draw the values of the threshold + * + * @method drawThresholdValues + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + */ + Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) { + + // min, max + drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Min" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle); + drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Max" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle); + // threshold values + for (var j = 0; j < gaugeOptionsi.threshold.values.length; j++) { + var threshold = gaugeOptionsi.threshold.values[j]; + if (threshold.value > gaugeOptionsi.gauge.min && threshold.value < gaugeOptionsi.gauge.max) { + var a = calculateAngle(gaugeOptionsi, layout, threshold.value); + drawThresholdValue(gaugeOptionsi, layout, cellLayout, i + "_" + j, threshold.value, a); + } + } + } + + /** + * draw the value of the threshold + * + * @method drawThresholdValue + * @param {Object} gaugeOptionsi the options of the gauge + * @param {Object} layout the layout properties + * @param {Object} cellLayout the cell layout properties + * @param {Number} i the index of the series + * @param {Number} value the value of the threshold + * @param {Number} a the angle of the value drawn + */ + function drawThresholdValue(gaugeOptionsi, layout, cellLayout, i, value, a) { + drawText( + cellLayout.cx + + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) + * Math.cos(toRad(a))), + cellLayout.cy + + ((layout.thresholdLabelMargin + (layout.thresholdLabelFontSize / 2) + layout.radius) + * Math.sin(toRad(a))), + "flotGagueThresholdValue" + i, + gaugeOptionsi.threshold.label.formatter ? gaugeOptionsi.threshold.label.formatter(value) : value, + gaugeOptionsi.threshold.label, + a); + } + + /** + * draw a text + * + * the textOptions is assumed as follows: + * + * textOptions: { + * background: { + * color: null, + * opacity: 0 + * }, + * font: { + * size: "auto" + * family: "\"MS ゴシック\",sans-serif" + * }, + * color: null + * } + * + * @method drawText + * @private + * @param {Number} x the x position of the text drawn (left top) + * @param {Number} y the y position of the text drawn (left top) + * @param {String} id the id of the dom element + * @param {String} text the text drawn + * @param {Object} textOptions the option of the text + * @param {Number} [a] the angle of the value drawn + */ + function drawText(x, y, id, text, textOptions, a) { + var span = $("." + id, placeholder); + var exists = span.length; + if (!exists) { + span = $("") + span.attr("id", id); + span.css("position", "absolute"); + span.css("top", y + "px"); + if (textOptions.font.size) { + span.css("font-size", textOptions.font.size + "px"); + } + if (textOptions.font.family) { + span.css("font-family", textOptions.font.family); + } + if (textOptions.color) { + span.css("color", textOptions.color); + } + if (textOptions.background.color) { + span.css("background-color", textOptions.background.color); + } + if (textOptions.background.opacity) { + span.css("opacity", textOptions.background.opacity); + } + placeholder.append(span); + } + span.text(text); + // after append, readjust the left position + span.css("left", x + "px"); // for redraw, resetting the left position is needed here + span.css("left", (parseInt(span.css("left")) - (span.width()/ 2)) + "px"); + + // at last, set angle + if (!exists && a) { + span.css("top", (parseInt(span.css("top")) - (span.height()/ 2)) + "px"); + span.css("transform", "rotate(" + ((180 * a) + 90) + "deg)"); // not supported for ie8 + } + } + + return Gauge; + })(); + /** + * get a instance of Logger + * + * @method getLogger + * @for flot.gauge + * @private + * @param {Object} debugOptions the options of debug + */ + function getLogger(debugOptions) { + return typeof Logger !== "undefined" ? new Logger(debugOptions) : null; + } + + /** + * calculate the index of columns for the specified data + * + * @method col + * @for flot.gauge + * @param {Number} columns the number of columns + * @param {Number} i the index of the series + * @return the index of columns + */ + function col(columns, i) { + return i % columns; + } + + /** + * calculate the index of rows for the specified data + * + * @method row + * @for flot.gauge + * @param {Number} columns the number of rows + * @param {Number} i the index of the series + * @return the index of rows + */ + function row(columns, i) { + return Math.floor(i / columns); + } + + /** + * calculate the angle in radians + * + * internally, use a number without PI (0 - 2). + * so, in this function, multiply PI + * + * @method toRad + * @for flot.gauge + * @param {Number} a the number of angle without PI + * @return the angle in radians + */ + function toRad(a) { + return a * Math.PI; + } + + /** + * draw an arc + * + * @method drawArc + * @for flot.gauge + * @param {Object} context the context of canvas + * @param {Number} cx the x position of the center + * @param {Number} cy the y position of the center + * @param {Number} r the radius of an arc + * @param {Number} w the width of an arc + * @param {Number} rd1 the start angle of an arc in radians + * @param {Number} rd2 the end angle of an arc in radians + * @param {String} lc the color of a line + * @param {Number} lw the widht of a line + * @param {String} fc the fill color of an arc + */ + function drawArc(context, cx, cy, r, w, rd1, rd2, lc, lw, fc) { + if (rd1 === rd2) { + return; + } + var counterClockwise = false; + context.save(); + context.beginPath(); + context.arc(cx, cy, r, rd1, rd2, counterClockwise); + context.lineTo(cx + (r - w) * Math.cos(rd2), + cy + (r - w) * Math.sin(rd2)); + context.arc(cx, cy, r - w, rd2, rd1, !counterClockwise); + context.closePath(); + if (lw) { + context.lineWidth = lw; + } + if (lc) { + context.strokeStyle = lc; + context.stroke(); + } + if (fc) { + context.fillStyle = fc; + context.fill(); + } + context.restore(); + } + + /** + * initialize plugin + * + * @method init + * @for flot.gauge + * @private + * @param {Object} plot a instance of plot + */ + function init (plot) { + // add processOptions hook + plot.hooks.processOptions.push(function(plot, options) { + var logger = getLogger(options.series.gauges.debug); + + + + + // turn 'grid' and 'legend' off + if (options.series.gauges.show) { + options.grid.show = false; + options.legend.show = false; + } + + // sort threshold + var thresholds = options.series.gauges.threshold.values; + + thresholds.sort(function(a, b) { + if (a.value < b.value) { + return -1; + } else if (a.value > b.value) { + return 1; + } else { + return 0; + } + }); + + + + }); + + // add draw hook + plot.hooks.draw.push(function(plot, context) { + var options = plot.getOptions(); + var gaugeOptions = options.series.gauges; + + var logger = getLogger(gaugeOptions.debug); + + + if (!gaugeOptions.show) { + return; + } + + var series = plot.getData(); + + if (!series || !series.length) { + return; // if no series were passed + } + + var gauge = new Gauge(plot, context); + + // calculate layout + var layout = gauge.calculateLayout(); + + // debug layout + if (gaugeOptions.debug.layout) { + + } + + // draw background + gauge.drawBackground(layout) + + // draw cells (label, gauge, value, threshold) + for (var i = 0; i < series.length; i++) { + var item = series[i]; + + var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges); + if (item.gauges) { + // re-calculate 'auto' values + gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth); + } + + // calculate cell layout + var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i); + + // draw cell background + gauge.drawCellBackground(gaugeOptionsi, cellLayout) + // debug layout + if (gaugeOptionsi.debug.layout) { + + } + // draw label + if (gaugeOptionsi.label.show) { + gauge.drawLable(gaugeOptionsi, layout, cellLayout, i, item); + } + // draw gauge + gauge.drawGauge(gaugeOptionsi, layout, cellLayout, item.label, item.data[0][1]); + // draw threshold + if (gaugeOptionsi.threshold.show) { + gauge.drawThreshold(gaugeOptionsi, layout, cellLayout); + } + if (gaugeOptionsi.threshold.label.show) { + gauge.drawThresholdValues(gaugeOptionsi, layout, cellLayout, i) + } + // draw value + if (gaugeOptionsi.value.show) { + gauge.drawValue(gaugeOptionsi, layout, cellLayout, i, item); + } + } + }); + } + + /** + * [defaults description] + * + * @property defaults + * @type {Object} + */ + var defaults = { + series: { + gauges: { + debug: { + log: false, + layout: false, + alert: false + }, + show: false, + layout: { + margin: 5, + columns: 3, + hMargin: 5, + vMargin: 5, + square: false + }, + frame: { + show: true + }, + cell: { + background: { + color: null + }, + border: { + show: true, + color: "black", + width: 1 + }, + margin: 5, + vAlign: "middle" // 'top' or 'middle' or 'bottom' + }, + gauge: { + width: "auto", // a specified number, or 'auto' + startAngle: 0.9, // 0 - 2 factor of the radians + endAngle: 2.1, // 0 - 2 factor of the radians + min: 0, + max: 100, + background: { + color: "white" + }, + border: { + color: "lightgray", + width: 2 + }, + shadow: { + show: true, + blur: 5 + } + }, + label: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: "sans-serif" + }, + color: null, + formatter: function(label, value) { + return label; + } + }, + value: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: "sans-serif" + }, + color: null, + formatter: function(label, value) { + return parseInt(value); + } + }, + threshold: { + show: true, + width: "auto", // a specified number, or 'auto' + label: { + show: true, + margin: "auto", // a specified number, or 'auto' + background: { + color: null, + opacity: 0 + }, + font: { + size: "auto", // a specified number, or 'auto' + family: ",sans-serif" + }, + color: null, + formatter: function(value) { + return value; + } + }, + values: [ + { + value: 50, + color: "lightgreen" + }, { + value: 80, + color: "yellow" + }, { + value: 100, + color: "red" + } + ] + } + } + } + }; + + // register the gauge plugin + $.plot.plugins.push({ + init: init, + options: defaults, + name: "gauge", + version: "1.1.0" + }); + +})(jQuery); diff --git a/tasks/default_task.js b/tasks/default_task.js index d2bebb89789..2aa0a7d8826 100644 --- a/tasks/default_task.js +++ b/tasks/default_task.js @@ -25,6 +25,19 @@ module.exports = function(grunt) { 'typescript:build' ]); - grunt.registerTask('test', ['default', 'karma:test']); + grunt.registerTask('test', ['default', 'karma:test', 'no-only-tests']); + grunt.registerTask('no-only-tests', function() { + var files = grunt.file.expand('public/**/*_specs\.ts', 'public/**/*_specs\.js'); + + files.forEach(function(spec) { + var rows = grunt.file.read(spec).split('\n'); + rows.forEach(function(row) { + if (row.indexOf('.only(') > 0) { + grunt.log.errorlns(row); + grunt.fail.warn('found only statement in test: ' + spec) + } + }); + }); + }); };