mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #11411 from grafana/11247-save-alerts
dashboard: allow alerts to be saved for new/provisioned dashboards
This commit is contained in:
@@ -13,11 +13,7 @@ func init() {
|
|||||||
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
|
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
|
||||||
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
||||||
|
|
||||||
if _, err := extractor.GetAlerts(); err != nil {
|
return extractor.ValidateAlerts()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
|
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
|
||||||
@@ -29,15 +25,12 @@ func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
|
|||||||
|
|
||||||
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
||||||
|
|
||||||
if alerts, err := extractor.GetAlerts(); err != nil {
|
alerts, err := extractor.GetAlerts()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
}
|
||||||
|
|
||||||
saveAlerts.Alerts = alerts
|
saveAlerts.Alerts = alerts
|
||||||
}
|
|
||||||
|
|
||||||
if err := bus.Dispatch(&saveAlerts); err != nil {
|
return bus.Dispatch(&saveAlerts)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,76 +11,78 @@ import (
|
|||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DashAlertExtractor extracts alerts from the dashboard json
|
||||||
type DashAlertExtractor struct {
|
type DashAlertExtractor struct {
|
||||||
Dash *m.Dashboard
|
Dash *m.Dashboard
|
||||||
OrgId int64
|
OrgID int64
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor {
|
// NewDashAlertExtractor returns a new DashAlertExtractor
|
||||||
|
func NewDashAlertExtractor(dash *m.Dashboard, orgID int64) *DashAlertExtractor {
|
||||||
return &DashAlertExtractor{
|
return &DashAlertExtractor{
|
||||||
Dash: dash,
|
Dash: dash,
|
||||||
OrgId: orgId,
|
OrgID: orgID,
|
||||||
log: log.New("alerting.extractor"),
|
log: log.New("alerting.extractor"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (*m.DataSource, error) {
|
func (e *DashAlertExtractor) lookupDatasourceID(dsName string) (*m.DataSource, error) {
|
||||||
if dsName == "" {
|
if dsName == "" {
|
||||||
query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
|
query := &m.GetDataSourcesQuery{OrgId: e.OrgID}
|
||||||
if err := bus.Dispatch(query); err != nil {
|
if err := bus.Dispatch(query); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
}
|
||||||
|
|
||||||
for _, ds := range query.Result {
|
for _, ds := range query.Result {
|
||||||
if ds.IsDefault {
|
if ds.IsDefault {
|
||||||
return ds, nil
|
return ds, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgId}
|
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgID}
|
||||||
if err := bus.Dispatch(query); err != nil {
|
if err := bus.Dispatch(query); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
return query.Result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return query.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("Could not find datasource id for " + dsName)
|
return nil, errors.New("Could not find datasource id for " + dsName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findPanelQueryByRefId(panel *simplejson.Json, refId string) *simplejson.Json {
|
func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Json {
|
||||||
for _, targetsObj := range panel.Get("targets").MustArray() {
|
for _, targetsObj := range panel.Get("targets").MustArray() {
|
||||||
target := simplejson.NewFromAny(targetsObj)
|
target := simplejson.NewFromAny(targetsObj)
|
||||||
|
|
||||||
if target.Get("refId").MustString() == refId {
|
if target.Get("refId").MustString() == refID {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyJson(in *simplejson.Json) (*simplejson.Json, error) {
|
func copyJSON(in *simplejson.Json) (*simplejson.Json, error) {
|
||||||
rawJson, err := in.MarshalJSON()
|
rawJSON, err := in.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return simplejson.NewJson(rawJson)
|
return simplejson.NewJson(rawJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) {
|
func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*m.Alert) bool) ([]*m.Alert, error) {
|
||||||
alerts := make([]*m.Alert, 0)
|
alerts := make([]*m.Alert, 0)
|
||||||
|
|
||||||
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
|
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
|
||||||
panel := simplejson.NewFromAny(panelObj)
|
panel := simplejson.NewFromAny(panelObj)
|
||||||
|
|
||||||
collapsedJson, collapsed := panel.CheckGet("collapsed")
|
collapsedJSON, collapsed := panel.CheckGet("collapsed")
|
||||||
// check if the panel is collapsed
|
// check if the panel is collapsed
|
||||||
if collapsed && collapsedJson.MustBool() {
|
if collapsed && collapsedJSON.MustBool() {
|
||||||
|
|
||||||
// extract alerts from sub panels for collapsed panels
|
// extract alerts from sub panels for collapsed panels
|
||||||
als, err := e.GetAlertFromPanels(panel)
|
als, err := e.getAlertFromPanels(panel, validateAlertFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -95,7 +97,7 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
panelId, err := panel.Get("id").Int64()
|
panelID, err := panel.Get("id").Int64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("panel id is required. err %v", err)
|
return nil, fmt.Errorf("panel id is required. err %v", err)
|
||||||
}
|
}
|
||||||
@@ -113,8 +115,8 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
|
|||||||
|
|
||||||
alert := &m.Alert{
|
alert := &m.Alert{
|
||||||
DashboardId: e.Dash.Id,
|
DashboardId: e.Dash.Id,
|
||||||
OrgId: e.OrgId,
|
OrgId: e.OrgID,
|
||||||
PanelId: panelId,
|
PanelId: panelID,
|
||||||
Id: jsonAlert.Get("id").MustInt64(),
|
Id: jsonAlert.Get("id").MustInt64(),
|
||||||
Name: jsonAlert.Get("name").MustString(),
|
Name: jsonAlert.Get("name").MustString(),
|
||||||
Handler: jsonAlert.Get("handler").MustInt64(),
|
Handler: jsonAlert.Get("handler").MustInt64(),
|
||||||
@@ -126,11 +128,11 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
|
|||||||
jsonCondition := simplejson.NewFromAny(condition)
|
jsonCondition := simplejson.NewFromAny(condition)
|
||||||
|
|
||||||
jsonQuery := jsonCondition.Get("query")
|
jsonQuery := jsonCondition.Get("query")
|
||||||
queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
|
queryRefID := jsonQuery.Get("params").MustArray()[0].(string)
|
||||||
panelQuery := findPanelQueryByRefId(panel, queryRefId)
|
panelQuery := findPanelQueryByRefID(panel, queryRefID)
|
||||||
|
|
||||||
if panelQuery == nil {
|
if panelQuery == nil {
|
||||||
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
|
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID)
|
||||||
return nil, ValidationError{Reason: reason}
|
return nil, ValidationError{Reason: reason}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,12 +143,13 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
|
|||||||
dsName = panel.Get("datasource").MustString()
|
dsName = panel.Get("datasource").MustString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if datasource, err := e.lookupDatasourceId(dsName); err != nil {
|
datasource, err := e.lookupDatasourceID(dsName)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
|
||||||
|
|
||||||
if interval, err := panel.Get("interval").String(); err == nil {
|
if interval, err := panel.Get("interval").String(); err == nil {
|
||||||
panelQuery.Set("interval", interval)
|
panelQuery.Set("interval", interval)
|
||||||
}
|
}
|
||||||
@@ -162,21 +165,28 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if alert.ValidToSave() {
|
if !validateAlertFunc(alert) {
|
||||||
alerts = append(alerts, alert)
|
|
||||||
} else {
|
|
||||||
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId)
|
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId)
|
||||||
return nil, m.ErrDashboardContainsInvalidAlertData
|
return nil, m.ErrDashboardContainsInvalidAlertData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alerts = append(alerts, alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
return alerts, nil
|
return alerts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
|
func validateAlertRule(alert *m.Alert) bool {
|
||||||
e.log.Debug("GetAlerts")
|
return alert.ValidToSave()
|
||||||
|
}
|
||||||
|
|
||||||
dashboardJson, err := copyJson(e.Dash.Data)
|
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data
|
||||||
|
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
|
||||||
|
return e.extractAlerts(validateAlertRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *m.Alert) bool) ([]*m.Alert, error) {
|
||||||
|
dashboardJSON, err := copyJSON(e.Dash.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -185,11 +195,11 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
|
|||||||
|
|
||||||
// We extract alerts from rows to be backwards compatible
|
// We extract alerts from rows to be backwards compatible
|
||||||
// with the old dashboard json model.
|
// with the old dashboard json model.
|
||||||
rows := dashboardJson.Get("rows").MustArray()
|
rows := dashboardJSON.Get("rows").MustArray()
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
for _, rowObj := range rows {
|
for _, rowObj := range rows {
|
||||||
row := simplejson.NewFromAny(rowObj)
|
row := simplejson.NewFromAny(rowObj)
|
||||||
a, err := e.GetAlertFromPanels(row)
|
a, err := e.getAlertFromPanels(row, validateFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -197,7 +207,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
|
|||||||
alerts = append(alerts, a...)
|
alerts = append(alerts, a...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
a, err := e.GetAlertFromPanels(dashboardJson)
|
a, err := e.getAlertFromPanels(dashboardJSON, validateFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -208,3 +218,10 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
|
|||||||
e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))
|
e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))
|
||||||
return alerts, nil
|
return alerts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
|
||||||
|
// in the first validation pass
|
||||||
|
func (e *DashAlertExtractor) ValidateAlerts() error {
|
||||||
|
_, err := e.extractAlerts(func(alert *m.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 })
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -240,5 +240,26 @@ func TestAlertRuleExtraction(t *testing.T) {
|
|||||||
So(len(alerts), ShouldEqual, 4)
|
So(len(alerts), ShouldEqual, 4)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Parse and validate dashboard without id and containing an alert", func() {
|
||||||
|
json, err := ioutil.ReadFile("./test-data/dash-without-id.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
dashJSON, err := simplejson.NewJson(json)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
dash := m.NewDashboardFromJson(dashJSON)
|
||||||
|
extractor := NewDashAlertExtractor(dash, 1)
|
||||||
|
|
||||||
|
err = extractor.ValidateAlerts()
|
||||||
|
|
||||||
|
Convey("Should validate without error", func() {
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should fail on save", func() {
|
||||||
|
_, err := extractor.GetAlerts()
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
281
pkg/services/alerting/test-data/dash-without-id.json
Normal file
281
pkg/services/alerting/test-data/dash-without-id.json
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
{
|
||||||
|
"title": "Influxdb",
|
||||||
|
"tags": [
|
||||||
|
"apa"
|
||||||
|
],
|
||||||
|
"style": "dark",
|
||||||
|
"timezone": "browser",
|
||||||
|
"editable": true,
|
||||||
|
"hideControls": false,
|
||||||
|
"sharedCrosshair": false,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"collapse": false,
|
||||||
|
"editable": true,
|
||||||
|
"height": "450px",
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"alert": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"evaluator": {
|
||||||
|
"params": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"type": "gt"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"params": [
|
||||||
|
"B",
|
||||||
|
"5m",
|
||||||
|
"now"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reducer": {
|
||||||
|
"params": [],
|
||||||
|
"type": "avg"
|
||||||
|
},
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frequency": "3s",
|
||||||
|
"handler": 1,
|
||||||
|
"name": "Influxdb",
|
||||||
|
"noDataState": "no_data",
|
||||||
|
"notifications": [
|
||||||
|
{
|
||||||
|
"id": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alerting": {},
|
||||||
|
"aliasColors": {
|
||||||
|
"logins.count.count": "#890F02"
|
||||||
|
},
|
||||||
|
"bars": false,
|
||||||
|
"datasource": "InfluxDB",
|
||||||
|
"editable": true,
|
||||||
|
"error": false,
|
||||||
|
"fill": 1,
|
||||||
|
"grid": {},
|
||||||
|
"id": 1,
|
||||||
|
"interval": ">10s",
|
||||||
|
"isNew": true,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "connected",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"span": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"$interval"
|
||||||
|
],
|
||||||
|
"type": "time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"datacenter"
|
||||||
|
],
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"type": "fill"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide": false,
|
||||||
|
"measurement": "logins.count",
|
||||||
|
"policy": "default",
|
||||||
|
"query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
|
||||||
|
"rawQuery": true,
|
||||||
|
"refId": "B",
|
||||||
|
"resultFormat": "time_series",
|
||||||
|
"select": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [],
|
||||||
|
"type": "count"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"$interval"
|
||||||
|
],
|
||||||
|
"type": "time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"type": "fill"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide": true,
|
||||||
|
"measurement": "cpu",
|
||||||
|
"policy": "default",
|
||||||
|
"refId": "A",
|
||||||
|
"resultFormat": "time_series",
|
||||||
|
"select": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [],
|
||||||
|
"type": "mean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"params": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"params": [],
|
||||||
|
"type": "sum"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [
|
||||||
|
{
|
||||||
|
"colorMode": "critical",
|
||||||
|
"fill": true,
|
||||||
|
"line": true,
|
||||||
|
"op": "gt",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Panel Title",
|
||||||
|
"tooltip": {
|
||||||
|
"msResolution": false,
|
||||||
|
"ordering": "alphabetical",
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "cumulative"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"editable": true,
|
||||||
|
"error": false,
|
||||||
|
"id": 2,
|
||||||
|
"isNew": true,
|
||||||
|
"limit": 10,
|
||||||
|
"links": [],
|
||||||
|
"show": "current",
|
||||||
|
"span": 2,
|
||||||
|
"stateFilter": [
|
||||||
|
"alerting"
|
||||||
|
],
|
||||||
|
"title": "Alert status",
|
||||||
|
"type": "alertlist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Row"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-5m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"now": true,
|
||||||
|
"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": []
|
||||||
|
},
|
||||||
|
"schemaVersion": 13,
|
||||||
|
"version": 120,
|
||||||
|
"links": [],
|
||||||
|
"gnetId": null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user