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

This commit is contained in:
Torkel Ödegaard
2016-04-28 19:08:35 +02:00
103 changed files with 2690 additions and 1121 deletions

View File

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

17
Makefile Normal file
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

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)

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() {

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}

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**:

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)

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"
}, },

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"},

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 == "" {

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")

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"`
} }

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)
}
} }
} }

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 {

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()

View File

@@ -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()
} }
} }

View File

@@ -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())
} }

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() {

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

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

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 {

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:"-"`
} }

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 {

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

View File

@@ -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>';

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;

View File

@@ -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) {

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;

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;
} }
} }
} }

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);
}); });
} }

View File

@@ -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);
}); });

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 () {});

View File

@@ -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: "="
}
};
});

View File

@@ -28,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;
@@ -53,13 +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();
// find current
var current = _.findWhere(this.datasources, {value: this.panel.datasource});
if (current) {
this.datasourceName = current.name;
}
} }
private onMetricsPanelRefresh() { private onMetricsPanelRefresh() {
@@ -257,16 +249,6 @@ class MetricsPanelCtrl extends PanelCtrl {
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};

View File

@@ -44,15 +44,15 @@ export class PanelCtrl {
this.pluginName = plugin.name; this.pluginName = plugin.name;
} }
$scope.$on("refresh", () => this.refresh()); $scope.$on("refresh", this.refresh.bind(this));
$scope.$on("render", () => this.render()); $scope.$on("render", this.render.bind(this));
$scope.$on("$destroy", () => this.events.emit('panel-teardown')); $scope.$on("$destroy", () => this.events.emit('panel-teardown'));
} }
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() {

View File

@@ -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">

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();
}
} }

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: "=",
},
}; };
} }

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;
}
}); });
} }

View File

@@ -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

View File

@@ -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: []
}; };

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -323,17 +323,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"});
}; };

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.datasourceName}} &nbsp; <span class="fa fa-caret-down"></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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>
<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>

View File

@@ -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="gf-form-group" ng-if="showOptions">
<div class="tight-form" ng-if="showOptions">
<div class="tight-form-inner-box tight-form-container"> <div class="gf-form offset-width-7" ng-if="agg.type === 'derivative'">
<div class="tight-form" ng-if="agg.type === 'derivative'"> <label class="gf-form-label width-10">Unit</label>
<ul class="tight-form-list"> <input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
<li class="tight-form-item" style="width: 75px;"> </div>
Unit
</li> <div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
<li> <label class="gf-form-label width-10">Window</label>
<input type="text" class="input-medium tight-form-input last" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'> <input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</li> </div>
</ul>
<div class="clearfix"></div> <div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
</div> <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 class="tight-form" ng-if="agg.type === 'moving_avg'"> </div>
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 75px;"> <div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
Window <label class="gf-form-label width-10">Percentiles</label>
</li> <input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
<li> </div>
<input type="number" class="input-medium tight-form-input last" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
</li> <div ng-if="agg.type === 'extended_stats'">
</ul> <gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
<div class="clearfix"></div>
</div> <div class="gf-form offset-width-7">
<div class="tight-form" ng-if="agg.type === 'moving_avg'"> <label class="gf-form-label width-10">Sigma</label>
<ul class="tight-form-list"> <input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
<li class="tight-form-item" style="width: 75px;"> </div>
Model </div>
</li>
<li> <div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
<input type="text" class="input-medium tight-form-input last" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'> <label class="gf-form-label width-10">Script</label>
</li> <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">
</ul> </div>
<div class="clearfix"></div>
</div> <div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
<div class="tight-form last" ng-if="agg.type === 'percentiles'"> <label class="gf-form-label width-10">
<ul class="tight-form-list"> Missing
<li class="tight-form-item"> <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>
Percentiles </label>
</li> <input type="number" class="gf-form-input max-width-12" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
<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">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
Script
</li>
<li>
<input type="text" class="input-medium tight-form-input last" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="aggDef.supportsMissing">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 100px;">
Missing
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
</li>
<li>
<input type="number" class="input-medium tight-form-input last" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -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>

View File

@@ -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 || {};

View File

@@ -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 [];

View File

@@ -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: [

View File

@@ -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>

View File

@@ -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>';

View File

@@ -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,

View File

@@ -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>

View File

@@ -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/)

View File

@@ -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);
}); });

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 + "'";
} }

View File

@@ -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);
}
} }

View File

@@ -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;
}

View File

@@ -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", () => {

View File

@@ -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>

View File

@@ -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>

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];

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;

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;

View File

@@ -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>

View File

@@ -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 = [];

View File

@@ -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">

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) {

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;

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;

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));

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: {

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";

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;

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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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