mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'issue-6566' of https://github.com/benrubson/grafana into benrubson-issue-6566
This commit is contained in:
commit
8d80812601
@ -5,6 +5,13 @@
|
||||
* **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528)
|
||||
* **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530)
|
||||
* **Cloudwatch**: Fixed cloudwatch datasource requesting to many datapoints, [#6544](https://github.com/grafana/grafana/issues/6544)
|
||||
* **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598)
|
||||
* **Templating**: Newly added variable was not visible directly only after dashboard reload, [#6622](https://github.com/grafana/grafana/issues/6622)
|
||||
|
||||
### Enhancements
|
||||
* **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595)
|
||||
* **Templating**: Don't persist variable options with refresh option [#6586](https://github.com/grafana/grafana/issues/6586)
|
||||
* **Alerting**: Add ability to have OR conditions (and mixing AND & OR) [#6579](https://github.com/grafana/grafana/issues/6579)
|
||||
|
||||
# 4.0-beta1 (2016-11-09)
|
||||
|
||||
|
@ -98,6 +98,6 @@ Amazon S3 for this and Webdav. So to set that up you need to configure the
|
||||
[external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini
|
||||
config file.
|
||||
|
||||
This is not an optional requirement, you can get slack and email notifications without setting this up.
|
||||
This is an optional requirement, you can get slack and email notifications without setting this up.
|
||||
|
||||
|
||||
|
@ -55,7 +55,10 @@ Currently the only condition type that exists is a `Query` condition that allows
|
||||
specify a query letter, time range and an aggregation function. The letter refers to
|
||||
a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is
|
||||
a single value that is then used in the threshold check. The query used in an alert rule cannot
|
||||
contain any template variables. Currently we only support `AND` operator between conditions.
|
||||
contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially.
|
||||
For example, we have 3 conditions in the following order:
|
||||
`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)`
|
||||
so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE.
|
||||
|
||||
We plan to add other condition types in the future, like `Other Alert`, where you can include the state
|
||||
of another alert in your conditions, and `Time Of Day`.
|
||||
|
@ -30,11 +30,5 @@ Even though the data source type name is with lowercase `g`, the directive uses
|
||||
that is how angular directives needs to be named in order to match an element with name `<metric-query-editor-graphite />`.
|
||||
You also specify the query controller here instead of in the query.editor.html partial like before.
|
||||
|
||||
### query.editor.html
|
||||
|
||||
This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html
|
||||
should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference.
|
||||
You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`).
|
||||
These query reference letters are going to be utilized in a later feature.
|
||||
|
||||
|
||||
|
@ -85,6 +85,34 @@ page_keywords: grafana, admin, http, api, documentation, orgs, organisation
|
||||
}
|
||||
}
|
||||
|
||||
## Create Organisation
|
||||
|
||||
`POST /api/org`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
POST /api/org HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name":"New Org."
|
||||
}
|
||||
|
||||
|
||||
**Example Response**:
|
||||
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orgId":"1",
|
||||
"message":"Organization created"
|
||||
}
|
||||
|
||||
|
||||
|
||||
## Update current Organisation
|
||||
|
||||
`PUT /api/org`
|
||||
|
@ -141,6 +141,18 @@ those options.
|
||||
- [OpenTSDB]({{< relref "datasources/opentsdb.md" >}})
|
||||
- [Prometheus]({{< relref "datasources/prometheus.md" >}})
|
||||
|
||||
### Server side image rendering
|
||||
|
||||
Server side image (png) rendering is a feature that is optional but very useful when sharing visualizations,
|
||||
for example in alert notifications.
|
||||
|
||||
If the image is missing text make sure you have font packages installed.
|
||||
|
||||
```
|
||||
yum install fontconfig
|
||||
yum install freetype*
|
||||
yum install urw-fonts
|
||||
```
|
||||
|
||||
## Installing from binary tar file
|
||||
|
||||
|
@ -119,7 +119,8 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response {
|
||||
res := backendCmd.Result
|
||||
|
||||
dtoRes := &dtos.AlertTestResult{
|
||||
Firing: res.Firing,
|
||||
Firing: res.Firing,
|
||||
ConditionEvals: res.ConditionEvals,
|
||||
}
|
||||
|
||||
if res.Error != nil {
|
||||
|
@ -307,4 +307,5 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
InitAppPluginRoutes(r)
|
||||
|
||||
r.NotFound(NotFoundHandler)
|
||||
}
|
||||
|
@ -35,11 +35,12 @@ type AlertTestCommand struct {
|
||||
}
|
||||
|
||||
type AlertTestResult struct {
|
||||
Firing bool `json:"firing"`
|
||||
TimeMs string `json:"timeMs"`
|
||||
Error string `json:"error,omitempty"`
|
||||
EvalMatches []*EvalMatch `json:"matches,omitempty"`
|
||||
Logs []*AlertTestResultLog `json:"logs,omitempty"`
|
||||
Firing bool `json:"firing"`
|
||||
ConditionEvals string `json:"conditionEvals"`
|
||||
TimeMs string `json:"timeMs"`
|
||||
Error string `json:"error,omitempty"`
|
||||
EvalMatches []*EvalMatch `json:"matches,omitempty"`
|
||||
Logs []*AlertTestResultLog `json:"logs,omitempty"`
|
||||
}
|
||||
|
||||
type AlertTestResultLog struct {
|
||||
|
@ -96,7 +96,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
}
|
||||
sslcli := &http.Client{Transport: tr}
|
||||
|
||||
oauthCtx = context.TODO()
|
||||
oauthCtx = context.Background()
|
||||
oauthCtx = context.WithValue(oauthCtx, oauth2.HTTPClient, sslcli)
|
||||
}
|
||||
|
||||
@ -106,6 +106,8 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
|
||||
return
|
||||
}
|
||||
// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
|
||||
token.TokenType = "Bearer"
|
||||
|
||||
ctx.Logger.Debug("OAuthLogin Got token")
|
||||
|
||||
|
@ -187,6 +187,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = title
|
||||
ctx.Data["AppSubUrl"] = setting.AppSubUrl
|
||||
ctx.HTML(status, strconv.Itoa(status))
|
||||
}
|
||||
|
||||
|
@ -19,53 +19,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/go-macaron/inject"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
panicHtml = `<html>
|
||||
<head><title>PANIC: %s</title>
|
||||
<meta charset="utf-8" />
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: "Roboto", sans-serif;
|
||||
color: #333333;
|
||||
background-color: #ea5343;
|
||||
margin: 0px;
|
||||
}
|
||||
h1 {
|
||||
color: #d04526;
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-bottom: 1px dashed #2b3848;
|
||||
}
|
||||
pre {
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid #2b3848;
|
||||
background-color: #ffffff;
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
</head><body>
|
||||
<h1>PANIC</h1>
|
||||
<pre style="font-weight: bold;">%s</pre>
|
||||
<pre>%s</pre>
|
||||
</body>
|
||||
</html>`
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
@ -151,21 +112,34 @@ func Recovery() macaron.Handler {
|
||||
|
||||
panicLogger.Error("Request error", "error", err, "stack", string(stack))
|
||||
|
||||
// Lookup the current responsewriter
|
||||
val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
|
||||
res := val.Interface().(http.ResponseWriter)
|
||||
c.Data["Title"] = "Server Error"
|
||||
c.Data["AppSubUrl"] = setting.AppSubUrl
|
||||
|
||||
if theErr, ok := err.(error); ok {
|
||||
c.Data["Title"] = theErr.Error()
|
||||
}
|
||||
|
||||
// respond with panic message while in development mode
|
||||
var body []byte
|
||||
if setting.Env == setting.DEV {
|
||||
res.Header().Set("Content-Type", "text/html")
|
||||
body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
|
||||
c.Data["ErrorMsg"] = string(stack)
|
||||
}
|
||||
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
if nil != body {
|
||||
res.Write(body)
|
||||
}
|
||||
c.HTML(500, "500")
|
||||
|
||||
// // Lookup the current responsewriter
|
||||
// val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
|
||||
// res := val.Interface().(http.ResponseWriter)
|
||||
//
|
||||
// // respond with panic message while in development mode
|
||||
// var body []byte
|
||||
// if setting.Env == setting.DEV {
|
||||
// res.Header().Set("Content-Type", "text/html")
|
||||
// body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
|
||||
// }
|
||||
//
|
||||
// res.WriteHeader(http.StatusInternalServerError)
|
||||
// if nil != body {
|
||||
// res.Write(body)
|
||||
// }
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -23,6 +23,7 @@ type QueryCondition struct {
|
||||
Query AlertQuery
|
||||
Reducer QueryReducer
|
||||
Evaluator AlertEvaluator
|
||||
Operator string
|
||||
HandleRequest tsdb.HandleRequestFunc
|
||||
}
|
||||
|
||||
@ -72,6 +73,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
|
||||
return &alerting.ConditionResult{
|
||||
Firing: evalMatchCount > 0,
|
||||
NoDataFound: emptySerieCount == len(seriesList),
|
||||
Operator: c.Operator,
|
||||
EvalMatches: matches,
|
||||
}, nil
|
||||
}
|
||||
@ -168,8 +170,12 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
condition.Evaluator = evaluator
|
||||
|
||||
operatorJson := model.Get("operator")
|
||||
operator := operatorJson.Get("type").MustString("and")
|
||||
condition.Operator = operator
|
||||
|
||||
return &condition, nil
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ type EvalContext struct {
|
||||
EvalMatches []*EvalMatch
|
||||
Logs []*ResultLogEntry
|
||||
Error error
|
||||
Description string
|
||||
ConditionEvals string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Rule *Rule
|
||||
|
@ -1,6 +1,8 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
@ -21,7 +23,10 @@ func NewEvalHandler() *DefaultEvalHandler {
|
||||
|
||||
func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
||||
firing := true
|
||||
for _, condition := range context.Rule.Conditions {
|
||||
conditionEvals := ""
|
||||
|
||||
for i := 0; i < len(context.Rule.Conditions); i++ {
|
||||
condition := context.Rule.Conditions[i]
|
||||
cr, err := condition.Eval(context)
|
||||
if err != nil {
|
||||
context.Error = err
|
||||
@ -32,15 +37,23 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
||||
break
|
||||
}
|
||||
|
||||
// break if result has not triggered yet
|
||||
if cr.Firing == false {
|
||||
firing = false
|
||||
break
|
||||
// calculating Firing based on operator
|
||||
if cr.Operator == "or" {
|
||||
firing = firing || cr.Firing
|
||||
} else {
|
||||
firing = firing && cr.Firing
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
conditionEvals = "[" + conditionEvals + " " + strings.ToUpper(cr.Operator) + " " + strconv.FormatBool(cr.Firing) + "]"
|
||||
} else {
|
||||
conditionEvals = strconv.FormatBool(firing)
|
||||
}
|
||||
|
||||
context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...)
|
||||
}
|
||||
|
||||
context.ConditionEvals = conditionEvals + " = " + strconv.FormatBool(firing)
|
||||
context.Firing = firing
|
||||
context.EndTime = time.Now()
|
||||
elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond
|
||||
|
@ -8,12 +8,13 @@ import (
|
||||
)
|
||||
|
||||
type conditionStub struct {
|
||||
firing bool
|
||||
matches []*EvalMatch
|
||||
firing bool
|
||||
operator string
|
||||
matches []*EvalMatch
|
||||
}
|
||||
|
||||
func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
|
||||
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches}, nil
|
||||
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator}, nil
|
||||
}
|
||||
|
||||
func TestAlertingExecutor(t *testing.T) {
|
||||
@ -29,18 +30,102 @@ func TestAlertingExecutor(t *testing.T) {
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, true)
|
||||
So(context.ConditionEvals, ShouldEqual, "true = true")
|
||||
})
|
||||
|
||||
Convey("Show return false with not passing asdf", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, matches: []*EvalMatch{&EvalMatch{}, &EvalMatch{}}},
|
||||
&conditionStub{firing: false},
|
||||
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{&EvalMatch{}, &EvalMatch{}}},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, false)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
|
||||
})
|
||||
|
||||
Convey("Show return true if any of the condition is passing with OR operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, true)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true OR false] = true")
|
||||
})
|
||||
|
||||
Convey("Show return false if any of the condition is failing with AND operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, false)
|
||||
So(context.ConditionEvals, ShouldEqual, "[true AND false] = false")
|
||||
})
|
||||
|
||||
Convey("Show return true if one condition is failing with nested OR operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, true)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND true] OR false] = true")
|
||||
})
|
||||
|
||||
Convey("Show return false if one condition is passing with nested OR operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, false)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND false] OR false] = false")
|
||||
})
|
||||
|
||||
Convey("Show return false if a condition is failing with nested AND operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, false)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true AND false] AND true] = false")
|
||||
})
|
||||
|
||||
Convey("Show return true if a condition is passing with nested OR operator", func() {
|
||||
context := NewEvalContext(context.TODO(), &Rule{
|
||||
Conditions: []Condition{
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
&conditionStub{firing: true, operator: "or"},
|
||||
},
|
||||
})
|
||||
|
||||
handler.Eval(context)
|
||||
So(context.Firing, ShouldEqual, true)
|
||||
So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ type Notifier interface {
|
||||
type ConditionResult struct {
|
||||
Firing bool
|
||||
NoDataFound bool
|
||||
Operator string
|
||||
EvalMatches []*EvalMatch
|
||||
}
|
||||
|
||||
|
@ -26,11 +26,32 @@ type Rule struct {
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
Reason string
|
||||
Reason string
|
||||
Err error
|
||||
Alertid int64
|
||||
DashboardId int64
|
||||
PanelId int64
|
||||
}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return e.Reason
|
||||
extraInfo := ""
|
||||
if e.Alertid != 0 {
|
||||
extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid)
|
||||
}
|
||||
|
||||
if e.PanelId != 0 {
|
||||
extraInfo = fmt.Sprintf("%s PanelId: %v ", extraInfo, e.PanelId)
|
||||
}
|
||||
|
||||
if e.DashboardId != 0 {
|
||||
extraInfo = fmt.Sprintf("%s DashboardId: %v", extraInfo, e.DashboardId)
|
||||
}
|
||||
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s %s%s", e.Err.Error(), e.Reason, extraInfo)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Failed to extract alert.Reason: %s %s", e.Reason, extraInfo)
|
||||
}
|
||||
|
||||
var (
|
||||
@ -83,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
||||
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||
jsonModel := simplejson.NewFromAny(v)
|
||||
if id, err := jsonModel.Get("id").Int64(); err != nil {
|
||||
return nil, ValidationError{Reason: "Invalid notification schema"}
|
||||
return nil, ValidationError{Reason: "Invalid notification schema", DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
model.Notifications = append(model.Notifications, id)
|
||||
}
|
||||
@ -93,10 +114,10 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
||||
conditionModel := simplejson.NewFromAny(condition)
|
||||
conditionType := conditionModel.Get("type").MustString()
|
||||
if factory, exist := conditionFactories[conditionType]; !exist {
|
||||
return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType}
|
||||
return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
if queryCondition, err := factory(conditionModel, index); err != nil {
|
||||
return nil, err
|
||||
return nil, ValidationError{Err: err, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId}
|
||||
} else {
|
||||
model.Conditions = append(model.Conditions, queryCondition)
|
||||
}
|
||||
|
@ -39,6 +39,9 @@ func (s *SchedulerImpl) Update(rules []*Rule) {
|
||||
|
||||
offset := ((rule.Frequency * 1000) / int64(len(rules))) * int64(i)
|
||||
job.Offset = int64(math.Floor(float64(offset) / 1000))
|
||||
if job.Offset == 0 { //zero offset causes division with 0 panics.
|
||||
job.Offset = 1
|
||||
}
|
||||
jobs[rule.Id] = job
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"regexp"
|
||||
@ -58,13 +57,12 @@ func (query *Query) renderTags() []string {
|
||||
}
|
||||
|
||||
textValue := ""
|
||||
numericValue, err := strconv.ParseFloat(tag.Value, 64)
|
||||
|
||||
// quote value unless regex or number
|
||||
if tag.Operator == "=~" || tag.Operator == "!~" {
|
||||
textValue = tag.Value
|
||||
} else if err == nil {
|
||||
textValue = fmt.Sprintf("%v", numericValue)
|
||||
} else if tag.Operator == "<" || tag.Operator == ">" {
|
||||
textValue = tag.Value
|
||||
} else {
|
||||
textValue = fmt.Sprintf("'%s'", tag.Value)
|
||||
}
|
||||
|
@ -106,13 +106,19 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
|
||||
Convey("can render number tags", func() {
|
||||
query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001", Key: "key"}}}
|
||||
|
||||
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 10001`)
|
||||
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = '10001'`)
|
||||
})
|
||||
|
||||
Convey("can render number tags with decimals", func() {
|
||||
query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001.1", Key: "key"}}}
|
||||
Convey("can render numbers less then condition tags", func() {
|
||||
query := &Query{Tags: []*Tag{&Tag{Operator: "<", Value: "10001", Key: "key"}}}
|
||||
|
||||
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 10001.1`)
|
||||
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" < 10001`)
|
||||
})
|
||||
|
||||
Convey("can render number greather then condition tags", func() {
|
||||
query := &Query{Tags: []*Tag{&Tag{Operator: ">", Value: "10001", Key: "key"}}}
|
||||
|
||||
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" > 10001`)
|
||||
})
|
||||
|
||||
Convey("can render string tags", func() {
|
||||
|
@ -40,7 +40,6 @@ export class GrafanaApp {
|
||||
|
||||
init() {
|
||||
var app = angular.module('grafana', []);
|
||||
app.constant('grafanaVersion', "@grafanaVersion@");
|
||||
|
||||
moment.locale(config.bootData.user.locale);
|
||||
|
||||
|
@ -147,9 +147,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
|
||||
}
|
||||
}
|
||||
|
||||
// mouse and keyboard is user activity
|
||||
body.mousemove(userActivityDetected);
|
||||
body.keydown(userActivityDetected);
|
||||
setInterval(checkForInActiveUser, 1000);
|
||||
// treat tab change as activity
|
||||
document.addEventListener('visibilitychange', userActivityDetected);
|
||||
|
||||
// check every 2 seconds
|
||||
setInterval(checkForInActiveUser, 2000);
|
||||
|
||||
appEvents.on('toggle-view-mode', () => {
|
||||
lastActivity = 0;
|
||||
|
@ -6,7 +6,6 @@ import "./directives/dash_class";
|
||||
import "./directives/confirm_click";
|
||||
import "./directives/dash_edit_link";
|
||||
import "./directives/dropdown_typeahead";
|
||||
import "./directives/grafana_version_check";
|
||||
import "./directives/metric_segment";
|
||||
import "./directives/misc";
|
||||
import "./directives/ng_model_on_blur";
|
||||
|
@ -1,31 +0,0 @@
|
||||
define([
|
||||
'../core_module',
|
||||
],
|
||||
function (coreModule) {
|
||||
'use strict';
|
||||
|
||||
coreModule.default.directive('grafanaVersionCheck', function($http, contextSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
if (contextSrv.version === 'master') {
|
||||
return;
|
||||
}
|
||||
|
||||
$http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
|
||||
.then(function(response) {
|
||||
if (!response.data || !response.data.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contextSrv.version !== response.data.version) {
|
||||
elem.append('<i class="icon-info-sign"></i> ' +
|
||||
'<a href="http://grafana.org/download" target="_blank"> ' +
|
||||
'New version available: ' + response.data.version +
|
||||
'</a>');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
@ -47,7 +47,7 @@ function (coreModule, kbn, rangeUtil) {
|
||||
if (ctrl.$isEmpty(modelValue)) {
|
||||
return true;
|
||||
}
|
||||
if (viewValue.indexOf('$') === 0) {
|
||||
if (viewValue.indexOf('$') === 0 || viewValue.indexOf('+$') === 0) {
|
||||
return true; // allow template variable
|
||||
}
|
||||
var info = rangeUtil.describeTextRange(viewValue);
|
||||
|
@ -420,11 +420,11 @@ function($, _, moment) {
|
||||
kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps');
|
||||
kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps');
|
||||
kbn.valueFormats.KBs = kbn.formatBuilders.decimalSIPrefix('Bs', 1);
|
||||
kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bits', 1);
|
||||
kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bps', 1);
|
||||
kbn.valueFormats.MBs = kbn.formatBuilders.decimalSIPrefix('Bs', 2);
|
||||
kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bits', 2);
|
||||
kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bps', 2);
|
||||
kbn.valueFormats.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3);
|
||||
kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bits', 3);
|
||||
kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bps', 3);
|
||||
|
||||
// Throughput
|
||||
kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops');
|
||||
|
@ -28,6 +28,11 @@ var evalFunctions = [
|
||||
{text: 'HAS NO VALUE' , value: 'no_value'}
|
||||
];
|
||||
|
||||
var evalOperators = [
|
||||
{text: 'OR', value: 'or'},
|
||||
{text: 'AND', value: 'and'},
|
||||
];
|
||||
|
||||
var reducerTypes = [
|
||||
{text: 'avg()', value: 'avg'},
|
||||
{text: 'min()', value: 'min'},
|
||||
@ -116,6 +121,7 @@ export default {
|
||||
getStateDisplayModel: getStateDisplayModel,
|
||||
conditionTypes: conditionTypes,
|
||||
evalFunctions: evalFunctions,
|
||||
evalOperators: evalOperators,
|
||||
noDataModes: noDataModes,
|
||||
executionErrorModes: executionErrorModes,
|
||||
reducerTypes: reducerTypes,
|
||||
|
@ -18,6 +18,7 @@ export class AlertTabCtrl {
|
||||
alert: any;
|
||||
conditionModels: any;
|
||||
evalFunctions: any;
|
||||
evalOperators: any;
|
||||
noDataModes: any;
|
||||
executionErrorModes: any;
|
||||
addNotificationSegment;
|
||||
@ -41,6 +42,7 @@ export class AlertTabCtrl {
|
||||
this.$scope.ctrl = this;
|
||||
this.subTabIndex = 0;
|
||||
this.evalFunctions = alertDef.evalFunctions;
|
||||
this.evalOperators = alertDef.evalOperators;
|
||||
this.conditionTypes = alertDef.conditionTypes;
|
||||
this.noDataModes = alertDef.noDataModes;
|
||||
this.executionErrorModes = alertDef.executionErrorModes;
|
||||
@ -194,6 +196,7 @@ export class AlertTabCtrl {
|
||||
query: {params: ['A', '5m', 'now']},
|
||||
reducer: {type: 'avg', params: []},
|
||||
evaluator: {type: 'gt', params: [null]},
|
||||
operator: {type: 'and'},
|
||||
};
|
||||
}
|
||||
|
||||
@ -250,6 +253,7 @@ export class AlertTabCtrl {
|
||||
cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
|
||||
cm.reducerPart = alertDef.createReducerPart(source.reducer);
|
||||
cm.evaluator = source.evaluator;
|
||||
cm.operator = source.operator;
|
||||
|
||||
return cm;
|
||||
}
|
||||
|
@ -38,23 +38,23 @@
|
||||
<h5 class="section-heading">Conditions</h5>
|
||||
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label query-keyword width-5" ng-if="$index">AND</span>
|
||||
<metric-segment-model css-class="query-keyword width-5" ng-if="$index" property="conditionModel.operator.type" options="ctrl.evalOperators" custom="false"></metric-segment-model>
|
||||
<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part" part="conditionModel.reducerPart" handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
|
||||
<query-part-editor class="gf-form-label query-part width-5" part="conditionModel.reducerPart" handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
<span class="gf-form-label query-keyword">OF</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart" handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
|
||||
<query-part-editor class="gf-form-label query-part width-10" part="conditionModel.queryPart" handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-keyword" on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-7" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
|
||||
<input class="gf-form-input max-width-7" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
|
@ -52,7 +52,7 @@ export class DashboardCtrl {
|
||||
.catch($scope.onInitFailed.bind(this, 'Templating init failed', false))
|
||||
// continue
|
||||
.finally(function() {
|
||||
dynamicDashboardSrv.init(dashboard, variableSrv);
|
||||
dynamicDashboardSrv.init(dashboard);
|
||||
dynamicDashboardSrv.process();
|
||||
|
||||
unsavedChangesSrv.init(dashboard, $scope);
|
||||
|
@ -12,12 +12,12 @@ export class DynamicDashboardSrv {
|
||||
dashboard: any;
|
||||
variables: any;
|
||||
|
||||
init(dashboard, variableSrv) {
|
||||
init(dashboard) {
|
||||
this.dashboard = dashboard;
|
||||
this.variables = variableSrv.variables;
|
||||
this.variables = dashboard.templating.list;
|
||||
}
|
||||
|
||||
process(options) {
|
||||
process(options?) {
|
||||
if (this.dashboard.snapshot || this.variables.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -31,6 +31,8 @@ export class DynamicDashboardSrv {
|
||||
// cleanup scopedVars
|
||||
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||
row = this.dashboard.rows[i];
|
||||
delete row.scopedVars;
|
||||
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
delete row.panels[j].scopedVars;
|
||||
}
|
||||
@ -64,6 +66,8 @@ export class DynamicDashboardSrv {
|
||||
j = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
row.panelSpanChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,7 @@ export class DashExportCtrl {
|
||||
constructor(private backendSrv, dashboardSrv, datasourceSrv, $scope) {
|
||||
this.exporter = new DashboardExporter(datasourceSrv);
|
||||
|
||||
var current = dashboardSrv.getCurrent().getSaveModelClone();
|
||||
|
||||
this.exporter.makeExportable(current).then(dash => {
|
||||
this.exporter.makeExportable(dashboardSrv.getCurrent()).then(dash => {
|
||||
$scope.$apply(() => {
|
||||
this.dash = dash;
|
||||
});
|
||||
|
@ -11,19 +11,40 @@ export class DashboardExporter {
|
||||
constructor(private datasourceSrv) {
|
||||
}
|
||||
|
||||
makeExportable(dash) {
|
||||
makeExportable(dashboard) {
|
||||
var dynSrv = new DynamicDashboardSrv();
|
||||
dynSrv.init(dash, {variables: dash.templating.list});
|
||||
|
||||
// clean up repeated rows and panels,
|
||||
// this is done on the live real dashboard instance, not on a clone
|
||||
// so we need to undo this
|
||||
// this is pretty hacky and needs to be changed
|
||||
dynSrv.init(dashboard);
|
||||
dynSrv.process({cleanUpOnly: true});
|
||||
|
||||
dash.id = null;
|
||||
var saveModel = dashboard.getSaveModelClone();
|
||||
saveModel.id = null;
|
||||
|
||||
// undo repeat cleanup
|
||||
dynSrv.process();
|
||||
|
||||
var inputs = [];
|
||||
var requires = {};
|
||||
var datasources = {};
|
||||
var promises = [];
|
||||
var variableLookup: any = {};
|
||||
|
||||
for (let variable of saveModel.templating.list) {
|
||||
variableLookup[variable.name] = variable;
|
||||
}
|
||||
|
||||
var templateizeDatasourceUsage = obj => {
|
||||
// ignore data source properties that contain a variable
|
||||
if (obj.datasource && obj.datasource.indexOf('$') === 0) {
|
||||
if (variableLookup[obj.datasource.substring(1)]){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
promises.push(this.datasourceSrv.get(obj.datasource).then(ds => {
|
||||
if (ds.meta.builtIn) {
|
||||
return;
|
||||
@ -50,7 +71,7 @@ export class DashboardExporter {
|
||||
};
|
||||
|
||||
// check up panel data sources
|
||||
for (let row of dash.rows) {
|
||||
for (let row of saveModel.rows) {
|
||||
for (let panel of row.panels) {
|
||||
if (panel.datasource !== undefined) {
|
||||
templateizeDatasourceUsage(panel);
|
||||
@ -77,7 +98,7 @@ export class DashboardExporter {
|
||||
}
|
||||
|
||||
// templatize template vars
|
||||
for (let variable of dash.templating.list) {
|
||||
for (let variable of saveModel.templating.list) {
|
||||
if (variable.type === 'query') {
|
||||
templateizeDatasourceUsage(variable);
|
||||
variable.options = [];
|
||||
@ -87,7 +108,7 @@ export class DashboardExporter {
|
||||
}
|
||||
|
||||
// templatize annotations vars
|
||||
for (let annotationDef of dash.annotations.list) {
|
||||
for (let annotationDef of saveModel.annotations.list) {
|
||||
templateizeDatasourceUsage(annotationDef);
|
||||
}
|
||||
|
||||
@ -105,7 +126,7 @@ export class DashboardExporter {
|
||||
});
|
||||
|
||||
// templatize constants
|
||||
for (let variable of dash.templating.list) {
|
||||
for (let variable of saveModel.templating.list) {
|
||||
if (variable.type === 'constant') {
|
||||
var refName = 'VAR_' + variable.name.replace(' ', '_').toUpperCase();
|
||||
inputs.push({
|
||||
@ -133,7 +154,7 @@ export class DashboardExporter {
|
||||
newObj["__inputs"] = inputs;
|
||||
newObj["__requires"] = requires;
|
||||
|
||||
_.defaults(newObj, dash);
|
||||
_.defaults(newObj, saveModel);
|
||||
|
||||
return newObj;
|
||||
}).catch(err => {
|
||||
|
@ -98,12 +98,14 @@ export class DashboardModel {
|
||||
var events = this.events;
|
||||
var meta = this.meta;
|
||||
var rows = this.rows;
|
||||
var variables = this.templating.list;
|
||||
|
||||
delete this.events;
|
||||
delete this.meta;
|
||||
|
||||
// prepare save model
|
||||
this.rows = _.map(this.rows, row => row.getSaveModel());
|
||||
events.emit('prepare-save-model');
|
||||
this.rows = _.map(rows, row => row.getSaveModel());
|
||||
this.templating.list = _.map(variables, variable => variable.getSaveModel ? variable.getSaveModel() : variable);
|
||||
|
||||
var copy = $.extend(true, {}, this);
|
||||
|
||||
@ -111,6 +113,8 @@ export class DashboardModel {
|
||||
this.events = events;
|
||||
this.meta = meta;
|
||||
this.rows = rows;
|
||||
this.templating.list = variables;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@ -233,7 +237,6 @@ export class DashboardModel {
|
||||
}
|
||||
|
||||
duplicatePanel(panel, row) {
|
||||
var rowIndex = _.indexOf(this.rows, row);
|
||||
var newPanel = angular.copy(panel);
|
||||
newPanel.id = this.getNextPanelId();
|
||||
|
||||
@ -241,9 +244,9 @@ export class DashboardModel {
|
||||
delete newPanel.repeatIteration;
|
||||
delete newPanel.repeatPanelId;
|
||||
delete newPanel.scopedVars;
|
||||
delete newPanel.alert;
|
||||
|
||||
var currentRow = this.rows[rowIndex];
|
||||
currentRow.panels.push(newPanel);
|
||||
row.addPanel(newPanel);
|
||||
return newPanel;
|
||||
}
|
||||
|
||||
|
@ -1,282 +0,0 @@
|
||||
<topnav title="Alerting" subnav="false">
|
||||
<ul class="nav">
|
||||
<li class="active" ><a href="global-alerts">Global Alerts</a></li>
|
||||
</ul>
|
||||
</topnav>
|
||||
|
||||
<div class="page-container">
|
||||
<div class="page-wide">
|
||||
<h1>Global alerts</h1>
|
||||
|
||||
<div class="filter-controls-filters">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">Filters:</li>
|
||||
<li class="tight-form-item">Alert State</li>
|
||||
<li><!-- <value-select-dropdown></value-select-dropdown> --></li>
|
||||
<li class="tight-form-item">Dashboards</li>
|
||||
<li><!-- <value-select-dropdown></value-select-dropdown> --></li>
|
||||
<li class="tight-form-item">
|
||||
<a class="pointer">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="filter-controls-actions">
|
||||
<li>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
|
||||
<input class="cr1" id="state-enabled" type="checkbox">
|
||||
<label for="state-enabled" class="cr1"></label> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a>All</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
|
||||
Bulk Actions <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a>Update notifications</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-inverse" data-toggle="dropdown">
|
||||
<i class="fa fa-fw fa-th-large"></i> New Dashboard from selected
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<span class="filter-controls-actions-selected">2 selected, showing 6 of 6 total</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="filter-list">
|
||||
<li>
|
||||
<ul class="filter-list-card">
|
||||
<li class="filter-list-card-select">
|
||||
<input class="cr1" id="alert1" type="checkbox">
|
||||
<label for="alert1" class="cr1"></label>
|
||||
</li>
|
||||
<li>
|
||||
<div class="filter-list-card-controls">
|
||||
<div class="filter-list-card-links">
|
||||
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Super Sekret</a></span>
|
||||
<span class="filter-list-card-link">Panel: <a href="">Prod CPU Data Writes</a></span>
|
||||
</div>
|
||||
<div class="filter-list-card-config">
|
||||
<a href="#"><i class="fa fa-cog"></i></a>
|
||||
</div>
|
||||
<div class="filter-list-card-expand" ng-click="alert1.expanded = !alert1.expanded">
|
||||
<i class="fa fa-angle-right" ng-show="!alert1.expanded"></i>
|
||||
<i class="fa fa-angle-down" ng-show="alert1.expanded"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filter-list-card-title">Prod CPU Data Writes</span>
|
||||
<span class="filter-list-card-status">
|
||||
<span class="filter-list-card-state online">Online</span> for 19 hours
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter-list-card-details" ng-show="alert1.expanded">
|
||||
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item">aliasByNode(2)</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="filter-list-card">
|
||||
<li class="filter-list-card-select">
|
||||
<input class="cr1" id="alert2" type="checkbox" checked>
|
||||
<label for="alert2" class="cr1"></label>
|
||||
</li>
|
||||
<li>
|
||||
<div class="filter-list-card-controls">
|
||||
<div class="filter-list-card-links">
|
||||
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Insanely Super Duper Sekret</a></span>
|
||||
<span class="filter-list-card-link">Panel: <a href="">client side full page load</a></span>
|
||||
</div>
|
||||
<div class="filter-list-card-config">
|
||||
<a href="#"><i class="fa fa-cog"></i></a>
|
||||
</div>
|
||||
<div class="filter-list-card-expand" ng-click="alert2.expanded = !alert2.expanded">
|
||||
<i class="fa fa-angle-right" ng-show="!alert2.expanded"></i>
|
||||
<i class="fa fa-angle-down" ng-show="alert2.expanded"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filter-list-card-title">Prod DB Reads</span>
|
||||
<span class="filter-list-card-status">
|
||||
<span class="filter-list-card-state warn">Warn</span> for 1 hour
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter-list-card-details" ng-show="alert2.expanded">
|
||||
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item">aliasByNode(2)</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="filter-list-card">
|
||||
<li class="filter-list-card-select">
|
||||
<input class="cr1" id="alert3" type="checkbox" checked>
|
||||
<label for="alert3" class="cr1"></label>
|
||||
</li>
|
||||
<li>
|
||||
<div class="filter-list-card-controls">
|
||||
<div class="filter-list-card-links">
|
||||
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Mildly Sekret</a></span>
|
||||
<span class="filter-list-card-link">Panel: <a href="">Memory/CPU</a></span>
|
||||
</div>
|
||||
<div class="filter-list-card-config">
|
||||
<a href="#"><i class="fa fa-cog"></i></a>
|
||||
</div>
|
||||
<div class="filter-list-card-expand" ng-click="alert3.expanded = !alert3.expanded">
|
||||
<i class="fa fa-angle-right" ng-show="!alert3.expanded"></i>
|
||||
<i class="fa fa-angle-down" ng-show="alert3.expanded"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filter-list-card-title">Prod CPU Data Writes</span>
|
||||
<span class="filter-list-card-status">
|
||||
<span class="filter-list-card-state critical">Online</span> for 10 minutes
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter-list-card-details" ng-show="alert3.expanded">
|
||||
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item">aliasByNode(2)</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="filter-list-card">
|
||||
<li class="filter-list-card-select">
|
||||
<input class="cr1" id="alert4" type="checkbox">
|
||||
<label for="alert4" class="cr1"></label>
|
||||
</li>
|
||||
<li>
|
||||
<div class="filter-list-card-controls">
|
||||
<div class="filter-list-card-links">
|
||||
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Super Sekret</a></span>
|
||||
<span class="filter-list-card-link">Panel: <a href="">Stacked lines</a></span>
|
||||
</div>
|
||||
<div class="filter-list-card-config">
|
||||
<a href="#"><i class="fa fa-cog"></i></a>
|
||||
</div>
|
||||
<div class="filter-list-card-expand" ng-click="alert4.expanded = !alert4.expanded">
|
||||
<i class="fa fa-angle-right" ng-show="!alert4.expanded"></i>
|
||||
<i class="fa fa-angle-down" ng-show="alert4.expanded"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filter-list-card-title">Critical Thing</span>
|
||||
<span class="filter-list-card-status">
|
||||
<span class="filter-list-card-state online">Online</span> for 5 weeks
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter-list-card-details" ng-show="alert4.expanded">
|
||||
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item">aliasByNode(2)</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="filter-list-card">
|
||||
<li class="filter-list-card-select">
|
||||
<input class="cr1" id="alert5" type="checkbox">
|
||||
<label for="alert5" class="cr1"></label>
|
||||
</li>
|
||||
<li>
|
||||
<div class="filter-list-card-controls">
|
||||
<div class="filter-list-card-links">
|
||||
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Public</a></span>
|
||||
<span class="filter-list-card-link">Panel: <a href="">More Critical Thing</a></span>
|
||||
</div>
|
||||
<div class="filter-list-card-config">
|
||||
<a href="#"><i class="fa fa-cog"></i></a>
|
||||
</div>
|
||||
<div class="filter-list-card-expand" ng-click="alert5.expanded = !alert5.expanded">
|
||||
<i class="fa fa-angle-right" ng-show="!alert5.expanded"></i>
|
||||
<i class="fa fa-angle-down" ng-show="alert5.expanded"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filter-list-card-title">More Critical Thing</span>
|
||||
<span class="filter-list-card-status">
|
||||
<span class="filter-list-card-state online">Online</span> for 2 months
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filter-list-card-details" ng-show="alert5.expanded">
|
||||
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
|
||||
<li class="tight-form-item">apps</li>
|
||||
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
|
||||
<li class="tight-form-item">fakesite</li>
|
||||
<li class="tight-form-item">counters</li>
|
||||
<li class="tight-form-item">requests</li>
|
||||
<li class="tight-form-item">count</li>
|
||||
<li class="tight-form-item">scaleToSeconds(1)</li>
|
||||
<li class="tight-form-item">aliasByNode(2)</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -5,7 +5,7 @@
|
||||
|
||||
<div class="gf-form-inline dash-row-add-panel-form">
|
||||
<div class="gf-form">
|
||||
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.panelSearch' give-focus='true' ng-keydown="ctrl.keyDown($event)" ng-change="ctrl.panelSearchChanged()" placeholder="panel search filter" ng-blur="ctrl.panelSearchBlur()"></input>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.panelSearch' give-focus='true' ng-keydown="ctrl.keyDown($event)" ng-change="ctrl.panelSearchChanged()" placeholder="panel search filter"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -45,12 +45,6 @@ export class AddPanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
panelSearchBlur() {
|
||||
// this.$timeout(() => {
|
||||
// this.rowCtrl.dropView = 0;
|
||||
// }, 400);
|
||||
}
|
||||
|
||||
moveSelection(direction) {
|
||||
var max = this.panelHits.length;
|
||||
var newIndex = this.activeIndex + direction;
|
||||
|
@ -19,7 +19,6 @@ export class DashRowCtrl {
|
||||
|
||||
if (this.row.isNew) {
|
||||
this.dropView = 1;
|
||||
delete this.row.isNew;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,8 +34,8 @@ export class DashRowCtrl {
|
||||
title: config.new_panel_title,
|
||||
type: panelId,
|
||||
id: this.dashboard.getNextPanelId(),
|
||||
isNew: true,
|
||||
},
|
||||
isNew: true,
|
||||
};
|
||||
} else {
|
||||
dragObject = this.dashboard.getPanelInfoById(panelId);
|
||||
@ -65,7 +64,7 @@ export class DashRowCtrl {
|
||||
this.row.panels.push(dragObject.panel);
|
||||
|
||||
// if not new remove from source row
|
||||
if (!dragObject.isNew) {
|
||||
if (!dragObject.panel.isNew) {
|
||||
dragObject.row.removePanel(dragObject.panel, false);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,11 @@ export class DashboardRow {
|
||||
}
|
||||
|
||||
getSaveModel() {
|
||||
this.model = {};
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
|
||||
// remove properties that dont server persisted purpose
|
||||
delete this.model.isNew;
|
||||
return this.model;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,9 @@ describe('dashboardSrv', function() {
|
||||
|
||||
it('duplicate panel should try to add it to same row', function() {
|
||||
var panel = { span: 4, attr: '123', id: 10 };
|
||||
dashboard.rows = [{ panels: [panel] }];
|
||||
|
||||
dashboard.addEmptyRow();
|
||||
dashboard.rows[0].addPanel(panel);
|
||||
dashboard.duplicatePanel(panel, dashboard.rows[0]);
|
||||
|
||||
expect(dashboard.rows[0].panels[0].span).to.be(4);
|
||||
@ -73,7 +75,9 @@ describe('dashboardSrv', function() {
|
||||
|
||||
it('duplicate panel should remove repeat data', function() {
|
||||
var panel = { span: 4, attr: '123', id: 10, repeat: 'asd', scopedVars: { test: 'asd' }};
|
||||
dashboard.rows = [{ panels: [panel] }];
|
||||
|
||||
dashboard.addEmptyRow();
|
||||
dashboard.rows[0].addPanel(panel);
|
||||
dashboard.duplicatePanel(panel, dashboard.rows[0]);
|
||||
|
||||
expect(dashboard.rows[0].panels[1].repeat).to.be(undefined);
|
||||
|
@ -20,7 +20,6 @@ function dynamicDashScenario(desc, func) {
|
||||
|
||||
beforeEach(angularMocks.inject(function(dashboardSrv) {
|
||||
ctx.dashboardSrv = dashboardSrv;
|
||||
ctx.variableSrv = {};
|
||||
|
||||
var model = {
|
||||
rows: [],
|
||||
@ -29,9 +28,8 @@ function dynamicDashScenario(desc, func) {
|
||||
|
||||
setupFunc(model);
|
||||
ctx.dash = ctx.dashboardSrv.create(model);
|
||||
ctx.variableSrv.variables = ctx.dash.templating.list;
|
||||
ctx.dynamicDashboardSrv = new DynamicDashboardSrv();
|
||||
ctx.dynamicDashboardSrv.init(ctx.dash, ctx.variableSrv);
|
||||
ctx.dynamicDashboardSrv.init(ctx.dash);
|
||||
ctx.dynamicDashboardSrv.process();
|
||||
ctx.rows = ctx.dash.rows;
|
||||
}));
|
||||
|
@ -34,6 +34,14 @@ describe('given dashboard with repeated panels', function() {
|
||||
options: []
|
||||
});
|
||||
|
||||
dash.templating.list.push({
|
||||
name: 'ds',
|
||||
type: 'datasource',
|
||||
query: 'testdb',
|
||||
current: {value: 'prod', text: 'prod'},
|
||||
options: []
|
||||
});
|
||||
|
||||
dash.annotations.list.push({
|
||||
name: 'logs',
|
||||
datasource: 'gfdb',
|
||||
@ -49,6 +57,7 @@ describe('given dashboard with repeated panels', function() {
|
||||
datasource: '-- Mixed --',
|
||||
targets: [{datasource: 'other'}],
|
||||
},
|
||||
{id: 5, datasource: '$ds'},
|
||||
]
|
||||
});
|
||||
|
||||
@ -87,7 +96,7 @@ describe('given dashboard with repeated panels', function() {
|
||||
});
|
||||
|
||||
it('exported dashboard should not contain repeated panels', function() {
|
||||
expect(exported.rows[0].panels.length).to.be(2);
|
||||
expect(exported.rows[0].panels.length).to.be(3);
|
||||
});
|
||||
|
||||
it('exported dashboard should not contain repeated rows', function() {
|
||||
|
@ -54,6 +54,12 @@ export class PanelCtrl {
|
||||
this.events.emit('panel-teardown');
|
||||
this.events.removeAllListeners();
|
||||
});
|
||||
|
||||
// we should do something interesting
|
||||
// with newly added panels
|
||||
if (this.panel.isNew) {
|
||||
delete this.panel.isNew;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -188,6 +194,9 @@ export class PanelCtrl {
|
||||
|
||||
duplicate() {
|
||||
this.dashboard.duplicatePanel(this.panel, this.row);
|
||||
this.$timeout(() => {
|
||||
this.$scope.$root.$broadcast('render');
|
||||
});
|
||||
}
|
||||
|
||||
updateColumnSpan(span) {
|
||||
|
@ -68,8 +68,8 @@ module.directive('grafanaPanel', function($rootScope) {
|
||||
|
||||
// the reason for handling these classes this way is for performance
|
||||
// limit the watchers on panels etc
|
||||
var transparentLastState;
|
||||
var lastHasAlertRule;
|
||||
var transparentLastState = false;
|
||||
var lastHasAlertRule = false;
|
||||
var lastAlertState;
|
||||
var hasAlertRule;
|
||||
var lastHeight = 0;
|
||||
@ -91,6 +91,12 @@ module.directive('grafanaPanel', function($rootScope) {
|
||||
lastHeight = ctrl.containerHeight;
|
||||
}
|
||||
|
||||
// set initial transparency
|
||||
if (ctrl.panel.transparent) {
|
||||
transparentLastState = true;
|
||||
panelContainer.addClass('panel-transparent', true);
|
||||
}
|
||||
|
||||
ctrl.events.on('render', () => {
|
||||
if (lastHeight !== ctrl.containerHeight) {
|
||||
panelContainer.css({minHeight: ctrl.containerHeight});
|
||||
|
@ -57,59 +57,3 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form" ng-if="false">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li ng-show="ctrl.error" class="tight-form-item">
|
||||
<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tight-form-item small" ng-show="ctrl.target.datasource">
|
||||
<em>{{ctrl.target.datasource}}</em>
|
||||
</li>
|
||||
<li class="tight-form-item" ng-if="ctrl.toggleEditorMode">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.toggleEditorMode()">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<div class="dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
|
||||
<i class="fa fa-bars"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(target)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">
|
||||
{{ctrl.target.refId}}
|
||||
</li>
|
||||
<li>
|
||||
<a class="tight-form-item" ng-click="ctrl.toggleHideQuery()" role="menuitem">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list" ng-transclude>
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
@ -26,7 +26,7 @@ export class AdhocVariable implements Variable {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
return this.model;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export class ConstantVariable implements Variable {
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
return this.model;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export class CustomVariable implements Variable {
|
||||
return this.variableSrv.setOptionAsCurrent(this, option);
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
return this.model;
|
||||
}
|
||||
|
@ -30,8 +30,11 @@ export class DatasourceVariable implements Variable {
|
||||
this.refresh = 1;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
|
||||
// dont persist options
|
||||
this.model.options = [];
|
||||
return this.model;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class IntervalVariable implements Variable {
|
||||
this.refresh = 2;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
return this.model;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@
|
||||
<div ng-if="current.type === 'custom'" class="gf-form-group">
|
||||
<h5 class="section-heading">Custom Options</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-13">Values separated by comma</span>
|
||||
<span class="gf-form-label width-14">Values separated by comma</span>
|
||||
<input type="text" class="gf-form-input" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue" required></input>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,9 +47,15 @@ export class QueryVariable implements Variable {
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getSaveModel() {
|
||||
// copy back model properties to model
|
||||
assignModelProperties(this.model, this, this.defaults);
|
||||
|
||||
// remove options
|
||||
if (this.refresh !== 0) {
|
||||
this.model.options = [];
|
||||
}
|
||||
|
||||
return this.model;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ describe('QueryVariable', function() {
|
||||
variable.regex = 'asd';
|
||||
variable.sort = 50;
|
||||
|
||||
var model = variable.getModel();
|
||||
var model = variable.getSaveModel();
|
||||
expect(model.options.length).to.be(1);
|
||||
expect(model.options[0].text).to.be('test');
|
||||
expect(model.datasource).to.be('google');
|
||||
@ -33,7 +33,14 @@ describe('QueryVariable', function() {
|
||||
expect(model.sort).to.be(50);
|
||||
});
|
||||
|
||||
});
|
||||
it('if refresh != 0 then remove options in presisted mode', () => {
|
||||
var variable = new QueryVariable({}, null, null, null, null);
|
||||
variable.options = [{text: 'test'}];
|
||||
variable.refresh = 1;
|
||||
|
||||
var model = variable.getSaveModel();
|
||||
expect(model.options.length).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,7 @@ export interface Variable {
|
||||
dependsOn(variable);
|
||||
setValueFromUrl(urlValue);
|
||||
getValueForUrl();
|
||||
getModel();
|
||||
getSaveModel();
|
||||
}
|
||||
|
||||
export var variableTypes = {};
|
||||
|
@ -20,12 +20,9 @@ export class VariableSrv {
|
||||
this.dashboard = dashboard;
|
||||
|
||||
// create working class models representing variables
|
||||
this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this));
|
||||
this.variables = dashboard.templating.list = dashboard.templating.list.map(this.createVariableFromModel.bind(this));
|
||||
this.templateSrv.init(this.variables);
|
||||
|
||||
// register event to sync back to persisted model
|
||||
this.dashboard.events.on('prepare-save-model', this.syncToDashboardModel.bind(this));
|
||||
|
||||
// init variables
|
||||
for (let variable of this.variables) {
|
||||
variable.initLock = this.$q.defer();
|
||||
@ -99,12 +96,6 @@ export class VariableSrv {
|
||||
return variable;
|
||||
}
|
||||
|
||||
syncToDashboardModel() {
|
||||
this.dashboard.templating.list = this.variables.map(variable => {
|
||||
return variable.getModel();
|
||||
});
|
||||
}
|
||||
|
||||
updateOptions(variable) {
|
||||
return variable.updateOptions();
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
<li ng-class="{active: active, disabled: disabled}">
|
||||
<a href ng-click="select()" tab-heading-transclude>{{heading}}</a>
|
||||
</li>
|
@ -1,11 +0,0 @@
|
||||
<div>
|
||||
<ul class="nav nav-tabs" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane"
|
||||
ng-repeat="tab in tabs"
|
||||
ng-class="{active: tab.active}"
|
||||
tab-content-transclude="tab">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,11 +1,12 @@
|
||||
<navbar title="404" icon="fa fa-fw fa-question" title-url="/">
|
||||
</navbar>
|
||||
|
||||
<div class="row-fluid" style="margin-top: 100px;">
|
||||
<div class="span2"></div>
|
||||
<div class="page-container">
|
||||
|
||||
<div class="grafana-info-box span8 text-center">
|
||||
<h3>Page not found (404)</h3>
|
||||
</div>
|
||||
|
||||
<div class="span2"></div>
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
Page not found (404)
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"revision": 5,
|
||||
"revision": 6,
|
||||
"title": "TestData - Graph Panel Last 1h",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
@ -7,8 +7,48 @@
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"hideControls": false,
|
||||
"sharedCrosshair": false,
|
||||
"hideControls": false,
|
||||
"time": {
|
||||
"from": "2016-11-16T16:59:38.294Z",
|
||||
"to": "2016-11-16T17:09:01.532Z"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"refresh": false,
|
||||
"schemaVersion": 13,
|
||||
"version": 4,
|
||||
"links": [],
|
||||
"gnetId": null,
|
||||
"rows": [
|
||||
{
|
||||
"collapse": false,
|
||||
@ -238,7 +278,13 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"title": "New row",
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"isNew": false,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
@ -332,7 +378,13 @@
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"title": "New row",
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"isNew": false,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
@ -371,7 +423,7 @@
|
||||
"yaxis": 2
|
||||
}
|
||||
],
|
||||
"span": 7.99561403508772,
|
||||
"span": 8,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@ -432,12 +484,18 @@
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4.00438596491228,
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"title": "New row",
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"isNew": false,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
@ -545,7 +603,7 @@
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 3,
|
||||
"span": 4,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@ -592,6 +650,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 13,
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row",
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"isNew": false,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null
|
||||
},
|
||||
{
|
||||
"isNew": false,
|
||||
"title": "Dashboard Row",
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
@ -624,7 +707,7 @@
|
||||
"zindex": -3
|
||||
}
|
||||
],
|
||||
"span": 5,
|
||||
"span": 8,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@ -687,49 +770,149 @@
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Stacking values on top of nulls, should treat the null values as zero. ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 14,
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"height": 250,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null,
|
||||
"collapse": false
|
||||
},
|
||||
{
|
||||
"isNew": false,
|
||||
"title": "Dashboard Row",
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 12,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [
|
||||
{
|
||||
"alias": "B-series",
|
||||
"zindex": -3
|
||||
}
|
||||
],
|
||||
"span": 8,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": "",
|
||||
"alias": ""
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"hide": false,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Stacking all series null segment",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Stacking when all values are null should leave a gap in the graph",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 15,
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"showTitle": false,
|
||||
"titleSize": "h6",
|
||||
"height": 250,
|
||||
"repeat": null,
|
||||
"repeatRowId": null,
|
||||
"repeatIteration": null,
|
||||
"collapse": false
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"refresh": false,
|
||||
"schemaVersion": 13,
|
||||
"version": 13,
|
||||
"links": [],
|
||||
"gnetId": null
|
||||
]
|
||||
}
|
||||
|
2
public/app/plugins/app/testdata/plugin.json
vendored
2
public/app/plugins/app/testdata/plugin.json
vendored
@ -9,7 +9,7 @@
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
},
|
||||
"version": "1.0.14",
|
||||
"version": "1.0.15",
|
||||
"updated": "2016-09-26"
|
||||
},
|
||||
|
||||
|
@ -37,7 +37,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
|
||||
query.statistics = target.statistics;
|
||||
|
||||
var period = this._getPeriod(target, query, options, start, end);
|
||||
var now = Math.round(Date.now() / 1000);
|
||||
var period = this._getPeriod(target, query, options, start, end, now);
|
||||
target.period = period;
|
||||
query.period = period;
|
||||
|
||||
@ -67,11 +68,19 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
});
|
||||
};
|
||||
|
||||
this._getPeriod = function(target, query, options, start, end) {
|
||||
this._getPeriod = function(target, query, options, start, end, now) {
|
||||
var period;
|
||||
var range = end - start;
|
||||
|
||||
if (!target.period) {
|
||||
var daySec = 60 * 60 * 24;
|
||||
var periodUnit = 60;
|
||||
if (now - start > (daySec * 15)) { // until 63 days ago
|
||||
periodUnit = period = 60 * 5;
|
||||
} else if (now - start > (daySec * 63)) { // until 455 days ago
|
||||
periodUnit = period = 60 * 60;
|
||||
} else if (now - start > (daySec * 455)) { // over 455 days, should return error, but try to long period
|
||||
periodUnit = period = 60 * 60;
|
||||
} else if (!target.period) {
|
||||
period = (query.namespace === 'AWS/EC2') ? 300 : 60;
|
||||
} else if (/^\d+$/.test(target.period)) {
|
||||
period = parseInt(target.period, 10);
|
||||
@ -82,7 +91,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
period = 60;
|
||||
}
|
||||
if (range / period >= 1440) {
|
||||
period = Math.ceil(range / 1440 / 60) * 60;
|
||||
period = Math.ceil(range / 1440 / periodUnit) * periodUnit;
|
||||
}
|
||||
|
||||
return period;
|
||||
|
@ -22,7 +22,7 @@ function ($) {
|
||||
var len = series.datapoints.points.length;
|
||||
for (var j = initial; j < len; j += ps) {
|
||||
// Special case of a non stepped line, highlight the very last point just before a null point
|
||||
if ((series.datapoints.points[initial] != null && series.datapoints.points[j] == null && ! series.lines.steps)
|
||||
if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null)
|
||||
//normal case
|
||||
|| series.datapoints.points[j] > posX) {
|
||||
return Math.max(j - ps, 0)/ps;
|
||||
@ -195,7 +195,7 @@ function ($) {
|
||||
}
|
||||
|
||||
var highlightClass = '';
|
||||
if (item && i === item.seriesIndex) {
|
||||
if (item && hoverInfo.index === item.seriesIndex) {
|
||||
highlightClass = 'graph-tooltip-list-item--highlight';
|
||||
}
|
||||
|
||||
|
@ -208,11 +208,8 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
// Add $__name variable for using in prefix or postfix
|
||||
data.scopedVars = {
|
||||
__name: {
|
||||
value: this.series[0].label
|
||||
}
|
||||
};
|
||||
data.scopedVars = _.extend({}, this.panel.scopedVars);
|
||||
data.scopedVars["__name"] = {value: this.series[0].label};
|
||||
}
|
||||
|
||||
// check value to text mappings if its enabled
|
||||
@ -526,7 +523,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
elem.toggleClass('pointer', panel.links.length > 0);
|
||||
|
||||
if (panel.links.length > 0) {
|
||||
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars);
|
||||
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], data.scopedVars);
|
||||
} else {
|
||||
linkInfo = null;
|
||||
}
|
||||
|
@ -50,11 +50,9 @@
|
||||
@import "components/tagsinput";
|
||||
@import "components/tables_lists";
|
||||
@import "components/search";
|
||||
@import "components/tightform";
|
||||
@import "components/gf-form";
|
||||
@import "components/sidemenu";
|
||||
@import "components/navbar";
|
||||
@import "components/gfbox";
|
||||
@import "components/timepicker";
|
||||
@import "components/filter-controls";
|
||||
@import "components/filter-list";
|
||||
|
@ -1,69 +0,0 @@
|
||||
.gf-box {
|
||||
margin: 10px 5px;
|
||||
background-color: $page-bg;
|
||||
position: relative;
|
||||
border: 1px solid $tight-form-func-bg;
|
||||
}
|
||||
|
||||
.gf-box-no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gf-box-header-close-btn {
|
||||
float: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
i {
|
||||
font-size: 120%;
|
||||
}
|
||||
color: $text-color;
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.gf-box-header-save-btn {
|
||||
padding: 7px 0;
|
||||
float: right;
|
||||
color: $gray-2;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.gf-box-body {
|
||||
padding: 20px;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.gf-box-footer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gf-box-header {
|
||||
border-bottom: 1px solid $tight-form-func-bg;
|
||||
overflow: hidden;
|
||||
background-color: $tight-form-bg;
|
||||
.tabs {
|
||||
float: left;
|
||||
}
|
||||
.nav {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gf-box-title {
|
||||
padding-right: 20px;
|
||||
padding-left: 10px;
|
||||
float: left;
|
||||
color: $link-color;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
line-height: 38px;
|
||||
margin: 0;
|
||||
.fa {
|
||||
padding: 0 8px 0 5px;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@
|
||||
}
|
||||
|
||||
// temp hack
|
||||
.modal-body, .gf-box {
|
||||
.modal-body {
|
||||
.nav-tabs {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
@ -67,3 +67,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
.grafana-metric-options {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.tight-form-func {
|
||||
background: $tight-form-func-bg;
|
||||
|
||||
&.show-function-controls {
|
||||
padding-top: 5px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"].tight-form-func-param {
|
||||
background: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tight-form-func-controls {
|
||||
display: none;
|
||||
text-align: center;
|
||||
|
||||
.fa-arrow-left {
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-arrow-right {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-remove {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.grafana-metric-options {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.tight-form-func {
|
||||
background: $tight-form-func-bg;
|
||||
|
||||
&.show-function-controls {
|
||||
padding-top: 5px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"].tight-form-func-param {
|
||||
background: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tight-form-func-controls {
|
||||
display: none;
|
||||
text-align: center;
|
||||
|
||||
.fa-arrow-left {
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-arrow-right {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-remove {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -74,12 +74,11 @@
|
||||
.add-panel-panels-scroll {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none
|
||||
}
|
||||
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.add-panel-panels {
|
||||
|
@ -6,6 +6,8 @@
|
||||
}
|
||||
|
||||
.shortcut-table {
|
||||
margin-bottom: $spacer;
|
||||
|
||||
.shortcut-table-category-header {
|
||||
font-weight: normal;
|
||||
font-size: $font-size-h6;
|
||||
@ -26,8 +28,6 @@
|
||||
text-align: right;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
|
||||
.shortcut-table-key {
|
||||
|
@ -7,11 +7,12 @@
|
||||
}
|
||||
|
||||
.annotation-segment {
|
||||
padding: 8px 7px;
|
||||
|
||||
label.cr1 {
|
||||
margin-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
padding: 8px 7px;
|
||||
}
|
||||
|
||||
.submenu-item {
|
||||
@ -31,14 +32,14 @@
|
||||
|
||||
.variable-value-link {
|
||||
padding-right: 10px;
|
||||
.label-tag {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
padding: 8px 7px;
|
||||
box-sizing: content-box;
|
||||
display: inline-block;
|
||||
color: $text-color;
|
||||
|
||||
.label-tag {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.variable-link-wrapper {
|
||||
|
@ -38,10 +38,10 @@
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: ($tabs-padding-top + $tabs-top-margin) $spacer $tabs-padding-bottom;
|
||||
color: $text-color;
|
||||
i {
|
||||
font-size: 120%;
|
||||
}
|
||||
color: $text-color;
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
|
@ -1,235 +0,0 @@
|
||||
.tight-form {
|
||||
border-top: 1px solid $tight-form-border;
|
||||
border-left: 1px solid $tight-form-border;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
background: $tight-form-bg;
|
||||
|
||||
&.last {
|
||||
border-bottom: 1px solid $tight-form-border;
|
||||
}
|
||||
|
||||
&.borderless {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline;
|
||||
padding-right: 4px;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-container-no-item-borders {
|
||||
border: 1px solid $tight-form-border;
|
||||
border-bottom: none;
|
||||
|
||||
.tight-form, .tight-form-item, [type="text"].tight-form-input, [type="text"].tight-form-clear-input {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spaced-form {
|
||||
.tight-form {
|
||||
margin: 7px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.borderless {
|
||||
.tight-form-item,
|
||||
.tight-form-input {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-container {
|
||||
border-bottom: 1px solid $tight-form-border;
|
||||
}
|
||||
|
||||
.tight-form-btn {
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.tight-form-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
>li {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-flex-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.grafana-metric-options {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.tight-form-item {
|
||||
padding: 8px 7px;
|
||||
box-sizing: content-box;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
display: inline-block;
|
||||
color: $text-color;
|
||||
|
||||
.has-open-function & {
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.tight-form-disabled & {
|
||||
color: $link-color-disabled;
|
||||
a {
|
||||
color: $link-color-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&a:hover {
|
||||
background: $tight-form-func-bg;
|
||||
}
|
||||
|
||||
&.last {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tight-form-item-icon {
|
||||
i {
|
||||
width: 15px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-func {
|
||||
background: $tight-form-func-bg;
|
||||
|
||||
&.show-function-controls {
|
||||
padding-top: 5px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"].tight-form-func-param {
|
||||
background: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="text"].tight-form-clear-input {
|
||||
padding: 8px 7px;
|
||||
border: none;
|
||||
margin: 0px;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
}
|
||||
|
||||
[type="text"],
|
||||
[type="email"],
|
||||
[type="number"],
|
||||
[type="password"] {
|
||||
&.tight-form-input {
|
||||
background-color: $input-bg;
|
||||
border: none;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
margin: 0px;
|
||||
border-radius: 0;
|
||||
padding: 8px 6px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
&.last {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"].tight-form-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tight-form-textarea {
|
||||
height: 200px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
select.tight-form-input {
|
||||
border: none;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
background-color: $input-bg;
|
||||
margin: 0px;
|
||||
border-radius: 0;
|
||||
height: 36px;
|
||||
padding: 9px 3px;
|
||||
&.last {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-func-controls {
|
||||
display: none;
|
||||
text-align: center;
|
||||
|
||||
.fa-arrow-left {
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-arrow-right {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.fa-remove {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-radio {
|
||||
input[type="radio"] {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-section {
|
||||
margin-bottom: 20px;
|
||||
margin-right: 40px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
.tight-form {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-align {
|
||||
padding-left: 66px;
|
||||
}
|
||||
|
||||
.tight-form-item-large { width: 115px; }
|
||||
.tight-form-item-xlarge { width: 150px; }
|
||||
.tight-form-item-xxlarge { width: 200px; }
|
||||
|
||||
.tight-form-input.tight-form-item-xxlarge {
|
||||
width: 215px;
|
||||
}
|
||||
|
||||
.tight-form-inner-box {
|
||||
margin: 20px 0 20px 148px;
|
||||
display: inline-block;
|
||||
}
|
@ -65,15 +65,17 @@
|
||||
}
|
||||
|
||||
.gf-timepicker-component {
|
||||
margin-bottom: 10px;
|
||||
padding: $spacer/2 0 $spacer 0;
|
||||
|
||||
td {
|
||||
padding: 1px;
|
||||
}
|
||||
button.btn-sm {
|
||||
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl);
|
||||
font-size: $font-size-sm;
|
||||
background-image: none;
|
||||
border: none;
|
||||
padding: 6px 10px;
|
||||
padding: 5px 11px;
|
||||
color: $text-color;
|
||||
&.active span {
|
||||
color: $blue;
|
||||
|
@ -62,12 +62,6 @@
|
||||
.admin-page {
|
||||
max-width: 800px;
|
||||
margin-left: 10px;
|
||||
.gf-box {
|
||||
margin-top: 0;
|
||||
}
|
||||
.gf-box-body {
|
||||
min-height: 0;
|
||||
}
|
||||
h2 {
|
||||
margin-left: 15px;
|
||||
margin-bottom: 0px;
|
||||
|
@ -61,7 +61,6 @@
|
||||
}
|
||||
|
||||
&--ok {
|
||||
box-shadow: 0 0 5px rgba(0,200,0,10.8);
|
||||
.panel-alert-icon:before {
|
||||
color: $online;
|
||||
content: "\e611";
|
||||
|
@ -172,6 +172,12 @@ div.flot-text {
|
||||
}
|
||||
}
|
||||
|
||||
.panel-in-fullscreen {
|
||||
.panel-drop-zone {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-time-info {
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
|
322
public/vendor/angular-ui/ui-bootstrap-tpls.js
vendored
322
public/vendor/angular-ui/ui-bootstrap-tpls.js
vendored
@ -5,8 +5,8 @@
|
||||
* Version: 0.13.4 - 2015-09-03
|
||||
* License: MIT
|
||||
*/
|
||||
angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker","ui.bootstrap.tabs"]);
|
||||
angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/tabs/tab.html","template/tabs/tabset.html"]);
|
||||
angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker"]);
|
||||
angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html"]);
|
||||
angular.module('ui.bootstrap.position', [])
|
||||
|
||||
/**
|
||||
@ -1180,302 +1180,6 @@ function($compile, $parse, $document, $rootScope, $position, dateFilter, datePar
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ui.bootstrap.tabs
|
||||
*
|
||||
* @description
|
||||
* AngularJS version of the tabs directive.
|
||||
*/
|
||||
|
||||
angular.module('ui.bootstrap.tabs', [])
|
||||
|
||||
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
|
||||
var ctrl = this,
|
||||
tabs = ctrl.tabs = $scope.tabs = [];
|
||||
|
||||
ctrl.select = function(selectedTab) {
|
||||
angular.forEach(tabs, function(tab) {
|
||||
if (tab.active && tab !== selectedTab) {
|
||||
tab.active = false;
|
||||
tab.onDeselect();
|
||||
selectedTab.selectCalled = false;
|
||||
}
|
||||
});
|
||||
selectedTab.active = true;
|
||||
// only call select if it has not already been called
|
||||
if (!selectedTab.selectCalled) {
|
||||
selectedTab.onSelect();
|
||||
selectedTab.selectCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.addTab = function addTab(tab) {
|
||||
tabs.push(tab);
|
||||
// we can't run the select function on the first tab
|
||||
// since that would select it twice
|
||||
if (tabs.length === 1 && tab.active !== false) {
|
||||
tab.active = true;
|
||||
} else if (tab.active) {
|
||||
ctrl.select(tab);
|
||||
} else {
|
||||
tab.active = false;
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.removeTab = function removeTab(tab) {
|
||||
var index = tabs.indexOf(tab);
|
||||
//Select a new tab if the tab to be removed is selected and not destroyed
|
||||
if (tab.active && tabs.length > 1 && !destroyed) {
|
||||
//If this is the last tab, select the previous tab. else, the next tab.
|
||||
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
|
||||
ctrl.select(tabs[newActiveIndex]);
|
||||
}
|
||||
tabs.splice(index, 1);
|
||||
};
|
||||
|
||||
var destroyed;
|
||||
$scope.$on('$destroy', function() {
|
||||
destroyed = true;
|
||||
});
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ui.bootstrap.tabs.directive:tabset
|
||||
* @restrict EA
|
||||
*
|
||||
* @description
|
||||
* Tabset is the outer container for the tabs directive
|
||||
*
|
||||
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
|
||||
* @param {boolean=} justified Whether or not to use justified styling for the tabs.
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<tabset>
|
||||
<tab heading="Tab 1"><b>First</b> Content!</tab>
|
||||
<tab heading="Tab 2"><i>Second</i> Content!</tab>
|
||||
</tabset>
|
||||
<hr />
|
||||
<tabset vertical="true">
|
||||
<tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
|
||||
<tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
|
||||
</tabset>
|
||||
<tabset justified="true">
|
||||
<tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
|
||||
<tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
|
||||
</tabset>
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
.directive('tabset', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: {
|
||||
type: '@'
|
||||
},
|
||||
controller: 'TabsetController',
|
||||
templateUrl: 'template/tabs/tabset.html',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
|
||||
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ui.bootstrap.tabs.directive:tab
|
||||
* @restrict EA
|
||||
*
|
||||
* @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
|
||||
* @param {string=} select An expression to evaluate when the tab is selected.
|
||||
* @param {boolean=} active A binding, telling whether or not this tab is selected.
|
||||
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
|
||||
*
|
||||
* @description
|
||||
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<div ng-controller="TabsDemoCtrl">
|
||||
<button class="btn btn-small" ng-click="items[0].active = true">
|
||||
Select item 1, using active binding
|
||||
</button>
|
||||
<button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
|
||||
Enable/disable item 2, using disabled binding
|
||||
</button>
|
||||
<br />
|
||||
<tabset>
|
||||
<tab heading="Tab 1">First Tab</tab>
|
||||
<tab select="alertMe()">
|
||||
<tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
|
||||
Second Tab, with alert callback and html heading!
|
||||
</tab>
|
||||
<tab ng-repeat="item in items"
|
||||
heading="{{item.title}}"
|
||||
disabled="item.disabled"
|
||||
active="item.active">
|
||||
{{item.content}}
|
||||
</tab>
|
||||
</tabset>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
function TabsDemoCtrl($scope) {
|
||||
$scope.items = [
|
||||
{ title:"Dynamic Title 1", content:"Dynamic Item 0" },
|
||||
{ title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
|
||||
];
|
||||
|
||||
$scope.alertMe = function() {
|
||||
setTimeout(function() {
|
||||
alert("You've selected the alert tab!");
|
||||
});
|
||||
};
|
||||
};
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ui.bootstrap.tabs.directive:tabHeading
|
||||
* @restrict EA
|
||||
*
|
||||
* @description
|
||||
* Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<tabset>
|
||||
<tab>
|
||||
<tab-heading><b>HTML</b> in my titles?!</tab-heading>
|
||||
And some content, too!
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
|
||||
That's right.
|
||||
</tab>
|
||||
</tabset>
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
.directive('tab', ['$parse', '$log', function($parse, $log) {
|
||||
return {
|
||||
require: '^tabset',
|
||||
restrict: 'EA',
|
||||
replace: true,
|
||||
templateUrl: 'template/tabs/tab.html',
|
||||
transclude: true,
|
||||
scope: {
|
||||
active: '=?',
|
||||
heading: '@',
|
||||
onSelect: '&select', //This callback is called in contentHeadingTransclude
|
||||
//once it inserts the tab's content into the dom
|
||||
onDeselect: '&deselect'
|
||||
},
|
||||
controller: function() {
|
||||
//Empty controller so other directives can require being 'under' a tab
|
||||
},
|
||||
link: function(scope, elm, attrs, tabsetCtrl, transclude) {
|
||||
scope.$watch('active', function(active) {
|
||||
if (active) {
|
||||
tabsetCtrl.select(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.disabled = false;
|
||||
if (attrs.disable) {
|
||||
scope.$parent.$watch($parse(attrs.disable), function(value) {
|
||||
scope.disabled = !! value;
|
||||
});
|
||||
}
|
||||
|
||||
// Deprecation support of "disabled" parameter
|
||||
// fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
|
||||
// This code is duplicated from the lines above to make it easy to remove once
|
||||
// the feature has been completely deprecated
|
||||
if (attrs.disabled) {
|
||||
$log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
|
||||
scope.$parent.$watch($parse(attrs.disabled), function(value) {
|
||||
scope.disabled = !! value;
|
||||
});
|
||||
}
|
||||
|
||||
scope.select = function() {
|
||||
if (!scope.disabled) {
|
||||
scope.active = true;
|
||||
}
|
||||
};
|
||||
|
||||
tabsetCtrl.addTab(scope);
|
||||
scope.$on('$destroy', function() {
|
||||
tabsetCtrl.removeTab(scope);
|
||||
});
|
||||
|
||||
//We need to transclude later, once the content container is ready.
|
||||
//when this link happens, we're inside a tab heading.
|
||||
scope.$transcludeFn = transclude;
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('tabHeadingTransclude', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '^tab',
|
||||
link: function(scope, elm, attrs, tabCtrl) {
|
||||
scope.$watch('headingElement', function updateHeadingElement(heading) {
|
||||
if (heading) {
|
||||
elm.html('');
|
||||
elm.append(heading);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('tabContentTransclude', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '^tabset',
|
||||
link: function(scope, elm, attrs) {
|
||||
var tab = scope.$eval(attrs.tabContentTransclude);
|
||||
|
||||
//Now our tab is ready to be transcluded: both the tab heading area
|
||||
//and the tab content area are loaded. Transclude 'em both.
|
||||
tab.$transcludeFn(tab.$parent, function(contents) {
|
||||
angular.forEach(contents, function(node) {
|
||||
if (isTabHeading(node)) {
|
||||
//Let tabHeadingTransclude know.
|
||||
tab.headingElement = node;
|
||||
} else {
|
||||
elm.append(node);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function isTabHeading(node) {
|
||||
return node.tagName && (
|
||||
node.hasAttribute('tab-heading') ||
|
||||
node.hasAttribute('data-tab-heading') ||
|
||||
node.hasAttribute('x-tab-heading') ||
|
||||
node.tagName.toLowerCase() === 'tab-heading' ||
|
||||
node.tagName.toLowerCase() === 'data-tab-heading' ||
|
||||
node.tagName.toLowerCase() === 'x-tab-heading'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
|
||||
$templateCache.put("template/datepicker/datepicker.html",
|
||||
"<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
|
||||
@ -1568,25 +1272,3 @@ angular.module("template/datepicker/year.html", []).run(["$templateCache", funct
|
||||
"");
|
||||
}]);
|
||||
|
||||
angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
|
||||
$templateCache.put("template/tabs/tab.html",
|
||||
"<li ng-class=\"{active: active, disabled: disabled}\">\n" +
|
||||
" <a href ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
|
||||
"</li>\n" +
|
||||
"");
|
||||
}]);
|
||||
|
||||
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
|
||||
$templateCache.put("template/tabs/tabset.html",
|
||||
"<div>\n" +
|
||||
" <ul class=\"nav nav-{{type || 'tabs'}} nav-tabs-alt\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
|
||||
" <div class=\"tab-content\">\n" +
|
||||
" <div class=\"tab-pane\" \n" +
|
||||
" ng-repeat=\"tab in tabs\" \n" +
|
||||
" ng-class=\"{active: tab.active}\"\n" +
|
||||
" tab-content-transclude=\"tab\">\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
"</div>\n" +
|
||||
"");
|
||||
}]);
|
||||
|
105
public/vendor/flot/jquery.flot.stack.js
vendored
105
public/vendor/flot/jquery.flot.stack.js
vendored
@ -72,52 +72,49 @@ charts or filled areas).
|
||||
horizontal = s.bars.horizontal,
|
||||
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
keyOffset = horizontal ? 1 : 0,
|
||||
accumulateOffset = horizontal ? 0 : 1,
|
||||
i = 0, j = 0, l, m;
|
||||
|
||||
while (true) {
|
||||
// browse all points from the current series and from the previous series
|
||||
if (i >= points.length && j >= otherpoints.length)
|
||||
break;
|
||||
|
||||
// newpoints will replace current series with
|
||||
// as many points as different timestamps we have in the 2 (current & previous) series
|
||||
l = newpoints.length;
|
||||
px = points[i + keyOffset];
|
||||
py = points[i + accumulateOffset];
|
||||
qx = otherpoints[j + keyOffset];
|
||||
qy = otherpoints[j + accumulateOffset];
|
||||
bottom = 0;
|
||||
|
||||
if (i < points.length && px == null) {
|
||||
// let's ignore null points from current series, nothing to do with them
|
||||
i += ps;
|
||||
}
|
||||
else if (j < otherpoints.length && qx == null) {
|
||||
// let's ignore null points from previous series, nothing to do with them
|
||||
j += otherps;
|
||||
}
|
||||
else if (i >= points.length) {
|
||||
// no more points in the current series, simply take the remaining points
|
||||
// from the previous series so that next series will correctly stack
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(otherpoints[j + m]);
|
||||
bottom = qy;
|
||||
j += otherps;
|
||||
}
|
||||
else if (j >= otherpoints.length) {
|
||||
// no more points in the previous series, of course let's take
|
||||
// the remaining points from the current series
|
||||
if (i < points.length && points[i] == null) {
|
||||
// copy gaps
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
i += ps;
|
||||
}
|
||||
else if (i >= points.length) {
|
||||
// take the remaining points from the previous series
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(otherpoints[j + m]);
|
||||
if (withbottom)
|
||||
newpoints[l + 2] = otherpoints[j + accumulateOffset];
|
||||
j += otherps;
|
||||
}
|
||||
else if (j >= otherpoints.length) {
|
||||
// take the remaining points from the current series
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
i += ps;
|
||||
}
|
||||
else if (j < otherpoints.length && otherpoints[j] == null) {
|
||||
// ignore point
|
||||
j += otherps;
|
||||
}
|
||||
else {
|
||||
// next available points from current and previous series have the same timestamp
|
||||
// cases where we actually got two points
|
||||
px = points[i + keyOffset];
|
||||
py = points[i + accumulateOffset];
|
||||
qx = otherpoints[j + keyOffset];
|
||||
qy = otherpoints[j + accumulateOffset];
|
||||
bottom = 0;
|
||||
|
||||
if (px == qx) {
|
||||
// so take the point from the current series and skip the previous' one
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
|
||||
@ -127,23 +124,27 @@ charts or filled areas).
|
||||
i += ps;
|
||||
j += otherps;
|
||||
}
|
||||
// next available point with the smallest timestamp is from the previous series
|
||||
else if (px > qx) {
|
||||
// so take the point from the previous series so that next series will correctly stack
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(otherpoints[j + m]);
|
||||
|
||||
// we might be able to interpolate
|
||||
if (i > 0 && points[i - ps] != null)
|
||||
newpoints[l + accumulateOffset] += py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||
|
||||
bottom = qy;
|
||||
// take the point from the previous series so that next series will correctly stack
|
||||
if (i == 0) {
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(otherpoints[j + m]);
|
||||
bottom = qy;
|
||||
}
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
if (i > 0 && points[i - ps] != null) {
|
||||
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||
newpoints.push(qx);
|
||||
newpoints.push(intery + qy);
|
||||
for (m = 2; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
}
|
||||
// (px < qx) next available point with the smallest timestamp is from the current series
|
||||
else {
|
||||
// so of course let's take the point from the current series
|
||||
else { // px < qx
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
|
||||
@ -156,10 +157,22 @@ charts or filled areas).
|
||||
|
||||
i += ps;
|
||||
}
|
||||
}
|
||||
|
||||
if (l != newpoints.length && withbottom)
|
||||
newpoints[l + 2] = bottom;
|
||||
fromgap = false;
|
||||
|
||||
if (l != newpoints.length && withbottom)
|
||||
newpoints[l + 2] = bottom;
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
if (withsteps && l != newpoints.length && l > 0
|
||||
&& newpoints[l] != null
|
||||
&& newpoints[l] != newpoints[l - ps]
|
||||
&& newpoints[l + 1] != newpoints[l - ps + 1]) {
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints[l + ps + m] = newpoints[l + m];
|
||||
newpoints[l + 1] = newpoints[l - ps + 1];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
|
@ -5,28 +5,32 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<title>Grafana</title>
|
||||
<title>Grafana - Error</title>
|
||||
|
||||
<link href='[[.AppSubUrl]]/public/css/fonts.min.css' rel='stylesheet' type='text/css'>
|
||||
|
||||
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.dark.min.css">
|
||||
|
||||
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.dark.min.css" title="Dark">
|
||||
<link rel="icon" type="image/png" href="[[.AppSubUrl]]/public/img/fav32.png">
|
||||
|
||||
<base href="[[.AppSubUrl]]/" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="gf-box" style="margin: 200px auto 0 auto; width: 500px;">
|
||||
<div class="gf-box-header">
|
||||
<span class="gf-box-title">
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
Server side error :(
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="gf-box-body">
|
||||
<h4>[[.Title]]</h4>
|
||||
[[.ErrorMsg]]
|
||||
</div>
|
||||
<h4>[[.Title]]</h4>
|
||||
|
||||
<pre>[[.ErrorMsg]]</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user