Merge branch 'master' into query-part-refactor
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
#*
|
||||
*.o
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
extern/
|
||||
node_modules/
|
||||
tmp/
|
||||
data/
|
||||
vendor/
|
||||
public_gen/
|
||||
dist/
|
||||
|
||||
+22
-1
@@ -1,5 +1,26 @@
|
||||
# 3.0.0 stable (unreleased)
|
||||
# 3.1.0
|
||||
|
||||
### Enhancements
|
||||
* **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319)
|
||||
* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189)
|
||||
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
|
||||
|
||||
# 3.0.3 Patch release (unreleased)
|
||||
* **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
|
||||
* **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522)
|
||||
|
||||
# 3.0.2 Patch release (2016-05-16)
|
||||
|
||||
* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
|
||||
* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
|
||||
* **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025)
|
||||
* **Graph**: Fixed broken xaxis on graph panel, fixes [#5024](https://github.com/grafana/grafana/issues/5024)
|
||||
|
||||
* **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005)
|
||||
|
||||
# 3.0.1 Stable (2016-05-11)
|
||||
|
||||
### Bug fixes
|
||||
* **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934)
|
||||
|
||||
# 3.0.0-beta7 (2016-05-02)
|
||||
|
||||
Generated
+5
@@ -124,6 +124,11 @@
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/aws/aws-sdk-go/service/sts",
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/bmizerany/assert",
|
||||
"Comment": "release.r60-6-ge17e998",
|
||||
|
||||
+1127
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
||||
package sts
|
||||
|
||||
import "github.com/aws/aws-sdk-go/aws/request"
|
||||
|
||||
func init() {
|
||||
initRequest = func(r *request.Request) {
|
||||
switch r.Operation.Name {
|
||||
case opAssumeRoleWithSAML, opAssumeRoleWithWebIdentity:
|
||||
r.Handlers.Sign.Clear() // these operations are unsigned
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
package sts_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
)
|
||||
|
||||
var svc = sts.New(unit.Session, &aws.Config{
|
||||
Region: aws.String("mock-region"),
|
||||
})
|
||||
|
||||
func TestUnsignedRequest_AssumeRoleWithSAML(t *testing.T) {
|
||||
req, _ := svc.AssumeRoleWithSAMLRequest(&sts.AssumeRoleWithSAMLInput{
|
||||
PrincipalArn: aws.String("ARN01234567890123456789"),
|
||||
RoleArn: aws.String("ARN01234567890123456789"),
|
||||
SAMLAssertion: aws.String("ASSERT"),
|
||||
})
|
||||
|
||||
err := req.Sign()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", req.HTTPRequest.Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
func TestUnsignedRequest_AssumeRoleWithWebIdentity(t *testing.T) {
|
||||
req, _ := svc.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{
|
||||
RoleArn: aws.String("ARN01234567890123456789"),
|
||||
RoleSessionName: aws.String("SESSION"),
|
||||
WebIdentityToken: aws.String("TOKEN"),
|
||||
})
|
||||
|
||||
err := req.Sign()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", req.HTTPRequest.Header.Get("Authorization"))
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
|
||||
package sts_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
)
|
||||
|
||||
var _ time.Duration
|
||||
var _ bytes.Buffer
|
||||
|
||||
func ExampleSTS_AssumeRole() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.AssumeRoleInput{
|
||||
RoleArn: aws.String("arnType"), // Required
|
||||
RoleSessionName: aws.String("roleSessionNameType"), // Required
|
||||
DurationSeconds: aws.Int64(1),
|
||||
ExternalId: aws.String("externalIdType"),
|
||||
Policy: aws.String("sessionPolicyDocumentType"),
|
||||
SerialNumber: aws.String("serialNumberType"),
|
||||
TokenCode: aws.String("tokenCodeType"),
|
||||
}
|
||||
resp, err := svc.AssumeRole(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func ExampleSTS_AssumeRoleWithSAML() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.AssumeRoleWithSAMLInput{
|
||||
PrincipalArn: aws.String("arnType"), // Required
|
||||
RoleArn: aws.String("arnType"), // Required
|
||||
SAMLAssertion: aws.String("SAMLAssertionType"), // Required
|
||||
DurationSeconds: aws.Int64(1),
|
||||
Policy: aws.String("sessionPolicyDocumentType"),
|
||||
}
|
||||
resp, err := svc.AssumeRoleWithSAML(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func ExampleSTS_AssumeRoleWithWebIdentity() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.AssumeRoleWithWebIdentityInput{
|
||||
RoleArn: aws.String("arnType"), // Required
|
||||
RoleSessionName: aws.String("roleSessionNameType"), // Required
|
||||
WebIdentityToken: aws.String("clientTokenType"), // Required
|
||||
DurationSeconds: aws.Int64(1),
|
||||
Policy: aws.String("sessionPolicyDocumentType"),
|
||||
ProviderId: aws.String("urlType"),
|
||||
}
|
||||
resp, err := svc.AssumeRoleWithWebIdentity(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func ExampleSTS_DecodeAuthorizationMessage() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.DecodeAuthorizationMessageInput{
|
||||
EncodedMessage: aws.String("encodedMessageType"), // Required
|
||||
}
|
||||
resp, err := svc.DecodeAuthorizationMessage(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func ExampleSTS_GetFederationToken() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.GetFederationTokenInput{
|
||||
Name: aws.String("userNameType"), // Required
|
||||
DurationSeconds: aws.Int64(1),
|
||||
Policy: aws.String("sessionPolicyDocumentType"),
|
||||
}
|
||||
resp, err := svc.GetFederationToken(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func ExampleSTS_GetSessionToken() {
|
||||
svc := sts.New(session.New())
|
||||
|
||||
params := &sts.GetSessionTokenInput{
|
||||
DurationSeconds: aws.Int64(1),
|
||||
SerialNumber: aws.String("serialNumberType"),
|
||||
TokenCode: aws.String("tokenCodeType"),
|
||||
}
|
||||
resp, err := svc.GetSessionToken(params)
|
||||
|
||||
if err != nil {
|
||||
// Print the error, cast err to awserr.Error to get the Code and
|
||||
// Message from an error.
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(resp)
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
|
||||
package sts
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/query"
|
||||
"github.com/aws/aws-sdk-go/private/signer/v4"
|
||||
)
|
||||
|
||||
// The AWS Security Token Service (STS) is a web service that enables you to
|
||||
// request temporary, limited-privilege credentials for AWS Identity and Access
|
||||
// Management (IAM) users or for users that you authenticate (federated users).
|
||||
// This guide provides descriptions of the STS API. For more detailed information
|
||||
// about using this service, go to Temporary Security Credentials (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html).
|
||||
//
|
||||
// As an alternative to using the API, you can use one of the AWS SDKs, which
|
||||
// consist of libraries and sample code for various programming languages and
|
||||
// platforms (Java, Ruby, .NET, iOS, Android, etc.). The SDKs provide a convenient
|
||||
// way to create programmatic access to STS. For example, the SDKs take care
|
||||
// of cryptographically signing requests, managing errors, and retrying requests
|
||||
// automatically. For information about the AWS SDKs, including how to download
|
||||
// and install them, see the Tools for Amazon Web Services page (http://aws.amazon.com/tools/).
|
||||
// For information about setting up signatures and authorization through the
|
||||
// API, go to Signing AWS API Requests (http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html"
|
||||
// target="_blank) in the AWS General Reference. For general information about
|
||||
// the Query API, go to Making Query Requests (http://docs.aws.amazon.com/IAM/latest/UserGuide/IAM_UsingQueryAPI.html"
|
||||
// target="_blank) in Using IAM. For information about using security tokens
|
||||
// with other AWS products, go to AWS Services That Work with IAM (http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)
|
||||
// in the Using IAM.
|
||||
//
|
||||
// If you're new to AWS and need additional technical information about a specific
|
||||
// AWS product, you can find the product's technical documentation at http://aws.amazon.com/documentation/
|
||||
// (http://aws.amazon.com/documentation/" target="_blank).
|
||||
//
|
||||
// Endpoints
|
||||
//
|
||||
// The AWS Security Token Service (STS) has a default endpoint of https://sts.amazonaws.com
|
||||
// that maps to the US East (N. Virginia) region. Additional regions are available,
|
||||
// but must first be activated in the AWS Management Console before you can
|
||||
// use a different region's endpoint. For more information about activating
|
||||
// a region for STS see Activating STS in a New Region (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
|
||||
// in the Using IAM.
|
||||
//
|
||||
// For information about STS endpoints, see Regions and Endpoints (http://docs.aws.amazon.com/general/latest/gr/rande.html#sts_region)
|
||||
// in the AWS General Reference.
|
||||
//
|
||||
// Recording API requests
|
||||
//
|
||||
// STS supports AWS CloudTrail, which is a service that records AWS calls for
|
||||
// your AWS account and delivers log files to an Amazon S3 bucket. By using
|
||||
// information collected by CloudTrail, you can determine what requests were
|
||||
// successfully made to STS, who made the request, when it was made, and so
|
||||
// on. To learn more about CloudTrail, including how to turn it on and find
|
||||
// your log files, see the AWS CloudTrail User Guide (http://docs.aws.amazon.com/awscloudtrail/latest/userguide/what_is_cloud_trail_top_level.html).
|
||||
//The service client's operations are safe to be used concurrently.
|
||||
// It is not safe to mutate any of the client's properties though.
|
||||
type STS struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// Used for custom client initialization logic
|
||||
var initClient func(*client.Client)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*request.Request)
|
||||
|
||||
// A ServiceName is the name of the service the client will make API calls to.
|
||||
const ServiceName = "sts"
|
||||
|
||||
// New creates a new instance of the STS client with a session.
|
||||
// If additional configuration is needed for the client instance use the optional
|
||||
// aws.Config parameter to add your extra config.
|
||||
//
|
||||
// Example:
|
||||
// // Create a STS client from just a session.
|
||||
// svc := sts.New(mySession)
|
||||
//
|
||||
// // Create a STS client with additional configuration
|
||||
// svc := sts.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
|
||||
func New(p client.ConfigProvider, cfgs ...*aws.Config) *STS {
|
||||
c := p.ClientConfig(ServiceName, cfgs...)
|
||||
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
|
||||
}
|
||||
|
||||
// newClient creates, initializes and returns a new service client instance.
|
||||
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *STS {
|
||||
svc := &STS{
|
||||
Client: client.New(
|
||||
cfg,
|
||||
metadata.ClientInfo{
|
||||
ServiceName: ServiceName,
|
||||
SigningRegion: signingRegion,
|
||||
Endpoint: endpoint,
|
||||
APIVersion: "2011-06-15",
|
||||
},
|
||||
handlers,
|
||||
),
|
||||
}
|
||||
|
||||
// Handlers
|
||||
svc.Handlers.Sign.PushBack(v4.Sign)
|
||||
svc.Handlers.Build.PushBack(query.Build)
|
||||
svc.Handlers.Unmarshal.PushBack(query.Unmarshal)
|
||||
svc.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta)
|
||||
svc.Handlers.UnmarshalError.PushBack(query.UnmarshalError)
|
||||
|
||||
// Run custom client initialization if present
|
||||
if initClient != nil {
|
||||
initClient(svc.Client)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a STS operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *STS) newRequest(op *request.Operation, params, data interface{}) *request.Request {
|
||||
req := c.NewRequest(op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
|
||||
// Package stsiface provides an interface for the AWS Security Token Service.
|
||||
package stsiface
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
)
|
||||
|
||||
// STSAPI is the interface type for sts.STS.
|
||||
type STSAPI interface {
|
||||
AssumeRoleRequest(*sts.AssumeRoleInput) (*request.Request, *sts.AssumeRoleOutput)
|
||||
|
||||
AssumeRole(*sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
|
||||
|
||||
AssumeRoleWithSAMLRequest(*sts.AssumeRoleWithSAMLInput) (*request.Request, *sts.AssumeRoleWithSAMLOutput)
|
||||
|
||||
AssumeRoleWithSAML(*sts.AssumeRoleWithSAMLInput) (*sts.AssumeRoleWithSAMLOutput, error)
|
||||
|
||||
AssumeRoleWithWebIdentityRequest(*sts.AssumeRoleWithWebIdentityInput) (*request.Request, *sts.AssumeRoleWithWebIdentityOutput)
|
||||
|
||||
AssumeRoleWithWebIdentity(*sts.AssumeRoleWithWebIdentityInput) (*sts.AssumeRoleWithWebIdentityOutput, error)
|
||||
|
||||
DecodeAuthorizationMessageRequest(*sts.DecodeAuthorizationMessageInput) (*request.Request, *sts.DecodeAuthorizationMessageOutput)
|
||||
|
||||
DecodeAuthorizationMessage(*sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error)
|
||||
|
||||
GetFederationTokenRequest(*sts.GetFederationTokenInput) (*request.Request, *sts.GetFederationTokenOutput)
|
||||
|
||||
GetFederationToken(*sts.GetFederationTokenInput) (*sts.GetFederationTokenOutput, error)
|
||||
|
||||
GetSessionTokenRequest(*sts.GetSessionTokenInput) (*request.Request, *sts.GetSessionTokenOutput)
|
||||
|
||||
GetSessionToken(*sts.GetSessionTokenInput) (*sts.GetSessionTokenOutput, error)
|
||||
}
|
||||
|
||||
var _ STSAPI = (*sts.STS)(nil)
|
||||
@@ -16,6 +16,7 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
|
||||
- [What's New in Grafana 2.0](http://docs.grafana.org/guides/whats-new-in-v2/)
|
||||
- [What's New in Grafana 2.1](http://docs.grafana.org/guides/whats-new-in-v2-1/)
|
||||
- [What's New in Grafana 2.5](http://docs.grafana.org/guides/whats-new-in-v2-5/)
|
||||
- [What's New in Grafana 3.0](http://docs.grafana.org/guides/whats-new-in-v3/)
|
||||
|
||||
## Features
|
||||
### Graphite Target Editor
|
||||
@@ -78,7 +79,7 @@ the latest master builds [here](http://grafana.org/download/builds)
|
||||
### Dependencies
|
||||
|
||||
- Go 1.5
|
||||
- NodeJS v0.12.0
|
||||
- NodeJS v4+
|
||||
- [Godep](https://github.com/tools/godep)
|
||||
|
||||
### Get Code
|
||||
@@ -109,7 +110,7 @@ go run build.go build
|
||||
|
||||
### Building frontend assets
|
||||
|
||||
To build less to css for the frontend you will need a recent version of of **node (v0.12.0)**,
|
||||
To build less to css for the frontend you will need a recent version of of **node (v4+)**,
|
||||
npm (v2.5.0) and grunt (v0.4.5). Run the following:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -132,12 +132,10 @@ func readVersionFromPackageJson() {
|
||||
if len(parts) > 1 {
|
||||
linuxPackageVersion = parts[0]
|
||||
linuxPackageIteration = parts[1]
|
||||
if linuxPackageIteration != "" {
|
||||
// add timestamp to iteration
|
||||
linuxPackageIteration = fmt.Sprintf("%s%v", linuxPackageIteration, time.Now().Unix())
|
||||
}
|
||||
log.Println(fmt.Sprintf("Iteration %v", linuxPackageIteration))
|
||||
}
|
||||
|
||||
// add timestamp to iteration
|
||||
linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration)
|
||||
}
|
||||
|
||||
type linuxPackageOptions struct {
|
||||
|
||||
@@ -172,6 +172,9 @@ verify_email_enabled = false
|
||||
# Background text for the user field on the login page
|
||||
login_hint = email or username
|
||||
|
||||
# Default UI theme ("dark" or "light")
|
||||
default_theme = dark
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
||||
@@ -155,6 +155,9 @@ check_for_updates = true
|
||||
# Background text for the user field on the login page
|
||||
;login_hint = email or username
|
||||
|
||||
# Default UI theme ("dark" or "light")
|
||||
;default_theme = dark
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
||||
@@ -8,3 +8,10 @@ graphite:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
|
||||
fake-graphite-data:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: graphite
|
||||
FD_PORT: 2003
|
||||
|
||||
|
||||
@@ -4,3 +4,11 @@ influxdb:
|
||||
- "2004:2004"
|
||||
- "8083:8083"
|
||||
- "8086:8086"
|
||||
|
||||
fake-influxdb-data:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: influxdb
|
||||
FD_PORT: 8086
|
||||
|
||||
|
||||
@@ -2,4 +2,10 @@ opentsdb:
|
||||
image: opower/opentsdb:latest
|
||||
ports:
|
||||
- "4242:4242"
|
||||
|
||||
|
||||
fake-opentsdb-data:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: opentsdb
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
prometheus:
|
||||
build: blocks/prometheus
|
||||
net: bridge
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- /var/docker/prometheus:/prometheus-data
|
||||
|
||||
fake-prometheus-data:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
ports:
|
||||
- "9091:9091"
|
||||
environment:
|
||||
FD_DATASOURCE: prom
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ Name | The data source name, important that this is the same as in Grafana v1.x
|
||||
Default | Default data source means that it will be pre-selected for new panels.
|
||||
Credentials profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default. This option was introduced in Grafana 2.5.1
|
||||
Default Region | Used in query editor to set region (can be changed on per query basis)
|
||||
Custom Metrics namespace | Specify the CloudWatch namespace of Custom metrics
|
||||
Assume Role Arn | Specify the ARN of the role to assume
|
||||
|
||||
## Authentication
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ page_keywords: grafana, opentsdb, documentation
|
||||
# OpenTSDB Guide
|
||||
The newest release of Grafana adds additional functionality when using an OpenTSDB Data source.
|
||||
|
||||

|
||||

|
||||
|
||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
|
||||
> NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization.
|
||||
|
||||
|
||||
@@ -39,12 +39,13 @@ entire experience right within Grafana.
|
||||
|
||||
<img src="/img/v3/grafana_net_tour.png">
|
||||
|
||||
A preview of [Grafana.net](http://grafana.net) is launching along with this release. We
|
||||
think it’s the perfect compliment to Grafana.
|
||||
[Grafana.net](https://grafana.net) offers a central repository where the community can come together to discover, create and
|
||||
share plugins (data sources, panels, apps) and dashboards.
|
||||
|
||||
Grafana.net currently offers a central repository where the community
|
||||
can come together to discover and share plugins (Data Sources, Panels,
|
||||
Apps) and Dashboards for Grafana 3.0 and above.
|
||||
We are also working on a hosted Graphite-compatible data source that will be optimized for use with Grafana.
|
||||
It’ll be easy to combine your existing data source(s) with this OpenSaaS option. Finally, Grafana.net can
|
||||
also be a hub to manage all your Grafana instances. You’ll be able to monitor their health and availability,
|
||||
perform dashboard backups, and more.
|
||||
|
||||
We are also working on a hosted Graphite-compatible Data Source that
|
||||
will be optimized for use with Grafana. It’ll be easy to combine your
|
||||
@@ -65,7 +66,6 @@ Grafana 3.0 comes with a new command line tool called grafana-cli. You
|
||||
can easily install plugins from Grafana.net with it. For
|
||||
example:
|
||||
|
||||
|
||||
```
|
||||
grafana-cli install grafana-pie-chart-panel
|
||||
```
|
||||
@@ -188,6 +188,33 @@ you can still install manually from [Grafana.net](http://grafana.net)
|
||||
* KairosDB: This data source has also no longer shipped with Grafana,
|
||||
you can install it manually from [Grafana.net](http://grafana.net)
|
||||
|
||||
## Plugin showcase
|
||||
|
||||
Discovering and installing plugins is very quick and easy with Grafana 3.0 and [Grafana.net](https://grafana.net). Here
|
||||
are a couple that I incurage you try!
|
||||
|
||||
#### [Clock Panel](https://grafana.net/plugins/grafana-clock-panel)
|
||||
Support's both current time and count down mode.
|
||||
<img src="/img/v3/clock_panel.png">
|
||||
|
||||
#### [Pie Chart Panel](https://grafana.net/plugins/grafana-piechart-panel)
|
||||
A simple pie chart panel is now available as an external plugin.
|
||||
<img src="/img/v3/pie_chart_panel.png">
|
||||
|
||||
#### [WorldPing App](https://grafana.net/plugins/raintank-worldping-app)
|
||||
This is full blown Grafana App that adds new panels, data sources and pages to give
|
||||
feature rich global performance monitoring directly from your on-prem Grafana.
|
||||
|
||||
<img src="/img/v3/wP-Screenshot-dash-web.png">
|
||||
|
||||
#### [Zabbix App](https://grafana.net/plugins/alexanderzobnin-zabbix-app)
|
||||
This app contains the already very pouplar Zabbix data source plugin, 2 dashboards and a triggers panel. It is
|
||||
created and maintained by [Alexander Zobnin](https://github.com/alexanderzobnin/grafana-zabbix).
|
||||
|
||||
<img src="/img/v3/zabbix_app.png">
|
||||
|
||||
Checkout the full list of plugins on [Grafana.net](https://grafana.net/plugins)
|
||||
|
||||
## CHANGELOG
|
||||
|
||||
For a detailed list and link to github issues for everything included
|
||||
|
||||
@@ -18,4 +18,5 @@ dashboards, creating users and updating data sources.
|
||||
* [User API](/http_api/user/)
|
||||
* [Admin API](/http_api/admin/)
|
||||
* [Snapshot API](/http_api/snapshot/)
|
||||
* [Preferences API](/http_api/preferences/)
|
||||
* [Other API](/http_api/other/)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
----
|
||||
page_title: Preferences API
|
||||
page_description: Grafana Preferences API Reference
|
||||
page_keywords: grafana, preferences, http, api, documentation
|
||||
---
|
||||
|
||||
# User and Org Preferences API
|
||||
|
||||
Keys:
|
||||
|
||||
- **theme** - One of: ``light``, ``dark``, or an empty string for the default theme
|
||||
- **homeDashboardId** - The numerical ``:id`` of a favorited dashboard, default: ``0``
|
||||
- **timezone** - One of: ``utc``, ``browser``, or an empty string for the default
|
||||
|
||||
Omitting a key will cause the current value to be replaced with the
|
||||
system default value.
|
||||
|
||||
## Get Current User Prefs
|
||||
|
||||
`GET /api/user/preferences`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
GET /api/user/preferences HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"theme":"","homeDashboardId":0,"timezone":""}
|
||||
|
||||
## Update Current User Prefs
|
||||
|
||||
`PUT /api/user/preferences`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
PUT /api/user/preferences HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"theme": "",
|
||||
"homeDashboardId":0,
|
||||
"timezone":"utc"
|
||||
}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
{"message":"Preferences updated"}
|
||||
|
||||
## Get Current Org Prefs
|
||||
|
||||
`GET /api/org/preferences`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
GET /api/org/preferences HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"theme":"","homeDashboardId":0,"timezone":""}
|
||||
|
||||
## Update Current Org Prefs
|
||||
|
||||
`PUT /api/org/preferences`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
PUT /api/org/preferences HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"theme": "",
|
||||
"homeDashboardId":0,
|
||||
"timezone":"utc"
|
||||
}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
{"message":"Preferences updated"}
|
||||
@@ -10,20 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
|
||||
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta71462173753_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb)
|
||||
Stable .deb for Debian-based Linux | [grafana_3.0.2-1463383025_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb)
|
||||
|
||||
## Install Stable
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_2.6.0_amd64.deb
|
||||
|
||||
## Install 3.0 Beta
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_3.0.0-beta71462173753_amd64.deb
|
||||
$ sudo dpkg -i grafana_3.0.2-1463383025_amd64.deb
|
||||
|
||||
## APT Repository
|
||||
|
||||
|
||||
@@ -6,8 +6,33 @@ page_keywords: grafana, installation, mac, osx, guide
|
||||
|
||||
# Installing on Mac
|
||||
|
||||
There is currently no binary build for Mac, but Grafana will happily build on Mac. Read the [build from
|
||||
source](/project/building_from_source) page for instructions on how to
|
||||
build it yourself.
|
||||
Installation can be done using [homebrew](http://brew.sh/)
|
||||
|
||||
Install latest stable:
|
||||
|
||||
```
|
||||
brew install grafana/grafana/grafana
|
||||
```
|
||||
|
||||
To start grafana look at the command printed after the homebrew install completes.
|
||||
|
||||
You can also add the grafana as tap.
|
||||
|
||||
```
|
||||
brew tap grafana/grafana
|
||||
brew install grafana
|
||||
```
|
||||
|
||||
Install latest unstable from master:
|
||||
|
||||
```
|
||||
brew install --HEAD grafana/grafana/grafana
|
||||
```
|
||||
|
||||
To upgrade use the reinstall command
|
||||
|
||||
```
|
||||
brew reinstall --HEAD grafana/grafana/grafana
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -10,43 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
|
||||
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta71462173753.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm)
|
||||
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.2-1463383025.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm)
|
||||
|
||||
## Install Stable Release from package file
|
||||
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-2.6.0-1.x86_64.rpm
|
||||
$ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm
|
||||
|
||||
## Install Beta Release from package file
|
||||
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-3.0.0-beta71462173753.x86_64.rpm
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-3.0.0-beta71462173753.x86_64.rpm
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-3.0.2-1463383025.x86_64.rpm
|
||||
|
||||
## Install via YUM Repository
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable Zip package for Windows | [grafana.2.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
||||
Stable Zip package for Windows | [grafana.3.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.2.windows-x64.zip)
|
||||
|
||||
## Configure
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ module.exports = function(config) {
|
||||
browsers: ['PhantomJS'],
|
||||
captureTimeout: 20000,
|
||||
singleRun: true,
|
||||
autoWatchBatchDelay: 10000,
|
||||
autoWatchBatchDelay: 1000,
|
||||
browserNoActivityTimeout: 60000,
|
||||
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "2.6.0",
|
||||
"testing": "3.0.0-beta7"
|
||||
"stable": "3.0.2",
|
||||
"testing": "3.0.2"
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "3.0.0-beta7",
|
||||
"version": "3.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
@@ -13,7 +13,7 @@
|
||||
"zone.js": "^0.6.6",
|
||||
"autoprefixer": "^6.3.3",
|
||||
"es6-promise": "^3.0.2",
|
||||
"es6-shim": "^0.35.0",
|
||||
"es6-shim": "^0.35.1",
|
||||
"expect.js": "~0.2.0",
|
||||
"glob": "~3.2.7",
|
||||
"grunt": "~0.4.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
deb_ver=3.0.0-beta51460725904
|
||||
rpm_ver=3.0.0-beta51460725904
|
||||
deb_ver=3.0.1
|
||||
rpm_ver=3.0.1-1
|
||||
|
||||
#rpm_ver=3.0.0-1
|
||||
|
||||
@@ -16,7 +16,7 @@ rpm_ver=3.0.0-beta51460725904
|
||||
#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
|
||||
|
||||
#package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
|
||||
package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
|
||||
#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
|
||||
|
||||
# package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
|
||||
# package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm
|
||||
package_cloud push grafana/stable/el/7 grafana-${rpm_ver}.x86_64.rpm
|
||||
package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
@@ -14,6 +16,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
@@ -44,31 +48,96 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
var awsCredentials map[string]*credentials.Credentials = make(map[string]*credentials.Credentials)
|
||||
type cache struct {
|
||||
credential *credentials.Credentials
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
func getCredentials(profile string) *credentials.Credentials {
|
||||
if _, ok := awsCredentials[profile]; ok {
|
||||
return awsCredentials[profile]
|
||||
var awsCredentialCache map[string]cache = make(map[string]cache)
|
||||
var credentialCacheLock sync.RWMutex
|
||||
|
||||
func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
|
||||
credentialCacheLock.RLock()
|
||||
if _, ok := awsCredentialCache[profile]; ok {
|
||||
if awsCredentialCache[profile].expiration != nil &&
|
||||
(*awsCredentialCache[profile].expiration).After(time.Now().UTC()) {
|
||||
result := awsCredentialCache[profile].credential
|
||||
credentialCacheLock.RUnlock()
|
||||
return result
|
||||
}
|
||||
}
|
||||
credentialCacheLock.RUnlock()
|
||||
|
||||
accessKeyId := ""
|
||||
secretAccessKey := ""
|
||||
sessionToken := ""
|
||||
var expiration *time.Time
|
||||
expiration = nil
|
||||
if strings.Index(assumeRoleArn, "arn:aws:iam:") == 0 {
|
||||
params := &sts.AssumeRoleInput{
|
||||
RoleArn: aws.String(assumeRoleArn),
|
||||
RoleSessionName: aws.String("GrafanaSession"),
|
||||
DurationSeconds: aws.Int64(900),
|
||||
}
|
||||
|
||||
stsSess := session.New()
|
||||
stsCreds := credentials.NewChainCredentials(
|
||||
[]credentials.Provider{
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
|
||||
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(stsSess), ExpiryWindow: 5 * time.Minute},
|
||||
})
|
||||
stsConfig := &aws.Config{
|
||||
Region: aws.String(region),
|
||||
Credentials: stsCreds,
|
||||
}
|
||||
svc := sts.New(session.New(stsConfig), stsConfig)
|
||||
resp, err := svc.AssumeRole(params)
|
||||
if err != nil {
|
||||
// ignore
|
||||
log.Error(3, "CloudWatch: Failed to assume role", err)
|
||||
}
|
||||
if resp.Credentials != nil {
|
||||
accessKeyId = *resp.Credentials.AccessKeyId
|
||||
secretAccessKey = *resp.Credentials.SecretAccessKey
|
||||
sessionToken = *resp.Credentials.SessionToken
|
||||
expiration = resp.Credentials.Expiration
|
||||
}
|
||||
}
|
||||
|
||||
sess := session.New()
|
||||
creds := credentials.NewChainCredentials(
|
||||
[]credentials.Provider{
|
||||
&credentials.StaticProvider{Value: credentials.Value{
|
||||
AccessKeyID: accessKeyId,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
}},
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
|
||||
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
|
||||
})
|
||||
awsCredentials[profile] = creds
|
||||
credentialCacheLock.Lock()
|
||||
awsCredentialCache[profile] = cache{
|
||||
credential: creds,
|
||||
expiration: expiration,
|
||||
}
|
||||
credentialCacheLock.Unlock()
|
||||
|
||||
return creds
|
||||
}
|
||||
|
||||
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
|
||||
func getAwsConfig(req *cwRequest) *aws.Config {
|
||||
assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
Credentials: getCredentials(req.DataSource.Database, req.Region, assumeRoleArn),
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
|
||||
cfg := getAwsConfig(req)
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
@@ -104,11 +173,7 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
|
||||
func handleListMetrics(req *cwRequest, c *middleware.Context) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
}
|
||||
|
||||
cfg := getAwsConfig(req)
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
@@ -144,11 +209,7 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
|
||||
func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
}
|
||||
|
||||
cfg := getAwsConfig(req)
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
@@ -187,11 +248,7 @@ func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
|
||||
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
}
|
||||
|
||||
cfg := getAwsConfig(req)
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
@@ -227,11 +284,7 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
|
||||
func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
}
|
||||
|
||||
cfg := getAwsConfig(req)
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
@@ -263,11 +316,7 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
|
||||
func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(req.Region),
|
||||
Credentials: getCredentials(req.DataSource.Database),
|
||||
}
|
||||
|
||||
cfg := getAwsConfig(req)
|
||||
svc := ec2.New(session.New(cfg), cfg)
|
||||
|
||||
reqParam := &struct {
|
||||
|
||||
@@ -166,7 +166,8 @@ func handleGetMetrics(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if namespaceMetrics, err = getMetricsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, getAllMetrics); err != nil {
|
||||
assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||
if namespaceMetrics, err = getMetricsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil {
|
||||
c.JsonApiErr(500, "Unable to call AWS API", err)
|
||||
return
|
||||
}
|
||||
@@ -199,7 +200,8 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if dimensionValues, err = getDimensionsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, getAllMetrics); err != nil {
|
||||
assumeRoleArn := req.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||
if dimensionValues, err = getDimensionsForCustomMetrics(req.Region, reqParam.Parameters.Namespace, req.DataSource.Database, assumeRoleArn, getAllMetrics); err != nil {
|
||||
c.JsonApiErr(500, "Unable to call AWS API", err)
|
||||
return
|
||||
}
|
||||
@@ -214,10 +216,10 @@ func handleGetDimensions(req *cwRequest, c *middleware.Context) {
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func getAllMetrics(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) {
|
||||
func getAllMetrics(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
|
||||
cfg := &aws.Config{
|
||||
Region: aws.String(region),
|
||||
Credentials: getCredentials(database),
|
||||
Credentials: getCredentials(database, region, assumeRoleArn),
|
||||
}
|
||||
|
||||
svc := cloudwatch.New(session.New(cfg), cfg)
|
||||
@@ -244,8 +246,8 @@ func getAllMetrics(region string, namespace string, database string) (cloudwatch
|
||||
|
||||
var metricsCacheLock sync.Mutex
|
||||
|
||||
func getMetricsForCustomMetrics(region string, namespace string, database string, getAllMetrics func(string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
|
||||
result, err := getAllMetrics(region, namespace, database)
|
||||
func getMetricsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
|
||||
result, err := getAllMetrics(region, namespace, database, assumeRoleArn)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@@ -282,8 +284,8 @@ func getMetricsForCustomMetrics(region string, namespace string, database string
|
||||
|
||||
var dimensionsCacheLock sync.Mutex
|
||||
|
||||
func getDimensionsForCustomMetrics(region string, namespace string, database string, getAllMetrics func(string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
|
||||
result, err := getAllMetrics(region, namespace, database)
|
||||
func getDimensionsForCustomMetrics(region string, namespace string, database string, assumeRoleArn string, getAllMetrics func(string, string, string, string) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
|
||||
result, err := getAllMetrics(region, namespace, database, assumeRoleArn)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ func TestCloudWatchMetrics(t *testing.T) {
|
||||
region := "us-east-1"
|
||||
namespace := "Foo"
|
||||
database := "default"
|
||||
f := func(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) {
|
||||
assumeRoleArn := ""
|
||||
f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
|
||||
return cloudwatch.ListMetricsOutput{
|
||||
Metrics: []*cloudwatch.Metric{
|
||||
{
|
||||
@@ -28,7 +29,7 @@ func TestCloudWatchMetrics(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
metrics, _ := getMetricsForCustomMetrics(region, namespace, database, f)
|
||||
metrics, _ := getMetricsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
|
||||
|
||||
Convey("Should contain Test_MetricName", func() {
|
||||
So(metrics, ShouldContain, "Test_MetricName")
|
||||
@@ -39,7 +40,8 @@ func TestCloudWatchMetrics(t *testing.T) {
|
||||
region := "us-east-1"
|
||||
namespace := "Foo"
|
||||
database := "default"
|
||||
f := func(region string, namespace string, database string) (cloudwatch.ListMetricsOutput, error) {
|
||||
assumeRoleArn := ""
|
||||
f := func(region string, namespace string, database string, assumeRoleArn string) (cloudwatch.ListMetricsOutput, error) {
|
||||
return cloudwatch.ListMetricsOutput{
|
||||
Metrics: []*cloudwatch.Metric{
|
||||
{
|
||||
@@ -53,7 +55,7 @@ func TestCloudWatchMetrics(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
dimensionKeys, _ := getDimensionsForCustomMetrics(region, namespace, database, f)
|
||||
dimensionKeys, _ := getDimensionsForCustomMetrics(region, namespace, database, assumeRoleArn, f)
|
||||
|
||||
Convey("Should contain Test_DimensionName", func() {
|
||||
So(dimensionKeys, ShouldContain, "Test_DimensionName")
|
||||
|
||||
@@ -142,6 +142,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
"buildstamp": setting.BuildStamp,
|
||||
"latestVersion": plugins.GrafanaLatestVersion,
|
||||
"hasUpdate": plugins.GrafanaHasUpdate,
|
||||
"env": setting.Env,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
)
|
||||
|
||||
@@ -14,20 +16,17 @@ func upgradeCommand(c CommandLine) error {
|
||||
return err
|
||||
}
|
||||
|
||||
remotePlugins, err2 := s.ListAllPlugins(c.GlobalString("repo"))
|
||||
v, err2 := s.GetPlugin(localPlugin.Id, c.GlobalString("repo"))
|
||||
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
for _, v := range remotePlugins.Plugins {
|
||||
if localPlugin.Id == v.Id {
|
||||
if ShouldUpgrade(localPlugin.Info.Version, v) {
|
||||
s.RemoveInstalledPlugin(pluginsDir, pluginName)
|
||||
return InstallPlugin(localPlugin.Id, "", c)
|
||||
}
|
||||
}
|
||||
if ShouldUpgrade(localPlugin.Info.Version, v) {
|
||||
s.RemoveInstalledPlugin(pluginsDir, pluginName)
|
||||
return InstallPlugin(localPlugin.Id, "", c)
|
||||
}
|
||||
|
||||
log.Infof("%s %s is up to date \n", color.GreenString("✔"), localPlugin.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func ReadPlugin(pluginDir, pluginName string) (m.InstalledPlugin, error) {
|
||||
}
|
||||
|
||||
if res.Id == "" {
|
||||
return m.InstalledPlugin{}, errors.New("could not read find plugin " + pluginName)
|
||||
return m.InstalledPlugin{}, errors.New("could not find plugin " + pluginName + " in " + pluginDir)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@@ -69,13 +69,21 @@ func RemoveInstalledPlugin(pluginPath, id string) error {
|
||||
}
|
||||
|
||||
func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
|
||||
resp, _ := ListAllPlugins(repoUrl)
|
||||
fullUrl := repoUrl + "/repo/" + pluginId
|
||||
|
||||
for _, i := range resp.Plugins {
|
||||
if i.Id == pluginId {
|
||||
return i, nil
|
||||
}
|
||||
res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do()
|
||||
if err != nil {
|
||||
return m.Plugin{}, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return m.Plugin{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode)
|
||||
}
|
||||
|
||||
return m.Plugin{}, errors.New("could not find plugin named \"" + pluginId + "\"")
|
||||
var resp m.Plugin
|
||||
err = res.Body.FromJsonTo(&resp)
|
||||
if err != nil {
|
||||
return m.Plugin{}, errors.New("Could not load plugin data")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
+10
-1
@@ -24,7 +24,16 @@ func GetPluginSettings(orgId int64) (map[string]*m.PluginSettingInfoDTO, error)
|
||||
}
|
||||
|
||||
// default to enabled true
|
||||
opt := &m.PluginSettingInfoDTO{Enabled: true}
|
||||
opt := &m.PluginSettingInfoDTO{
|
||||
PluginId: pluginDef.Id,
|
||||
OrgId: orgId,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// apps are disabled by default
|
||||
if pluginDef.Type == PluginTypeApp {
|
||||
opt.Enabled = false
|
||||
}
|
||||
|
||||
// if it's included in app check app settings
|
||||
if pluginDef.IncludedInAppId != "" {
|
||||
|
||||
@@ -91,14 +91,14 @@ func checkForUpdates() {
|
||||
|
||||
resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json")
|
||||
if err != nil {
|
||||
log.Trace("Failed to get lates.json repo from github: %v", err.Error())
|
||||
log.Trace("Failed to get latest.json repo from github: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer resp2.Body.Close()
|
||||
body, err = ioutil.ReadAll(resp2.Body)
|
||||
if err != nil {
|
||||
log.Trace("Update check failed, reading response from github.net, %v", err.Error())
|
||||
log.Trace("Update check failed, reading response from github.com, %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -26,7 +28,7 @@ func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error
|
||||
}
|
||||
|
||||
res := &m.Preferences{
|
||||
Theme: "dark",
|
||||
Theme: setting.DefaultTheme,
|
||||
Timezone: "browser",
|
||||
HomeDashboardId: 0,
|
||||
}
|
||||
|
||||
+18
-6
@@ -88,6 +88,7 @@ var (
|
||||
AutoAssignOrgRole string
|
||||
VerifyEmailEnabled bool
|
||||
LoginHint string
|
||||
DefaultTheme string
|
||||
|
||||
// Http auth
|
||||
AdminUser string
|
||||
@@ -454,6 +455,7 @@ func NewConfigContext(args *CommandLineArgs) error {
|
||||
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
|
||||
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
|
||||
LoginHint = users.Key("login_hint").String()
|
||||
DefaultTheme = users.Key("default_theme").String()
|
||||
|
||||
// anonymous access
|
||||
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
|
||||
@@ -526,6 +528,17 @@ var logLevels = map[string]int{
|
||||
"Critical": 5,
|
||||
}
|
||||
|
||||
func getLogLevel(key string, defaultName string) (string, int) {
|
||||
levelName := Cfg.Section(key).Key("level").In(defaultName, []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
log.Fatal(4, "Unknown log level: %s", levelName)
|
||||
}
|
||||
|
||||
return levelName, level
|
||||
}
|
||||
|
||||
func initLogging(args *CommandLineArgs) {
|
||||
//close any existing log handlers.
|
||||
log.Close()
|
||||
@@ -533,8 +546,12 @@ func initLogging(args *CommandLineArgs) {
|
||||
LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
|
||||
LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
|
||||
|
||||
defaultLevelName, _ := getLogLevel("log", "Info")
|
||||
|
||||
LogConfigs = make([]util.DynMap, len(LogModes))
|
||||
|
||||
for i, mode := range LogModes {
|
||||
|
||||
mode = strings.TrimSpace(mode)
|
||||
sec, err := Cfg.GetSection("log." + mode)
|
||||
if err != nil {
|
||||
@@ -542,12 +559,7 @@ func initLogging(args *CommandLineArgs) {
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.Section("log."+mode).Key("level").In("Trace",
|
||||
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
log.Fatal(4, "Unknown log level: %s", levelName)
|
||||
}
|
||||
_, level := getLogLevel("log."+mode, defaultLevelName)
|
||||
|
||||
// Generate log configuration.
|
||||
switch mode {
|
||||
|
||||
+3
-1
@@ -42,7 +42,9 @@ export class GrafanaApp {
|
||||
app.constant('grafanaVersion', "@grafanaVersion@");
|
||||
|
||||
app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) => {
|
||||
//$compileProvider.debugInfoEnabled(false);
|
||||
if (config.buildInfo.env !== 'development') {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
this.registerFunctions.controller = $controllerProvider.register;
|
||||
this.registerFunctions.directive = $compileProvider.directive;
|
||||
|
||||
@@ -28,7 +28,7 @@ export function parse(text, roundUp?) {
|
||||
mathString = text.substring(index + 2);
|
||||
}
|
||||
// We're going to just require ISO8601 timestamps, k?
|
||||
time = moment(parseString);
|
||||
time = moment(parseString, moment.ISO_8601);
|
||||
}
|
||||
|
||||
if (!mathString.length) {
|
||||
|
||||
@@ -396,6 +396,7 @@ function($, _) {
|
||||
kbn.valueFormats.ev = kbn.formatBuilders.decimalSIPrefix('eV');
|
||||
kbn.valueFormats.amp = kbn.formatBuilders.decimalSIPrefix('A');
|
||||
kbn.valueFormats.volt = kbn.formatBuilders.decimalSIPrefix('V');
|
||||
kbn.valueFormats.dBm = kbn.formatBuilders.decimalSIPrefix('dBm');
|
||||
|
||||
// Temperature
|
||||
kbn.valueFormats.celsius = kbn.formatBuilders.fixedUnit('°C');
|
||||
@@ -677,6 +678,7 @@ function($, _) {
|
||||
{text: 'electron volt (eV)', value: 'ev' },
|
||||
{text: 'Ampere (A)', value: 'amp' },
|
||||
{text: 'Volt (V)', value: 'volt' },
|
||||
{text: 'Decibel-milliwatt (dBm)', value: 'dBm' },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -55,10 +55,11 @@ define([
|
||||
}, this);
|
||||
});
|
||||
|
||||
promiseCached = $q.all(promises)
|
||||
.then(function() {
|
||||
return list;
|
||||
});
|
||||
promiseCached = $q.all(promises).then(function() {
|
||||
return list;
|
||||
}).catch(function(err) {
|
||||
$rootScope.appEvent('alert-error', ['Annotations failed', (err.message || err)]);
|
||||
});
|
||||
|
||||
return promiseCached;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ function (angular, $, _, moment) {
|
||||
|
||||
// cleans meta data and other non peristent state
|
||||
p.getSaveModelClone = function() {
|
||||
var copy = angular.copy(this);
|
||||
var copy = $.extend(true, {}, this);
|
||||
delete copy.meta;
|
||||
return copy;
|
||||
};
|
||||
|
||||
@@ -52,6 +52,8 @@ function (angular, _) {
|
||||
else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
|
||||
row.panels = _.without(row.panels, panel);
|
||||
j = j - 1;
|
||||
} else if (row.repeat || row.repeatRowId) {
|
||||
continue;
|
||||
} else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) {
|
||||
panel.scopedVars = {};
|
||||
}
|
||||
@@ -118,7 +120,6 @@ function (angular, _) {
|
||||
panel = copy.panels[i];
|
||||
panel.scopedVars = {};
|
||||
panel.scopedVars[variable.name] = option;
|
||||
panel.repeatIteration = this.iteration;
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ define([
|
||||
|
||||
if (!isNaN(value)) {
|
||||
var epoch = parseInt(value);
|
||||
return moment(epoch);
|
||||
return moment.utc(epoch);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -10,7 +10,7 @@ var template = `
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<i class="icon-gf icon-gf-datasource"></i>
|
||||
<i class="icon-gf icon-gf-datasources"></i>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
Panel data source
|
||||
|
||||
@@ -97,7 +97,8 @@ function (angular, _) {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
return str.indexOf('$' + variableName) !== -1 || str.indexOf('[[' + variableName + ']]') !== -1;
|
||||
var match = this._regex.exec(str);
|
||||
return match && (match[1] === variableName || match[2] === variableName);
|
||||
};
|
||||
|
||||
this.highlightVariablesAsHtml = function(str) {
|
||||
|
||||
@@ -204,7 +204,7 @@ function (angular, _, kbn) {
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
options.push({text: 'No datasurces found', value: ''});
|
||||
options.push({text: 'No data sources found', value: ''});
|
||||
}
|
||||
|
||||
variable.options = options;
|
||||
|
||||
@@ -24,4 +24,11 @@
|
||||
Namespaces of Custom Metrics
|
||||
</info-popover>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Assume Role ARN</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.assumeRoleArn' placeholder="arn:aws:iam:*"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
ARN of Assume Role
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class InfluxDatasource {
|
||||
var i, y;
|
||||
|
||||
var allQueries = _.map(options.targets, (target) => {
|
||||
if (target.hide) { return []; }
|
||||
if (target.hide) { return ""; }
|
||||
|
||||
queryTargets.push(target);
|
||||
|
||||
@@ -54,8 +54,12 @@ export default class InfluxDatasource {
|
||||
var query = queryModel.render(true);
|
||||
query = query.replace(/\$interval/g, (target.interval || options.interval));
|
||||
return query;
|
||||
|
||||
}).join(";");
|
||||
}).reduce((acc, current) => {
|
||||
if (current !== "") {
|
||||
acc += ";" + current;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
|
||||
// replace grafana variables
|
||||
allQueries = allQueries.replace(/\$timeFilter/g, timeFilter);
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<ul>
|
||||
<li>$m = replaced with measurement name</li>
|
||||
<li>$measurement = replaced with measurement name</li>
|
||||
<li>$1 - $9 = replaced with part of measurement name (if you seperate your measurement name with dots)</li>
|
||||
<li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li>
|
||||
<li>$col = replaced with column name</li>
|
||||
<li>$tag_hostname = replaced with the value of the hostname tag</li>
|
||||
<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
|
||||
|
||||
@@ -16,7 +16,8 @@ export class OpenTsConfigCtrl {
|
||||
|
||||
tsdbVersions = [
|
||||
{name: '<=2.1', value: 1},
|
||||
{name: '>=2.2', value: 2},
|
||||
{name: '==2.2', value: 2},
|
||||
{name: '==2.3', value: 3},
|
||||
];
|
||||
|
||||
tsdbResolutions = [
|
||||
|
||||
@@ -54,13 +54,12 @@ function (angular, _, dateMath) {
|
||||
});
|
||||
|
||||
return this.performTimeSeriesQuery(queries, start, end).then(function(response) {
|
||||
var metricToTargetMapping = mapMetricsToTargets(response.data, options);
|
||||
var metricToTargetMapping = mapMetricsToTargets(response.data, options, this.tsdbVersion);
|
||||
var result = _.map(response.data, function(metricData, index) {
|
||||
index = metricToTargetMapping[index];
|
||||
if (index === -1) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
this._saveTagKeys(metricData);
|
||||
|
||||
return transformMetricData(metricData, groupByTags, options.targets[index], options, this.tsdbResolution);
|
||||
@@ -114,6 +113,9 @@ function (angular, _, dateMath) {
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true
|
||||
};
|
||||
if (this.tsdbVersion === 3) {
|
||||
reqBody.showQuery = true;
|
||||
}
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
@@ -393,23 +395,27 @@ function (angular, _, dateMath) {
|
||||
return query;
|
||||
}
|
||||
|
||||
function mapMetricsToTargets(metrics, options) {
|
||||
function mapMetricsToTargets(metrics, options, tsdbVersion) {
|
||||
var interpolatedTagValue;
|
||||
return _.map(metrics, function(metricData) {
|
||||
return _.findIndex(options.targets, function(target) {
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.filters, function(filter) {
|
||||
return filter.tagk === interpolatedTagValue === "*";
|
||||
});
|
||||
} else {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.tags, function(tagV, tagK) {
|
||||
interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe');
|
||||
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
|
||||
});
|
||||
}
|
||||
});
|
||||
if (tsdbVersion === 3) {
|
||||
return metricData.query.index;
|
||||
} else {
|
||||
return _.findIndex(options.targets, function(target) {
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.filters, function(filter) {
|
||||
return filter.tagk === interpolatedTagValue === "*";
|
||||
});
|
||||
} else {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.tags, function(tagV, tagK) {
|
||||
interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe');
|
||||
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.tsdbVersion == 2">
|
||||
<div class="gf-form" ng-if="ctrl.tsdbVersion >= 2">
|
||||
<label class="gf-form-label query-keyword width-6">Fill</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion == 2">
|
||||
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion >= 2">
|
||||
<div class="gf-form">
|
||||
|
||||
<label class="gf-form-label query-keyword width-8">
|
||||
@@ -170,7 +170,7 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-8">
|
||||
Tags
|
||||
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion == 2">
|
||||
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion >= 2">
|
||||
Please use filters, tags are deprecated in opentsdb 2.2
|
||||
</info-popover>
|
||||
</label>
|
||||
|
||||
@@ -10,7 +10,7 @@ import PrometheusMetricFindQuery from './metric_find_query';
|
||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
||||
|
||||
/** @ngInject */
|
||||
export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
|
||||
this.type = 'prometheus';
|
||||
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
|
||||
this.name = instanceSettings.name;
|
||||
@@ -43,7 +43,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
|
||||
}
|
||||
|
||||
function interpolateQueryExpr(value, variable, defaultFormatFn) {
|
||||
this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
|
||||
// if no multi or include all do not regexEscape
|
||||
if (!variable.multi && !variable.includeAll) {
|
||||
return value;
|
||||
@@ -59,6 +59,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
// Called once per panel (graph)
|
||||
this.query = function(options) {
|
||||
var self = this;
|
||||
var start = getPrometheusTime(options.range.from, false);
|
||||
var end = getPrometheusTime(options.range.to, true);
|
||||
|
||||
@@ -73,7 +74,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
activeTargets.push(target);
|
||||
|
||||
var query: any = {};
|
||||
query.expr = templateSrv.replace(target.expr, options.scopedVars, interpolateQueryExpr);
|
||||
query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
|
||||
|
||||
var interval = target.interval || options.interval;
|
||||
var intervalFactor = target.intervalFactor || 1;
|
||||
@@ -99,7 +100,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return this.performTimeSeriesQuery(query, start, end);
|
||||
}, this));
|
||||
|
||||
var self = this;
|
||||
return $q.all(allQueryPromise)
|
||||
.then(function(allResponse) {
|
||||
var result = [];
|
||||
@@ -140,12 +140,12 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = templateSrv.replace(query);
|
||||
interpolated = templateSrv.replace(query, {}, this.interpolateQueryExpr);
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
||||
var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated);
|
||||
var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated, timeSrv);
|
||||
return metricFindQuery.process();
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = templateSrv.replace(expr, {}, interpolateQueryExpr);
|
||||
interpolated = templateSrv.replace(expr, {}, this.interpolateQueryExpr);
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
define([
|
||||
'lodash',
|
||||
'moment',
|
||||
'lodash'
|
||||
],
|
||||
function (_, moment) {
|
||||
function (_) {
|
||||
'use strict';
|
||||
|
||||
function PrometheusMetricFindQuery(datasource, query) {
|
||||
function PrometheusMetricFindQuery(datasource, query, timeSrv) {
|
||||
this.datasource = datasource;
|
||||
this.query = query;
|
||||
this.range = timeSrv.timeRange();
|
||||
}
|
||||
|
||||
PrometheusMetricFindQuery.prototype.process = function() {
|
||||
@@ -51,7 +51,9 @@ function (_, moment) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
url = '/api/v1/series?match[]=' + encodeURIComponent(metric);
|
||||
url = '/api/v1/series?match[]=' + encodeURIComponent(metric)
|
||||
+ '&start=' + (this.range.from.valueOf() / 1000)
|
||||
+ '&end=' + (this.range.to.valueOf() / 1000);
|
||||
|
||||
return this.datasource._request('GET', url)
|
||||
.then(function(result) {
|
||||
@@ -86,7 +88,7 @@ function (_, moment) {
|
||||
};
|
||||
|
||||
PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) {
|
||||
var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (moment().valueOf() / 1000);
|
||||
var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (this.range.to.valueOf() / 1000);
|
||||
|
||||
return this.datasource._request('GET', url)
|
||||
.then(function(result) {
|
||||
@@ -107,7 +109,9 @@ function (_, moment) {
|
||||
};
|
||||
|
||||
PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) {
|
||||
var url = '/api/v1/series?match[]=' + encodeURIComponent(query);
|
||||
var url = '/api/v1/series?match[]=' + encodeURIComponent(query)
|
||||
+ '&start=' + (this.range.from.valueOf() / 1000)
|
||||
+ '&end=' + (this.range.to.valueOf() / 1000);
|
||||
|
||||
var self = this;
|
||||
return this.datasource._request('GET', url)
|
||||
|
||||
@@ -83,7 +83,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
|
||||
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
|
||||
var expr = {
|
||||
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars),
|
||||
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
|
||||
range_input: rangeDiff + 's',
|
||||
end_input: endTime,
|
||||
step_input: '',
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('PrometheusMetricFindQuery', function() {
|
||||
data: ["value1", "value2", "value3"]
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)');
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
@@ -43,13 +43,22 @@ describe('PrometheusMetricFindQuery', function() {
|
||||
{__name__: "metric", resource: "value3"}
|
||||
]
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)');
|
||||
ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
expect(results.length).to.be(3);
|
||||
});
|
||||
it('label_values(metric, resource) should pass correct time', function() {
|
||||
ctx.timeSrv.setTime({ from: moment.utc('2011-01-01'), to: moment.utc('2015-01-01') });
|
||||
ctx.$httpBackend.expect('GET',
|
||||
/proxied\/api\/v1\/series\?match\[\]=metric&start=1293840000&end=1420070400/).respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() {
|
||||
response = {
|
||||
status: "success",
|
||||
@@ -59,8 +68,8 @@ describe('PrometheusMetricFindQuery', function() {
|
||||
{__name__: "metric", resource: "value3"}
|
||||
]
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)');
|
||||
ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
@@ -72,7 +81,7 @@ describe('PrometheusMetricFindQuery', function() {
|
||||
data: ["metric1","metric2","metric3","nomatch"]
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)');
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
@@ -90,7 +99,7 @@ describe('PrometheusMetricFindQuery', function() {
|
||||
}
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response);
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)');
|
||||
var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv);
|
||||
pm.process().then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
ctx.$rootScope.$apply();
|
||||
|
||||
@@ -282,7 +282,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel['x-axis'],
|
||||
show: panel.xaxis.show,
|
||||
mode: "time",
|
||||
min: min,
|
||||
max: max,
|
||||
@@ -452,12 +452,21 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
|
||||
url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
|
||||
url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
|
||||
url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : '';
|
||||
url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : '';
|
||||
url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : '';
|
||||
url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : '';
|
||||
url += panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
if (panel.yaxes && panel.yaxes.length > 0) {
|
||||
var showYaxis = false;
|
||||
for(var i = 0; panel.yaxes.length > i; i++) {
|
||||
if (panel.yaxes[i].show) {
|
||||
url += (panel.yaxes[i].min !== null && panel.yaxes[i].min !== undefined) ? '&yMin=' + panel.yaxes[i].min : '';
|
||||
url += (panel.yaxes[i].max !== null && panel.yaxes[i].max !== undefined) ? '&yMax=' + panel.yaxes[i].max : '';
|
||||
showYaxis = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
url += showYaxis ? '' : '&hideYAxis=true';
|
||||
}
|
||||
|
||||
url += panel.xaxis.show ? '' : '&hideAxes=true';
|
||||
|
||||
switch(panel.yaxes[0].format) {
|
||||
case 'bytes':
|
||||
|
||||
@@ -81,9 +81,9 @@ function ($) {
|
||||
// Stacked series can increase its length on each new stacked serie if null points found,
|
||||
// to speed the index search we begin always on the last found hoverIndex.
|
||||
var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
|
||||
results.push({ value: value, hoverIndex: newhoverIndex });
|
||||
results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label });
|
||||
} else {
|
||||
results.push({ value: value, hoverIndex: hoverIndex });
|
||||
results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,18 @@ function ($) {
|
||||
|
||||
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
|
||||
|
||||
// Dynamically reorder the hovercard for the current time point if the
|
||||
// option is enabled.
|
||||
if (panel.tooltip.ordering === 'decreasing') {
|
||||
seriesHoverInfo.sort(function(a, b) {
|
||||
return parseFloat(b.value) - parseFloat(a.value);
|
||||
});
|
||||
} else if (panel.tooltip.ordering === 'increasing') {
|
||||
seriesHoverInfo.sort(function(a, b) {
|
||||
return parseFloat(a.value) - parseFloat(b.value);
|
||||
});
|
||||
}
|
||||
|
||||
for (i = 0; i < seriesHoverInfo.length; i++) {
|
||||
hoverInfo = seriesHoverInfo[i];
|
||||
|
||||
@@ -150,7 +162,7 @@ function ($) {
|
||||
value = series.formatValue(hoverInfo.value);
|
||||
|
||||
seriesHtml += '<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
|
||||
seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
|
||||
seriesHtml += '<i class="fa fa-minus" style="color:' + hoverInfo.color +';"></i> ' + hoverInfo.label + ':</div>';
|
||||
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
|
||||
plot.highlight(i, hoverInfo.hoverIndex);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
shared: true,
|
||||
ordering: 'alphabetical',
|
||||
msResolution: false,
|
||||
},
|
||||
// time overrides
|
||||
|
||||
@@ -42,23 +42,29 @@
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Misc options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Null value</label>
|
||||
<label class="gf-form-label width-9">Null value</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-8" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Renderer</label>
|
||||
<label class="gf-form-label width-9">Renderer</label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.renderer" ng-options="f for f in ['flot', 'png']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Tooltip mode</label>
|
||||
<label class="gf-form-label width-9">Tooltip mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.shared" ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Tooltip ordering<tip>The ordering from top to bottom</tip></label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.ordering" ng-options="f.value as f.text for f in [{text: 'Alphabetical', value: 'alphabetical'}, {text: 'Increasing', value: 'increasing'}, {text: 'Decreasing', value: 'decreasing'}]" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
|
||||
@@ -204,35 +204,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section" style="margin-bottom: 20px">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
<strong>Value to text mapping</strong>
|
||||
</li>
|
||||
<li class="tight-form-item" ng-repeat-start="map in ctrl.panel.valueMaps">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</li>
|
||||
<li ng-repeat-end>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">
|
||||
Type
|
||||
</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mappingType"
|
||||
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==1">
|
||||
<h5 class="page-heading">Set value mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
|
||||
</span>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</span>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addValueMap();">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add a value mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==2">
|
||||
<h5 class="page-heading">Set range mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="rangeMap in ctrl.panel.rangeMaps">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeRangeMap(rangeMap)"></i>
|
||||
</span>
|
||||
<span class="gf-form-label">From</span>
|
||||
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">To</span>
|
||||
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">Text</span>
|
||||
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addRangeMap()">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add a range mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,6 +35,14 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
valueMaps: [
|
||||
{ value: 'null', op: '=', text: 'N/A' }
|
||||
],
|
||||
mappingTypes: [
|
||||
{name: 'value to text', value: 1},
|
||||
{name: 'range to text', value: 2},
|
||||
],
|
||||
rangeMaps: [
|
||||
{ from: 'null', to: 'null', text: 'N/A' }
|
||||
],
|
||||
mappingType: 1,
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
@@ -73,6 +81,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
onInitEditMode() {
|
||||
this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2);
|
||||
this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3);
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
|
||||
@@ -192,23 +201,45 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
// check value to text mappings
|
||||
for (var i = 0; i < this.panel.valueMaps.length; i++) {
|
||||
var map = this.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
// check value to text mappings if its enabled
|
||||
if (this.panel.mappingType === 1) {
|
||||
for (var i = 0; i < this.panel.valueMaps.length; i++) {
|
||||
var map = this.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if (this.panel.mappingType === 2) {
|
||||
for (var i = 0; i < this.panel.rangeMaps.length; i++) {
|
||||
var map = this.panel.rangeMaps[i];
|
||||
// special null case
|
||||
if (map.from === 'null' && map.to === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
// value/number to range mapping
|
||||
var from = parseFloat(map.from);
|
||||
var to = parseFloat(map.to);
|
||||
if (to >= data.valueRounded && from <= data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +258,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.panel.valueMaps.push({value: '', op: '=', text: '' });
|
||||
}
|
||||
|
||||
removeRangeMap(rangeMap) {
|
||||
var index = _.indexOf(this.panel.rangeMaps, rangeMap);
|
||||
this.panel.rangeMaps.splice(index, 1);
|
||||
this.render();
|
||||
};
|
||||
|
||||
addRangeMap() {
|
||||
this.panel.rangeMaps.push({from: '', to: '', text: ''});
|
||||
}
|
||||
|
||||
link(scope, elem, attrs, ctrl) {
|
||||
var $location = this.$location;
|
||||
var linkSrv = this.linkSrv;
|
||||
|
||||
@@ -84,4 +84,29 @@ describe('SingleStatCtrl', function() {
|
||||
expect(ctx.data.valueFormated).to.be('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.datapoints = [[41,50]];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
|
||||
});
|
||||
|
||||
it('Should replace value with text OK', function() {
|
||||
expect(ctx.data.valueFormated).to.be('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.datapoints = [[65,75]];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', function() {
|
||||
expect(ctx.data.valueFormated).to.be('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 116 KiB |
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'grafana-icons';
|
||||
src: url('../fonts/grafana-icons.eot?h6rv8b');
|
||||
src: url('../fonts/grafana-icons.eot?h6rv8b#iefix') format('embedded-opentype'),
|
||||
url('../fonts/grafana-icons.ttf?h6rv8b') format('truetype'),
|
||||
url('../fonts/grafana-icons.woff?h6rv8b') format('woff'),
|
||||
url('../fonts/grafana-icons.svg?h6rv8b#grafana-icons') format('svg');
|
||||
src: url('../fonts/grafana-icons.eot?okx5td');
|
||||
src: url('../fonts/grafana-icons.eot?okx5td#iefix') format('embedded-opentype'),
|
||||
url('../fonts/grafana-icons.ttf?okx5td') format('truetype'),
|
||||
url('../fonts/grafana-icons.woff?okx5td') format('woff'),
|
||||
url('../fonts/grafana-icons.svg?okx5td#grafana-icons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -61,6 +61,9 @@
|
||||
.icon-gf-endpoint:before {
|
||||
content: "\e609";
|
||||
}
|
||||
.icon-gf-page:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-gf-filter:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
@@ -112,9 +115,6 @@
|
||||
.icon-gf-save:before {
|
||||
content: "\e614";
|
||||
}
|
||||
.icon-gf-settings:before {
|
||||
content: "\e615";
|
||||
}
|
||||
.icon-gf-share:before {
|
||||
content: "\e616";
|
||||
}
|
||||
@@ -124,10 +124,13 @@
|
||||
.icon-gf-search:before {
|
||||
content: "\e618";
|
||||
}
|
||||
.icon-gf-tag-add:before {
|
||||
.icon-gf-settings:before {
|
||||
content: "\e615";
|
||||
}
|
||||
.icon-gf-add:before {
|
||||
content: "\e619";
|
||||
}
|
||||
.icon-gf-tag-remove:before {
|
||||
.icon-gf-remove:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
.icon-gf-video:before {
|
||||
@@ -169,6 +172,12 @@
|
||||
.icon-gf-scale:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-gf-pending:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-gf-verified:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
.icon-gf-worldping:before {
|
||||
content: "\e627";
|
||||
}
|
||||
@@ -176,10 +185,3 @@
|
||||
content: "\e903";
|
||||
}
|
||||
|
||||
.icon-gf-app:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-gf-datasource:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ define([
|
||||
|
||||
it('should generate a repeartRowId based on repeat row index', function() {
|
||||
expect(ctx.rows[1].repeatRowId).to.be(1);
|
||||
expect(ctx.rows[1].repeatIteration).to.be(ctx.dynamicDashboardSrv.iteration);
|
||||
});
|
||||
|
||||
it('should set scopedVars on row panels', function() {
|
||||
|
||||
@@ -138,6 +138,10 @@ define([
|
||||
this.replace = function(target) {
|
||||
return target;
|
||||
};
|
||||
|
||||
this.setTime = function(time) {
|
||||
this.time = time;
|
||||
};
|
||||
}
|
||||
|
||||
function ContextSrvStub() {
|
||||
|
||||
@@ -190,6 +190,11 @@ define([
|
||||
expect(contains).to.be(true);
|
||||
});
|
||||
|
||||
it('should not find it if only part matches with $var syntax', function() {
|
||||
var contains = _templateSrv.containsVariable('this.$ServerDomain.filters', 'Server');
|
||||
expect(contains).to.be(false);
|
||||
});
|
||||
|
||||
it('should find it with [[var]] syntax', function() {
|
||||
var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test');
|
||||
expect(contains).to.be(true);
|
||||
|
||||
@@ -29,6 +29,7 @@ define([
|
||||
|
||||
beforeEach(function() {
|
||||
dash = _dashboardSrv.create({
|
||||
refresh: false,
|
||||
rows: [
|
||||
{
|
||||
panels: [{ test: "asd", legend: { } }]
|
||||
|
||||
Reference in New Issue
Block a user