mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'alerting' into grafana-annotations
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
* **SingleStat**: Add seriename as option in singlestat panel, closes [#4740](https://github.com/grafana/grafana/issues/4740)
|
* **SingleStat**: Add seriename as option in singlestat panel, closes [#4740](https://github.com/grafana/grafana/issues/4740)
|
||||||
* **Localization**: Week start day now dependant on browser locale setting, closes [#3003](https://github.com/grafana/grafana/issues/3003)
|
* **Localization**: Week start day now dependant on browser locale setting, closes [#3003](https://github.com/grafana/grafana/issues/3003)
|
||||||
* **Templating**: Update panel repeats for variables that change on time refresh, closes [#5021](https://github.com/grafana/grafana/issues/5021)
|
* **Templating**: Update panel repeats for variables that change on time refresh, closes [#5021](https://github.com/grafana/grafana/issues/5021)
|
||||||
|
* **Templating**: Add support for numeric and alphabetical sorting of variable values, closes [#2839](https://github.com/grafana/grafana/issues/2839)
|
||||||
* **Elasticsearch**: Support to set Precision Threshold for Unique Count metric, closes [#4689](https://github.com/grafana/grafana/issues/4689)
|
* **Elasticsearch**: Support to set Precision Threshold for Unique Count metric, closes [#4689](https://github.com/grafana/grafana/issues/4689)
|
||||||
* **Navigation**: Add search to org swithcer, closes [#2609](https://github.com/grafana/grafana/issues/2609)
|
* **Navigation**: Add search to org swithcer, closes [#2609](https://github.com/grafana/grafana/issues/2609)
|
||||||
* **Database**: Allow database config using one propertie, closes [#5456](https://github.com/grafana/grafana/pull/5456)
|
* **Database**: Allow database config using one propertie, closes [#5456](https://github.com/grafana/grafana/pull/5456)
|
||||||
@@ -15,6 +16,9 @@
|
|||||||
### Breaking changes
|
### Breaking changes
|
||||||
* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
|
* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
* **Table Panel**: Fixed problem when switching to Mixed datasource in metrics tab, fixes [#5999](https://github.com/grafana/grafana/pull/5999)
|
||||||
|
|
||||||
# 3.1.2 (unreleased)
|
# 3.1.2 (unreleased)
|
||||||
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
|
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
|
||||||
* **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767)
|
* **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ host = "127.0.0.1"
|
|||||||
port = 389
|
port = 389
|
||||||
# Set to true if ldap server supports TLS
|
# Set to true if ldap server supports TLS
|
||||||
use_ssl = false
|
use_ssl = false
|
||||||
|
# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS)
|
||||||
|
start_tls = false
|
||||||
# set to true if you want to skip ssl cert validation
|
# set to true if you want to skip ssl cert validation
|
||||||
ssl_skip_verify = false
|
ssl_skip_verify = false
|
||||||
# set to the path to your root CA certificate or leave unset to use system defaults
|
# set to the path to your root CA certificate or leave unset to use system defaults
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pages:
|
|||||||
- ['installation/performance.md', 'Installation', 'Performance Tips']
|
- ['installation/performance.md', 'Installation', 'Performance Tips']
|
||||||
- ['installation/troubleshooting.md', 'Installation', 'Troubleshooting']
|
- ['installation/troubleshooting.md', 'Installation', 'Troubleshooting']
|
||||||
- ['installation/migrating_to2.md', 'Installation', 'Migrating from v1.x to v2.x']
|
- ['installation/migrating_to2.md', 'Installation', 'Migrating from v1.x to v2.x']
|
||||||
|
- ['installation/behind_proxy.md', 'Installation', 'Grafana behind reverse proxy']
|
||||||
|
|
||||||
- ['guides/basic_concepts.md', 'User Guides', 'Basic Concepts']
|
- ['guides/basic_concepts.md', 'User Guides', 'Basic Concepts']
|
||||||
- ['guides/gettingstarted.md', 'User Guides', 'Getting Started']
|
- ['guides/gettingstarted.md', 'User Guides', 'Getting Started']
|
||||||
|
|||||||
64
docs/sources/installation/behind_proxy.md
Normal file
64
docs/sources/installation/behind_proxy.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
page_title: Running Grafana behind a reverse proxy
|
||||||
|
page_description: Guide for running Grafana behind a reverse proxy
|
||||||
|
page_keywords: Grafana, reverse proxy, nginx, haproxy
|
||||||
|
---
|
||||||
|
|
||||||
|
# Running Grafana behind a reverse proxy
|
||||||
|
|
||||||
|
It should be straight forward to get Grafana up and running behind a reverse proxy. But here are some things that you might run into.
|
||||||
|
|
||||||
|
Links and redirects will not be rendered correctly unless you set the server.domain setting.
|
||||||
|
```
|
||||||
|
[server]
|
||||||
|
domain = foo.bar
|
||||||
|
```
|
||||||
|
|
||||||
|
To use sub *path* ex `http://foo.bar/grafana` make sure to include `/grafana` in the end of root_url.
|
||||||
|
Otherwise Grafana will not behave correctly. See example below.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
Here are some example configurations for running Grafana behind a reverse proxy.
|
||||||
|
|
||||||
|
## Grafana configuration (ex http://foo.bar.com)
|
||||||
|
```
|
||||||
|
[server]
|
||||||
|
domain = foo.bar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nginx configuration
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/www;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Examples with **sub path** (ex http://foo.bar.com/grafana)
|
||||||
|
|
||||||
|
## Grafana configuration with sub path
|
||||||
|
```
|
||||||
|
[server]
|
||||||
|
domain = foo.bar
|
||||||
|
root_url = %(protocol)s://%(domain)s:/grafana
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nginx configuration with sub path
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/www;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
location /grafana/ {
|
||||||
|
proxy_pass http://localhost:3000/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ host = "127.0.0.1"
|
|||||||
port = 389
|
port = 389
|
||||||
# Set to true if ldap server supports TLS
|
# Set to true if ldap server supports TLS
|
||||||
use_ssl = false
|
use_ssl = false
|
||||||
|
# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS)
|
||||||
|
start_tls = false
|
||||||
# set to true if you want to skip ssl cert validation
|
# set to true if you want to skip ssl cert validation
|
||||||
ssl_skip_verify = false
|
ssl_skip_verify = false
|
||||||
# set to the path to your root CA certificate or leave unset to use system defaults
|
# set to the path to your root CA certificate or leave unset to use system defaults
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ 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/Firehose": {"DeliveryToElasticsearch.Bytes", "DeliveryToElasticsearch.Records", "DeliveryToElasticsearch.Success", "DeliveryToRedshift.Bytes", "DeliveryToRedshift.Records", "DeliveryToRedshift.Success", "DeliveryToS3.Bytes", "DeliveryToS3.DataFreshness", "DeliveryToS3.Records", "DeliveryToS3.Success", "IncomingBytes", "IncomingRecords", "DescribeDeliveryStream.Latency", "DescribeDeliveryStream.Requests", "ListDeliveryStreams.Latency", "ListDeliveryStreams.Requests", "PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Requests", "PutRecordBatch.Bytes", "PutRecordBatch.Latency", "PutRecordBatch.Records", "PutRecordBatch.Requests", "UpdateDeliveryStream.Latency", "UpdateDeliveryStream.Requests"},
|
||||||
|
"AWS/IoT": {"PublishIn.Success", "PublishOut.Success", "Subscribe.Success", "Ping.Success", "Connect.Success", "GetThingShadow.Accepted"},
|
||||||
"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/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/KinesisAnalytics": {"Bytes", "MillisBehindLatest", "Records", "Success"},
|
||||||
"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"},
|
||||||
@@ -86,6 +89,7 @@ func init() {
|
|||||||
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
|
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
|
||||||
"AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
|
"AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
|
||||||
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
|
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
|
||||||
|
"KMS": {"SecondsUntilKeyMaterialExpiration"},
|
||||||
}
|
}
|
||||||
dimensionsMap = map[string][]string{
|
dimensionsMap = map[string][]string{
|
||||||
"AWS/ApiGateway": {"ApiName", "Method", "Resource", "Stage"},
|
"AWS/ApiGateway": {"ApiName", "Method", "Resource", "Stage"},
|
||||||
@@ -106,7 +110,10 @@ func init() {
|
|||||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||||
"AWS/ES": {"ClientId", "DomainName"},
|
"AWS/ES": {"ClientId", "DomainName"},
|
||||||
"AWS/Events": {"RuleName"},
|
"AWS/Events": {"RuleName"},
|
||||||
|
"AWS/Firehose": {},
|
||||||
|
"AWS/IoT": {"Protocol"},
|
||||||
"AWS/Kinesis": {"StreamName", "ShardID"},
|
"AWS/Kinesis": {"StreamName", "ShardID"},
|
||||||
|
"AWS/KinesisAnalytics": {"Flow", "Id", "Application"},
|
||||||
"AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"},
|
"AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"},
|
||||||
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||||
@@ -121,6 +128,7 @@ func init() {
|
|||||||
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
|
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
|
||||||
"AWS/WAF": {"Rule", "WebACL"},
|
"AWS/WAF": {"Rule", "WebACL"},
|
||||||
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
|
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
|
||||||
|
"KMS": {"KeyId"},
|
||||||
}
|
}
|
||||||
|
|
||||||
customMetricsMetricsMap = make(map[string]map[string]map[string]*CustomMetricsCache)
|
customMetricsMetricsMap = make(map[string]map[string]map[string]*CustomMetricsCache)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"gopkg.in/macaron.v1"
|
"gopkg.in/macaron.v1"
|
||||||
@@ -88,10 +87,8 @@ func ApiError(status int, message string, err error) *NormalResponse {
|
|||||||
|
|
||||||
switch status {
|
switch status {
|
||||||
case 404:
|
case 404:
|
||||||
metrics.M_Api_Status_404.Inc(1)
|
|
||||||
data["message"] = "Not Found"
|
data["message"] = "Not Found"
|
||||||
case 500:
|
case 500:
|
||||||
metrics.M_Api_Status_500.Inc(1)
|
|
||||||
data["message"] = "Internal Server Error"
|
data["message"] = "Internal Server Error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func newMacaron() *macaron.Macaron {
|
|||||||
|
|
||||||
m.Use(middleware.GetContextHandler())
|
m.Use(middleware.GetContextHandler())
|
||||||
m.Use(middleware.Sessioner(&setting.SessionOptions))
|
m.Use(middleware.Sessioner(&setting.SessionOptions))
|
||||||
|
m.Use(middleware.RequestMetrics())
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,16 @@ func (a *ldapAuther) Dial() error {
|
|||||||
ServerName: host,
|
ServerName: host,
|
||||||
RootCAs: certPool,
|
RootCAs: certPool,
|
||||||
}
|
}
|
||||||
|
if a.server.StartTLS {
|
||||||
|
a.conn, err = ldap.Dial("tcp", address)
|
||||||
|
if err == nil {
|
||||||
|
if err = a.conn.StartTLS(tlsCfg); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
a.conn, err = ldap.DialTLS("tcp", address, tlsCfg)
|
a.conn, err = ldap.DialTLS("tcp", address, tlsCfg)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
a.conn, err = ldap.Dial("tcp", address)
|
a.conn, err = ldap.Dial("tcp", address)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type LdapServerConf struct {
|
|||||||
Host string `toml:"host"`
|
Host string `toml:"host"`
|
||||||
Port int `toml:"port"`
|
Port int `toml:"port"`
|
||||||
UseSSL bool `toml:"use_ssl"`
|
UseSSL bool `toml:"use_ssl"`
|
||||||
|
StartTLS bool `toml:"start_tls"`
|
||||||
SkipVerifySSL bool `toml:"ssl_skip_verify"`
|
SkipVerifySSL bool `toml:"ssl_skip_verify"`
|
||||||
RootCACert string `toml:"root_ca_cert"`
|
RootCACert string `toml:"root_ca_cert"`
|
||||||
BindDN string `toml:"bind_dn"`
|
BindDN string `toml:"bind_dn"`
|
||||||
|
|||||||
@@ -13,8 +13,15 @@ var (
|
|||||||
M_Page_Status_200 Counter
|
M_Page_Status_200 Counter
|
||||||
M_Page_Status_500 Counter
|
M_Page_Status_500 Counter
|
||||||
M_Page_Status_404 Counter
|
M_Page_Status_404 Counter
|
||||||
M_Api_Status_500 Counter
|
M_Page_Status_Unknown Counter
|
||||||
|
M_Api_Status_200 Counter
|
||||||
M_Api_Status_404 Counter
|
M_Api_Status_404 Counter
|
||||||
|
M_Api_Status_500 Counter
|
||||||
|
M_Api_Status_Unknown Counter
|
||||||
|
M_Proxy_Status_200 Counter
|
||||||
|
M_Proxy_Status_404 Counter
|
||||||
|
M_Proxy_Status_500 Counter
|
||||||
|
M_Proxy_Status_Unknown Counter
|
||||||
M_Api_User_SignUpStarted Counter
|
M_Api_User_SignUpStarted Counter
|
||||||
M_Api_User_SignUpCompleted Counter
|
M_Api_User_SignUpCompleted Counter
|
||||||
M_Api_User_SignUpInvite Counter
|
M_Api_User_SignUpInvite Counter
|
||||||
@@ -54,9 +61,17 @@ func initMetricVars(settings *MetricSettings) {
|
|||||||
M_Page_Status_200 = RegCounter("page.resp_status", "code", "200")
|
M_Page_Status_200 = RegCounter("page.resp_status", "code", "200")
|
||||||
M_Page_Status_500 = RegCounter("page.resp_status", "code", "500")
|
M_Page_Status_500 = RegCounter("page.resp_status", "code", "500")
|
||||||
M_Page_Status_404 = RegCounter("page.resp_status", "code", "404")
|
M_Page_Status_404 = RegCounter("page.resp_status", "code", "404")
|
||||||
|
M_Page_Status_Unknown = RegCounter("page.resp_status", "code", "unknown")
|
||||||
|
|
||||||
M_Api_Status_500 = RegCounter("api.resp_status", "code", "500")
|
M_Api_Status_200 = RegCounter("api.resp_status", "code", "200")
|
||||||
M_Api_Status_404 = RegCounter("api.resp_status", "code", "404")
|
M_Api_Status_404 = RegCounter("api.resp_status", "code", "404")
|
||||||
|
M_Api_Status_500 = RegCounter("api.resp_status", "code", "500")
|
||||||
|
M_Api_Status_Unknown = RegCounter("api.resp_status", "code", "unknown")
|
||||||
|
|
||||||
|
M_Proxy_Status_200 = RegCounter("proxy.resp_status", "code", "200")
|
||||||
|
M_Proxy_Status_404 = RegCounter("proxy.resp_status", "code", "404")
|
||||||
|
M_Proxy_Status_500 = RegCounter("proxy.resp_status", "code", "500")
|
||||||
|
M_Proxy_Status_Unknown = RegCounter("proxy.resp_status", "code", "unknown")
|
||||||
|
|
||||||
M_Api_User_SignUpStarted = RegCounter("api.user.signup_started")
|
M_Api_User_SignUpStarted = RegCounter("api.user.signup_started")
|
||||||
M_Api_User_SignUpCompleted = RegCounter("api.user.signup_completed")
|
M_Api_User_SignUpCompleted = RegCounter("api.user.signup_completed")
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ func Logger() macaron.Handler {
|
|||||||
if ctx, ok := c.Data["ctx"]; ok {
|
if ctx, ok := c.Data["ctx"]; ok {
|
||||||
ctxTyped := ctx.(*Context)
|
ctxTyped := ctx.(*Context)
|
||||||
if status == 500 {
|
if status == 500 {
|
||||||
ctxTyped.Logger.Error("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ns", timeTakenMs, "size", rw.Size())
|
ctxTyped.Logger.Error("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ms", timeTakenMs, "size", rw.Size())
|
||||||
} else {
|
} else {
|
||||||
ctxTyped.Logger.Info("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ns", timeTakenMs, "size", rw.Size())
|
ctxTyped.Logger.Info("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ms", timeTakenMs, "size", rw.Size())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,15 +208,6 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch status {
|
|
||||||
case 200:
|
|
||||||
metrics.M_Page_Status_200.Inc(1)
|
|
||||||
case 404:
|
|
||||||
metrics.M_Page_Status_404.Inc(1)
|
|
||||||
case 500:
|
|
||||||
metrics.M_Page_Status_500.Inc(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["Title"] = title
|
ctx.Data["Title"] = title
|
||||||
ctx.HTML(status, strconv.Itoa(status))
|
ctx.HTML(status, strconv.Itoa(status))
|
||||||
}
|
}
|
||||||
@@ -243,10 +234,8 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
|
|||||||
|
|
||||||
switch status {
|
switch status {
|
||||||
case 404:
|
case 404:
|
||||||
metrics.M_Api_Status_404.Inc(1)
|
|
||||||
resp["message"] = "Not Found"
|
resp["message"] = "Not Found"
|
||||||
case 500:
|
case 500:
|
||||||
metrics.M_Api_Status_500.Inc(1)
|
|
||||||
resp["message"] = "Internal Server Error"
|
resp["message"] = "Internal Server Error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
65
pkg/middleware/request_metrics.go
Normal file
65
pkg/middleware/request_metrics.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
|
"gopkg.in/macaron.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequestMetrics() macaron.Handler {
|
||||||
|
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||||
|
rw := res.(macaron.ResponseWriter)
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
status := rw.Status()
|
||||||
|
|
||||||
|
if strings.HasPrefix(req.RequestURI, "/api/datasources/proxy") {
|
||||||
|
countProxyRequests(status)
|
||||||
|
} else if strings.HasPrefix(req.RequestURI, "/api/") {
|
||||||
|
countApiRequests(status)
|
||||||
|
} else {
|
||||||
|
countPageRequests(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func countApiRequests(status int) {
|
||||||
|
switch status {
|
||||||
|
case 200:
|
||||||
|
metrics.M_Api_Status_200.Inc(1)
|
||||||
|
case 404:
|
||||||
|
metrics.M_Api_Status_404.Inc(1)
|
||||||
|
case 500:
|
||||||
|
metrics.M_Api_Status_500.Inc(1)
|
||||||
|
default:
|
||||||
|
metrics.M_Api_Status_Unknown.Inc(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func countPageRequests(status int) {
|
||||||
|
switch status {
|
||||||
|
case 200:
|
||||||
|
metrics.M_Page_Status_200.Inc(1)
|
||||||
|
case 404:
|
||||||
|
metrics.M_Page_Status_404.Inc(1)
|
||||||
|
case 500:
|
||||||
|
metrics.M_Page_Status_500.Inc(1)
|
||||||
|
default:
|
||||||
|
metrics.M_Page_Status_Unknown.Inc(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func countProxyRequests(status int) {
|
||||||
|
switch status {
|
||||||
|
case 200:
|
||||||
|
metrics.M_Proxy_Status_200.Inc(1)
|
||||||
|
case 404:
|
||||||
|
metrics.M_Proxy_Status_404.Inc(1)
|
||||||
|
case 500:
|
||||||
|
metrics.M_Proxy_Status_500.Inc(1)
|
||||||
|
default:
|
||||||
|
metrics.M_Proxy_Status_Unknown.Inc(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
// type AlertState struct {
|
|
||||||
// Id int64 `json:"-"`
|
|
||||||
// OrgId int64 `json:"-"`
|
|
||||||
// AlertId int64 `json:"alertId"`
|
|
||||||
// State string `json:"state"`
|
|
||||||
// Created time.Time `json:"created"`
|
|
||||||
// Info string `json:"info"`
|
|
||||||
// TriggeredAlerts *simplejson.Json `json:"triggeredAlerts"`
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (this *UpdateAlertStateCommand) IsValidState() bool {
|
|
||||||
// for _, v := range alertstates.ValidStates {
|
|
||||||
// if this.State == v {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Commands
|
|
||||||
//
|
|
||||||
// type UpdateAlertStateCommand struct {
|
|
||||||
// AlertId int64 `json:"alertId" binding:"Required"`
|
|
||||||
// OrgId int64 `json:"orgId" binding:"Required"`
|
|
||||||
// State string `json:"state" binding:"Required"`
|
|
||||||
// Info string `json:"info"`
|
|
||||||
//
|
|
||||||
// Result *Alert
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Queries
|
|
||||||
//
|
|
||||||
// type GetAlertsStateQuery struct {
|
|
||||||
// OrgId int64 `json:"orgId" binding:"Required"`
|
|
||||||
// AlertId int64 `json:"alertId" binding:"Required"`
|
|
||||||
//
|
|
||||||
// Result *[]AlertState
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// type GetLastAlertStateQuery struct {
|
|
||||||
// AlertId int64
|
|
||||||
// OrgId int64
|
|
||||||
//
|
|
||||||
// Result *AlertState
|
|
||||||
// }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package conditions
|
|
||||||
@@ -38,6 +38,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emptySerieCount := 0
|
||||||
for _, series := range seriesList {
|
for _, series := range seriesList {
|
||||||
reducedValue := c.Reducer.Reduce(series)
|
reducedValue := c.Reducer.Reduce(series)
|
||||||
evalMatch := c.Evaluator.Eval(reducedValue)
|
evalMatch := c.Evaluator.Eval(reducedValue)
|
||||||
@@ -55,13 +56,14 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Firing = evalMatch
|
|
||||||
|
|
||||||
// handle no data scenario
|
// handle no data scenario
|
||||||
if reducedValue == nil {
|
if reducedValue == nil {
|
||||||
context.NoDataFound = true
|
emptySerieCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.NoDataFound = emptySerieCount == len(seriesList)
|
||||||
|
context.Firing = len(context.EvalMatches) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) {
|
func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) {
|
||||||
|
|||||||
@@ -59,6 +59,45 @@ func TestQueryCondition(t *testing.T) {
|
|||||||
So(ctx.result.Error, ShouldBeNil)
|
So(ctx.result.Error, ShouldBeNil)
|
||||||
So(ctx.result.Firing, ShouldBeFalse)
|
So(ctx.result.Firing, ShouldBeFalse)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Should fire if only first serie matches", func() {
|
||||||
|
one := float64(120)
|
||||||
|
two := float64(0)
|
||||||
|
ctx.series = tsdb.TimeSeriesSlice{
|
||||||
|
tsdb.NewTimeSeries("test1", [][2]*float64{{&one, &two}}),
|
||||||
|
tsdb.NewTimeSeries("test2", [][2]*float64{{&two, &two}}),
|
||||||
|
}
|
||||||
|
ctx.exec()
|
||||||
|
|
||||||
|
So(ctx.result.Error, ShouldBeNil)
|
||||||
|
So(ctx.result.Firing, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Empty series", func() {
|
||||||
|
Convey("Should set NoDataFound both series are empty", func() {
|
||||||
|
ctx.series = tsdb.TimeSeriesSlice{
|
||||||
|
tsdb.NewTimeSeries("test1", [][2]*float64{}),
|
||||||
|
tsdb.NewTimeSeries("test2", [][2]*float64{}),
|
||||||
|
}
|
||||||
|
ctx.exec()
|
||||||
|
|
||||||
|
So(ctx.result.Error, ShouldBeNil)
|
||||||
|
So(ctx.result.NoDataFound, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not set NoDataFound if one serie is empty", func() {
|
||||||
|
one := float64(120)
|
||||||
|
two := float64(0)
|
||||||
|
ctx.series = tsdb.TimeSeriesSlice{
|
||||||
|
tsdb.NewTimeSeries("test1", [][2]*float64{}),
|
||||||
|
tsdb.NewTimeSeries("test2", [][2]*float64{{&one, &two}}),
|
||||||
|
}
|
||||||
|
ctx.exec()
|
||||||
|
|
||||||
|
So(ctx.result.Error, ShouldBeNil)
|
||||||
|
So(ctx.result.NoDataFound, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type DefaultEvalHandler struct {
|
|||||||
func NewEvalHandler() *DefaultEvalHandler {
|
func NewEvalHandler() *DefaultEvalHandler {
|
||||||
return &DefaultEvalHandler{
|
return &DefaultEvalHandler{
|
||||||
log: log.New("alerting.evalHandler"),
|
log: log.New("alerting.evalHandler"),
|
||||||
alertJobTimeout: time.Second * 5,
|
alertJobTimeout: time.Second * 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(e.alertJobTimeout):
|
case <-time.After(e.alertJobTimeout):
|
||||||
context.Error = fmt.Errorf("Timeout")
|
context.Error = fmt.Errorf("Execution timed out after %v", e.alertJobTimeout)
|
||||||
context.EndTime = time.Now()
|
context.EndTime = time.Now()
|
||||||
e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id)
|
e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id, "timeout setting", e.alertJobTimeout)
|
||||||
e.retry(context)
|
e.retry(context)
|
||||||
case <-context.DoneChan:
|
case <-context.DoneChan:
|
||||||
e.log.Debug("Job Execution done", "timeMs", context.GetDurationMs(), "alertId", context.Rule.Id, "firing", context.Firing)
|
e.log.Debug("Job Execution done", "timeMs", context.GetDurationMs(), "alertId", context.Rule.Id, "firing", context.Firing)
|
||||||
@@ -45,10 +45,10 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
|||||||
func (e *DefaultEvalHandler) retry(context *EvalContext) {
|
func (e *DefaultEvalHandler) retry(context *EvalContext) {
|
||||||
e.log.Debug("Retrying eval exeuction", "alertId", context.Rule.Id)
|
e.log.Debug("Retrying eval exeuction", "alertId", context.Rule.Id)
|
||||||
|
|
||||||
context.RetryCount++
|
if context.RetryCount < MaxRetries {
|
||||||
if context.RetryCount > MaxRetries {
|
|
||||||
context.DoneChan = make(chan bool, 1)
|
context.DoneChan = make(chan bool, 1)
|
||||||
context.CancelChan = make(chan bool, 1)
|
context.CancelChan = make(chan bool, 1)
|
||||||
|
context.RetryCount++
|
||||||
e.Eval(context)
|
e.Eval(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDataSources(query *m.GetDataSourcesQuery) error {
|
func GetDataSources(query *m.GetDataSourcesQuery) error {
|
||||||
sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
|
sess := x.Limit(1000, 0).Where("org_id=?", query.OrgId).Asc("name")
|
||||||
|
|
||||||
query.Result = make([]*m.DataSource, 0)
|
query.Result = make([]*m.DataSource, 0)
|
||||||
return sess.Find(&query.Result)
|
return sess.Find(&query.Result)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package graphite
|
package graphite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -15,10 +16,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
HttpClient = http.Client{Timeout: time.Duration(10 * time.Second)}
|
|
||||||
)
|
|
||||||
|
|
||||||
type GraphiteExecutor struct {
|
type GraphiteExecutor struct {
|
||||||
*tsdb.DataSourceInfo
|
*tsdb.DataSourceInfo
|
||||||
}
|
}
|
||||||
@@ -27,11 +24,23 @@ func NewGraphiteExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
|
|||||||
return &GraphiteExecutor{dsInfo}
|
return &GraphiteExecutor{dsInfo}
|
||||||
}
|
}
|
||||||
|
|
||||||
var glog log.Logger
|
var (
|
||||||
|
glog log.Logger
|
||||||
|
HttpClient http.Client
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
glog = log.New("tsdb.graphite")
|
glog = log.New("tsdb.graphite")
|
||||||
tsdb.RegisterExecutor("graphite", NewGraphiteExecutor)
|
tsdb.RegisterExecutor("graphite", NewGraphiteExecutor)
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient = http.Client{
|
||||||
|
Timeout: time.Duration(10 * time.Second),
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
|
func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|||||||
if (datasource.meta.mixed) {
|
if (datasource.meta.mixed) {
|
||||||
_.each(this.panel.targets, target => {
|
_.each(this.panel.targets, target => {
|
||||||
target.datasource = this.panel.datasource;
|
target.datasource = this.panel.datasource;
|
||||||
if (target.datasource === null) {
|
if (!target.datasource) {
|
||||||
target.datasource = config.defaultDatasource;
|
target.datasource = config.defaultDatasource;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ export class PanelCtrl {
|
|||||||
var modalScope = this.$scope.$new();
|
var modalScope = this.$scope.$new();
|
||||||
modalScope.panel = this.panel;
|
modalScope.panel = this.panel;
|
||||||
modalScope.dashboard = this.dashboard;
|
modalScope.dashboard = this.dashboard;
|
||||||
modalScope.inspector = angular.copy(this.inspector);
|
modalScope.inspector = $.extend(true, {}, this.inspector);
|
||||||
|
|
||||||
this.publishAppEvent('show-modal', {
|
this.publishAppEvent('show-modal', {
|
||||||
src: 'public/app/partials/inspector.html',
|
src: 'public/app/partials/inspector.html',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ function (angular, _) {
|
|||||||
type: 'query',
|
type: 'query',
|
||||||
datasource: null,
|
datasource: null,
|
||||||
refresh: 0,
|
refresh: 0,
|
||||||
|
sort: 1,
|
||||||
name: '',
|
name: '',
|
||||||
hide: 0,
|
hide: 0,
|
||||||
options: [],
|
options: [],
|
||||||
@@ -34,6 +35,14 @@ function (angular, _) {
|
|||||||
{value: 2, text: "On Time Range Change"},
|
{value: 2, text: "On Time Range Change"},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$scope.sortOptions = [
|
||||||
|
{value: 0, text: "Without Sort"},
|
||||||
|
{value: 1, text: "Alphabetical (asc)"},
|
||||||
|
{value: 2, text: "Alphabetical (desc)"},
|
||||||
|
{value: 3, text: "Numerical (asc)"},
|
||||||
|
{value: 4, text: "Numerical (desc)"},
|
||||||
|
];
|
||||||
|
|
||||||
$scope.hideOptions = [
|
$scope.hideOptions = [
|
||||||
{value: 0, text: ""},
|
{value: 0, text: ""},
|
||||||
{value: 1, text: "Label"},
|
{value: 1, text: "Label"},
|
||||||
@@ -114,6 +123,7 @@ function (angular, _) {
|
|||||||
$scope.currentIsNew = false;
|
$scope.currentIsNew = false;
|
||||||
$scope.mode = 'edit';
|
$scope.mode = 'edit';
|
||||||
|
|
||||||
|
$scope.current.sort = $scope.current.sort || replacementDefaults.sort;
|
||||||
if ($scope.current.datasource === void 0) {
|
if ($scope.current.datasource === void 0) {
|
||||||
$scope.current.datasource = null;
|
$scope.current.datasource = null;
|
||||||
$scope.current.type = 'query';
|
$scope.current.type = 'query';
|
||||||
|
|||||||
@@ -181,6 +181,17 @@
|
|||||||
<select class="gf-form-input" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
|
<select class="gf-form-input" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gf-form max-width-21">
|
||||||
|
<span class="gf-form-label width-7">
|
||||||
|
Sort
|
||||||
|
<info-popover mode="right-normal">
|
||||||
|
How to sort the values of this variable.
|
||||||
|
</info-popover>
|
||||||
|
</span>
|
||||||
|
<div class="gf-form-select-wrapper max-width-14">
|
||||||
|
<select class="gf-form-input" ng-model="current.sort" ng-options="f.value as f.text for f in sortOptions" ng-change="runQuery()"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-7">Query</span>
|
<span class="gf-form-label width-7">Query</span>
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ function (angular, _, $, kbn) {
|
|||||||
|
|
||||||
this.metricNamesToVariableValues = function(variable, metricNames) {
|
this.metricNamesToVariableValues = function(variable, metricNames) {
|
||||||
var regex, options, i, matches;
|
var regex, options, i, matches;
|
||||||
options = {}; // use object hash to remove duplicates
|
options = [];
|
||||||
|
|
||||||
if (variable.regex) {
|
if (variable.regex) {
|
||||||
regex = kbn.stringToJsRegex(templateSrv.replace(variable.regex));
|
regex = kbn.stringToJsRegex(templateSrv.replace(variable.regex));
|
||||||
@@ -370,16 +370,43 @@ function (angular, _, $, kbn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options[value] = {text: text, value: value};
|
options.push({text: text, value: value});
|
||||||
}
|
}
|
||||||
|
options = _.uniq(options, 'value');
|
||||||
|
|
||||||
return _.sortBy(options, 'text');
|
return this.sortVariableValues(options, variable.sort);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addAllOption = function(variable) {
|
this.addAllOption = function(variable) {
|
||||||
variable.options.unshift({text: 'All', value: "$__all"});
|
variable.options.unshift({text: 'All', value: "$__all"});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.sortVariableValues = function(options, sortOrder) {
|
||||||
|
if (sortOrder === 0) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortType = Math.ceil(sortOrder / 2);
|
||||||
|
var reverseSort = (sortOrder % 2 === 0);
|
||||||
|
if (sortType === 1) {
|
||||||
|
options = _.sortBy(options, 'text');
|
||||||
|
} else if (sortType === 2) {
|
||||||
|
options = _.sortBy(options, function(opt) {
|
||||||
|
var matches = opt.text.match(/.*?(\d+).*/);
|
||||||
|
if (!matches) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return parseInt(matches[1], 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (reverseSort) {
|
||||||
|
options = options.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,9 +68,9 @@
|
|||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<label>Stack trace:</label>
|
<label>Stack trace:</label>
|
||||||
<pre>
|
<pre>
|
||||||
{{stack_trace}}
|
{{stack_trace}}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default class InfluxDatasource {
|
|||||||
name: string;
|
name: string;
|
||||||
database: any;
|
database: any;
|
||||||
basicAuth: any;
|
basicAuth: any;
|
||||||
|
withCredentials: any;
|
||||||
interval: any;
|
interval: any;
|
||||||
supportAnnotations: boolean;
|
supportAnnotations: boolean;
|
||||||
supportMetrics: boolean;
|
supportMetrics: boolean;
|
||||||
@@ -33,6 +34,7 @@ export default class InfluxDatasource {
|
|||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.database = instanceSettings.database;
|
this.database = instanceSettings.database;
|
||||||
this.basicAuth = instanceSettings.basicAuth;
|
this.basicAuth = instanceSettings.basicAuth;
|
||||||
|
this.withCredentials = instanceSettings.withCredentials;
|
||||||
this.interval = (instanceSettings.jsonData || {}).timeInterval;
|
this.interval = (instanceSettings.jsonData || {}).timeInterval;
|
||||||
this.supportAnnotations = true;
|
this.supportAnnotations = true;
|
||||||
this.supportMetrics = true;
|
this.supportMetrics = true;
|
||||||
@@ -187,6 +189,9 @@ export default class InfluxDatasource {
|
|||||||
};
|
};
|
||||||
|
|
||||||
options.headers = options.headers || {};
|
options.headers = options.headers || {};
|
||||||
|
if (this.basicAuth || this.withCredentials) {
|
||||||
|
options.withCredentials = true;
|
||||||
|
}
|
||||||
if (self.basicAuth) {
|
if (self.basicAuth) {
|
||||||
options.headers.Authorization = self.basicAuth;
|
options.headers.Authorization = self.basicAuth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
|||||||
return this.getOriginalMetricName(labelData);
|
return this.getOriginalMetricName(labelData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.renderTemplate(options.legendFormat, labelData) || '{}';
|
return this.renderTemplate(templateSrv.replace(options.legendFormat), labelData) || '{}';
|
||||||
};
|
};
|
||||||
|
|
||||||
this.renderTemplate = function(aliasPattern, aliasData) {
|
this.renderTemplate = function(aliasPattern, aliasData) {
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
|
|||||||
console.log('flotcharts error', e);
|
console.log('flotcharts error', e);
|
||||||
ctrl.error = e.message || "Render Error";
|
ctrl.error = e.message || "Render Error";
|
||||||
ctrl.renderError = true;
|
ctrl.renderError = true;
|
||||||
ctrl.inspector = {error: ctrl.error};
|
ctrl.inspector = {error: e};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incrementRenderCounter) {
|
if (incrementRenderCounter) {
|
||||||
|
|||||||
@@ -386,5 +386,69 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describeUpdateVariable('without sort', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 0};
|
||||||
|
scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return options without sort', function() {
|
||||||
|
expect(scenario.variable.options[0].text).to.be('bbb2');
|
||||||
|
expect(scenario.variable.options[1].text).to.be('aaa10');
|
||||||
|
expect(scenario.variable.options[2].text).to.be('ccc3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describeUpdateVariable('with alphabetical sort (asc)', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 1};
|
||||||
|
scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return options with alphabetical sort', function() {
|
||||||
|
expect(scenario.variable.options[0].text).to.be('aaa10');
|
||||||
|
expect(scenario.variable.options[1].text).to.be('bbb2');
|
||||||
|
expect(scenario.variable.options[2].text).to.be('ccc3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describeUpdateVariable('with alphabetical sort (desc)', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 2};
|
||||||
|
scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return options with alphabetical sort', function() {
|
||||||
|
expect(scenario.variable.options[0].text).to.be('ccc3');
|
||||||
|
expect(scenario.variable.options[1].text).to.be('bbb2');
|
||||||
|
expect(scenario.variable.options[2].text).to.be('aaa10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describeUpdateVariable('with numerical sort (asc)', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 3};
|
||||||
|
scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return options with numerical sort', function() {
|
||||||
|
expect(scenario.variable.options[0].text).to.be('bbb2');
|
||||||
|
expect(scenario.variable.options[1].text).to.be('ccc3');
|
||||||
|
expect(scenario.variable.options[2].text).to.be('aaa10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describeUpdateVariable('with numerical sort (desc)', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 4};
|
||||||
|
scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return options with numerical sort', function() {
|
||||||
|
expect(scenario.variable.options[0].text).to.be('aaa10');
|
||||||
|
expect(scenario.variable.options[1].text).to.be('ccc3');
|
||||||
|
expect(scenario.variable.options[2].text).to.be('bbb2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user