Merge branch 'master' into dash-edit-mode

Conflicts:
	public/app/features/dashboard/partials/settings.html
This commit is contained in:
Torkel Ödegaard
2016-04-29 14:50:17 +02:00
119 changed files with 3040 additions and 1289 deletions
+26
View File
@@ -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) # 3.0.0-beta5 (2016-04-15)
### Bug fixes ### Bug fixes
+17
View File
@@ -0,0 +1,17 @@
all: deps build
deps:
go run build.go setup
godep restore
npm install
build:
go run build.go build
npm run build
test:
godep go test -v ./pkg/...
npm test
run:
./bin/grafana-server
+1 -3
View File
@@ -103,8 +103,7 @@ npm (v2.5.0) and grunt (v0.4.5). Run the following:
```bash ```bash
npm install npm install
npm install -g grunt-cli npm run build
grunt
``` ```
### Recompile backend on source change ### 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. Grafana is distributed under Apache 2.0 License.
Work in progress Grafana 2.0 (with included Grafana backend) Work in progress Grafana 2.0 (with included Grafana backend)
+1 -1
View File
@@ -306,7 +306,7 @@ func ChangeWorkingDir(dir string) {
} }
func grunt(params ...string) { func grunt(params ...string) {
runPrint("./node_modules/grunt-cli/bin/grunt", params...) runPrint("./node_modules/.bin/grunt", params...)
} }
func setup() { func setup() {
+2 -2
View File
@@ -25,12 +25,12 @@ test:
# Go test # Go test
- godep go test -v ./pkg/... - godep go test -v ./pkg/...
# js tests # js tests
- ./node_modules/grunt-cli/bin/grunt test - npm test
- npm run coveralls - npm run coveralls
deployment: deployment:
master: master:
branch: master branch: master
owner: grafana owner: grafana
commands: commands:
- ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} - ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}
+1 -1
View File
@@ -191,7 +191,7 @@ Will return the home dashboard.
`GET /api/dashboards/tags` `GET /api/dashboards/tags`
Get all tabs of dashboards Get all tags of dashboards
**Example Request**: **Example Request**:
+3 -3
View File
@@ -11,7 +11,7 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download 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) 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 ## Install Stable
@@ -21,9 +21,9 @@ Beta .deb for Debian-based Linux | [grafana_3.0.0-beta51460725904_amd64.deb](h
## Install 3.0 Beta ## 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 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 ## APT Repository
+4 -4
View File
@@ -11,7 +11,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download 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) 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 ## Install Stable Release from package file
@@ -34,18 +34,18 @@ Or install manually using `rpm`.
You can install Grafana using Yum directly. 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`. Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat: #### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig $ 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: #### 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 ## Install via YUM Repository
+1 -1
View File
@@ -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 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. can grow even stronger and develop new plugins that we would never think about.
You can discover available plugins on [Grafana.net](http://grafana.net) You can discover available plugins on [Grafana.net](https://grafana.net)
+2 -1
View File
@@ -54,7 +54,7 @@
"phantomjs-prebuilt": "^2.1.3", "phantomjs-prebuilt": "^2.1.3",
"reflect-metadata": "0.1.2", "reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.4", "rxjs": "5.0.0-beta.4",
"sass-lint": "^1.5.0", "sass-lint": "^1.6.0",
"systemjs": "0.19.24" "systemjs": "0.19.24"
}, },
"engines": { "engines": {
@@ -62,6 +62,7 @@
"npm": "2.14.x" "npm": "2.14.x"
}, },
"scripts": { "scripts": {
"build": "grunt",
"test": "grunt test", "test": "grunt test",
"coveralls": "grunt karma:coveralls && rm -rf ./coverage" "coveralls": "grunt karma:coveralls && rm -rf ./coverage"
}, },
+2 -2
View File
@@ -56,7 +56,7 @@ func init() {
"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"}, "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/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/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/Lambda": {"Invocations", "Errors", "Duration", "Throttles"},
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"}, "AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
"AWS/ML": {"PredictCount", "PredictFailureCount"}, "AWS/ML": {"PredictCount", "PredictFailureCount"},
@@ -88,7 +88,7 @@ func init() {
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"}, "AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
"AWS/ES": {}, "AWS/ES": {},
"AWS/Events": {"RuleName"}, "AWS/Events": {"RuleName"},
"AWS/Kinesis": {"StreamName"}, "AWS/Kinesis": {"StreamName", "ShardID"},
"AWS/Lambda": {"FunctionName"}, "AWS/Lambda": {"FunctionName"},
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"}, "AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
"AWS/ML": {"MLModelId", "RequestMode"}, "AWS/ML": {"MLModelId", "RequestMode"},
+4
View File
@@ -21,6 +21,10 @@ func GetSharingOptions(c *middleware.Context) {
} }
func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) { func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
if cmd.Name == "" {
cmd.Name = "Unnamed snapshot"
}
if cmd.External { if cmd.External {
// external snapshot ref requires key and delete key // external snapshot ref requires key and delete key
if cmd.Key == "" || cmd.DeleteKey == "" { if cmd.Key == "" || cmd.DeleteKey == "" {
-1
View File
@@ -41,7 +41,6 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
req.URL.RawQuery = reqQueryVals.Encode() req.URL.RawQuery = reqQueryVals.Encode()
} else if ds.Type == m.DS_INFLUXDB { } else if ds.Type == m.DS_INFLUXDB {
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
reqQueryVals.Add("db", ds.Database)
req.URL.RawQuery = reqQueryVals.Encode() req.URL.RawQuery = reqQueryVals.Encode()
if !ds.BasicAuth { if !ds.BasicAuth {
req.Header.Del("Authorization") req.Header.Del("Authorization")
+1 -1
View File
@@ -48,6 +48,6 @@ func (slice PluginList) Swap(i, j int) {
type ImportDashboardCommand struct { type ImportDashboardCommand struct {
PluginId string `json:"pluginId"` PluginId string `json:"pluginId"`
Path string `json:"path"` Path string `json:"path"`
Reinstall bool `json:"reinstall"` Overwrite bool `json:"overwrite"`
Inputs []plugins.ImportDashboardInput `json:"inputs"` Inputs []plugins.ImportDashboardInput `json:"inputs"`
} }
+8 -1
View File
@@ -103,6 +103,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
} }
for _, include := range plugin.Includes { for _, include := range plugin.Includes {
if !c.HasUserRole(include.Role) {
continue
}
if include.Type == "page" && include.AddToNav { if include.Type == "page" && include.AddToNav {
link := &dtos.NavLink{ link := &dtos.NavLink{
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug, 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) appLink.Children = append(appLink.Children, link)
} }
if include.Type == "dashboard" && include.AddToNav { if include.Type == "dashboard" && include.AddToNav {
link := &dtos.NavLink{ link := &dtos.NavLink{
Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug, 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"}) appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
} }
data.MainNavLinks = append(data.MainNavLinks, appLink) if len(appLink.Children) > 0 {
data.MainNavLinks = append(data.MainNavLinks, appLink)
}
} }
} }
+6 -5
View File
@@ -156,11 +156,12 @@ func GetPluginReadme(c *middleware.Context) Response {
func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response { func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
cmd := plugins.ImportDashboardCommand{ cmd := plugins.ImportDashboardCommand{
OrgId: c.OrgId, OrgId: c.OrgId,
UserId: c.UserId, UserId: c.UserId,
PluginId: apiCmd.PluginId, PluginId: apiCmd.PluginId,
Path: apiCmd.Path, Path: apiCmd.Path,
Inputs: apiCmd.Inputs, Inputs: apiCmd.Inputs,
Overwrite: apiCmd.Overwrite,
} }
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"os" "os"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/fatih/color"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log" "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} cmd := &contextCommandLine{context}
if err := command(cmd); err != nil { if err := command(cmd); err != nil {
log.Error("\nError: ") log.Errorf("\n%s: ", color.RedString("Error"))
log.Errorf("%s\n\n", err) log.Errorf("%s\n\n", err)
cmd.ShowHelp() cmd.ShowHelp()
@@ -126,11 +126,16 @@ func downloadFile(pluginName, filePath, url string) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
retryCount++ retryCount++
if retryCount == 1 { if retryCount < 3 {
log.Debug("\nFailed downloading. Will retry once.\n") fmt.Println("Failed downloading. Will retry once.")
downloadFile(pluginName, filePath, url) err = downloadFile(pluginName, filePath, url)
} else { } 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) return fmt.Errorf(permissionsDeniedMessage, newFile)
} }
defer dst.Close()
src, err := zf.Open() src, err := zf.Open()
if err != nil { if err != nil {
log.Errorf("%v", err) log.Errorf("Failed to extract file: %v", err)
} }
defer src.Close()
io.Copy(dst, src) io.Copy(dst, src)
dst.Close()
src.Close()
} }
} }
@@ -3,7 +3,7 @@ package commands
import ( import (
"errors" "errors"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log" "fmt"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
) )
@@ -15,22 +15,17 @@ func removeCommand(c CommandLine) error {
pluginPath := c.GlobalString("pluginsDir") pluginPath := c.GlobalString("pluginsDir")
localPlugins := getPluginss(pluginPath) localPlugins := getPluginss(pluginPath)
log.Info("remove!\n")
plugin := c.Args().First() plugin := c.Args().First()
log.Info("plugin: " + plugin + "\n")
if plugin == "" { if plugin == "" {
return errors.New("Missing plugin parameter") return errors.New("Missing plugin parameter")
} }
log.Infof("plugins : \n%v\n", localPlugins)
for _, p := range localPlugins { for _, p := range localPlugins {
if p.Id == c.Args().First() { if p.Id == c.Args().First() {
log.Infof("removing plugin %s", p.Id)
removePlugin(pluginPath, p.Id) removePlugin(pluginPath, p.Id)
return nil
} }
} }
return nil return fmt.Errorf("Could not find plugin named %s", c.Args().First())
} }
+5 -5
View File
@@ -8,7 +8,6 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log" "github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"strings"
) )
var version = "master" var version = "master"
@@ -18,7 +17,7 @@ func getGrafanaPluginDir() string {
defaultNix := "/var/lib/grafana/plugins" defaultNix := "/var/lib/grafana/plugins"
if currentOS == "windows" { if currentOS == "windows" {
return "C:\\opt\\grafana\\plugins" return "../data/plugins"
} }
pwd, err := os.Getwd() pwd, err := os.Getwd()
@@ -29,16 +28,17 @@ func getGrafanaPluginDir() string {
} }
if isDevenvironment(pwd) { if isDevenvironment(pwd) {
return "../../../data/plugins" return "../data/plugins"
} }
return defaultNix return defaultNix
} }
func isDevenvironment(pwd string) bool { 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. // that its in development environment.
return strings.HasSuffix(pwd, "/pkg/cmd/grafana-cli") _, err := os.Stat("../conf/defaults.ini")
return err == nil
} }
func main() { func main() {
+1 -1
View File
@@ -45,7 +45,7 @@ type DashboardSnapshotDTO struct {
type CreateDashboardSnapshotCommand struct { type CreateDashboardSnapshotCommand struct {
Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
Name string `json:"name" binding:"Required"` Name string `json:"name"`
Expires int64 `json:"expires"` Expires int64 `json:"expires"`
// these are passed when storing an external snapshot ref // these are passed when storing an external snapshot ref
+22
View File
@@ -1,7 +1,9 @@
package models package models
import ( import (
"encoding/json"
"errors" "errors"
"fmt"
"time" "time"
) )
@@ -37,6 +39,26 @@ func (r RoleType) Includes(other RoleType) bool {
return r == other 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 { type OrgUser struct {
Id int64 Id int64
OrgId int64 OrgId int64
+7 -5
View File
@@ -11,12 +11,13 @@ import (
) )
type ImportDashboardCommand struct { type ImportDashboardCommand struct {
Path string `json:"string"` Path string
Inputs []ImportDashboardInput `json:"inputs"` Inputs []ImportDashboardInput
Overwrite bool
OrgId int64 `json:"-"` OrgId int64
UserId int64 `json:"-"` UserId int64
PluginId string `json:"-"` PluginId string
Result *PluginDashboardInfoDTO Result *PluginDashboardInfoDTO
} }
@@ -67,6 +68,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
Dashboard: generatedDash, Dashboard: generatedDash,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
UserId: cmd.UserId, UserId: cmd.UserId,
Overwrite: cmd.Overwrite,
} }
if err := bus.Dispatch(&saveCmd); err != nil { if err := bus.Dispatch(&saveCmd); err != nil {
+15 -9
View File
@@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/grafana/grafana/pkg/log" "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" "github.com/grafana/grafana/pkg/setting"
) )
@@ -69,6 +69,12 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error {
pb.Dependencies.GrafanaVersion = "*" pb.Dependencies.GrafanaVersion = "*"
} }
for _, include := range pb.Includes {
if include.Role == "" {
include.Role = m.RoleType(m.ROLE_VIEWER)
}
}
pb.PluginDir = pluginDir pb.PluginDir = pluginDir
Plugins[pb.Id] = pb Plugins[pb.Id] = pb
return nil return nil
@@ -80,14 +86,14 @@ type PluginDependencies struct {
} }
type PluginInclude struct { type PluginInclude struct {
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
Type string `json:"type"` Type string `json:"type"`
Component string `json:"component"` Component string `json:"component"`
Role models.RoleType `json:"role"` Role m.RoleType `json:"role"`
AddToNav bool `json:"addToNav"` AddToNav bool `json:"addToNav"`
DefaultNav bool `json:"defaultNav"` DefaultNav bool `json:"defaultNav"`
Slug string `json:"slug"` Slug string `json:"slug"`
Id string `json:"-"` Id string `json:"-"`
} }
+1 -1
View File
@@ -216,7 +216,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
return inTransaction2(func(sess *session) error { return inTransaction2(func(sess *session) error {
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId} dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
has, err := x.Get(&dashboard) has, err := sess.Get(&dashboard)
if err != nil { if err != nil {
return err return err
} else if has == false { } else if has == false {
-1
View File
@@ -61,7 +61,6 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
for key, data := range cmd.SecureJsonData { for key, data := range cmd.SecureJsonData {
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
} }
pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
pluginSetting.Updated = time.Now() pluginSetting.Updated = time.Now()
pluginSetting.Enabled = cmd.Enabled pluginSetting.Enabled = cmd.Enabled
pluginSetting.JsonData = cmd.JsonData pluginSetting.JsonData = cmd.JsonData
@@ -7,6 +7,9 @@ import coreModule from 'app/core/core_module';
var template = ` var template = `
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select> <select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
<info-popover mode="right-absolute">
Not finding dashboard you want? Star it first, then it should appear in this select box.
</info-popover>
`; `;
export class DashboardSelectorCtrl { export class DashboardSelectorCtrl {
+12 -8
View File
@@ -8,21 +8,24 @@ import Drop from 'tether-drop';
export function infoPopover() { export function infoPopover() {
return { return {
restrict: 'E', restrict: 'E',
template: '<i class="fa fa-info-circle"></i>',
transclude: true, transclude: true,
link: function(scope, elem, attrs, ctrl, transclude) { 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 offset = attrs.offset || '0 -10px';
var position = attrs.position || 'right middle'; var position = attrs.position || 'right middle';
var classes = 'drop-help drop-hide-out-of-bounds'; var classes = 'drop-help drop-hide-out-of-bounds';
var openOn = 'hover';
elem.addClass('gf-form-help-icon');
if (attrs.wide) { if (attrs.wide) {
classes += ' drop-wide'; classes += ' drop-wide';
} }
if (attrs.mode) {
elem.addClass('gf-form-help-icon--' + attrs.mode);
}
transclude(function(clone, newScope) { transclude(function(clone, newScope) {
var content = document.createElement("div"); var content = document.createElement("div");
_.each(clone, (node) => { _.each(clone, (node) => {
@@ -30,11 +33,12 @@ export function infoPopover() {
}); });
var drop = new Drop({ var drop = new Drop({
target: inputElem[0], target: elem[0],
content: content, content: content,
position: position, position: position,
classes: classes, classes: classes,
openOn: 'click', openOn: openOn,
hoverOpenDelay: 400,
tetherOptions: { tetherOptions: {
offset: offset offset: offset
} }
+6 -17
View File
@@ -7,7 +7,12 @@ import coreModule from 'app/core/core_module';
import Drop from 'tether-drop'; import Drop from 'tether-drop';
var template = ` var template = `
<label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">{{ctrl.label}}</label> <label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">
{{ctrl.label}}
<info-popover mode="right-normal" ng-if="ctrl.tooltip">
{{ctrl.tooltip}}
</info-popover>
</label>
<div class="gf-form-switch {{ctrl.switchClass}}" ng-if="ctrl.show"> <div class="gf-form-switch {{ctrl.switchClass}}" ng-if="ctrl.show">
<input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()"> <input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()">
<label for="check-{{ctrl.id}}" data-on="Yes" data-off="No"></label> <label for="check-{{ctrl.id}}" data-on="Yes" data-off="No"></label>
@@ -49,22 +54,6 @@ export function switchDirective() {
onChange: "&", onChange: "&",
}, },
template: template, 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();
});
}
}
}; };
} }
@@ -9,10 +9,10 @@ function (_, $, coreModule) {
coreModule.default.directive('dropdownTypeahead', function($compile) { coreModule.default.directive('dropdownTypeahead', function($compile) {
var inputTemplate = '<input type="text"'+ var inputTemplate = '<input type="text"'+
' class="tight-form-input input-medium tight-form-input"' + ' class="gf-form-input input-medium tight-form-input"' +
' spellcheck="false" style="display:none"></input>'; ' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item tight-form-func dropdown-toggle"' + var buttonTemplate = '<a class="gf-form-label tight-form-func dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' + ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' data-placement="top"><i class="fa fa-plus"></i></a>'; ' data-placement="top"><i class="fa fa-plus"></i></a>';
+7 -4
View File
@@ -8,10 +8,13 @@ function (_, $, coreModule) {
coreModule.default.directive('metricSegment', function($compile, $sce) { coreModule.default.directive('metricSegment', function($compile, $sce) {
var inputTemplate = '<input type="text" data-provide="typeahead" ' + var inputTemplate = '<input type="text" data-provide="typeahead" ' +
' class="tight-form-clear-input input-medium"' + ' class="gf-form-input input-medium"' +
' spellcheck="false" style="display:none"></input>'; ' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' + var linkTemplate = '<a class="gf-form-label" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
var selectTemplate = '<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>'; 'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
return { return {
@@ -20,9 +23,9 @@ function (_, $, coreModule) {
getOptions: "&", getOptions: "&",
onChange: "&", onChange: "&",
}, },
link: function($scope, elem) { link: function($scope, elem, attrs) {
var $input = $(inputTemplate); var $input = $(inputTemplate);
var $button = $(buttonTemplate); var $button = $(attrs.styleMode === 'select' ? selectTemplate : linkTemplate);
var segment = $scope.segment; var segment = $scope.segment;
var options = null; var options = null;
var cancelBlur = null; var cancelBlur = null;
@@ -206,9 +206,15 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
}); });
$compile(child)(scope); $compile(child)(scope);
elem.empty(); 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) { function registerPluginComponent(scope, elem, attrs, componentInfo) {
-2
View File
@@ -25,7 +25,6 @@ export class ContextSrv {
isGrafanaAdmin: any; isGrafanaAdmin: any;
isEditor: any; isEditor: any;
sidemenu: any; sidemenu: any;
lightTheme: any;
constructor() { constructor() {
this.pinned = store.getBool('grafana.sidemenu.pinned', false); this.pinned = store.getBool('grafana.sidemenu.pinned', false);
@@ -41,7 +40,6 @@ export class ContextSrv {
} }
this.version = config.buildInfo.version; this.version = config.buildInfo.version;
this.lightTheme = false;
this.user = new User(); this.user = new User();
this.isSignedIn = this.user.isSignedIn; this.isSignedIn = this.user.isSignedIn;
this.isGrafanaAdmin = this.user.isGrafanaAdmin; this.isGrafanaAdmin = this.user.isGrafanaAdmin;
+56 -29
View File
@@ -7,36 +7,11 @@ define([
function (angular, _, coreModule, config) { function (angular, _, coreModule, config) {
'use strict'; 'use strict';
coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope) { coreModule.default.service('datasourceSrv', function($q, $injector, $rootScope, templateSrv) {
var self = this; var self = this;
this.init = function() { this.init = function() {
this.datasources = {}; 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) { this.get = function(name) {
@@ -44,6 +19,8 @@ function (angular, _, coreModule, config) {
return this.get(config.defaultDatasource); return this.get(config.defaultDatasource);
} }
name = templateSrv.replace(name);
if (this.datasources[name]) { if (this.datasources[name]) {
return $q.when(this.datasources[name]); return $q.when(this.datasources[name]);
} }
@@ -89,11 +66,61 @@ function (angular, _, coreModule, config) {
}; };
this.getAnnotationSources = function() { 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() { this.getMetricSources = function(options) {
return this.metricSources; 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(); this.init();
+1 -1
View File
@@ -19,7 +19,7 @@ function (angular, _, coreModule) {
if (_.isString(options)) { if (_.isString(options)) {
this.value = options; this.value = options;
this.html = $sce.trustAsHtml(this.value); this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
return; return;
} }
+2
View File
@@ -32,6 +32,8 @@ export default class TableModel {
if (options.desc) { if (options.desc) {
this.rows.reverse(); this.rows.reverse();
this.columns[options.col].desc = true; this.columns[options.col].desc = true;
} else {
this.columns[options.col].desc = false;
} }
} }
} }
+1 -1
View File
@@ -23,7 +23,7 @@ export class Emitter {
this.emitter.on(name, handler); this.emitter.on(name, handler);
if (scope) { if (scope) {
scope.$on('$destroy', function() { scope.$on('$destroy', () => {
this.emitter.off(name, handler); this.emitter.off(name, handler);
}); });
} }
@@ -26,7 +26,10 @@
<input type="text" class="gf-form-input width-25" ng-model='dashboard.title'></input> <input type="text" class="gf-form-input width-25" ng-model='dashboard.title'></input>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-7">Tags<tip>Press enter to a add tag</tip></label> <label class="gf-form-label width-7">
Tags
<info-popover mode="right-normal">Press enter to a add tag</info-popover>
</label>
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags"> <bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput> </bootstrap-tagsinput>
</div> </div>
@@ -46,19 +49,19 @@
label="Editable" label="Editable"
tooltip="Uncheck, then save and reload to disable all dashboard editing" tooltip="Uncheck, then save and reload to disable all dashboard editing"
checked="dashboard.editable" checked="dashboard.editable"
label-class="width-10"> label-class="width-11">
</gf-form-switch> </gf-form-switch>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="Build Mode" label="Build Mode"
tooltip="Enable build mode. Shortcut: CTRL+B" tooltip="Enable build mode. Shortcut: CTRL+B"
checked="dashboard.editMode" checked="dashboard.editMode"
label-class="width-10"> label-class="width-11">
</gf-form-switch> </gf-form-switch>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="Shared Crosshair" label="Shared Crosshair"
tooltip="Shared Crosshair line on all graphs. Shortcut: CTRL+O" tooltip="Shared Crosshair line on all graphs. Shortcut: CTRL+O"
checked="dashboard.sharedCrosshair" checked="dashboard.sharedCrosshair"
label-class="width-10"> label-class="width-11">
</gf-form-switch> </gf-form-switch>
</div> </div>
</div> </div>
@@ -3,7 +3,7 @@
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom"> <li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom">
<a ng-click='ctrl.move(-1)'><i class="fa fa-chevron-left"></i></a> <a ng-click='ctrl.move(-1)'><i class="fa fa-chevron-left"></i></a>
</li> </li>
<li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom in <br> CTRL+Z'" data-placement="bottom"> <li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom out <br> CTRL+Z'" data-placement="bottom">
<a ng-click='ctrl.zoom(2)'>Zoom Out</a></li> <a ng-click='ctrl.zoom(2)'>Zoom Out</a></li>
</li> </li>
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom"> <li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom">
@@ -92,7 +92,7 @@ export class TimePickerCtrl {
move(direction) { move(direction) {
var range = this.timeSrv.timeRange(); 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; var to, from;
if (direction === -1) { if (direction === -1) {
to = range.to.valueOf() - timespan; to = range.to.valueOf() - timespan;
@@ -36,7 +36,7 @@ function (angular, _, $) {
self.update(payload); self.update(payload);
}); });
$scope.onAppEvent('panel-instantiated', function(evt, payload) { $scope.onAppEvent('panel-initialized', function(evt, payload) {
self.registerPanel(payload.scope); self.registerPanel(payload.scope);
}); });
+3 -4
View File
@@ -49,7 +49,7 @@ export class PrefsControlCtrl {
} }
var template = ` var template = `
<form name="ctrl.prefsForm" class="gf-form-group"> <form name="ctrl.prefsForm" class="section gf-form-group">
<h3 class="page-heading">Preferences</h3> <h3 class="page-heading">Preferences</h3>
<div class="gf-form"> <div class="gf-form">
@@ -61,9 +61,8 @@ var template = `
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-9">Home Dashboard</span> <span class="gf-form-label width-9">Home Dashboard</span>
<dashboard-selector <dashboard-selector class="gf-form-select-wrapper max-width-20 gf-form-select-wrapper--has-help-icon"
class="gf-form-select-wrapper max-width-20" model="ctrl.prefs.homeDashboardId">
model="ctrl.prefs.homeDashboardId">
</dashboard-selector> </dashboard-selector>
</div> </div>
+1
View File
@@ -5,4 +5,5 @@ define([
'./query_ctrl', './query_ctrl',
'./panel_editor_tab', './panel_editor_tab',
'./query_editor_row', './query_editor_row',
'./metrics_ds_selector',
], function () {}); ], function () {});
@@ -0,0 +1,110 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
var module = angular.module('grafana.directives');
var template = `
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">
<i class="icon-gf icon-gf-datasource"></i>
</label>
<label class="gf-form-label">
Panel data source
</label>
<metric-segment segment="ctrl.dsSegment" style-mode="select"
get-options="ctrl.getOptions()"
on-change="ctrl.datasourceChanged()"></metric-segment>
</div>
<div class="gf-form gf-form--offset-1">
<button class="btn btn-inverse gf-form-btn" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.current.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Add query
</button>
<div class="dropdown" ng-if="ctrl.current.meta.mixed">
<button class="btn btn-inverse dropdown-toggle gf-form-btn" data-toggle="dropdown">
Add Query&nbsp;<span class="fa fa-caret-down"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
`;
export class MetricsDsSelectorCtrl {
dsSegment: any;
dsName: string;
panelCtrl: any;
datasources: any[];
current: any;
/** @ngInject */
constructor(private uiSegmentSrv, datasourceSrv) {
this.datasources = datasourceSrv.getMetricSources();
var dsValue = this.panelCtrl.panel.datasource || null;
for (let ds of this.datasources) {
if (ds.value === dsValue) {
this.current = ds;
}
}
if (!this.current) {
this.current = {name: dsValue + ' not found', value: null};
}
this.dsSegment = uiSegmentSrv.newSegment(this.current.name);
}
getOptions() {
return Promise.resolve(this.datasources.map(value => {
return this.uiSegmentSrv.newSegment(value.name);
}));
}
datasourceChanged() {
var ds = _.findWhere(this.datasources, {name: this.dsSegment.value});
if (ds) {
this.current = ds;
this.panelCtrl.setDatasource(ds);
}
}
addDataQuery(datasource) {
var target: any = {isNew: true};
if (datasource) {
target.datasource = datasource.name;
}
this.panelCtrl.panel.targets.push(target);
}
}
module.directive('metricsDsSelector', function() {
return {
restrict: 'E',
template: template,
controller: MetricsDsSelectorCtrl,
bindToController: true,
controllerAs: 'ctrl',
transclude: true,
scope: {
panelCtrl: "="
}
};
});
@@ -15,6 +15,7 @@ class MetricsPanelCtrl extends PanelCtrl {
error: boolean; error: boolean;
loading: boolean; loading: boolean;
datasource: any; datasource: any;
datasourceName: any;
$q: any; $q: any;
$timeout: any; $timeout: any;
datasourceSrv: any; datasourceSrv: any;
@@ -27,7 +28,6 @@ class MetricsPanelCtrl extends PanelCtrl {
resolution: any; resolution: any;
timeInfo: any; timeInfo: any;
skipDataOnInit: boolean; skipDataOnInit: boolean;
datasources: any[];
dataStream: any; dataStream: any;
dataSubscription: any; dataSubscription: any;
@@ -52,7 +52,6 @@ class MetricsPanelCtrl extends PanelCtrl {
private onInitMetricsPanelEditMode() { private onInitMetricsPanelEditMode() {
this.addEditorTab('Metrics', 'public/app/partials/metrics.html'); this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html'); this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
this.datasources = this.datasourceSrv.getMetricSources();
} }
private onMetricsPanelRefresh() { private onMetricsPanelRefresh() {
@@ -246,19 +245,10 @@ class MetricsPanelCtrl extends PanelCtrl {
} }
this.panel.datasource = datasource.value; this.panel.datasource = datasource.value;
this.datasourceName = datasource.name;
this.datasource = null; this.datasource = null;
this.refresh(); this.refresh();
} }
addDataQuery(datasource) {
var target: any = {};
if (datasource) {
target.datasource = datasource.name;
}
this.panel.targets.push(target);
}
} }
export {MetricsPanelCtrl}; export {MetricsPanelCtrl};
+2 -2
View File
@@ -50,9 +50,9 @@ export class PanelCtrl {
} }
init() { init() {
this.publishAppEvent('panel-instantiated', {scope: this.$scope});
this.calculatePanelHeight(); this.calculatePanelHeight();
this.refresh(); this.publishAppEvent('panel-initialized', {scope: this.$scope});
this.events.emit('panel-initialized');
} }
renderingCompleted() { renderingCompleted() {
@@ -1,4 +1,63 @@
<div class="tight-form">
<div class="gf-form-query">
<div class="gf-form">
<label class="gf-form-label gf-form-query-letter-cell">
<a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
<i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
</span>
<span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
</a>
</label>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ctrl.collapsedText}}
</label>
</div>
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
</div>
<div class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
</li>
</ul>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</label>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="tight-form" ng-if="false">
<ul class="tight-form-list pull-right"> <ul class="tight-form-list pull-right">
<li ng-show="ctrl.error" class="tight-form-item"> <li ng-show="ctrl.error" class="tight-form-item">
<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem"> <a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">
-34
View File
@@ -13,45 +13,11 @@ export class QueryCtrl {
constructor(public $scope, private $injector) { constructor(public $scope, private $injector) {
this.panel = this.panelCtrl.panel; this.panel = this.panelCtrl.panel;
if (!this.target.refId) {
this.target.refId = this.getNextQueryLetter();
}
}
getNextQueryLetter() {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
return _.find(letters, refId => {
return _.every(this.panel.targets, function(other) {
return other.refId !== refId;
});
});
}
removeQuery() {
this.panel.targets = _.without(this.panel.targets, this.target);
this.panelCtrl.refresh();
};
duplicateQuery() {
var clone = angular.copy(this.target);
clone.refId = this.getNextQueryLetter();
this.panel.targets.push(clone);
}
moveQuery(direction) {
var index = _.indexOf(this.panel.targets, this.target);
_.move(this.panel.targets, index, index + direction);
} }
refresh() { refresh() {
this.panelCtrl.refresh(); this.panelCtrl.refresh();
} }
toggleHideQuery() {
this.target.hide = !this.target.hide;
this.panelCtrl.refresh();
}
} }
+100 -2
View File
@@ -1,17 +1,115 @@
///<reference path="../../headers/common.d.ts" /> ///<reference path="../../headers/common.d.ts" />
import angular from 'angular'; import angular from 'angular';
import $ from 'jquery'; import _ from 'lodash';
var module = angular.module('grafana.directives'); var module = angular.module('grafana.directives');
export class QueryRowCtrl {
collapsedText: string;
canCollapse: boolean;
getCollapsedText: any;
target: any;
queryCtrl: any;
panelCtrl: any;
panel: any;
collapsed: any;
constructor() {
this.panelCtrl = this.queryCtrl.panelCtrl;
this.target = this.queryCtrl.target;
this.panel = this.panelCtrl.panel;
if (!this.target.refId) {
this.target.refId = this.getNextQueryLetter();
}
this.toggleCollapse(true);
if (this.target.isNew) {
delete this.target.isNew;
this.toggleCollapse(false);
}
}
toggleHideQuery() {
this.target.hide = !this.target.hide;
this.panelCtrl.refresh();
}
getNextQueryLetter() {
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
return _.find(letters, refId => {
return _.every(this.panel.targets, function(other) {
return other.refId !== refId;
});
});
}
toggleCollapse(init) {
if (!this.canCollapse) {
return;
}
if (!this.panelCtrl.__collapsedQueryCache) {
this.panelCtrl.__collapsedQueryCache = {};
}
if (init) {
this.collapsed = this.panelCtrl.__collapsedQueryCache[this.target.refId] !== false;
} else {
this.collapsed = !this.collapsed;
this.panelCtrl.__collapsedQueryCache[this.target.refId] = this.collapsed;
}
try {
this.collapsedText = this.queryCtrl.getCollapsedText();
} catch (e) {
var err = e.message || e.toString();
this.collapsedText = 'Error: ' + err;
}
}
toggleEditorMode() {
if (this.canCollapse && this.collapsed) {
this.collapsed = false;
}
this.queryCtrl.toggleEditorMode();
}
removeQuery() {
delete this.panelCtrl.__collapsedQueryCache[this.target.refId];
this.panel.targets = _.without(this.panel.targets, this.target);
this.panelCtrl.refresh();
}
duplicateQuery() {
var clone = angular.copy(this.target);
clone.refId = this.getNextQueryLetter();
this.panel.targets.push(clone);
}
moveQuery(direction) {
var index = _.indexOf(this.panel.targets, this.target);
_.move(this.panel.targets, index, index + direction);
}
}
/** @ngInject **/ /** @ngInject **/
function queryEditorRowDirective() { function queryEditorRowDirective() {
return { return {
restrict: 'E', restrict: 'E',
controller: QueryRowCtrl,
bindToController: true,
controllerAs: "ctrl",
templateUrl: 'public/app/features/panel/partials/query_editor_row.html', templateUrl: 'public/app/features/panel/partials/query_editor_row.html',
transclude: true, transclude: true,
scope: {ctrl: "="}, scope: {
queryCtrl: "=",
canCollapse: "=",
hasTextEditMode: "=",
},
}; };
} }
+4 -4
View File
@@ -98,9 +98,7 @@ export class DataSourceEditCtrl {
this.datasourceSrv.get(this.current.name).then(datasource => { this.datasourceSrv.get(this.current.name).then(datasource => {
if (!datasource.testDatasource) { if (!datasource.testDatasource) {
this.testing.message = 'Data source does not support test connection feature.'; delete this.testing;
this.testing.status = 'warning';
this.testing.title = 'Unknown';
return; return;
} }
@@ -118,7 +116,9 @@ export class DataSourceEditCtrl {
} }
}); });
}).finally(() => { }).finally(() => {
this.testing.done = true; if (this.testing) {
this.testing.done = true;
}
}); });
} }
@@ -15,16 +15,16 @@
</td> </td>
<td> <td>
v{{dash.revision}} v{{dash.revision}}
</td> <span ng-if="dash.installed">
<td ng-if="dash.installed"> &nbsp;(Imported v{{dash.installedRevision}})
Imported v{{dash.installedRevision}} <span>
</td> </td>
<td style="text-align: right"> <td style="text-align: right">
<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed"> <button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
Import Import
</button> </button>
<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed"> <button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
Re-Import Update
</button> </button>
<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed"> <button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
Delete Delete
@@ -43,11 +43,11 @@ export class DashImportListCtrl {
}); });
} }
import(dash, reinstall) { import(dash, overwrite) {
var installCmd = { var installCmd = {
pluginId: this.plugin.id, pluginId: this.plugin.id,
path: dash.path, path: dash.path,
reinstall: reinstall, overwrite: overwrite,
inputs: [] inputs: []
}; };
@@ -31,18 +31,18 @@
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Name</span> <span class="gf-form-label width-7">Name</span>
<input class="gf-form-input max-width-21" type="text" ng-model="ctrl.current.name" placeholder="My data source name" required> <input class="gf-form-input max-width-21" type="text" ng-model="ctrl.current.name" placeholder="My data source name" required>
<info-popover offset="0px -135px" mode="right-absolute">
The name is used when you select the data source in panels.
The <em>Default</em> data source is preselected in new
panels.
</info-popover>
</div> </div>
<info-popover offset="0px -130px">
The name is used when you select the data source in panels.
The <em>Default</em> data source is preselected in new
panels.
</info-popover>
<gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch> <gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Type</span> <span class="gf-form-label width-7">Type</span>
<div class="gf-form-select-wrapper max-width-21"> <div class="gf-form-select-wrapper max-width-23">
<select class="gf-form-input" ng-model="ctrl.current.type" ng-options="v.id as v.name for v in ctrl.types" ng-change="ctrl.typeChanged()"></select> <select class="gf-form-input" ng-model="ctrl.current.type" ng-options="v.id as v.name for v in ctrl.types" ng-change="ctrl.typeChanged()"></select>
</div> </div>
</div> </div>
@@ -53,7 +53,7 @@
</plugin-component> </plugin-component>
</rebuild-on-change> </rebuild-on-change>
<div ng-if="ctrl.testing" style="margin-top: 25px"> <div ng-if="ctrl.testing" class="gf-form-group">
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5> <h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<div class="alert-{{ctrl.testing.status}} alert"> <div class="alert-{{ctrl.testing.status}} alert">
<div class="alert-title">{{ctrl.testing.title}}</div> <div class="alert-title">{{ctrl.testing.title}}</div>
@@ -3,29 +3,34 @@
<div class="gf-form-group"> <div class="gf-form-group">
<h3 class="page-heading">Http settings</h3> <h3 class="page-heading">Http settings</h3>
<div class="gf-form"> <div class="gf-form-inline">
<span class="gf-form-label width-7">Url</span> <div class="gf-form max-width-30">
<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input> <span class="gf-form-label width-7">Url</span>
<input class="gf-form-input" type="text" ng-model='current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
<info-popover> <info-popover mode="right-absolute">
<p>Specify a complete HTTP url (for example http://your_server:8080)</p> <p>Specify a complete HTTP url (for example http://your_server:8080)</p>
<span ng-show="current.access === 'direct'"> <span ng-show="current.access === 'direct'">
Your access method is <em>Direct</em>, this means the url Your access method is <em>Direct</em>, this means the url
needs to be accessable from the browser. needs to be accessable from the browser.
</span> </span>
<span ng-show="current.access === 'proxy'"> <span ng-show="current.access === 'proxy'">
Your access method is currently <em>Proxy</em>, this means the url Your access method is currently <em>Proxy</em>, this means the url
needs to be accessable from the grafana backend. needs to be accessable from the grafana backend.
</span> </span>
</info-popover> </info-popover>
</div>
</div> </div>
<div class="gf-form"> <div class="gf-form-inline">
<span class="gf-form-label width-7"> <div class="gf-form max-width-30">
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip> <span class="gf-form-label width-7">Access</span>
</span> <div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24">
<div class="gf-form-select-wrapper"> <select class="gf-form-input" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select> <info-popover mode="right-absolute">
Direct = url is used directly from browser<br>
Proxy = Grafana backend will proxy the request
</info-popover>
</div>
</div> </div>
</div> </div>
@@ -34,12 +39,12 @@
<label class="gf-form-label width-7">Http Auth</label> <label class="gf-form-label width-7">Http Auth</label>
</div> </div>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="Basic Auth" label="Basic Auth"
checked="current.basicAuth" switch-class="max-width-6"> checked="current.basicAuth" switch-class="max-width-6">
</gf-form-switch> </gf-form-switch>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="With Credentials" label="With Credentials"
checked="current.withCredentials" switch-class="max-width-6"> checked="current.withCredentials" switch-class="max-width-6">
</gf-form-switch> </gf-form-switch>
</div> </div>
@@ -14,8 +14,8 @@
<div class="gf-form-group"> <div class="gf-form-group">
<p>Type the following on the command line to update {{plugin.name}}.</p> <p>Type the following on the command line to update {{plugin.name}}.</p>
<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre> <pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span> <span class="small">Check out {{plugin.name}} on <a href="https://grafana.net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
</div> </div>
<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div> <p class="pluginlist-none-installed"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
</div> </div>
</div> </div>
@@ -20,6 +20,13 @@ function (angular, _) {
multi: false, multi: false,
}; };
$scope.variableTypes = [
{value: "query", text: "Query"},
{value: "interval", text: "Interval"},
{value: "datasource", text: "Data source"},
{value: "custom", text: "Custom"},
];
$scope.refreshOptions = [ $scope.refreshOptions = [
{value: 0, text: "Never"}, {value: 0, text: "Never"},
{value: 1, text: "On Dashboard Load"}, {value: 1, text: "On Dashboard Load"},
@@ -35,10 +42,16 @@ function (angular, _) {
$scope.init = function() { $scope.init = function() {
$scope.mode = 'list'; $scope.mode = 'list';
$scope.datasourceTypes = {};
$scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) { $scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) {
$scope.datasourceTypes[ds.meta.id] = {text: ds.meta.name, value: ds.meta.id};
return !ds.meta.builtIn; return !ds.meta.builtIn;
}); });
$scope.datasourceTypes = _.map($scope.datasourceTypes, function(value) {
return value;
});
$scope.variables = templateSrv.variables; $scope.variables = templateSrv.variables;
$scope.reset(); $scope.reset();
@@ -132,9 +145,16 @@ function (angular, _) {
if ($scope.current.type === 'interval') { if ($scope.current.type === 'interval') {
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d'; $scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
} }
if ($scope.current.type === 'query') { if ($scope.current.type === 'query') {
$scope.current.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) { $scope.removeVariable = function(variable) {
@@ -75,39 +75,49 @@
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form max-width-19"> <div class="gf-form max-width-19">
<span class="gf-form-label width-7">Name</span> <span class="gf-form-label width-6">Name</span>
<input type="text" class="gf-form-input max-width-12" placeholder="name" ng-model='current.name'></input> <input type="text" class="gf-form-input" placeholder="name" ng-model='current.name'></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-4">Type</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-7" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
</div>
</div> </div>
<div class="gf-form max-width-19"> <div class="gf-form max-width-19">
<span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span> <span class="gf-form-label width-6">
<div class="gf-form-select-wrapper max-width-12" ng-show="current.type === 'query'"> Type
<select class="gf-form-input" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select> <info-popover mode="right-normal">
<dl>
<dt>Query</dt>
<dd>Variable values are fetched from a metric names query to a data source</dd>
<dt>Interval</dt>
<dd>Timespan variable type</dd>
<dt>Datasource</dt>
<dd>Dynamically switch data sources using this type of variable</dd>
<dt>Custom</dt>
<dd>Define variable values manually</dd>
</dl>
<a href="http://docs.grafana.org/reference/templating" target="_blank">Templating docs</a>
</info-popover>
</span>
<div class="gf-form-select-wrapper max-width-17">
<select class="gf-form-input" ng-model="current.type" ng-options="f.value as f.text for f in variableTypes" ng-change="typeChanged()"></select>
</div> </div>
</div> </div>
</div> </div>
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form max-width-19"> <div class="gf-form max-width-19">
<span class="gf-form-label width-7">Label</span> <span class="gf-form-label width-6">Label</span>
<input type="text" class="gf-form-input max-width-12" ng-model='current.label' placeholder="optional display name"></input> <input type="text" class="gf-form-input" ng-model='current.label' placeholder="optional display name"></input>
</div> </div>
<div class="gf-form"> <div class="gf-form max-width-19">
<span class="gf-form-label width-4">Hide</span> <span class="gf-form-label width-6">Hide</span>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper max-width-15">
<select class="gf-form-input width-7" ng-model="current.hide" ng-options="f.value as f.text for f in hideOptions"></select> <select class="gf-form-input" ng-model="current.hide" ng-options="f.value as f.text for f in hideOptions"></select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<h5 class="section-heading">Value Options</h5>
<div ng-show="current.type === 'interval'" class="gf-form-group"> <div ng-show="current.type === 'interval'" class="gf-form-group">
<h5 class="section-heading">Interval Options</h5>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-9">Values</span> <span class="gf-form-label width-9">Values</span>
<input type="text" class="gf-form-input" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input> <input type="text" class="gf-form-input" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
@@ -135,6 +145,7 @@
</div> </div>
<div ng-show="current.type === 'custom'" class="gf-form-group"> <div ng-show="current.type === 'custom'" class="gf-form-group">
<h5 class="section-heading">Custom Options</h5>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-13">Values seperated by comma</span> <span class="gf-form-label width-13">Values seperated by comma</span>
<input type="text" class="gf-form-input" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input> <input type="text" class="gf-form-input" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
@@ -142,43 +153,69 @@
</div> </div>
<div ng-show="current.type === 'query'" class="gf-form-group"> <div ng-show="current.type === 'query'" class="gf-form-group">
<div class="gf-form"> <h5 class="section-heading">Query Options</h5>
<span class="gf-form-label width-7">Query</span>
<input type="text" class="gf-form-input" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
<!-- <info&#45;popover position="bottom center" wide="true"> -->
<!-- Example queries: -->
<!-- <ul> -->
<!-- <li> -->
<!-- <code>SHOW TAG VALUES WITH KEY = "hostname"</code> -->
<!-- </li> -->
<!-- <li> -->
<!-- <code>SHOW TAG VALUES WITH KEY = "hostname"</code> -->
<!-- </li> -->
<!-- <li> -->
<!-- <code>SHOW TAG VALUES WITH KEY = "hostname"</code> -->
<!-- </li> -->
<!-- <li> -->
<!-- <a href="http://docs.grafana.org" target="_blank">Templating docs</a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </info&#45;popover> -->
</div>
<div class="gf-form">
<span class="gf-form-label width-7">
Regex
<tip>Optional, if you want to extract part of a series name or metric node segment</tip>
</span>
<input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Refresh</span>
<select class="gf-form-input max-width-14" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
<tip>When to update the values of this variable, will slow down dashboard load / time change</tip>
</div>
</div>
<div class="section gf-form-group" > <div class="gf-form-inline">
<h5 class="section-heading">Selection Options</h5> <div class="gf-form max-width-21">
<span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span>
<div class="gf-form-select-wrapper max-width-14">
<select class="gf-form-input" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
</div>
</div>
<div class="gf-form max-width-21">
<span class="gf-form-label width-7">
Refresh
<info-popover mode="right-normal">
When to update the values of this variable.
</info-popover>
</span>
<div class="gf-form-select-wrapper max-width-14">
<select class="gf-form-input" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
</div>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Query</span>
<input type="text" class="gf-form-input" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">
Regex
<info-popover mode="right-normal">
Optional, if you want to extract part of a series name or metric node segment.
</info-popover>
</span>
<input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
</div>
</div>
<div ng-show="current.type === 'datasource'" class="gf-form-group">
<h5 class="section-heading">Data source options</h5>
<div class="gf-form">
<label class="gf-form-label width-12">Type</label>
<div class="gf-form-select-wrapper max-width-18">
<select class="gf-form-input" ng-model="current.query" ng-options="f.value as f.text for f in datasourceTypes" ng-change="runQuery()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-12">
Instance name filter
<info-popover mode="right-normal">
Regex filter for which data source instances to choose from in
the variable value dropdown. Leave empty for all.
<br><br>
Example: <code>/^prod/</code>
</info-popover>
</label>
<input type="text" class="gf-form-input max-width-18" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
</div>
</div>
<div class="section gf-form-group" ng-hide="current.type === 'datasource'">
<h5 class="section-heading">Selection Options</h5>
<div class="section"> <div class="section">
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="Multi-value" label="Multi-value"
@@ -217,10 +254,10 @@
<div class="gf-form-group"> <div class="gf-form-group">
<h5>Preview of values (shows max 20)</h5> <h5>Preview of values (shows max 20)</h5>
<div class="gf-form"> <div class="gf-form-inline">
<span class="gf-form-label" ng-repeat="option in current.options | limitTo: 20"> <div class="gf-form" ng-repeat="option in current.options | limitTo: 20">
{{option.text}} <span class="gf-form-label">{{option.text}}</span>
</span> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -57,7 +57,7 @@ function (angular, _) {
} }
var escapedValues = _.map(value, regexEscape); var escapedValues = _.map(value, regexEscape);
return escapedValues.join('|'); return '(' + escapedValues.join('|') + ')';
} }
case "lucene": { case "lucene": {
if (typeof value === 'string') { if (typeof value === 'string') {
@@ -152,6 +152,10 @@ function (angular, _) {
value = variable.current.value; value = variable.current.value;
if (self.isAllValue(value)) { if (self.isAllValue(value)) {
value = self.getAllValue(variable); value = self.getAllValue(variable);
// skip formating of custom all values
if (variable.allValue) {
return value;
}
} }
var res = self.formatValue(value, format, variable); var res = self.formatValue(value, format, variable);
@@ -63,7 +63,9 @@ function (angular, _, kbn) {
// determine our dependencies. // determine our dependencies.
if (variable.type === "query") { if (variable.type === "query") {
_.forEach(this.variables, function(v) { _.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); dependencies.push(self.variableLock[v.name].promise);
} }
}); });
@@ -149,7 +151,8 @@ function (angular, _, kbn) {
if (otherVariable === updatedVariable) { if (otherVariable === updatedVariable) {
return; 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); return self.updateOptions(otherVariable);
} }
}); });
@@ -158,6 +161,11 @@ function (angular, _, kbn) {
}; };
this._updateNonQueryVariable = function(variable) { this._updateNonQueryVariable = function(variable) {
if (variable.type === 'datasource') {
self.updateDataSourceVariable(variable);
return;
}
// extract options in comma seperated string // extract options in comma seperated string
variable.options = _.map(variable.query.split(/[,]+/), function(text) { variable.options = _.map(variable.query.split(/[,]+/), function(text) {
return { text: text.trim(), value: text.trim() }; 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) { this.updateOptions = function(variable) {
if (variable.type !== 'query') { if (variable.type !== 'query') {
self._updateNonQueryVariable(variable); self._updateNonQueryVariable(variable);
@@ -288,17 +326,10 @@ function (angular, _, kbn) {
options[value] = {text: text, value: value}; options[value] = {text: text, value: value};
} }
return _.map(_.keys(options).sort(), function(key) { return _.sortBy(options, 'text');
return options[key];
});
}; };
this.addAllOption = function(variable) { this.addAllOption = function(variable) {
if (variable.allValue) {
variable.options.unshift({text: 'All', value: variable.allValue});
return;
}
variable.options.unshift({text: 'All', value: "$__all"}); variable.options.unshift({text: 'All', value: "$__all"});
}; };
+14 -51
View File
@@ -1,56 +1,19 @@
<div class="editor-row">
<div class="tight-form-container">
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'tight-form-disabled': target.hide}">
<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
<plugin-component type="query-ctrl">
</plugin-component>
</rebuild-on-change>
</div>
</div>
<div style="margin: 20px 0 0 0">
<button class="btn btn-inverse" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.datasource.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Query
</button>
<div class="dropdown" ng-if="ctrl.datasource.meta.mixed">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>&nbsp;
Query &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
</div>
<rebuild-on-change property="ctrl.panel.datasource" show-null="true">
<plugin-component type="query-options-ctrl">
</plugin-component>
</rebuild-on-change>
<div class="query-editor-rows gf-form-group">
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
<plugin-component type="query-ctrl">
</plugin-component>
</rebuild-on-change>
</div>
</div> </div>
<div class="editor-row"> <metrics-ds-selector panel-ctrl="ctrl"></metrics-ds-selector>
<div class="pull-right dropdown" style="margin-right: 10px;"> <div class="gf-form-group">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'"> <rebuild-on-change property="ctrl.panel.datasource" show-null="true">
<i class="fa fa-database"></i>&nbsp; <plugin-component type="query-options-ctrl">
{{ctrl.datasource.name}} &nbsp; <span class="caret"></span> </plugin-component>
</button> </rebuild-on-change>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in ctrl.datasources" role="menuitem">
<a ng-click="ctrl.setDatasource(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
<div class="clearfix"></div>
</div> </div>
@@ -1,22 +1,27 @@
<h3 class="page-heading">CloudWatch details</h3> <h3 class="page-heading">CloudWatch details</h3>
<div class="gf-form-group"> <div class="gf-form-group max-width-30">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-14"> <label class="gf-form-label width-13">Credentials profile name</label>
Credentials profile name<tip>Credentials profile name, as specified in ~/.aws/credentials, leave blank for default</tip> <input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.database' placeholder="default"></input>
</label> <info-popover mode="right-absolute">
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.database' placeholder="default"></input> Credentials profile name, as specified in ~/.aws/credentials, leave blank for default
</info-popover>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-14"> <label class="gf-form-label width-13">Default Region</label>
Default Region<tip>Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.</tip> <div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
</label> <select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select>
<div class="gf-form-select-wrapper"> <info-popover mode="right-absolute">
<select class="gf-form-input max-width-15" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select> Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
</info-popover>
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-14">Custom Metrics namespace<tip>Namespaces of Custom Metrics</tip></label> <label class="gf-form-label width-13">Custom Metrics namespace</label>
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input> <input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input>
<info-popover mode="right-absolute">
Namespaces of Custom Metrics
</info-popover>
</div> </div>
</div> </div>
@@ -1,4 +1,4 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="false">
<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>
</query-editor-row> </query-editor-row>
<cloudwatch-query-parameter target="ctrl.target" datasource="ctrl.datasource" on-change="ctrl.refresh()"></cloudwatch-query-parameter>
@@ -1,58 +1,59 @@
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list" role="menu"> <div class="gf-form">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px"> <label class="gf-form-label query-keyword width-7">Metric</label>
Metric
</li>
<li>
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
</li>
<li class="tight-form-item query-keyword">
Stats
</li>
<li ng-repeat="segment in statSegments">
<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
</li>
</ul>
<div class="clearfix"></div> <metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
<metric-segment segment="metricSegment" get-options="getMetrics()" on-change="metricChanged()"></metric-segment>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">Stats</label>
</div>
<div class="gf-form" ng-repeat="segment in statSegments">
<metric-segment segment="segment" get-options="getStatSegments(segment, $index)" on-change="statSegmentChanged(segment, $index)"></metric-segment>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list" role="menu"> <div class="gf-form">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px"> <label class="gf-form-label query-keyword width-7">Dimensions</label>
Dimensions <metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
</li> </div>
<li ng-repeat="segment in dimSegments">
<metric-segment segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
</li>
</ul>
<div class="clearfix"></div> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list" role="menu"> <div class="gf-form">
<li class="tight-form-item query-keyword tight-form-align" style="width: 100px"> <label class="gf-form-label query-keyword width-7">
Period Period
<tip>Interval between points in seconds</tip> <info-popover mode="right-normal">Interval between points in seconds</info-popover>
</li> </label>
<li> <input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
<input type="text" class="input-mini tight-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" /> </div>
</li> <div class="gf-form max-width-30">
<li class="tight-form-item query-keyword"> <label class="gf-form-label query-keyword width-7">Alias</label>
Alias <input type="text" class="gf-form-input" ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()">
<tip>{{metric}} {{stat}} {{namespace}} {{region}} {{DIMENSION_NAME}}</tip> <info-popover mode="right-absolute">
</li> Alias replacement variables:
<li> <ul ng-non-bindable>
<input type="text" class="input-xlarge tight-form-input" ng-model="target.alias" spellcheck='false' ng-model-onblur ng-change="onChange()"> <li>{{metric}}</li>
</li> <li>{{stat}}</li>
</ul> <li>{{namespace}}</li>
<div class="clearfix"></div> <li>{{region}}</li>
<li>{{DIMENSION_NAME}}</li>
</ul>
</info-popover>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
@@ -60,6 +60,10 @@ function (angular, _, queryDef) {
$scope.agg.query = '*'; $scope.agg.query = '*';
break; break;
} }
case 'geohash_grid': {
$scope.agg.settings.precision = 3;
break;
}
} }
$scope.validateModel(); $scope.validateModel();
@@ -121,6 +125,13 @@ function (angular, _, queryDef) {
if (settings.trimEdges && settings.trimEdges > 0) { if (settings.trimEdges && settings.trimEdges > 0) {
settingsLinkText += ', Trim edges: ' + settings.trimEdges; 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;
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 38 KiB

@@ -1,131 +1,96 @@
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list"> <div class="gf-form">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;"> <label class="gf-form-label query-keyword width-7">
<span ng-show="isFirst">Group by</span> <span ng-show="isFirst">Group by</span>
<span ng-hide="isFirst">Then by</span> <span ng-hide="isFirst">Then by</span>
</li> </label>
<li>
<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="tight-form-item-large"></metric-segment-model> <metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="width-10"></metric-segment-model>
</li> <metric-segment-model ng-if="agg.field" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment-model>
<li ng-if="agg.field"> </div>
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
</li> <div class="gf-form gf-form--grow">
<li ng-if="!agg.field"> <label class="gf-form-label gf-form-label--grow">
<span class="tight-form-item tight-form-item-xxlarge">&nbsp;</span>
</li>
<li class="tight-form-item last" ng-if="settingsLinkText">
<a ng-click="toggleOptions()"> <a ng-click="toggleOptions()">
<i class="fa fa-caret-down" ng-show="showOptions"></i> <i class="fa fa-caret-down" ng-show="showOptions"></i>
<i class="fa fa-caret-right" ng-hide="showOptions"></i> <i class="fa fa-caret-right" ng-hide="showOptions"></i>
{{settingsLinkText}} {{settingsLinkText}}
</a> </a>
</li> </label>
</ul> </div>
<ul class="tight-form-list pull-right"> <div class="gf-form">
<li class="tight-form-item last" ng-if="isFirst"> <label class="gf-form-label" ng-if="isFirst">
<a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a> <a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a>
</li> </label>
<li class="tight-form-item last"> <label class="gf-form-label">
<a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a> <a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a>
</li> </label>
</ul> </div>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form" ng-if="showOptions"> <div class="gf-form-group" ng-if="showOptions">
<div class="tight-form-inner-box" ng-if="agg.type === 'date_histogram'"> <div ng-if="agg.type === 'date_histogram'">
<div class="tight-form"> <div class="gf-form offset-width-7">
<ul class="tight-form-list"> <label class="gf-form-label width-10">Interval</label>
<li class="tight-form-item" style="width: 170px"> <metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="width-12" custom="true"></metric-segment-model>
Interval
</li>
<li>
<metric-segment-model property="agg.settings.interval" get-options="getIntervalOptions()" on-change="onChangeInternal()" css-class="last" custom="true"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form">
<ul class="tight-form-list"> <div class="gf-form offset-width-7">
<li class="tight-form-item" style="width: 170px"> <label class="gf-form-label width-10">Min Doc Count</label>
Min Doc Count <input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
</li>
<li>
<input type="number" class="tight-form-input" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form last">
<ul class="tight-form-list"> <div class="gf-form offset-width-7">
<li class="tight-form-item" style="width: 170px"> <label class="gf-form-label width-10">
Trim edges points Trim edges
</li> <info-popover mode="right-normal">
<li> Trim the edges on the timeseries datapoints
<input class="tight-form-input" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()"> </info-popover>
</li> </label>
<li class="tight-form-item last"> <input class="gf-form-input max-width-12" type="number" ng-model="agg.settings.trimEdges" ng-change="onChangeInternal()">
<i class="fa fa-question-circle" bs-tooltip="'Trim the edges on the timeseries x datapoints'" data-placement="right"></i>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
<div class="tight-form-inner-box" ng-if="agg.type === 'terms'">
<div class="tight-form"> <div ng-if="agg.type === 'terms'">
<ul class="tight-form-list"> <div class="gf-form offset-width-7">
<li class="tight-form-item" style="width: 60px"> <label class="gf-form-label">Order</label>
Order <metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</li>
<li>
<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form">
<ul class="tight-form-list"> <div class="gf-form offset-width-7">
<li class="tight-form-item" style="width: 60px"> <label class="gf-form-label width-10">Size</label>
Size <metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</li>
<li>
<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form last">
<ul class="tight-form-list"> <div class="gf-form offset-width-7">
<li class="tight-form-item" style="width: 60px"> <label class="gf-form-label width-10">Order By</label>
Order By <metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
</li>
<li>
<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div> </div>
<div class="tight-form-inner-box" ng-if="agg.type === 'filters'">
<div class="tight-form" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}"> <div ng-if="agg.type === 'filters'">
<ul class="tight-form-list"> <div class="gf-form-inline" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
<li class="tight-form-item" style="width: 100px"> <div class="gf-form">
Query {{$index + 1}} <label class="gf-form-item width-10">Query {{$index + 1}}</label>
</li> <input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
<li> </div>
<input type="text" class="tight-form-input input-large" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()"> <div class="gf-form">
</li> <label class="gf-form-label" ng-if="$first">
<li class="tight-form-item last" ng-if="$first">
<a class="pointer" ng-click="addFiltersQuery()"><i class="fa fa-plus"></i></a> <a class="pointer" ng-click="addFiltersQuery()"><i class="fa fa-plus"></i></a>
</li> </label>
<li class="tight-form-item last" ng-if="!$first"> <label class="gf-form-label" ng-if="!$first">
<a class="pointer" ng-click="removeFiltersQuery(filter)"><i class="fa fa-minus"></i></a> <a class="pointer" ng-click="removeFiltersQuery(filter)"><i class="fa fa-minus"></i></a>
</li> </label>
</ul> </div>
<div class="clearfix"></div>
</div> </div>
</div>
<div ng-if="agg.type === 'geohash_grid'">
<div class="gf-form offset-width-7">
<label class="gf-form-label">Precision</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
</div>
</div> </div>
</div> </div>
@@ -1,138 +1,82 @@
<div class="tight-form" ng-class="{'tight-form-disabled': agg.hide}"> <div class="gf-form-inline" ng-class="{'gf-form-disabled': agg.hide}">
<ul class="tight-form-list"> <div class="gf-form">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;"> <label class="gf-form-label query-keyword width-7">
Metric Metric
&nbsp; &nbsp;
<a ng-click="toggleShowMetric()" bs-tooltip="'Click to toggle show / hide metric'"> <a ng-click="toggleShowMetric()" bs-tooltip="'Click to toggle show / hide metric'">
<i class="fa fa-eye" ng-hide="agg.hide"></i> <i class="fa fa-eye" ng-hide="agg.hide"></i>
<i class="fa fa-eye-slash" ng-show="agg.hide"></i> <i class="fa fa-eye-slash" ng-show="agg.hide"></i>
</a> </a>
</li> </label>
<li> </div>
<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
</li> <div class="gf-form">
<li ng-if="aggDef.requiresField"> <metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="width-10"></metric-segment-model>
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment-model> <metric-segment-model ng-if="aggDef.requiresField" property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="width-12"></metric-segment-model>
</li> <metric-segment-model ng-if="aggDef.isPipelineAgg" property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="width-12"></metric-segment-model>
<li ng-if="aggDef.isPipelineAgg"> </div>
<metric-segment-model property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="tight-form-item-xxlarge"></metric-segment-model>
</li> <div class="gf-form gf-form--grow">
<li class="tight-form-item last" ng-if="settingsLinkText"> <label class="gf-form-label gf-form-label--grow">
<a ng-click="toggleOptions()"> <a ng-click="toggleOptions()" ng-if="settingsLinkText">
<i class="fa fa-caret-down" ng-show="showOptions"></i> <i class="fa fa-caret-down" ng-show="showOptions"></i>
<i class="fa fa-caret-right" ng-hide="showOptions"></i> <i class="fa fa-caret-right" ng-hide="showOptions"></i>
{{settingsLinkText}} {{settingsLinkText}}
</a> </a>
</li> </label>
</ul> </div>
<ul class="tight-form-list pull-right"> <div class="gf-form">
<li class="tight-form-item last" ng-if="isFirst"> <label class="gf-form-label" ng-if="isFirst">
<a class="pointer" ng-click="addMetricAgg()"><i class="fa fa-plus"></i></a> <a class="pointer" ng-click="addMetricAgg()"><i class="fa fa-plus"></i></a>
</li> </label>
<li class="tight-form-item last" ng-if="!isSingle"> <label class="gf-form-label" ng-if="!isSingle">
<a class="pointer" ng-click="removeMetricAgg()"><i class="fa fa-minus"></i></a> <a class="pointer" ng-click="removeMetricAgg()"><i class="fa fa-minus"></i></a>
</li> </label>
</ul> </div>
<div class="clearfix"></div>
</div> </div>
<div class="tight-form" ng-if="showOptions"> <div class="gf-form-group" ng-if="showOptions">
<div class="tight-form-inner-box tight-form-container">
<div class="tight-form" ng-if="agg.type === 'derivative'"> <div class="gf-form offset-width-7" ng-if="agg.type === 'derivative'">
<ul class="tight-form-list"> <label class="gf-form-label width-10">Unit</label>
<li class="tight-form-item" style="width: 75px;"> <input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
Unit </div>
</li>
<li> <div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
<input type="text" class="input-medium tight-form-input last" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'> <label class="gf-form-label width-10">Window</label>
</li> <input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</ul> </div>
<div class="clearfix"></div>
</div> <div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
<label class="gf-form-label width-10">Model</label>
<input type="text" class="gf-form-input max-width-12" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
</div>
<div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
<label class="gf-form-label width-10">Percentiles</label>
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
</div>
<div class="tight-form" ng-if="agg.type === 'moving_avg'"> <div ng-if="agg.type === 'extended_stats'">
<ul class="tight-form-list"> <gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
<li class="tight-form-item" style="width: 75px;">
Window
</li>
<li>
<input type="number" class="input-medium tight-form-input last" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="agg.type === 'moving_avg'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 75px;">
Model
</li>
<li>
<input type="text" class="input-medium tight-form-input last" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last" ng-if="agg.type === 'percentiles'">
<ul class="tight-form-list">
<li class="tight-form-item">
Percentiles
</li>
<li>
<input type="text" class="input-xlarge tight-form-input last" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div ng-if="agg.type === 'extended_stats'">
<div class="tight-form" ng-repeat="stat in extendedStats">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
{{stat.text}}
</li>
<li class="tight-form-item last">
<editor-checkbox text="" model="agg.meta.{{stat.value}}" change="onChange()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="tight-form" ng-if="agg.type === 'extended_stats'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px">
Sigma
</li>
<li>
<input type="number" class="input-mini tight-form-input last" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="aggDef.supportsInlineScript"> <div class="gf-form offset-width-7">
<ul class="tight-form-list"> <label class="gf-form-label width-10">Sigma</label>
<li class="tight-form-item" style="width: 100px;"> <input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
Script
</li>
<li>
<input type="text" class="input-medium tight-form-input last" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
</li>
</ul>
<div class="clearfix"></div>
</div> </div>
</div>
<div class="tight-form" ng-if="aggDef.supportsMissing"> <div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
<ul class="tight-form-list"> <label class="gf-form-label width-10">Script</label>
<li class="tight-form-item" style="width: 100px;"> <input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
Missing </div>
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
</li>
<li>
<input type="number" class="input-medium tight-form-input last" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
<label class="gf-form-label width-10">
Missing
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
</label>
<input type="number" class="gf-form-input max-width-12" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
</div> </div>
</div> </div>
@@ -1,32 +1,31 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="true">
<li class="tight-form-item query-keyword" style="width: 75px">
Query <div class="gf-form-inline">
</li> <div class="gf-form gf-form--grow">
<li> <label class="gf-form-label query-keyword width-7">Query</label>
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()"> <input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck='false' placeholder="Lucene query" ng-blur="ctrl.refresh()">
</li> </div>
<li class="tight-form-item query-keyword"> <div class="gf-form max-width-15">
Alias <label class="gf-form-label query-keyword">Alias</label>
</li> <input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns" ng-blur="ctrl.refresh()">
<li> </div>
<input type="text" class="tight-form-input" style="width: 200px;" ng-model="ctrl.target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="ctrl.refresh()"> </div>
</li>
<div ng-repeat="agg in ctrl.target.metrics">
<elastic-metric-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()"
es-version="ctrl.esVersion">
</elastic-metric-agg>
</div>
<div ng-repeat="agg in ctrl.target.bucketAggs">
<elastic-bucket-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()">
</elastic-bucket-agg>
</div>
</query-editor-row> </query-editor-row>
<div ng-repeat="agg in ctrl.target.metrics">
<elastic-metric-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()"
es-version="ctrl.esVersion">
</elastic-metric-agg>
</div>
<div ng-repeat="agg in ctrl.target.bucketAggs">
<elastic-bucket-agg
target="ctrl.target" index="$index"
get-fields="ctrl.getFields($fieldType)"
on-change="ctrl.queryUpdated()">
</elastic-bucket-agg>
</div>
@@ -153,6 +153,10 @@ function (queryDef) {
this.buildTermsAgg(aggDef, esAgg, target); this.buildTermsAgg(aggDef, esAgg, target);
break; break;
} }
case 'geohash_grid': {
esAgg['geohash_grid'] = {field: aggDef.field, precision: aggDef.settings.precision};
break;
}
} }
nestedAggs.aggs = nestedAggs.aggs || {}; nestedAggs.aggs = nestedAggs.aggs || {};
@@ -5,6 +5,7 @@ import './metric_agg';
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import queryDef from './query_def';
import {QueryCtrl} from 'app/plugins/sdk'; import {QueryCtrl} from 'app/plugins/sdk';
export class ElasticQueryCtrl extends QueryCtrl { export class ElasticQueryCtrl extends QueryCtrl {
@@ -38,6 +39,48 @@ export class ElasticQueryCtrl extends QueryCtrl {
this.$rootScope.appEvent('elastic-query-updated'); 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) { handleQueryError(err) {
this.error = err.message || 'Failed to issue metric query'; this.error = err.message || 'Failed to issue metric query';
return []; return [];
@@ -20,9 +20,10 @@ function (_) {
], ],
bucketAggTypes: [ bucketAggTypes: [
{text: "Terms", value: 'terms' }, {text: "Terms", value: 'terms', requiresField: true},
{text: "Filters", value: 'filters' }, {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: [ orderByOptions: [
@@ -1,5 +1,7 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="false">
<li class="tight-form-item"> <div class="gf-form-inline">
Test metric (fake data source) <div class="gf-form">
</li> <label class="gf-form-label">Test metric (fake data source)</label>
</div>
</div>
</query-editor-row> </query-editor-row>
@@ -11,10 +11,10 @@ function (angular, _, $, gfunc) {
.module('grafana.directives') .module('grafana.directives')
.directive('graphiteAddFunc', function($compile) { .directive('graphiteAddFunc', function($compile) {
var inputTemplate = '<input type="text"'+ var inputTemplate = '<input type="text"'+
' class="tight-form-input input-medium tight-form-input"' + ' class="gf-form-input"' +
' spellcheck="false" style="display:none"></input>'; ' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="tight-form-item tight-form-func dropdown-toggle"' + var buttonTemplate = '<a class="gf-form-label query-part dropdown-toggle"' +
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' + ' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
'<i class="fa fa-plus"></i></a>'; '<i class="fa fa-plus"></i></a>';
@@ -80,6 +80,13 @@ function (_, $) {
category: categories.Calculate, category: categories.Calculate,
}); });
addFuncDef({
name: 'stddevSeries',
params: optionalSeriesRefArgs,
defaultParams: [''],
category: categories.Calculate,
});
addFuncDef({ addFuncDef({
name: 'divideSeries', name: 'divideSeries',
params: optionalSeriesRefArgs, params: optionalSeriesRefArgs,
@@ -1,21 +1,27 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
<li class="tight-form-flex-wrapper" ng-show="ctrl.target.textEditor"> <div class="gf-form" ng-show="ctrl.target.textEditor">
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="ctrl.target.target" give-focus="ctrl.target.textEditor" spellcheck='false' ng-model-onblur ng-change="ctrl.targetTextChanged()"></input> <input type="text" class="gf-form-input" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.refresh()"></input>
</li> </div>
<li ng-hide-start="ctrl.target.textEditor"></li> <div ng-hide="ctrl.target.textEditor">
<div class="gf-form-inline">
<div ng-repeat="segment in ctrl.segments" role="menuitem" class="gf-form">
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
</div>
<li ng-repeat="segment in ctrl.segments" role="menuitem"> <div ng-repeat="func in ctrl.functions" class="gf-form">
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment> <span graphite-func-editor class="gf-form-label query-part"></span>
</li> </div>
<li ng-repeat="func in ctrl.functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>
</li>
<li class="dropdown" graphite-add-func>
</li>
<li ng-hide-end></li> <div class="gf-form dropdown">
<span graphite-add-func></span>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
</query-editor-row> </query-editor-row>
@@ -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. 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: Read more about InfluxDB here:
[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/) [http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
@@ -152,7 +152,9 @@ export default class InfluxQuery {
if (interpolate) { if (interpolate) {
value = this.templateSrv.replace(value, this.scopedVars); value = this.templateSrv.replace(value, this.scopedVars);
} }
value = "'" + value.replace('\\', '\\\\') + "'"; if (isNaN(+value)) {
value = "'" + value.replace('\\', '\\\\') + "'";
}
} else if (interpolate){ } else if (interpolate){
value = this.templateSrv.replace(value, this.scopedVars, 'regex'); value = this.templateSrv.replace(value, this.scopedVars, 'regex');
} }
@@ -160,12 +162,14 @@ export default class InfluxQuery {
return str + '"' + tag.key + '" ' + operator + ' ' + value; return str + '"' + tag.key + '" ' + operator + ' ' + value;
} }
getMeasurementAndPolicy() { getMeasurementAndPolicy(interpolate) {
var policy = this.target.policy; var policy = this.target.policy;
var measurement = this.target.measurement; var measurement = this.target.measurement || 'measurement';
if (!measurement.match('^/.*/')) { if (!measurement.match('^/.*/')) {
measurement = '"' + measurement+ '"'; measurement = '"' + measurement+ '"';
} else if (interpolate) {
measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
} }
if (policy !== 'default') { if (policy !== 'default') {
@@ -188,10 +192,6 @@ export default class InfluxQuery {
} }
} }
if (!target.measurement) {
throw {message: "Metric measurement is missing"};
}
var query = 'SELECT '; var query = 'SELECT ';
var i, y; var i, y;
for (i = 0; i < this.selectModels.length; i++) { for (i = 0; i < this.selectModels.length; i++) {
@@ -208,7 +208,7 @@ export default class InfluxQuery {
query += selectText; query += selectText;
} }
query += ' FROM ' + this.getMeasurementAndPolicy() + ' WHERE '; query += ' FROM ' + this.getMeasurementAndPolicy(interpolate) + ' WHERE ';
var conditions = _.map(target.tags, (tag, index) => { var conditions = _.map(target.tags, (tag, index) => {
return this.renderTagCondition(tag, index, interpolate); return this.renderTagCondition(tag, index, interpolate);
}); });
@@ -1,73 +1,96 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<ul class="tight-form-list" ng-hide="ctrl.target.rawQuery">
<li class="tight-form-item query-keyword" style="width: 75px"> <div class="gf-form" ng-if="ctrl.target.rawQuery">
FROM <input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" ng-blur="ctrl.refresh()"></input>
</li> </div>
<li>
<div ng-if="!ctrl.target.rawQuery">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">FROM</label>
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment> <metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
</li>
<li>
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements()" on-change="ctrl.measurementChanged()"></metric-segment> <metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements()" on-change="ctrl.measurementChanged()"></metric-segment>
</li> </div>
<li class="tight-form-item query-keyword" style="padding-left: 15px; padding-right: 15px;">
WHERE <div class="gf-form">
</li> <label class="gf-form-label query-keyword">WHERE</label>
<li ng-repeat="segment in ctrl.tagSegments"> </div>
<div class="gf-form" ng-repeat="segment in ctrl.tagSegments">
<metric-segment segment="segment" get-options="ctrl.getTagsOrValues(segment, $index)" on-change="ctrl.tagSegmentUpdated(segment, $index)"></metric-segment> <metric-segment segment="segment" get-options="ctrl.getTagsOrValues(segment, $index)" on-change="ctrl.tagSegmentUpdated(segment, $index)"></metric-segment>
</li> </div>
</ul>
<div class="tight-form-flex-wrapper" ng-show="ctrl.target.rawQuery"> <div class="gf-form gf-form--grow">
<input type="text" class="tight-form-clear-input" ng-model="ctrl.target.query" spellcheck="false" style="width: 100%;" ng-blur="ctrl.refresh()"></input> <div class="gf-form-label gf-form-label--grow"></div>
</div>
</div> </div>
<div class="gf-form-inline" ng-repeat="selectParts in ctrl.queryModel.selectModels">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
<span ng-show="$index === 0">SELECT</span>
</label>
</div>
<div class="gf-form" ng-repeat="part in selectParts">
<influx-query-part-editor
class="gf-form-label query-part"
part="part"
remove-action="ctrl.removeSelectPart(selectParts, part)"
part-updated="ctrl.selectPartUpdated(selectParts, part)"
get-options="ctrl.getPartOptions(part)">
</influx-query-part-editor>
</div>
<div class="gf-form">
<label class="dropdown"
dropdown-typeahead="ctrl.selectMenu"
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">
<span>GROUP BY</span>
</label>
<influx-query-part-editor
ng-repeat="part in ctrl.queryModel.groupByParts"
part="part"
class="gf-form-label query-part"
remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)">
</influx-query-part-editor>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<label class="gf-form-label query-keyword width-7">ALIAS BY</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
</div>
<div class="gf-form">
<label class="gf-form-label">Format as</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row> </query-editor-row>
<div ng-hide="ctrl.target.rawQuery">
<div class="tight-form" ng-repeat="selectParts in ctrl.queryModel.selectModels">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<span ng-show="$index === 0">SELECT</span>
</li>
<li ng-repeat="part in selectParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeSelectPart(selectParts, part)" part-updated="ctrl.selectPartUpdated(selectParts, part)" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
</li>
<li class="dropdown" dropdown-typeahead="ctrl.selectMenu" dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<span>GROUP BY</span>
</li>
<li ng-repeat="part in ctrl.queryModel.groupByParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)"></influx-query-part-editor>
</li>
<li>
<metric-segment segment="ctrl.groupBySegment" get-options="ctrl.getGroupByOptions()" on-change="ctrl.groupByAction(part, $index)"></metric-segment>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
ALIAS BY
</li>
<li>
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
</li>
<li class="tight-form-item">
Format as
</li>
<li>
<select class="input-small tight-form-input" style="width: 104px" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
@@ -38,7 +38,7 @@
</section> </section>
<div class="editor-row"> <div class="editor-row">
<div class="pull-left" style="margin-top: 30px;"> <div class="pull-left">
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
<h5>Alias patterns</h5> <h5>Alias patterns</h5>
@@ -2,4 +2,4 @@
<span class="pointer fa fa-remove" ng-click="removeActionInternal()" ></span> <span class="pointer fa fa-remove" ng-click="removeActionInternal()" ></span>
</div> </div>
<a ng-click="toggleControls()">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span> <a ng-click="toggleControls()" class="query-part-name">{{part.def.type}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>
@@ -25,8 +25,8 @@ function (_) {
} }
} }
// quote value unless regex // quote value unless regex or number
if (operator !== '=~' && operator !== '!~') { if (operator !== '=~' && operator !== '!~' && isNaN(+value)) {
value = "'" + value + "'"; value = "'" + value + "'";
} }
@@ -23,6 +23,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
measurementSegment: any; measurementSegment: any;
removeTagFilterSegment: any; removeTagFilterSegment: any;
/** @ngInject **/ /** @ngInject **/
constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) { constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
super($scope, $injector); super($scope, $injector);
@@ -154,7 +155,11 @@ export class InfluxQueryCtrl extends QueryCtrl {
} }
toggleEditorMode() { 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; this.target.rawQuery = !this.target.rawQuery;
} }
@@ -316,5 +321,9 @@ export class InfluxQueryCtrl extends QueryCtrl {
return '='; return '=';
} }
} }
getCollapsedText() {
return this.queryModel.render(false);
}
} }
@@ -12,17 +12,29 @@ export default class ResponseParser {
return []; return [];
} }
var series = influxResults.series[0]; var influxdb11format = query.toLowerCase().indexOf('show tag values') >= 0;
return _.map(series.values, (value) => {
if (_.isArray(value)) { var res = {};
if (query.toLowerCase().indexOf('show tag values') >= 0) { _.each(influxResults.series, serie => {
return { text: (value[1] || value[0]) }; _.each(serie.values, value => {
if (_.isArray(value)) {
if (influxdb11format) {
addUnique(res, value[1] || value[0]);
} else {
addUnique(res, value[0]);
}
} else { } 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;
}
@@ -38,7 +38,7 @@ describe("influxdb response parser", () => {
{ {
"name": "hostnameTagValues", "name": "hostnameTagValues",
"columns": ["hostname"], "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 = { var response = {
"results": [ "results": [
{ {
@@ -62,8 +62,19 @@ describe("influxdb response parser", () => {
{ {
"name": "cpu", "name": "cpu",
"columns": [ "key", "value"], "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); var result = this.parser.parse(query, response);
it("should get two responses", () => { 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[0].text).to.be('site');
expect(result[1].text).to.be('api'); expect(result[1].text).to.be('api');
expect(result[2].text).to.be('webapi');
}); });
}); });
}); });
describe("SHOW FIELD response", () => { describe("SHOW FIELD response", () => {
@@ -1,223 +1,253 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="false">
<li class="tight-form-item query-keyword" style="width: 100px"> <div class="gf-form-inline">
Metric <div class="gf-form max-width-25">
</li> <label class="gf-form-label query-keyword width-8">
<li> Metric
<input type="text" class="input-large tight-form-input" ng-model="ctrl.target.metric" <label class="gf-form-label" bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric">
spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100 <i class="fa fa-warning"></i>
ng-blur="ctrl.targetBlur()"> </label>
</input> </label>
<a bs-tooltip="ctrl.errors.metric" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.metric"> <input type="text" class="gf-form-input" ng-model="ctrl.target.metric"
<i class="fa fa-warning"></i> spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100
</a> ng-blur="ctrl.targetBlur()">
</li> </input>
<li class="tight-form-item query-keyword"> </div>
Aggregator <div class="gf-form">
</li> <label class="gf-form-label query-keyword">
<li> Aggregator
<select ng-model="ctrl.target.aggregator" class="tight-form-input input-small" <a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator">
ng-options="agg for agg in ctrl.aggregators" <i class="fa fa-warning"></i>
ng-change="ctrl.targetBlur()"> </a>
</select> </label>
<a bs-tooltip="ctrl.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="ctrl.errors.aggregator"> <div class="gf-form-select-wrapper max-width-15">
<i class="fa fa-warning"></i> <select ng-model="ctrl.target.aggregator" class="gf-form-input"
</a> ng-options="agg for agg in ctrl.aggregators"
</li> ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-6">
Alias:
<info-popover mode="right-normal">
Use patterns like $tag_tagname to replace part of the alias for a tag value
</info-popover>
</label>
<input type="text" class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</div>
<li class="tight-form-item query-keyword"> <div class="gf-form gf-form--grow">
Alias: <div class="gf-form-label gf-form-label--grow"></div>
<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip> </div>
</li> </div>
<li>
<input type="text" class="tight-form-input input-large"
ng-model="ctrl.target.alias"
spellcheck='false'
placeholder="series alias"
data-min-length=0 data-items=100
ng-blur="ctrl.targetBlur()"></input>
</li>
</query-editor-row>
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list" role="menu"> <div class="gf-form max-width-25">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px"> <label class="gf-form-label query-keyword width-8">Down sample</label>
Down sample <input type="text" class="gf-form-input"
</li> ng-model="ctrl.target.downsampleInterval"
ng-model-onblur
ng-change="ctrl.targetBlur()"
placeholder="interval"></input>
<info-popover mode="right-absolute">
blank for auto, or for example <code>1m</code>
</info-popover>
</div>
<li> <div class="gf-form">
<input type="text" class="input-large tight-form-input" <label class="gf-form-label query-keyword">Aggregator</label>
ng-model="ctrl.target.downsampleInterval" <div class="gf-form-select-wrapper">
ng-model-onblur <select ng-model="ctrl.target.downsampleAggregator" class="gf-form-input"
ng-change="ctrl.targetBlur()" ng-options="agg for agg in ctrl.aggregators"
placeholder="interval (empty = auto)"></input> ng-change="ctrl.targetBlur()">
</li> </select>
</div>
</div>
<li class="tight-form-item query-keyword"> <div class="gf-form" ng-if="ctrl.tsdbVersion == 2">
Aggregator <label class="gf-form-label query-keyword width-6">Fill</label>
</li> <div class="gf-form-select-wrapper">
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</div>
</div>
<li> <gf-form-switch class="gf-form"
<select ng-model="ctrl.target.downsampleAggregator" class="tight-form-input input-small" label="Disable downsampling"
ng-options="agg for agg in ctrl.aggregators" checked="ctrl.target.disableDownsampling"
ng-change="ctrl.targetBlur()"> on-change="ctrl.targetBlur()">
</select> </gf-form-switch>
</li>
<li class="tight-form-item query-keyword" style="width: 59px" ng-if="ctrl.tsdbVersion == 2"> <div class="gf-form gf-form--grow">
Fill <div class="gf-form-label gf-form-label--grow"></div>
</li> </div>
</div>
<li ng-if="ctrl.tsdbVersion == 2"> <div class="gf-form-inline" ng-if="ctrl.tsdbVersion == 2">
<select ng-model="ctrl.target.downsampleFillPolicy" class="tight-form-input input-small" <div class="gf-form">
ng-options="agg for agg in ctrl.fillPolicies"
ng-change="ctrl.targetBlur()">
</select>
</li>
<li class="tight-form-item query-keyword"> <label class="gf-form-label query-keyword width-8">
Disable downsampling <editor-checkbox text="" model="ctrl.target.disableDownsampling" change="ctrl.targetBlur()"></editor-checkbox> Filters
</li> <info-popover mode="right-normal">
Filters does not work with tags, either of the two will work but not both.
</info-popover>
</label>
</ul> <div ng-repeat="fil in ctrl.target.filters track by $index" class="gf-form-label">
<div class="clearfix"></div> {{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
</div> <a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</div>
<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</label>
</div>
<div class="tight-form" ng-if="ctrl.tsdbVersion == 2"> <div class="gf-form-inline" ng-show="ctrl.addFilterMode">
<ul class="tight-form-list" role="menu"> <div class="gf-form">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px"> <input type="text" class="gf-form-input" spellcheck='false'
Filters bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
<tip ng-if="ctrl.tsdbVersion == 2">Filters does not work with tags, either of the two will work but not both.</tip> ng-model="ctrl.target.currentFilterKey" placeholder="key">
</li> </input>
<li ng-repeat="fil in ctrl.target.filters track by $index" class="tight-form-item"> </div>
{{fil.tagk}}&nbsp;=&nbsp;{{fil.type}}&#40;{{fil.filter}}&#41;&nbsp;&#44&nbsp;groupBy&nbsp;=&nbsp;{{fil.groupBy}}
<a ng-click="ctrl.editFilter(fil, $index)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item query-keyword" ng-hide="ctrl.addFilterMode">
<a ng-click="ctrl.addFilter()">
<i class="fa fa-plus"></i>
</a>
</li>
<li class="query-keyword" ng-show="ctrl.addFilterMode"> <div class="gf-form">
<input type="text" class="input-small tight-form-input" spellcheck='false' <label class="gf-form-label">Type</label>
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100 <div class="gf-form-select-wrapper">
ng-model="ctrl.target.currentFilterKey" placeholder="key"></input> <select ng-model="ctrl.target.currentFilterType" class="gf-form-input" ng-options="filType for filType in ctrl.filterTypes">
</select>
</div>
</div>
Type <select ng-model="ctrl.target.currentFilterType" <div class="gf-form">
class="tight-form-input input-small" <input type="text" class="gf-form-input" spellcheck='false' bs-typeahead="ctrl.suggestTagValues" data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
ng-options="filType for filType in ctrl.filterTypes"> </input>
</select> </div>
<input type="text" class="input-small tight-form-input"
spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
data-min-length=0 data-items=100 ng-model="ctrl.target.currentFilterValue" placeholder="filter">
</input>
groupBy <editor-checkbox text="" model="ctrl.target.currentFilterGroupBy"></editor-checkbox> <gf-form-switch class="gf-form" label="Group by" checked="ctrl.target.currentFilterGroupBy" on-change="ctrl.targetBlur()">
</gf-form-switch>
<a bs-tooltip="ctrl.errors.filters" <div class="gf-form" ng-show="ctrl.addFilterMode">
style="color: rgb(229, 189, 28)" <label class="gf-form-label" ng-show="ctrl.errors.filters">
ng-show="ctrl.errors.filters"> <a bs-tooltip="ctrl.errors.filters" style="color: rgb(229, 189, 28)" >
<i class="fa fa-warning"></i> <i class="fa fa-warning"></i>
</a> </a>
</label>
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">
add filter
</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
</a>
</li> <label class="gf-form-label">
</ul> <a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
<div class="clearfix"></div> <a ng-click="ctrl.closeAddFilterMode()">
</div> <i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="tight-form"> </div>
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px">
Tags
<tip ng-if="ctrl.tsdbVersion == 2">Please use filters, tags are deprecated in opentsdb 2.2</tip>
</li>
<li ng-repeat="(key, value) in ctrl.target.tags track by $index" class="tight-form-item">
{{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</li>
<li class="tight-form-item query-keyword" ng-hide="ctrl.addTagMode"> <div class="gf-form gf-form--grow">
<a ng-click="ctrl.addTag()"> <div class="gf-form-label gf-form-label--grow"></div>
<i class="fa fa-plus"></i> </div>
</a> </div>
</li>
<li ng-show="ctrl.addTagMode"> <div class="gf-form-inline">
<input type="text" class="input-small tight-form-input" spellcheck='false' <div class="gf-form">
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100 <label class="gf-form-label query-keyword width-8">
ng-model="ctrl.target.currentTagKey" placeholder="key"></input> Tags
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion == 2">
Please use filters, tags are deprecated in opentsdb 2.2
</info-popover>
</label>
</div>
<input type="text" class="input-small tight-form-input" <div class="gf-form" ng-repeat="(key, value) in ctrl.target.tags track by $index" class="gf-form">
spellcheck='false' bs-typeahead="ctrl.suggestTagValues" <label class="gf-form-label">
data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value"> {{key}}&nbsp;=&nbsp;{{value}}
<a ng-click="ctrl.editTag(key, value)">
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
</a>
</label>
</div>
<div class="gf-form" ng-hide="ctrl.addTagMode">
<label class="gf-form-label query-keyword">
<a ng-click="ctrl.addTag()"><i class="fa fa-plus"></i></a>
</label>
</div>
<div class="gf-form" ng-show="ctrl.addTagMode">
<input type="text"
class="gf-form-input" spellcheck='false'
bs-typeahead="ctrl.suggestTagKeys" data-min-length=0 data-items=100
ng-model="ctrl.target.currentTagKey" placeholder="key">
</input> </input>
<a bs-tooltip="ctrl.errors.tags" <input type="text" class="gf-form-input"
style="color: rgb(229, 189, 28)" spellcheck='false' bs-typeahead="ctrl.suggestTagValues"
ng-show="ctrl.errors.tags"> data-min-length=0 data-items=100 ng-model="ctrl.target.currentTagValue" placeholder="value">
<i class="fa fa-warning"></i> </input>
</a>
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags"> <label class="gf-form-label" ng-show="ctrl.errors.tags">
add tag <a bs-tooltip="ctrl.errors.tags" style="color: rgb(229, 189, 28)" >
</a> <i class="fa fa-warning"></i>
<a ng-click="ctrl.closeAddTagMode()"> </a>
<i class="fa fa-remove"></i> </label>
</a> <label class="gf-form-label" >
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
</li> <a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
</ul> </label>
<div class="clearfix"></div> </div>
</div>
<div class="tight-form"> <div class="gf-form gf-form--grow">
<ul class="tight-form-list" role="menu"> <div class="gf-form-label gf-form-label--grow"></div>
<li class="tight-form-item tight-form-align query-keyword" style="width: 100px"> </div>
Rate <editor-checkbox text="" model="ctrl.target.shouldComputeRate" change="ctrl.targetBlur()"></editor-checkbox> </div>
</li>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.shouldComputeRate"> <div class="gf-form-inline">
Counter <editor-checkbox text="" model="ctrl.target.isCounter" change="ctrl.targetBlur()"></editor-checkbox> <gf-form-switch class="gf-form" label="Rate" label-class="width-8 query-keyword" checked="ctrl.target.shouldComputeRate" on-change="ctrl.targetBlur()">
</li> </gf-form-switch>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate"> <gf-form-switch ng-hide="!ctrl.target.shouldComputeRate"
Counter Max: class="gf-form" label="Counter" checked="ctrl.target.isCounter" on-change="ctrl.targetBlur()">
</li> </gf-form-switch>
<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()"></input>
</li>
<li class="tight-form-item query-keyword" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
Reset Value:
</li>
<li ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
<input type="text" class="tight-form-input input-small" ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()"></input>
</li>
</ul>
<div class="clearfix"></div> <div class="gf-form" ng-hide="!ctrl.target.isCounter || !ctrl.target.shouldComputeRate">
</div> <label class="gf-form-label">Counter Max</label>
<input type="text" class="gf-form-input"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterMax" spellcheck='false'
placeholder="max value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
<label class="gf-form-label">Reset Value</label>
<input type="text" class="tight-form-input input-small"
ng-disabled="!ctrl.target.shouldComputeRate"
ng-model="ctrl.target.counterResetValue" spellcheck='false'
placeholder="reset value" ng-model-onblur
ng-blur="ctrl.targetBlur()">
</input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
@@ -1,80 +1,54 @@
<query-editor-row ctrl="ctrl"> <query-editor-row query-ctrl="ctrl" can-collapse="false">
<li class="tight-form-item" style="width: 94px"> <div class="gf-form-inline">
Query <div class="gf-form gf-form--grow">
</li> <label class="gf-form-label width-8">Query</label>
<li> <input type="text" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck='false' placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()">
<input type="text" </div>
class="input-xxlarge tight-form-input" <div class="gf-form max-width-22">
ng-model="ctrl.target.expr" <label class="gf-form-label">Metric lookup</label>
spellcheck='false' <input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
placeholder="query expression" </div>
data-min-length=0 data-items=100 </div>
ng-model-onblur
ng-change="ctrl.refreshMetricData()">
</li>
<li class="tight-form-item">
Metric
</li>
<li>
<input type="text"
class="input-medium tight-form-input"
ng-model="ctrl.target.metric"
spellcheck='false'
bs-typeahead="ctrl.suggestMetrics"
placeholder="metric name"
data-min-length=0 data-items=100>
</li>
</query-editor-row>
<div class="tight-form"> <div class="gf-form-inline">
<ul class="tight-form-list" role="menu"> <div class="gf-form max-width-26">
<li class="tight-form-item tight-form-align" style="width: 94px"> <label class="gf-form-label width-8">Legend format</label>
Legend format <input type="text" class="gf-form-input" ng-model="ctrl.target.legendFormat"
</li>
<li>
<input type="text" class="tight-form-input input-xxlarge" ng-model="ctrl.target.legendFormat"
spellcheck='false' placeholder="legend format" data-min-length=0 data-items=1000 spellcheck='false' placeholder="legend format" data-min-length=0 data-items=1000
ng-model-onblur ng-change="ctrl.refreshMetricData()"> ng-model-onblur ng-change="ctrl.refreshMetricData()">
</input> </input>
</li> </div>
</ul> <div class="gf-form">
<label class="gf-form-label width-5">Step</label>
<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
data-placement="right"
spellcheck='false'
placeholder="{{ctrl.panelCtrl.interval}}"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="ctrl.refreshMetricData()"/>
<info-popover mode="right-absolute">
Leave blank for auto handling based on time range and panel width
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label">Resolution</label>
<div class="gf-form-select-wrapper max-width-15">
<select ng-model="ctrl.target.intervalFactor" class="gf-form-input"
ng-options="r.factor as r.label for r in ctrl.resolutions"
ng-change="ctrl.refreshMetricData()">
</select>
</div>
<label class="gf-form-label">
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
<i class="fa fa-share-square-o"></i>
</a>
</label>
</div>
<div class="clearfix"></div> <div class="gf-form gf-form--grow">
</div> <div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="tight-form"> </query-editor-row>
<ul class="tight-form-list" role="menu">
<li class="tight-form-item tight-form-align" style="width: 94px">
Step
</li>
<li>
<input type="text" class="input-mini tight-form-input" ng-model="ctrl.target.interval"
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
data-placement="right"
spellcheck='false'
placeholder="{{ctrl.panelCtrl.interval}}"
data-min-length=0 data-items=100
ng-model-onblur
ng-change="ctrl.refreshMetricData()"
/>
</input>
</li>
<li class="tight-form-item">
Resolution
</li>
<li>
<select ng-model="ctrl.target.intervalFactor" class="tight-form-input input-mini"
ng-options="r.factor as r.label for r in ctrl.resolutions"
ng-change="ctrl.refreshMetricData()">
</select>
</li>
<li class="tight-form-item">
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
<i class="fa fa-share-square-o"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
+11 -12
View File
@@ -5,27 +5,26 @@ import config from 'app/core/config';
import {PanelCtrl} from 'app/plugins/sdk'; import {PanelCtrl} from 'app/plugins/sdk';
import {impressions} from 'app/features/dashboard/impression_store'; 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 { class DashListCtrl extends PanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
groups: any[]; groups: any[];
modes: any[]; modes: any[];
panelDefaults = {
query: '',
limit: 10,
tags: [],
recent: false,
search: false,
starred: true,
headings: true,
};
/** @ngInject */ /** @ngInject */
constructor($scope, $injector, private backendSrv) { constructor($scope, $injector, private backendSrv) {
super($scope, $injector); super($scope, $injector);
_.defaults(this.panel, panelDefaults); _.defaults(this.panel, this.panelDefaults);
if (this.panel.tag) { if (this.panel.tag) {
this.panel.tags = [this.panel.tag]; this.panel.tags = [this.panel.tag];
+1 -1
View File
@@ -73,7 +73,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
var legendSeries = _.filter(data, function(series) { var legendSeries = _.filter(data, function(series) {
return series.hideFromLegend(panel.legend) === false; 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)); return Math.min(total, Math.floor(panelHeight/2));
} else { } else {
return 26; return 26;
+83 -83
View File
@@ -13,85 +13,6 @@ import TimeSeries from 'app/core/time_series2';
import * as fileExport from 'app/core/utils/file_export'; import * as fileExport from 'app/core/utils/file_export';
import {MetricsPanelCtrl} from 'app/plugins/sdk'; 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 { class GraphCtrl extends MetricsPanelCtrl {
static template = template; static template = template;
@@ -105,14 +26,93 @@ class GraphCtrl extends MetricsPanelCtrl {
datapointsWarning: boolean; datapointsWarning: boolean;
colors: any = []; 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 */ /** @ngInject */
constructor($scope, $injector, private annotationsSrv) { constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector); super($scope, $injector);
_.defaults(this.panel, angular.copy(panelDefaults)); _.defaults(this.panel, this.panelDefaults);
_.defaults(this.panel.tooltip, panelDefaults.tooltip); _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
_.defaults(this.panel.grid, panelDefaults.grid); _.defaults(this.panel.grid, this.panelDefaults.grid);
_.defaults(this.panel.legend, panelDefaults.legend); _.defaults(this.panel.legend, this.panelDefaults.legend);
this.colors = $scope.$root.colors; this.colors = $scope.$root.colors;
@@ -22,7 +22,7 @@
</a> </a>
</div> </div>
<div class="pluginlist-item" ng-show="category.list.length === 0"> <div class="pluginlist-item" ng-show="category.list.length === 0">
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana.net/plugins/"> <a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="https://grafana.net/plugins">
<span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span> <span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span>
</a> </a>
</div> </div>
@@ -4,20 +4,20 @@ import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import {PanelCtrl} from '../../../features/panel/panel_ctrl'; import {PanelCtrl} from '../../../features/panel/panel_ctrl';
// Set and populate defaults
var panelDefaults = {
};
class PluginListCtrl extends PanelCtrl { class PluginListCtrl extends PanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
pluginList: any[]; pluginList: any[];
viewModel: any; viewModel: any;
// Set and populate defaults
panelDefaults = {
};
/** @ngInject */ /** @ngInject */
constructor($scope, $injector, private backendSrv, private $location) { constructor($scope, $injector, private backendSrv, private $location) {
super($scope, $injector); 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('init-edit-mode', this.onInitEditMode.bind(this));
this.pluginList = []; this.pluginList = [];
@@ -156,6 +156,55 @@
</div> </div>
</div> </div>
<div class="editor-row">
<div class="section" style="margin-bottom: 20px">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 80px">
<strong>Gauge</strong>
</li>
<li class="tight-form-item">
Show&nbsp;
<input class="cr1" id="panel.gauge.show" type="checkbox"
ng-model="ctrl.panel.gauge.show" ng-checked="ctrl.panel.gauge.show" ng-change="ctrl.render()">
<label for="panel.gauge.show" class="cr1"></label>
</li>
<li class="tight-form-item">
Min
</li>
<li>
<input type="number" class="input-small tight-form-input" ng-model="ctrl.panel.gauge.minValue" ng-blur="ctrl.render()" placeholder="0"></input>
</li>
<li class="tight-form-item last">
Max
</li>
<li>
<input type="number" class="input-small tight-form-input last" ng-model="ctrl.panel.gauge.maxValue" ng-blur="ctrl.render()" placeholder="100"></input>
<span class="alert-state-critical" ng-show="ctrl.invalidGaugeRange">
&nbsp;
<i class="fa fa-warning"></i>
Min value is bigger than max.
</span>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form last">
<li class="tight-form-item">
Threshold labels&nbsp;
<input class="cr1" id="panel.gauge.thresholdLabels" type="checkbox" ng-model="ctrl.panel.gauge.thresholdLabels" ng-checked="ctrl.panel.gauge.thresholdLabels" ng-change="ctrl.render()">
<label for="panel.gauge.thresholdLabels" class="cr1"></label>
</li>
<li class="tight-form-item">
Threshold markers&nbsp;
<input class="cr1" id="panel.gauge.thresholdMarkers" type="checkbox" ng-model="ctrl.panel.gauge.thresholdMarkers" ng-checked="ctrl.panel.gauge.thresholdMarkers" ng-change="ctrl.render()">
<label for="panel.gauge.thresholdMarkers" class="cr1"></label>
</li>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="editor-row"> <div class="editor-row">
<div class="section" style="margin-bottom: 20px"> <div class="section" style="margin-bottom: 20px">
<div class="tight-form last"> <div class="tight-form last">
+152 -35
View File
@@ -4,43 +4,13 @@ import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import 'jquery.flot'; import 'jquery.flot';
import 'jquery.flot.gauge';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import {MetricsPanelCtrl} from 'app/plugins/sdk'; 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 { class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
@@ -48,11 +18,51 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data: any; data: any;
fontSizes: any[]; fontSizes: any[];
unitFormats: 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 */ /** @ngInject */
constructor($scope, $injector, private $location, private linkSrv) { constructor($scope, $injector, private $location, private linkSrv) {
super($scope, $injector); 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-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this)); this.events.on('data-error', this.onDataError.bind(this));
@@ -270,6 +280,109 @@ class SingleStatCtrl extends MetricsPanelCtrl {
return body; return body;
} }
function getValueText() {
var result = panel.prefix ? panel.prefix : '';
result += data.valueFormated;
result += panel.postfix ? panel.postfix : '';
return result;
}
function addGauge() {
ctrl.invalidGaugeRange = false;
if (panel.gauge.minValue > panel.gauge.maxValue) {
ctrl.invalidGaugeRange = true;
return;
}
var plotCanvas = $('<div></div>');
var width = elem.width();
var height = elem.height();
var plotCss = {
top: '10px',
margin: 'auto',
position: 'relative',
height: (height * 0.9) + 'px',
width: width + 'px'
};
plotCanvas.css(plotCss);
var thresholds = [];
for (var i = 0; i < data.thresholds.length; i++) {
thresholds.push({
value: data.thresholds[i],
color: data.colorMap[i]
});
}
thresholds.push({
value: panel.gauge.maxValue,
color: data.colorMap[data.colorMap.length - 1]
});
var bgColor = config.bootData.user.lightTheme
? 'rgb(230,230,230)'
: 'rgb(38,38,38)';
var dimension = Math.min(width, height);
var fontSize = Math.min(dimension/4, 100);
var gaugeWidth = Math.min(dimension/6, 60);
var thresholdMarkersWidth = gaugeWidth/5;
var options = {
series: {
gauges: {
gauge: {
min: panel.gauge.minValue,
max: panel.gauge.maxValue,
background: { color: bgColor },
border: { color: null },
shadow: { show: false },
width: gaugeWidth,
},
frame: { show: false },
label: { show: false },
layout: { margin: 0, thresholdWidth: 0 },
cell: { border: { width: 0 } },
threshold: {
values: thresholds,
label: {
show: panel.gauge.thresholdLabels,
margin: 8,
font: { size: 18 }
},
show: panel.gauge.thresholdMarkers,
width: thresholdMarkersWidth,
},
value: {
color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null,
formatter: function() { return getValueText(); },
font: { size: fontSize, family: 'Helvetica Neue", Helvetica, Arial, sans-serif' }
},
show: true
}
}
};
elem.append(plotCanvas);
var plotSeries = {
data: [[0, data.valueRounded]]
};
$.plot(plotCanvas, [plotSeries], options);
}
function getGaugeFontSize() {
if (panel.valueFontSize) {
var num = parseInt(panel.valueFontSize.substring(0, panel.valueFontSize.length - 1));
return (30 * (num / 100)) + 15;
} else {
return 30;
}
}
function addSparkline() { function addSparkline() {
var width = elem.width() + 20; var width = elem.width() + 20;
if (width < 30) { if (width < 30) {
@@ -331,11 +444,11 @@ class SingleStatCtrl extends MetricsPanelCtrl {
function render() { function render() {
if (!ctrl.data) { return; } if (!ctrl.data) { return; }
ctrl.setValues(ctrl.data);
data = ctrl.data; data = ctrl.data;
setElementHeight(); setElementHeight();
var body = getBigValueHtml(); var body = panel.gauge.show ? '' : getBigValueHtml();
if (panel.colorBackground && !isNaN(data.valueRounded)) { if (panel.colorBackground && !isNaN(data.valueRounded)) {
var color = getColorForValue(data, data.valueRounded); var color = getColorForValue(data, data.valueRounded);
@@ -358,6 +471,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
addSparkline(); addSparkline();
} }
if (panel.gauge.show) {
addGauge();
}
elem.toggleClass('pointer', panel.links.length > 0); elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) { if (panel.links.length > 0) {
+33 -28
View File
@@ -10,33 +10,6 @@ import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor'; import {tablePanelEditor} from './editor';
import {TableRenderer} from './renderer'; 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 { class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
@@ -44,6 +17,33 @@ class TablePanelCtrl extends MetricsPanelCtrl {
dataRaw: any; dataRaw: any;
table: 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 */ /** @ngInject */
constructor($scope, $injector, private annotationsSrv) { constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector); super($scope, $injector);
@@ -56,7 +56,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
delete this.panel.fields; 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-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this)); this.events.on('data-error', this.onDataError.bind(this));
@@ -120,6 +120,11 @@ class TablePanelCtrl extends MetricsPanelCtrl {
} }
toggleColumnSort(col, colIndex) { 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.col === colIndex) {
if (this.panel.sort.desc) { if (this.panel.sort.desc) {
this.panel.sort.desc = false; this.panel.sort.desc = false;
+1 -1
View File
@@ -30,7 +30,7 @@ export class TableRenderer {
} }
if (_.isArray(v)) { if (_.isArray(v)) {
v = v.join(',&nbsp;'); v = v.join(', ');
} }
return v; return v;
+6 -8
View File
@@ -3,23 +3,21 @@
import _ from 'lodash'; import _ from 'lodash';
import {PanelCtrl} from 'app/plugins/sdk'; import {PanelCtrl} from 'app/plugins/sdk';
// Set and populate defaults
var panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
export class TextPanelCtrl extends PanelCtrl { export class TextPanelCtrl extends PanelCtrl {
static templateUrl = `public/app/plugins/panel/text/module.html`; static templateUrl = `public/app/plugins/panel/text/module.html`;
remarkable: any; remarkable: any;
content: string; content: string;
// Set and populate defaults
panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
/** @ngInject */ /** @ngInject */
constructor($scope, $injector, private templateSrv, private $sce) { constructor($scope, $injector, private templateSrv, private $sce) {
super($scope, $injector); 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('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('refresh', this.onRender.bind(this)); this.events.on('refresh', this.onRender.bind(this));
+2 -1
View File
@@ -27,7 +27,8 @@ System.config({
"jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent", "jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",
"jquery.flot.time": "vendor/flot/jquery.flot.time", "jquery.flot.time": "vendor/flot/jquery.flot.time",
"jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair", "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: { packages: {
+1
View File
@@ -70,6 +70,7 @@
@import "components/drop"; @import "components/drop";
@import "components/query_editor"; @import "components/query_editor";
@import "components/tabbed_view"; @import "components/tabbed_view";
@import "components/query_part";
// PAGES // PAGES
@import "pages/login"; @import "pages/login";
+3 -3
View File
@@ -67,8 +67,8 @@ $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98%
// Links // Links
// ------------------------- // -------------------------
$link-color: darken($white,11%); $link-color: darken($white, 11%);
$link-color-disabled: darken($link-color,30%); $link-color-disabled: darken($link-color, 30%);
$link-hover-color: $white; $link-hover-color: $white;
$external-link-color: $blue; $external-link-color: $blue;
@@ -76,7 +76,7 @@ $external-link-color: $blue;
// ------------------------- // -------------------------
$headings-color: darken($white,11%); $headings-color: darken($white,11%);
$abbr-border-color: $gray-3 !default; $abbr-border-color: $gray-3 !default;
$text-muted: darken($link-color,30%); $text-muted: $text-color-weak;
$blockquote-small-color: $gray-3 !default; $blockquote-small-color: $gray-3 !default;
$blockquote-border-color: $gray-4 !default; $blockquote-border-color: $gray-4 !default;
+1 -1
View File
@@ -82,7 +82,7 @@ $external-link-color: $blue;
// ------------------------- // -------------------------
$headings-color: $text-color; $headings-color: $text-color;
$abbr-border-color: $gray-2 !default; $abbr-border-color: $gray-2 !default;
$text-muted: darken($link-color,30%); $text-muted: $text-color-weak;
$blockquote-small-color: $gray-2 !default; $blockquote-small-color: $gray-2 !default;
$blockquote-border-color: $gray-3 !default; $blockquote-border-color: $gray-3 !default;

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