mirror of
https://github.com/grafana/grafana.git
synced 2026-07-29 15:59:50 -05:00
Merge branch 'master' into alerting_opentsdb
This commit is contained in:
+8
-4
@@ -12,11 +12,15 @@ module.exports = function (grunt) {
|
|||||||
platform: process.platform.replace('win32', 'windows'),
|
platform: process.platform.replace('win32', 'windows'),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.platform.match(/^win/)) {
|
if (grunt.option('arch')) {
|
||||||
config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86';
|
config.arch = grunt.option('arch');
|
||||||
}
|
} else {
|
||||||
|
config.arch = os.arch();
|
||||||
|
|
||||||
config.arch = grunt.option('arch') || os.arch();
|
if (process.platform.match(/^win/)) {
|
||||||
|
config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.phjs = grunt.option('phjsToRelease');
|
config.phjs = grunt.option('phjsToRelease');
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -25,10 +25,13 @@ install:
|
|||||||
build_script:
|
build_script:
|
||||||
- go run build.go build
|
- go run build.go build
|
||||||
- grunt release
|
- grunt release
|
||||||
#- 7z a grafana.zip %APPVEYOR_BUILD_FOLDER%\dist\*
|
- go run build.go sha1-dist
|
||||||
- cp dist/* .
|
- cp dist/* .
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: grafana-*windows-ia32.zip
|
- path: grafana-*windows-*.*
|
||||||
#- path: dist/*
|
|
||||||
name: binzip
|
name: binzip
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
- provider: Environment
|
||||||
|
name: GrafanaBuildsS3
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ func main() {
|
|||||||
createDebPackages()
|
createDebPackages()
|
||||||
sha1FilesInDist()
|
sha1FilesInDist()
|
||||||
|
|
||||||
|
case "sha1-dist":
|
||||||
|
sha1FilesInDist()
|
||||||
|
|
||||||
case "latest":
|
case "latest":
|
||||||
makeLatestDistCopies()
|
makeLatestDistCopies()
|
||||||
sha1FilesInDist()
|
sha1FilesInDist()
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
bind-address = "0.0.0.0"
|
|
||||||
|
|
||||||
[logging]
|
|
||||||
level = "debug"
|
|
||||||
file = "/opt/influxdb/shared/data/influxdb.log" # stdout to log to standard out
|
|
||||||
|
|
||||||
[admin]
|
|
||||||
port = 8083 # binding is disabled if the port isn't set
|
|
||||||
assets = "/opt/influxdb/current/admin"
|
|
||||||
|
|
||||||
[api]
|
|
||||||
port = 8086 # binding is disabled if the port isn't set
|
|
||||||
|
|
||||||
read-timeout = "5s"
|
|
||||||
|
|
||||||
[input_plugins]
|
|
||||||
|
|
||||||
[input_plugins.graphite]
|
|
||||||
enabled = true
|
|
||||||
port = 2004
|
|
||||||
database = "graphite" # store graphite data in this database
|
|
||||||
|
|
||||||
|
|
||||||
[raft]
|
|
||||||
port = 8090
|
|
||||||
dir = "/opt/influxdb/shared/data/raft"
|
|
||||||
|
|
||||||
[storage]
|
|
||||||
dir = "/opt/influxdb/shared/data/db"
|
|
||||||
# How many requests to potentially buffer in memory. If the buffer gets filled then writes
|
|
||||||
# will still be logged and once the local storage has caught up (or compacted) the writes
|
|
||||||
# will be replayed from the WAL
|
|
||||||
write-buffer-size = 10000
|
|
||||||
default-engine = "rocksdb"
|
|
||||||
max-open-shards = 0
|
|
||||||
point-batch-size = 100
|
|
||||||
write-batch-size = 5000000
|
|
||||||
retention-sweep-period = "10m"
|
|
||||||
|
|
||||||
[storage.engines.rocksdb]
|
|
||||||
max-open-files = 1000
|
|
||||||
lru-cache-size = "200m"
|
|
||||||
|
|
||||||
[storage.engines.leveldb]
|
|
||||||
max-open-files = 1000
|
|
||||||
lru-cache-size = "200m"
|
|
||||||
|
|
||||||
[cluster]
|
|
||||||
protobuf_port = 8099
|
|
||||||
protobuf_timeout = "2s" # the write timeout on the protobuf conn any duration parseable by time.ParseDuration
|
|
||||||
protobuf_heartbeat = "200ms" # the heartbeat interval between the servers. must be parseable by time.ParseDuration
|
|
||||||
protobuf_min_backoff = "1s" # the minimum backoff after a failed heartbeat attempt
|
|
||||||
protobuf_max_backoff = "10s" # the maxmimum backoff after a failed heartbeat attempt
|
|
||||||
write-buffer-size = 10000
|
|
||||||
ax-response-buffer-size = 100000
|
|
||||||
oncurrent-shard-query-limit = 10
|
|
||||||
|
|
||||||
[sharding]
|
|
||||||
replication-factor = 1
|
|
||||||
|
|
||||||
[sharding.short-term]
|
|
||||||
duration = "7d"
|
|
||||||
split = 1
|
|
||||||
|
|
||||||
[sharding.long-term]
|
|
||||||
duration = "30d"
|
|
||||||
split = 1
|
|
||||||
# split-random = "/^Hf.*/"
|
|
||||||
|
|
||||||
[wal]
|
|
||||||
dir = "/opt/influxdb/shared/data/wal"
|
|
||||||
flush-after = 1000 # the number of writes after which wal will be flushed, 0 for flushing on every write
|
|
||||||
bookmark-after = 1000 # the number of writes after which a bookmark will be created
|
|
||||||
index-after = 1000
|
|
||||||
requests-per-logfile = 10000
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
influxdb:
|
influxdb:
|
||||||
#image: influxdb/influxdb:1.0-alpine
|
|
||||||
image: influxdb:latest
|
image: influxdb:latest
|
||||||
container_name: influxdb
|
container_name: influxdb
|
||||||
ports:
|
ports:
|
||||||
- "2004:2004"
|
- "2004:2004"
|
||||||
- "8083:8083"
|
- "8083:8083"
|
||||||
- "8086:8086"
|
- "8086:8086"
|
||||||
|
volumes:
|
||||||
|
- ./blocks/influxdb/influxdb.conf:/etc/influxdb/influxdb.conf
|
||||||
|
|
||||||
fake-influxdb-data:
|
fake-influxdb-data:
|
||||||
image: grafana/fake-data-gen
|
image: grafana/fake-data-gen
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
reporting-disabled = false
|
||||||
|
|
||||||
|
[meta]
|
||||||
|
# Where the metadata/raft database is stored
|
||||||
|
dir = "/var/lib/influxdb/meta"
|
||||||
|
|
||||||
|
retention-autocreate = true
|
||||||
|
|
||||||
|
# If log messages are printed for the meta service
|
||||||
|
logging-enabled = true
|
||||||
|
pprof-enabled = false
|
||||||
|
|
||||||
|
# The default duration for leases.
|
||||||
|
lease-duration = "1m0s"
|
||||||
|
|
||||||
|
[data]
|
||||||
|
# Controls if this node holds time series data shards in the cluster
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
dir = "/var/lib/influxdb/data"
|
||||||
|
|
||||||
|
# These are the WAL settings for the storage engine >= 0.9.3
|
||||||
|
wal-dir = "/var/lib/influxdb/wal"
|
||||||
|
wal-logging-enabled = true
|
||||||
|
|
||||||
|
|
||||||
|
[coordinator]
|
||||||
|
write-timeout = "10s"
|
||||||
|
max-concurrent-queries = 0
|
||||||
|
query-timeout = "0"
|
||||||
|
log-queries-after = "0"
|
||||||
|
max-select-point = 0
|
||||||
|
max-select-series = 0
|
||||||
|
max-select-buckets = 0
|
||||||
|
|
||||||
|
[retention]
|
||||||
|
enabled = true
|
||||||
|
check-interval = "30m"
|
||||||
|
|
||||||
|
[shard-precreation]
|
||||||
|
enabled = true
|
||||||
|
check-interval = "10m"
|
||||||
|
advance-period = "30m"
|
||||||
|
|
||||||
|
[monitor]
|
||||||
|
store-enabled = true # Whether to record statistics internally.
|
||||||
|
store-database = "_internal" # The destination database for recorded statistics
|
||||||
|
store-interval = "10s" # The interval at which to record statistics
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
enabled = true
|
||||||
|
bind-address = ":8083"
|
||||||
|
https-enabled = false
|
||||||
|
https-certificate = "/etc/ssl/influxdb.pem"
|
||||||
|
|
||||||
|
[http]
|
||||||
|
enabled = true
|
||||||
|
bind-address = ":8086"
|
||||||
|
auth-enabled = true
|
||||||
|
log-enabled = true
|
||||||
|
write-tracing = false
|
||||||
|
pprof-enabled = false
|
||||||
|
https-enabled = false
|
||||||
|
https-certificate = "/etc/ssl/influxdb.pem"
|
||||||
|
### Use a separate private key location.
|
||||||
|
# https-private-key = ""
|
||||||
|
max-row-limit = 10000
|
||||||
|
realm = "InfluxDB"
|
||||||
|
|
||||||
|
unix-socket-enabled = false # enable http service over unix domain socket
|
||||||
|
# bind-socket = "/var/run/influxdb.sock"
|
||||||
|
|
||||||
|
[subscriber]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[graphite]]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[[collectd]]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[[opentsdb]]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[[udp]]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[continuous_queries]
|
||||||
|
log-enabled = true
|
||||||
|
enabled = true
|
||||||
|
# run-interval = "1s" # interval for how often continuous queries will be checked if they need to run
|
||||||
|
|
||||||
+1
-1
@@ -252,7 +252,7 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R
|
|||||||
return ApiSuccess("Test notification sent")
|
return ApiSuccess("Test notification sent")
|
||||||
}
|
}
|
||||||
|
|
||||||
//POST /api/:alertId/pause
|
//POST /api/alerts/:alertId/pause
|
||||||
func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response {
|
func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response {
|
||||||
cmd := models.PauseAlertCommand{
|
cmd := models.PauseAlertCommand{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
|
|||||||
@@ -44,3 +44,19 @@ func GetAnnotations(c *middleware.Context) Response {
|
|||||||
|
|
||||||
return Json(200, result)
|
return Json(200, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteAnnotations(c *middleware.Context, cmd dtos.DeleteAnnotationsCmd) Response {
|
||||||
|
repo := annotations.GetRepository()
|
||||||
|
|
||||||
|
err := repo.Delete(&annotations.DeleteParams{
|
||||||
|
AlertId: cmd.PanelId,
|
||||||
|
DashboardId: cmd.DashboardId,
|
||||||
|
PanelId: cmd.PanelId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ApiError(500, "Failed to delete annotations", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiSuccess("Annotations deleted")
|
||||||
|
}
|
||||||
|
|||||||
+3
-2
@@ -252,7 +252,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
|
|
||||||
r.Group("/alerts", func() {
|
r.Group("/alerts", func() {
|
||||||
r.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest))
|
r.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest))
|
||||||
r.Post("/:alertId/pause", ValidateOrgAlert, bind(dtos.PauseAlertCommand{}), wrap(PauseAlert))
|
r.Post("/:alertId/pause", bind(dtos.PauseAlertCommand{}), wrap(PauseAlert), reqEditorRole)
|
||||||
r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
||||||
r.Get("/", wrap(GetAlerts))
|
r.Get("/", wrap(GetAlerts))
|
||||||
r.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard))
|
r.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard))
|
||||||
@@ -266,9 +266,10 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
|
r.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
|
||||||
r.Get("/:notificationId", wrap(GetAlertNotificationById))
|
r.Get("/:notificationId", wrap(GetAlertNotificationById))
|
||||||
r.Delete("/:notificationId", wrap(DeleteAlertNotification))
|
r.Delete("/:notificationId", wrap(DeleteAlertNotification))
|
||||||
}, reqOrgAdmin)
|
}, reqEditorRole)
|
||||||
|
|
||||||
r.Get("/annotations", wrap(GetAnnotations))
|
r.Get("/annotations", wrap(GetAnnotations))
|
||||||
|
r.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations))
|
||||||
|
|
||||||
// error test
|
// error test
|
||||||
r.Get("/metrics/error", wrap(GenerateError))
|
r.Get("/metrics/error", wrap(GenerateError))
|
||||||
|
|||||||
@@ -15,3 +15,9 @@ type Annotation struct {
|
|||||||
|
|
||||||
Data *simplejson.Json `json:"data"`
|
Data *simplejson.Json `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteAnnotationsCmd struct {
|
||||||
|
AlertId int64 `json:"alertId"`
|
||||||
|
DashboardId int64 `json:"dashboardId"`
|
||||||
|
PanelId int64 `json:"panelId"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ func writePIDFile() {
|
|||||||
|
|
||||||
func listenToSystemSignals(server models.GrafanaServer) {
|
func listenToSystemSignals(server models.GrafanaServer) {
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
ignoreChan := make(chan os.Signal, 1)
|
||||||
code := 0
|
code := 0
|
||||||
|
|
||||||
|
signal.Notify(ignoreChan, syscall.SIGHUP)
|
||||||
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -17,17 +17,19 @@ type SendEmailCommandSync struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SendWebhook struct {
|
type SendWebhook struct {
|
||||||
Url string
|
Url string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
Body string
|
Body string
|
||||||
|
HttpMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendWebhookSync struct {
|
type SendWebhookSync struct {
|
||||||
Url string
|
Url string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
Body string
|
Body string
|
||||||
|
HttpMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendResetPasswordEmailCommand struct {
|
type SendResetPasswordEmailCommand struct {
|
||||||
|
|||||||
@@ -24,16 +24,18 @@ func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
|
|||||||
Url: url,
|
Url: url,
|
||||||
User: model.Settings.Get("user").MustString(),
|
User: model.Settings.Get("user").MustString(),
|
||||||
Password: model.Settings.Get("password").MustString(),
|
Password: model.Settings.Get("password").MustString(),
|
||||||
|
HttpMethod: model.Settings.Get("httpMethod").MustString("POST"),
|
||||||
log: log.New("alerting.notifier.webhook"),
|
log: log.New("alerting.notifier.webhook"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebhookNotifier struct {
|
type WebhookNotifier struct {
|
||||||
NotifierBase
|
NotifierBase
|
||||||
Url string
|
Url string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
log log.Logger
|
HttpMethod string
|
||||||
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
|
func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||||
@@ -59,10 +61,11 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
|
|||||||
body, _ := bodyJSON.MarshalJSON()
|
body, _ := bodyJSON.MarshalJSON()
|
||||||
|
|
||||||
cmd := &m.SendWebhookSync{
|
cmd := &m.SendWebhookSync{
|
||||||
Url: this.Url,
|
Url: this.Url,
|
||||||
User: this.User,
|
User: this.User,
|
||||||
Password: this.Password,
|
Password: this.Password,
|
||||||
Body: string(body),
|
Body: string(body),
|
||||||
|
HttpMethod: this.HttpMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
|
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import "github.com/grafana/grafana/pkg/components/simplejson"
|
|||||||
type Repository interface {
|
type Repository interface {
|
||||||
Save(item *Item) error
|
Save(item *Item) error
|
||||||
Find(query *ItemQuery) ([]*Item, error)
|
Find(query *ItemQuery) ([]*Item, error)
|
||||||
|
Delete(params *DeleteParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemQuery struct {
|
type ItemQuery struct {
|
||||||
@@ -20,6 +21,12 @@ type ItemQuery struct {
|
|||||||
Limit int64 `json:"alertId"`
|
Limit int64 `json:"alertId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteParams struct {
|
||||||
|
AlertId int64 `json:"alertId"`
|
||||||
|
DashboardId int64 `json:"dashboardId"`
|
||||||
|
PanelId int64 `json:"panelId"`
|
||||||
|
}
|
||||||
|
|
||||||
var repositoryInstance Repository
|
var repositoryInstance Repository
|
||||||
|
|
||||||
func GetRepository() Repository {
|
func GetRepository() Repository {
|
||||||
|
|||||||
@@ -61,19 +61,21 @@ func Init() error {
|
|||||||
|
|
||||||
func SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error {
|
func SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error {
|
||||||
return sendWebRequestSync(ctx, &Webhook{
|
return sendWebRequestSync(ctx, &Webhook{
|
||||||
Url: cmd.Url,
|
Url: cmd.Url,
|
||||||
User: cmd.User,
|
User: cmd.User,
|
||||||
Password: cmd.Password,
|
Password: cmd.Password,
|
||||||
Body: cmd.Body,
|
Body: cmd.Body,
|
||||||
|
HttpMethod: cmd.HttpMethod,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendWebhook(cmd *m.SendWebhook) error {
|
func sendWebhook(cmd *m.SendWebhook) error {
|
||||||
addToWebhookQueue(&Webhook{
|
addToWebhookQueue(&Webhook{
|
||||||
Url: cmd.Url,
|
Url: cmd.Url,
|
||||||
User: cmd.User,
|
User: cmd.User,
|
||||||
Password: cmd.Password,
|
Password: cmd.Password,
|
||||||
Body: cmd.Body,
|
Body: cmd.Body,
|
||||||
|
HttpMethod: cmd.HttpMethod,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
Url string
|
Url string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
Body string
|
Body string
|
||||||
|
HttpMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
var webhookQueue chan *Webhook
|
var webhookQueue chan *Webhook
|
||||||
@@ -44,13 +45,17 @@ func processWebhookQueue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendWebRequestSync(ctx context.Context, webhook *Webhook) error {
|
func sendWebRequestSync(ctx context.Context, webhook *Webhook) error {
|
||||||
webhookLog.Debug("Sending webhook", "url", webhook.Url)
|
webhookLog.Debug("Sending webhook", "url", webhook.Url, "http method", webhook.HttpMethod)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: time.Duration(10 * time.Second),
|
Timeout: time.Duration(10 * time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := http.NewRequest(http.MethodPost, webhook.Url, bytes.NewReader([]byte(webhook.Body)))
|
if webhook.HttpMethod == "" {
|
||||||
|
webhook.HttpMethod = http.MethodPost
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(webhook.HttpMethod, webhook.Url, bytes.NewReader([]byte(webhook.Body)))
|
||||||
if webhook.User != "" && webhook.Password != "" {
|
if webhook.User != "" && webhook.Password != "" {
|
||||||
request.Header.Add("Authorization", util.GetBasicAuthHeader(webhook.User, webhook.Password))
|
request.Header.Add("Authorization", util.GetBasicAuthHeader(webhook.User, webhook.Password))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,23 @@ func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteAlertByIdInternal(alertId int64, reason string, sess *xorm.Session) error {
|
||||||
|
sqlog.Debug("Deleting alert", "id", alertId, "reason", reason)
|
||||||
|
|
||||||
|
if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", alertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.Exec("DELETE FROM annotation WHERE alert_id = ?", alertId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteAlertById(cmd *m.DeleteAlertCommand) error {
|
func DeleteAlertById(cmd *m.DeleteAlertCommand) error {
|
||||||
return inTransaction(func(sess *xorm.Session) error {
|
return inTransaction(func(sess *xorm.Session) error {
|
||||||
if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", cmd.AlertId); err != nil {
|
return deleteAlertByIdInternal(cmd.AlertId, "DeleteAlertCommand", sess)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +120,7 @@ func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error {
|
|||||||
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
|
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
|
||||||
|
|
||||||
for _, alert := range alerts {
|
for _, alert := range alerts {
|
||||||
_, err := sess.Exec("DELETE FROM alert WHERE id = ? ", alert.Id)
|
deleteAlertByIdInternal(alert.Id, "Dashboard deleted", sess)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlog.Debug("Alert deleted (due to dashboard deletion)", "name", alert.Name, "id", alert.Id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -195,12 +200,7 @@ func deleteMissingAlerts(alerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xorm
|
|||||||
}
|
}
|
||||||
|
|
||||||
if missing {
|
if missing {
|
||||||
_, err := sess.Exec("DELETE FROM alert WHERE id = ?", missingAlert.Id)
|
deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlog.Debug("Alert deleted", "name", missingAlert.Name, "id", missingAlert.Id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,9 @@ func PauseAlertRule(cmd *m.PauseAlertCommand) error {
|
|||||||
return inTransaction(func(sess *xorm.Session) error {
|
return inTransaction(func(sess *xorm.Session) error {
|
||||||
alert := m.Alert{}
|
alert := m.Alert{}
|
||||||
|
|
||||||
if has, err := sess.Id(cmd.AlertId).Get(&alert); err != nil {
|
has, err := x.Where("id = ? AND org_id=?", cmd.AlertId, cmd.OrgId).Get(&alert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return fmt.Errorf("Could not find alert")
|
return fmt.Errorf("Could not find alert")
|
||||||
|
|||||||
@@ -84,3 +84,17 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
|
|||||||
|
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SqlAnnotationRepo) Delete(params *annotations.DeleteParams) error {
|
||||||
|
return inTransaction(func(sess *xorm.Session) error {
|
||||||
|
|
||||||
|
sql := "DELETE FROM annotation WHERE dashboard_id = ? AND panel_id = ?"
|
||||||
|
|
||||||
|
_, err := sess.Exec(sql, params.DashboardId, params.PanelId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
|||||||
return &BasicUserInfo{
|
return &BasicUserInfo{
|
||||||
Name: data.Name,
|
Name: data.Name,
|
||||||
Email: data.Email,
|
Email: data.Email,
|
||||||
|
Login: data.Email,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,10 +124,15 @@ func (e *InfluxDBExecutor) createRequest(query string) (*http.Request, error) {
|
|||||||
req.URL.RawQuery = params.Encode()
|
req.URL.RawQuery = params.Encode()
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Grafana")
|
req.Header.Set("User-Agent", "Grafana")
|
||||||
|
|
||||||
if e.BasicAuth {
|
if e.BasicAuth {
|
||||||
req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
|
req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.User != "" {
|
||||||
|
req.SetBasicAuth(e.User, e.Password)
|
||||||
|
}
|
||||||
|
|
||||||
glog.Debug("Influxdb request", "url", req.URL.String())
|
glog.Debug("Influxdb request", "url", req.URL.String())
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,10 @@ func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string {
|
|||||||
reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`)
|
reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`)
|
||||||
|
|
||||||
result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte {
|
result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte {
|
||||||
ind := strings.Replace(strings.Replace(string(in), "{{", "", 1), "}}", "", 1)
|
labelName := strings.Replace(string(in), "{{", "", 1)
|
||||||
if val, exists := metric[pmodel.LabelName(ind)]; exists {
|
labelName = strings.Replace(labelName, "}}", "", 1)
|
||||||
|
labelName = strings.TrimSpace(labelName)
|
||||||
|
if val, exists := metric[pmodel.LabelName(labelName)]; exists {
|
||||||
return []byte(val)
|
return []byte(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func TestPrometheus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &PrometheusQuery{
|
query := &PrometheusQuery{
|
||||||
LegendFormat: "legend {{app}} {{device}} {{broken}}",
|
LegendFormat: "legend {{app}} {{ device }} {{broken}}",
|
||||||
}
|
}
|
||||||
|
|
||||||
So(formatLegend(metric, query), ShouldEqual, "legend backend mobile {{broken}}")
|
So(formatLegend(metric, query), ShouldEqual, "legend backend mobile {{broken}}")
|
||||||
|
|||||||
@@ -82,8 +82,9 @@ function formatDate(date) {
|
|||||||
// now/d
|
// now/d
|
||||||
// if no to <expr> then to now is assumed
|
// if no to <expr> then to now is assumed
|
||||||
export function describeTextRange(expr: any) {
|
export function describeTextRange(expr: any) {
|
||||||
|
let isLast = (expr.indexOf('+') !== 0);
|
||||||
if (expr.indexOf('now') === -1) {
|
if (expr.indexOf('now') === -1) {
|
||||||
expr = 'now-' + expr;
|
expr = (isLast ? 'now-' : 'now') + expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
let opt = rangeIndex[expr + ' to now'];
|
let opt = rangeIndex[expr + ' to now'];
|
||||||
@@ -91,15 +92,20 @@ export function describeTextRange(expr: any) {
|
|||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
opt = {from: expr, to: 'now'};
|
if (isLast) {
|
||||||
|
opt = {from: expr, to: 'now'};
|
||||||
|
} else {
|
||||||
|
opt = {from: 'now', to: expr};
|
||||||
|
}
|
||||||
|
|
||||||
let parts = /^now-(\d+)(\w)/.exec(expr);
|
let parts = /^now([-+])(\d+)(\w)/.exec(expr);
|
||||||
if (parts) {
|
if (parts) {
|
||||||
let unit = parts[2];
|
let unit = parts[3];
|
||||||
let amount = parseInt(parts[1]);
|
let amount = parseInt(parts[2]);
|
||||||
let span = spans[unit];
|
let span = spans[unit];
|
||||||
if (span) {
|
if (span) {
|
||||||
opt.display = 'Last ' + amount + ' ' + span.display;
|
opt.display = isLast ? 'Last ' : 'Next ';
|
||||||
|
opt.display += amount + ' ' + span.display;
|
||||||
opt.section = span.section;
|
opt.section = span.section;
|
||||||
if (amount > 1) {
|
if (amount > 1) {
|
||||||
opt.display += 's';
|
opt.display += 's';
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class AlertTabCtrl {
|
|||||||
this.panelCtrl.render();
|
this.panelCtrl.render();
|
||||||
});
|
});
|
||||||
|
|
||||||
// build notification model
|
// build notification model
|
||||||
this.notifications = [];
|
this.notifications = [];
|
||||||
this.alertNotifications = [];
|
this.alertNotifications = [];
|
||||||
this.alertHistory = [];
|
this.alertHistory = [];
|
||||||
@@ -352,6 +352,24 @@ export class AlertTabCtrl {
|
|||||||
this.evaluatorParamsChanged();
|
this.evaluatorParamsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearHistory() {
|
||||||
|
appEvents.emit('confirm-modal', {
|
||||||
|
title: 'Delete Alert History',
|
||||||
|
text: 'Are you sure you want to remove all history & annotations for this alert?',
|
||||||
|
icon: 'fa-trash',
|
||||||
|
yesText: 'Yes',
|
||||||
|
onConfirm: () => {
|
||||||
|
this.backendSrv.post('/api/annotations/mass-delete', {
|
||||||
|
dashboardId: this.panelCtrl.dashboard.id,
|
||||||
|
panelId: this.panel.id,
|
||||||
|
}).then(res => {
|
||||||
|
this.alertHistory = [];
|
||||||
|
this.panelCtrl.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test() {
|
test() {
|
||||||
this.testing = true;
|
this.testing = true;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class AlertNotificationEditCtrl {
|
|||||||
this.model = {
|
this.model = {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
settings: {
|
settings: {
|
||||||
severityFilter: 'none'
|
httpMethod: 'POST'
|
||||||
},
|
},
|
||||||
isDefault: false
|
isDefault: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,7 +125,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2">
|
<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2">
|
||||||
<h5 class="section-heading">State history <span class="muted small">(last 50 state changes)</span></h5>
|
<button class="btn btn-mini btn-danger pull-right" ng-click="ctrl.clearHistory()"><i class="fa fa-trash"></i> Clear history</button>
|
||||||
|
<h5 class="section-heading" style="whitespace: nowrap">
|
||||||
|
State history <span class="muted small">(last 50 state changes)</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div ng-show="ctrl.alertHistory.length === 0">
|
||||||
|
<br>
|
||||||
|
<i>No state changes recorded</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="card-section card-list-layout-list">
|
<section class="card-section card-list-layout-list">
|
||||||
<ol class="card-list" >
|
<ol class="card-list" >
|
||||||
<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">
|
<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">
|
||||||
|
|||||||
@@ -32,19 +32,24 @@
|
|||||||
<div class="gf-form-group" ng-if="ctrl.model.type === 'webhook'">
|
<div class="gf-form-group" ng-if="ctrl.model.type === 'webhook'">
|
||||||
<h3 class="page-heading">Webhook settings</h3>
|
<h3 class="page-heading">Webhook settings</h3>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-6">Url</span>
|
<span class="gf-form-label width-10">Url</span>
|
||||||
<input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url"></input>
|
<input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url"></input>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form">
|
||||||
<div class="gf-form">
|
<span class="gf-form-label width-10">Http Method</span>
|
||||||
<span class="gf-form-label width-6">Username</span>
|
<div class="gf-form-select-wrapper width-14">
|
||||||
<input type="text" class="gf-form-input max-width-10" ng-model="ctrl.model.settings.username"></input>
|
<select class="gf-form-input" ng-model="ctrl.model.settings.httpMethod" ng-options="t for t in ['POST', 'PUT']">
|
||||||
</div>
|
</select>
|
||||||
<div class="gf-form">
|
|
||||||
<span class="gf-form-label width-6">Password</span>
|
|
||||||
<input type="text" class="gf-form-input max-width-10" ng-model="ctrl.model.settings.password"></input>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-10">Username</span>
|
||||||
|
<input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.username"></input>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-10">Password</span>
|
||||||
|
<input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.password"></input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-group" ng-if="ctrl.model.type === 'slack'">
|
<div class="gf-form-group" ng-if="ctrl.model.type === 'slack'">
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import angular from 'angular';
|
||||||
|
import moment from 'moment';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import coreModule from 'app/core/core_module';
|
||||||
|
|
||||||
|
export class AlertingSrv {
|
||||||
|
dashboard: any;
|
||||||
|
alerts: any[];
|
||||||
|
|
||||||
|
init(dashboard, alerts) {
|
||||||
|
this.dashboard = dashboard;
|
||||||
|
this.alerts = alerts || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
coreModule.service('alertingSrv', AlertingSrv);
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
define([
|
define([
|
||||||
'./dashboard_ctrl',
|
'./dashboard_ctrl',
|
||||||
|
'./alerting_srv',
|
||||||
'./dashboardLoaderSrv',
|
'./dashboardLoaderSrv',
|
||||||
'./dashnav/dashnav',
|
'./dashnav/dashnav',
|
||||||
'./submenu/submenu',
|
'./submenu/submenu',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export class DashboardCtrl {
|
|||||||
dashboardKeybindings,
|
dashboardKeybindings,
|
||||||
timeSrv,
|
timeSrv,
|
||||||
variableSrv,
|
variableSrv,
|
||||||
|
alertingSrv,
|
||||||
dashboardSrv,
|
dashboardSrv,
|
||||||
unsavedChangesSrv,
|
unsavedChangesSrv,
|
||||||
dynamicDashboardSrv,
|
dynamicDashboardSrv,
|
||||||
@@ -43,6 +44,7 @@ export class DashboardCtrl {
|
|||||||
|
|
||||||
// init services
|
// init services
|
||||||
timeSrv.init(dashboard);
|
timeSrv.init(dashboard);
|
||||||
|
alertingSrv.init(dashboard, data.alerts);
|
||||||
|
|
||||||
// template values service needs to initialize completely before
|
// template values service needs to initialize completely before
|
||||||
// the rest of the dashboard can load
|
// the rest of the dashboard can load
|
||||||
|
|||||||
@@ -1,595 +1,113 @@
|
|||||||
///<reference path="../../headers/common.d.ts" />
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
import config from 'app/core/config';
|
|
||||||
import angular from 'angular';
|
|
||||||
import moment from 'moment';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
|
||||||
|
|
||||||
import {Emitter} from 'app/core/core';
|
|
||||||
import {contextSrv} from 'app/core/services/context_srv';
|
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
|
import {DashboardModel} from './model';
|
||||||
export class DashboardModel {
|
|
||||||
id: any;
|
|
||||||
title: any;
|
|
||||||
autoUpdate: any;
|
|
||||||
description: any;
|
|
||||||
tags: any;
|
|
||||||
style: any;
|
|
||||||
timezone: any;
|
|
||||||
editable: any;
|
|
||||||
hideControls: any;
|
|
||||||
sharedCrosshair: any;
|
|
||||||
rows: any;
|
|
||||||
time: any;
|
|
||||||
timepicker: any;
|
|
||||||
templating: any;
|
|
||||||
annotations: any;
|
|
||||||
refresh: any;
|
|
||||||
snapshot: any;
|
|
||||||
schemaVersion: number;
|
|
||||||
version: number;
|
|
||||||
revision: number;
|
|
||||||
links: any;
|
|
||||||
gnetId: any;
|
|
||||||
meta: any;
|
|
||||||
events: any;
|
|
||||||
|
|
||||||
constructor(data, meta) {
|
|
||||||
if (!data) {
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.events = new Emitter();
|
|
||||||
this.id = data.id || null;
|
|
||||||
this.revision = data.revision;
|
|
||||||
this.title = data.title || 'No Title';
|
|
||||||
this.autoUpdate = data.autoUpdate;
|
|
||||||
this.description = data.description;
|
|
||||||
this.tags = data.tags || [];
|
|
||||||
this.style = data.style || "dark";
|
|
||||||
this.timezone = data.timezone || '';
|
|
||||||
this.editable = data.editable !== false;
|
|
||||||
this.hideControls = data.hideControls || false;
|
|
||||||
this.sharedCrosshair = data.sharedCrosshair || false;
|
|
||||||
this.rows = data.rows || [];
|
|
||||||
this.time = data.time || { from: 'now-6h', to: 'now' };
|
|
||||||
this.timepicker = data.timepicker || {};
|
|
||||||
this.templating = this.ensureListExist(data.templating);
|
|
||||||
this.annotations = this.ensureListExist(data.annotations);
|
|
||||||
this.refresh = data.refresh;
|
|
||||||
this.snapshot = data.snapshot;
|
|
||||||
this.schemaVersion = data.schemaVersion || 0;
|
|
||||||
this.version = data.version || 0;
|
|
||||||
this.links = data.links || [];
|
|
||||||
this.gnetId = data.gnetId || null;
|
|
||||||
|
|
||||||
this.updateSchema(data);
|
|
||||||
this.initMeta(meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initMeta(meta) {
|
|
||||||
meta = meta || {};
|
|
||||||
|
|
||||||
meta.canShare = meta.canShare !== false;
|
|
||||||
meta.canSave = meta.canSave !== false;
|
|
||||||
meta.canStar = meta.canStar !== false;
|
|
||||||
meta.canEdit = meta.canEdit !== false;
|
|
||||||
|
|
||||||
if (!this.editable) {
|
|
||||||
meta.canEdit = false;
|
|
||||||
meta.canDelete = false;
|
|
||||||
meta.canSave = false;
|
|
||||||
this.hideControls = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.meta = meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleans meta data and other non peristent state
|
|
||||||
getSaveModelClone() {
|
|
||||||
// temp remove stuff
|
|
||||||
var events = this.events;
|
|
||||||
var meta = this.meta;
|
|
||||||
delete this.events;
|
|
||||||
delete this.meta;
|
|
||||||
|
|
||||||
events.emit('prepare-save-model');
|
|
||||||
var copy = $.extend(true, {}, this);
|
|
||||||
|
|
||||||
// restore properties
|
|
||||||
this.events = events;
|
|
||||||
this.meta = meta;
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureListExist(data) {
|
|
||||||
if (!data) { data = {}; }
|
|
||||||
if (!data.list) { data.list = []; }
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNextPanelId() {
|
|
||||||
var i, j, row, panel, max = 0;
|
|
||||||
for (i = 0; i < this.rows.length; i++) {
|
|
||||||
row = this.rows[i];
|
|
||||||
for (j = 0; j < row.panels.length; j++) {
|
|
||||||
panel = row.panels[j];
|
|
||||||
if (panel.id > max) { max = panel.id; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEachPanel(callback) {
|
|
||||||
var i, j, row;
|
|
||||||
for (i = 0; i < this.rows.length; i++) {
|
|
||||||
row = this.rows[i];
|
|
||||||
for (j = 0; j < row.panels.length; j++) {
|
|
||||||
callback(row.panels[j], j, row, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPanelById(id) {
|
|
||||||
for (var i = 0; i < this.rows.length; i++) {
|
|
||||||
var row = this.rows[i];
|
|
||||||
for (var j = 0; j < row.panels.length; j++) {
|
|
||||||
var panel = row.panels[j];
|
|
||||||
if (panel.id === id) {
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
rowSpan(row) {
|
|
||||||
return _.reduce(row.panels, function(p,v) {
|
|
||||||
return p + v.span;
|
|
||||||
},0);
|
|
||||||
};
|
|
||||||
|
|
||||||
addPanel(panel, row) {
|
|
||||||
var rowSpan = this.rowSpan(row);
|
|
||||||
var panelCount = row.panels.length;
|
|
||||||
var space = (12 - rowSpan) - panel.span;
|
|
||||||
panel.id = this.getNextPanelId();
|
|
||||||
|
|
||||||
// try to make room of there is no space left
|
|
||||||
if (space <= 0) {
|
|
||||||
if (panelCount === 1) {
|
|
||||||
row.panels[0].span = 6;
|
|
||||||
panel.span = 6;
|
|
||||||
} else if (panelCount === 2) {
|
|
||||||
row.panels[0].span = 4;
|
|
||||||
row.panels[1].span = 4;
|
|
||||||
panel.span = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
row.panels.push(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSubmenuFeaturesEnabled() {
|
|
||||||
var visableTemplates = _.filter(this.templating.list, function(template) {
|
|
||||||
return template.hideVariable === undefined || template.hideVariable === false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return visableTemplates.length > 0 || this.annotations.list.length > 0 || this.links.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPanelInfoById(panelId) {
|
|
||||||
var result: any = {};
|
|
||||||
_.each(this.rows, function(row) {
|
|
||||||
_.each(row.panels, function(panel, index) {
|
|
||||||
if (panel.id === panelId) {
|
|
||||||
result.panel = panel;
|
|
||||||
result.row = row;
|
|
||||||
result.index = index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.panel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicatePanel(panel, row) {
|
|
||||||
var rowIndex = _.indexOf(this.rows, row);
|
|
||||||
var newPanel = angular.copy(panel);
|
|
||||||
newPanel.id = this.getNextPanelId();
|
|
||||||
|
|
||||||
delete newPanel.repeat;
|
|
||||||
delete newPanel.repeatIteration;
|
|
||||||
delete newPanel.repeatPanelId;
|
|
||||||
delete newPanel.scopedVars;
|
|
||||||
|
|
||||||
var currentRow = this.rows[rowIndex];
|
|
||||||
currentRow.panels.push(newPanel);
|
|
||||||
return newPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate(date, format) {
|
|
||||||
date = moment.isMoment(date) ? date : moment(date);
|
|
||||||
format = format || 'YYYY-MM-DD HH:mm:ss';
|
|
||||||
this.timezone = this.getTimezone();
|
|
||||||
|
|
||||||
return this.timezone === 'browser' ?
|
|
||||||
moment(date).format(format) :
|
|
||||||
moment.utc(date).format(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRelativeTime(date) {
|
|
||||||
date = moment.isMoment(date) ? date : moment(date);
|
|
||||||
|
|
||||||
return this.timezone === 'browser' ?
|
|
||||||
moment(date).fromNow() :
|
|
||||||
moment.utc(date).fromNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
getNextQueryLetter(panel) {
|
|
||||||
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
|
|
||||||
return _.find(letters, function(refId) {
|
|
||||||
return _.every(panel.targets, function(other) {
|
|
||||||
return other.refId !== refId;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isTimezoneUtc() {
|
|
||||||
return this.getTimezone() === 'utc';
|
|
||||||
}
|
|
||||||
|
|
||||||
getTimezone() {
|
|
||||||
return this.timezone ? this.timezone : contextSrv.user.timezone;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateSchema(old) {
|
|
||||||
var i, j, k;
|
|
||||||
var oldVersion = this.schemaVersion;
|
|
||||||
var panelUpgrades = [];
|
|
||||||
this.schemaVersion = 13;
|
|
||||||
|
|
||||||
if (oldVersion === this.schemaVersion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// version 2 schema changes
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
|
|
||||||
if (old.services) {
|
|
||||||
if (old.services.filter) {
|
|
||||||
this.time = old.services.filter.time;
|
|
||||||
this.templating.list = old.services.filter.list || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
// rename panel type
|
|
||||||
if (panel.type === 'graphite') {
|
|
||||||
panel.type = 'graph';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.type !== 'graph') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; }
|
|
||||||
|
|
||||||
if (panel.grid) {
|
|
||||||
if (panel.grid.min) {
|
|
||||||
panel.grid.leftMin = panel.grid.min;
|
|
||||||
delete panel.grid.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.grid.max) {
|
|
||||||
panel.grid.leftMax = panel.grid.max;
|
|
||||||
delete panel.grid.max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.y_format) {
|
|
||||||
panel.y_formats[0] = panel.y_format;
|
|
||||||
delete panel.y_format;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.y2_format) {
|
|
||||||
panel.y_formats[1] = panel.y2_format;
|
|
||||||
delete panel.y2_format;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// schema version 3 changes
|
|
||||||
if (oldVersion < 3) {
|
|
||||||
// ensure panel ids
|
|
||||||
var maxId = this.getNextPanelId();
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (!panel.id) {
|
|
||||||
panel.id = maxId;
|
|
||||||
maxId += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// schema version 4 changes
|
|
||||||
if (oldVersion < 4) {
|
|
||||||
// move aliasYAxis changes
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (panel.type !== 'graph') { return; }
|
|
||||||
_.each(panel.aliasYAxis, function(value, key) {
|
|
||||||
panel.seriesOverrides = [{ alias: key, yaxis: value }];
|
|
||||||
});
|
|
||||||
delete panel.aliasYAxis;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 6) {
|
|
||||||
// move pulldowns to new schema
|
|
||||||
var annotations = _.find(old.pulldowns, { type: 'annotations' });
|
|
||||||
|
|
||||||
if (annotations) {
|
|
||||||
this.annotations = {
|
|
||||||
list: annotations.annotations || [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// update template variables
|
|
||||||
for (i = 0 ; i < this.templating.list.length; i++) {
|
|
||||||
var variable = this.templating.list[i];
|
|
||||||
if (variable.datasource === void 0) { variable.datasource = null; }
|
|
||||||
if (variable.type === 'filter') { variable.type = 'query'; }
|
|
||||||
if (variable.type === void 0) { variable.type = 'query'; }
|
|
||||||
if (variable.allFormat === void 0) { variable.allFormat = 'glob'; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 7) {
|
|
||||||
if (old.nav && old.nav.length) {
|
|
||||||
this.timepicker = old.nav[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure query refIds
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
_.each(panel.targets, function(target) {
|
|
||||||
if (!target.refId) {
|
|
||||||
target.refId = this.getNextQueryLetter(panel);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 8) {
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
_.each(panel.targets, function(target) {
|
|
||||||
// update old influxdb query schema
|
|
||||||
if (target.fields && target.tags && target.groupBy) {
|
|
||||||
if (target.rawQuery) {
|
|
||||||
delete target.fields;
|
|
||||||
delete target.fill;
|
|
||||||
} else {
|
|
||||||
target.select = _.map(target.fields, function(field) {
|
|
||||||
var parts = [];
|
|
||||||
parts.push({type: 'field', params: [field.name]});
|
|
||||||
parts.push({type: field.func, params: []});
|
|
||||||
if (field.mathExpr) {
|
|
||||||
parts.push({type: 'math', params: [field.mathExpr]});
|
|
||||||
}
|
|
||||||
if (field.asExpr) {
|
|
||||||
parts.push({type: 'alias', params: [field.asExpr]});
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
});
|
|
||||||
delete target.fields;
|
|
||||||
_.each(target.groupBy, function(part) {
|
|
||||||
if (part.type === 'time' && part.interval) {
|
|
||||||
part.params = [part.interval];
|
|
||||||
delete part.interval;
|
|
||||||
}
|
|
||||||
if (part.type === 'tag' && part.key) {
|
|
||||||
part.params = [part.key];
|
|
||||||
delete part.key;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (target.fill) {
|
|
||||||
target.groupBy.push({type: 'fill', params: [target.fill]});
|
|
||||||
delete target.fill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// schema version 9 changes
|
|
||||||
if (oldVersion < 9) {
|
|
||||||
// move aliasYAxis changes
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; }
|
|
||||||
|
|
||||||
if (panel.thresholds) {
|
|
||||||
var k = panel.thresholds.split(",");
|
|
||||||
|
|
||||||
if (k.length >= 3) {
|
|
||||||
k.shift();
|
|
||||||
panel.thresholds = k.join(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// schema version 10 changes
|
|
||||||
if (oldVersion < 10) {
|
|
||||||
// move aliasYAxis changes
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (panel.type !== 'table') { return; }
|
|
||||||
|
|
||||||
_.each(panel.styles, function(style) {
|
|
||||||
if (style.thresholds && style.thresholds.length >= 3) {
|
|
||||||
var k = style.thresholds;
|
|
||||||
k.shift();
|
|
||||||
style.thresholds = k;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 12) {
|
|
||||||
// update template variables
|
|
||||||
_.each(this.templating.list, function(templateVariable) {
|
|
||||||
if (templateVariable.refresh) { templateVariable.refresh = 1; }
|
|
||||||
if (!templateVariable.refresh) { templateVariable.refresh = 0; }
|
|
||||||
if (templateVariable.hideVariable) {
|
|
||||||
templateVariable.hide = 2;
|
|
||||||
} else if (templateVariable.hideLabel) {
|
|
||||||
templateVariable.hide = 1;
|
|
||||||
} else {
|
|
||||||
templateVariable.hide = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 12) {
|
|
||||||
// update graph yaxes changes
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (panel.type !== 'graph') { return; }
|
|
||||||
if (!panel.grid) { return; }
|
|
||||||
|
|
||||||
if (!panel.yaxes) {
|
|
||||||
panel.yaxes = [
|
|
||||||
{
|
|
||||||
show: panel['y-axis'],
|
|
||||||
min: panel.grid.leftMin,
|
|
||||||
max: panel.grid.leftMax,
|
|
||||||
logBase: panel.grid.leftLogBase,
|
|
||||||
format: panel.y_formats[0],
|
|
||||||
label: panel.leftYAxisLabel,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show: panel['y-axis'],
|
|
||||||
min: panel.grid.rightMin,
|
|
||||||
max: panel.grid.rightMax,
|
|
||||||
logBase: panel.grid.rightLogBase,
|
|
||||||
format: panel.y_formats[1],
|
|
||||||
label: panel.rightYAxisLabel,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
panel.xaxis = {
|
|
||||||
show: panel['x-axis'],
|
|
||||||
};
|
|
||||||
|
|
||||||
delete panel.grid.leftMin;
|
|
||||||
delete panel.grid.leftMax;
|
|
||||||
delete panel.grid.leftLogBase;
|
|
||||||
delete panel.grid.rightMin;
|
|
||||||
delete panel.grid.rightMax;
|
|
||||||
delete panel.grid.rightLogBase;
|
|
||||||
delete panel.y_formats;
|
|
||||||
delete panel.leftYAxisLabel;
|
|
||||||
delete panel.rightYAxisLabel;
|
|
||||||
delete panel['y-axis'];
|
|
||||||
delete panel['x-axis'];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 13) {
|
|
||||||
// update graph yaxes changes
|
|
||||||
panelUpgrades.push(function(panel) {
|
|
||||||
if (panel.type !== 'graph') { return; }
|
|
||||||
|
|
||||||
panel.thresholds = [];
|
|
||||||
var t1: any = {}, t2: any = {};
|
|
||||||
|
|
||||||
if (panel.grid.threshold1 !== null) {
|
|
||||||
t1.value = panel.grid.threshold1;
|
|
||||||
if (panel.grid.thresholdLine) {
|
|
||||||
t1.line = true;
|
|
||||||
t1.lineColor = panel.grid.threshold1Color;
|
|
||||||
t1.colorMode = 'custom';
|
|
||||||
} else {
|
|
||||||
t1.fill = true;
|
|
||||||
t1.fillColor = panel.grid.threshold1Color;
|
|
||||||
t1.colorMode = 'custom';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.grid.threshold2 !== null) {
|
|
||||||
t2.value = panel.grid.threshold2;
|
|
||||||
if (panel.grid.thresholdLine) {
|
|
||||||
t2.line = true;
|
|
||||||
t2.lineColor = panel.grid.threshold2Color;
|
|
||||||
t2.colorMode = 'custom';
|
|
||||||
} else {
|
|
||||||
t2.fill = true;
|
|
||||||
t2.fillColor = panel.grid.threshold2Color;
|
|
||||||
t2.colorMode = 'custom';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isNumber(t1.value)) {
|
|
||||||
if (_.isNumber(t2.value)) {
|
|
||||||
if (t1.value > t2.value) {
|
|
||||||
t1.op = t2.op = 'lt';
|
|
||||||
panel.thresholds.push(t1);
|
|
||||||
panel.thresholds.push(t2);
|
|
||||||
} else {
|
|
||||||
t1.op = t2.op = 'gt';
|
|
||||||
panel.thresholds.push(t1);
|
|
||||||
panel.thresholds.push(t2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t1.op = 'gt';
|
|
||||||
panel.thresholds.push(t1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete panel.grid.threshold1;
|
|
||||||
delete panel.grid.threshold1Color;
|
|
||||||
delete panel.grid.threshold2;
|
|
||||||
delete panel.grid.threshold2Color;
|
|
||||||
delete panel.grid.thresholdLine;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panelUpgrades.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < this.rows.length; i++) {
|
|
||||||
var row = this.rows[i];
|
|
||||||
for (j = 0; j < row.panels.length; j++) {
|
|
||||||
for (k = 0; k < panelUpgrades.length; k++) {
|
|
||||||
panelUpgrades[k].call(this, row.panels[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class DashboardSrv {
|
export class DashboardSrv {
|
||||||
currentDashboard: any;
|
dash: any;
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
constructor(private backendSrv, private $rootScope, private $location) {
|
||||||
|
}
|
||||||
|
|
||||||
create(dashboard, meta) {
|
create(dashboard, meta) {
|
||||||
return new DashboardModel(dashboard, meta);
|
return new DashboardModel(dashboard, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrent(dashboard) {
|
setCurrent(dashboard) {
|
||||||
this.currentDashboard = dashboard;
|
this.dash = dashboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrent() {
|
getCurrent() {
|
||||||
return this.currentDashboard;
|
return this.dash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveDashboard(options) {
|
||||||
|
if (!this.dash.meta.canSave && options.makeEditable !== true) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
var clone = this.dash.getSaveModelClone();
|
||||||
|
|
||||||
|
return this.backendSrv.saveDashboard(clone, options).then(data => {
|
||||||
|
this.dash.version = data.version;
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('dashboard-saved', this.dash);
|
||||||
|
|
||||||
|
var dashboardUrl = '/dashboard/db/' + data.slug;
|
||||||
|
if (dashboardUrl !== this.$location.path()) {
|
||||||
|
this.$location.url(dashboardUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]);
|
||||||
|
}).catch(this.handleSaveDashboardError.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveDashboardError(err) {
|
||||||
|
if (err.data && err.data.status === "version-mismatch") {
|
||||||
|
err.isHandled = true;
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('confirm-modal', {
|
||||||
|
title: 'Conflict',
|
||||||
|
text: 'Someone else has updated this dashboard.',
|
||||||
|
text2: 'Would you still like to save this dashboard?',
|
||||||
|
yesText: "Save & Overwrite",
|
||||||
|
icon: "fa-warning",
|
||||||
|
onConfirm: () => {
|
||||||
|
this.saveDashboard({overwrite: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.data && err.data.status === "name-exists") {
|
||||||
|
err.isHandled = true;
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('confirm-modal', {
|
||||||
|
title: 'Conflict',
|
||||||
|
text: 'Dashboard with the same name exists.',
|
||||||
|
text2: 'Would you still like to save this dashboard?',
|
||||||
|
yesText: "Save & Overwrite",
|
||||||
|
icon: "fa-warning",
|
||||||
|
onConfirm: () => {
|
||||||
|
this.saveDashboard({overwrite: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.data && err.data.status === "plugin-dashboard") {
|
||||||
|
err.isHandled = true;
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('confirm-modal', {
|
||||||
|
title: 'Plugin Dashboard',
|
||||||
|
text: err.data.message,
|
||||||
|
text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.',
|
||||||
|
yesText: "Overwrite",
|
||||||
|
icon: "fa-warning",
|
||||||
|
altActionText: "Save As",
|
||||||
|
onAltAction: () => {
|
||||||
|
this.saveDashboardAs();
|
||||||
|
},
|
||||||
|
onConfirm: function() {
|
||||||
|
this.saveDashboard({overwrite: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveDashboardAs() {
|
||||||
|
var newScope = this.$rootScope.$new();
|
||||||
|
newScope.clone = this.dash.getSaveModelClone();
|
||||||
|
newScope.clone.editable = true;
|
||||||
|
newScope.clone.hideControls = false;
|
||||||
|
|
||||||
|
this.$rootScope.appEvent('show-modal', {
|
||||||
|
src: 'public/app/features/dashboard/partials/saveDashboardAs.html',
|
||||||
|
scope: newScope,
|
||||||
|
modalClass: 'modal--narrow'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.service('dashboardSrv', DashboardSrv);
|
coreModule.service('dashboardSrv', DashboardSrv);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {DashboardExporter} from '../export/exporter';
|
|||||||
export class DashNavCtrl {
|
export class DashNavCtrl {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $rootScope, alertSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) {
|
constructor($scope, $rootScope, dashboardSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) {
|
||||||
|
|
||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
$scope.onAppEvent('save-dashboard', $scope.saveDashboard);
|
$scope.onAppEvent('save-dashboard', $scope.saveDashboard);
|
||||||
@@ -71,88 +71,14 @@ export class DashNavCtrl {
|
|||||||
$scope.makeEditable = function() {
|
$scope.makeEditable = function() {
|
||||||
$scope.dashboard.editable = true;
|
$scope.dashboard.editable = true;
|
||||||
|
|
||||||
var clone = $scope.dashboard.getSaveModelClone();
|
return dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(function() {
|
||||||
|
|
||||||
backendSrv.saveDashboard(clone, {overwrite: false}).then(function(data) {
|
|
||||||
$scope.dashboard.version = data.version;
|
|
||||||
$scope.appEvent('dashboard-saved', $scope.dashboard);
|
|
||||||
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]);
|
|
||||||
|
|
||||||
// force refresh whole page
|
// force refresh whole page
|
||||||
window.location.href = window.location.href;
|
window.location.href = window.location.href;
|
||||||
}, $scope.handleSaveDashError);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.saveDashboard = function(options) {
|
$scope.saveDashboard = function(options) {
|
||||||
if ($scope.dashboardMeta.canSave === false) {
|
return dashboardSrv.saveDashboard(options);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clone = $scope.dashboard.getSaveModelClone();
|
|
||||||
|
|
||||||
backendSrv.saveDashboard(clone, options).then(function(data) {
|
|
||||||
$scope.dashboard.version = data.version;
|
|
||||||
$scope.appEvent('dashboard-saved', $scope.dashboard);
|
|
||||||
|
|
||||||
var dashboardUrl = '/dashboard/db/' + data.slug;
|
|
||||||
|
|
||||||
if (dashboardUrl !== $location.path()) {
|
|
||||||
$location.url(dashboardUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]);
|
|
||||||
}, $scope.handleSaveDashError);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.handleSaveDashError = function(err) {
|
|
||||||
if (err.data && err.data.status === "version-mismatch") {
|
|
||||||
err.isHandled = true;
|
|
||||||
|
|
||||||
$scope.appEvent('confirm-modal', {
|
|
||||||
title: 'Conflict',
|
|
||||||
text: 'Someone else has updated this dashboard.',
|
|
||||||
text2: 'Would you still like to save this dashboard?',
|
|
||||||
yesText: "Save & Overwrite",
|
|
||||||
icon: "fa-warning",
|
|
||||||
onConfirm: function() {
|
|
||||||
$scope.saveDashboard({overwrite: true});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.data && err.data.status === "name-exists") {
|
|
||||||
err.isHandled = true;
|
|
||||||
|
|
||||||
$scope.appEvent('confirm-modal', {
|
|
||||||
title: 'Conflict',
|
|
||||||
text: 'Dashboard with the same name exists.',
|
|
||||||
text2: 'Would you still like to save this dashboard?',
|
|
||||||
yesText: "Save & Overwrite",
|
|
||||||
icon: "fa-warning",
|
|
||||||
onConfirm: function() {
|
|
||||||
$scope.saveDashboard({overwrite: true});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.data && err.data.status === "plugin-dashboard") {
|
|
||||||
err.isHandled = true;
|
|
||||||
|
|
||||||
$scope.appEvent('confirm-modal', {
|
|
||||||
title: 'Plugin Dashboard',
|
|
||||||
text: err.data.message,
|
|
||||||
text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.',
|
|
||||||
yesText: "Overwrite",
|
|
||||||
icon: "fa-warning",
|
|
||||||
altActionText: "Save As",
|
|
||||||
onAltAction: function() {
|
|
||||||
$scope.saveDashboardAs();
|
|
||||||
},
|
|
||||||
onConfirm: function() {
|
|
||||||
$scope.saveDashboard({overwrite: true});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.deleteDashboard = function() {
|
$scope.deleteDashboard = function() {
|
||||||
@@ -189,16 +115,7 @@ export class DashNavCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.saveDashboardAs = function() {
|
$scope.saveDashboardAs = function() {
|
||||||
var newScope = $rootScope.$new();
|
return dashboardSrv.saveDashboardAs();
|
||||||
newScope.clone = $scope.dashboard.getSaveModelClone();
|
|
||||||
newScope.clone.editable = true;
|
|
||||||
newScope.clone.hideControls = false;
|
|
||||||
|
|
||||||
$scope.appEvent('show-modal', {
|
|
||||||
src: 'public/app/features/dashboard/partials/saveDashboardAs.html',
|
|
||||||
scope: newScope,
|
|
||||||
modalClass: 'modal--narrow'
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.viewJson = function() {
|
$scope.viewJson = function() {
|
||||||
|
|||||||
@@ -0,0 +1,576 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import angular from 'angular';
|
||||||
|
import moment from 'moment';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import {Emitter} from 'app/core/core';
|
||||||
|
import {contextSrv} from 'app/core/services/context_srv';
|
||||||
|
|
||||||
|
export class DashboardModel {
|
||||||
|
id: any;
|
||||||
|
title: any;
|
||||||
|
autoUpdate: any;
|
||||||
|
description: any;
|
||||||
|
tags: any;
|
||||||
|
style: any;
|
||||||
|
timezone: any;
|
||||||
|
editable: any;
|
||||||
|
hideControls: any;
|
||||||
|
sharedCrosshair: any;
|
||||||
|
rows: any;
|
||||||
|
time: any;
|
||||||
|
timepicker: any;
|
||||||
|
templating: any;
|
||||||
|
annotations: any;
|
||||||
|
refresh: any;
|
||||||
|
snapshot: any;
|
||||||
|
schemaVersion: number;
|
||||||
|
version: number;
|
||||||
|
revision: number;
|
||||||
|
links: any;
|
||||||
|
gnetId: any;
|
||||||
|
meta: any;
|
||||||
|
events: any;
|
||||||
|
|
||||||
|
constructor(data, meta) {
|
||||||
|
if (!data) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events = new Emitter();
|
||||||
|
this.id = data.id || null;
|
||||||
|
this.revision = data.revision;
|
||||||
|
this.title = data.title || 'No Title';
|
||||||
|
this.autoUpdate = data.autoUpdate;
|
||||||
|
this.description = data.description;
|
||||||
|
this.tags = data.tags || [];
|
||||||
|
this.style = data.style || "dark";
|
||||||
|
this.timezone = data.timezone || '';
|
||||||
|
this.editable = data.editable !== false;
|
||||||
|
this.hideControls = data.hideControls || false;
|
||||||
|
this.sharedCrosshair = data.sharedCrosshair || false;
|
||||||
|
this.rows = data.rows || [];
|
||||||
|
this.time = data.time || { from: 'now-6h', to: 'now' };
|
||||||
|
this.timepicker = data.timepicker || {};
|
||||||
|
this.templating = this.ensureListExist(data.templating);
|
||||||
|
this.annotations = this.ensureListExist(data.annotations);
|
||||||
|
this.refresh = data.refresh;
|
||||||
|
this.snapshot = data.snapshot;
|
||||||
|
this.schemaVersion = data.schemaVersion || 0;
|
||||||
|
this.version = data.version || 0;
|
||||||
|
this.links = data.links || [];
|
||||||
|
this.gnetId = data.gnetId || null;
|
||||||
|
|
||||||
|
this.updateSchema(data);
|
||||||
|
this.initMeta(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initMeta(meta) {
|
||||||
|
meta = meta || {};
|
||||||
|
|
||||||
|
meta.canShare = meta.canShare !== false;
|
||||||
|
meta.canSave = meta.canSave !== false;
|
||||||
|
meta.canStar = meta.canStar !== false;
|
||||||
|
meta.canEdit = meta.canEdit !== false;
|
||||||
|
|
||||||
|
if (!this.editable) {
|
||||||
|
meta.canEdit = false;
|
||||||
|
meta.canDelete = false;
|
||||||
|
meta.canSave = false;
|
||||||
|
this.hideControls = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleans meta data and other non peristent state
|
||||||
|
getSaveModelClone() {
|
||||||
|
// temp remove stuff
|
||||||
|
var events = this.events;
|
||||||
|
var meta = this.meta;
|
||||||
|
delete this.events;
|
||||||
|
delete this.meta;
|
||||||
|
|
||||||
|
events.emit('prepare-save-model');
|
||||||
|
var copy = $.extend(true, {}, this);
|
||||||
|
|
||||||
|
// restore properties
|
||||||
|
this.events = events;
|
||||||
|
this.meta = meta;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureListExist(data) {
|
||||||
|
if (!data) { data = {}; }
|
||||||
|
if (!data.list) { data.list = []; }
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNextPanelId() {
|
||||||
|
var i, j, row, panel, max = 0;
|
||||||
|
for (i = 0; i < this.rows.length; i++) {
|
||||||
|
row = this.rows[i];
|
||||||
|
for (j = 0; j < row.panels.length; j++) {
|
||||||
|
panel = row.panels[j];
|
||||||
|
if (panel.id > max) { max = panel.id; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachPanel(callback) {
|
||||||
|
var i, j, row;
|
||||||
|
for (i = 0; i < this.rows.length; i++) {
|
||||||
|
row = this.rows[i];
|
||||||
|
for (j = 0; j < row.panels.length; j++) {
|
||||||
|
callback(row.panels[j], j, row, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelById(id) {
|
||||||
|
for (var i = 0; i < this.rows.length; i++) {
|
||||||
|
var row = this.rows[i];
|
||||||
|
for (var j = 0; j < row.panels.length; j++) {
|
||||||
|
var panel = row.panels[j];
|
||||||
|
if (panel.id === id) {
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowSpan(row) {
|
||||||
|
return _.reduce(row.panels, function(p,v) {
|
||||||
|
return p + v.span;
|
||||||
|
},0);
|
||||||
|
};
|
||||||
|
|
||||||
|
addPanel(panel, row) {
|
||||||
|
var rowSpan = this.rowSpan(row);
|
||||||
|
var panelCount = row.panels.length;
|
||||||
|
var space = (12 - rowSpan) - panel.span;
|
||||||
|
panel.id = this.getNextPanelId();
|
||||||
|
|
||||||
|
// try to make room of there is no space left
|
||||||
|
if (space <= 0) {
|
||||||
|
if (panelCount === 1) {
|
||||||
|
row.panels[0].span = 6;
|
||||||
|
panel.span = 6;
|
||||||
|
} else if (panelCount === 2) {
|
||||||
|
row.panels[0].span = 4;
|
||||||
|
row.panels[1].span = 4;
|
||||||
|
panel.span = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.panels.push(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmenuFeaturesEnabled() {
|
||||||
|
var visableTemplates = _.filter(this.templating.list, function(template) {
|
||||||
|
return template.hideVariable === undefined || template.hideVariable === false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return visableTemplates.length > 0 || this.annotations.list.length > 0 || this.links.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelInfoById(panelId) {
|
||||||
|
var result: any = {};
|
||||||
|
_.each(this.rows, function(row) {
|
||||||
|
_.each(row.panels, function(panel, index) {
|
||||||
|
if (panel.id === panelId) {
|
||||||
|
result.panel = panel;
|
||||||
|
result.row = row;
|
||||||
|
result.index = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.panel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicatePanel(panel, row) {
|
||||||
|
var rowIndex = _.indexOf(this.rows, row);
|
||||||
|
var newPanel = angular.copy(panel);
|
||||||
|
newPanel.id = this.getNextPanelId();
|
||||||
|
|
||||||
|
delete newPanel.repeat;
|
||||||
|
delete newPanel.repeatIteration;
|
||||||
|
delete newPanel.repeatPanelId;
|
||||||
|
delete newPanel.scopedVars;
|
||||||
|
|
||||||
|
var currentRow = this.rows[rowIndex];
|
||||||
|
currentRow.panels.push(newPanel);
|
||||||
|
return newPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDate(date, format) {
|
||||||
|
date = moment.isMoment(date) ? date : moment(date);
|
||||||
|
format = format || 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
this.timezone = this.getTimezone();
|
||||||
|
|
||||||
|
return this.timezone === 'browser' ?
|
||||||
|
moment(date).format(format) :
|
||||||
|
moment.utc(date).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelativeTime(date) {
|
||||||
|
date = moment.isMoment(date) ? date : moment(date);
|
||||||
|
|
||||||
|
return this.timezone === 'browser' ?
|
||||||
|
moment(date).fromNow() :
|
||||||
|
moment.utc(date).fromNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
getNextQueryLetter(panel) {
|
||||||
|
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
|
||||||
|
return _.find(letters, function(refId) {
|
||||||
|
return _.every(panel.targets, function(other) {
|
||||||
|
return other.refId !== refId;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isTimezoneUtc() {
|
||||||
|
return this.getTimezone() === 'utc';
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimezone() {
|
||||||
|
return this.timezone ? this.timezone : contextSrv.user.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSchema(old) {
|
||||||
|
var i, j, k;
|
||||||
|
var oldVersion = this.schemaVersion;
|
||||||
|
var panelUpgrades = [];
|
||||||
|
this.schemaVersion = 13;
|
||||||
|
|
||||||
|
if (oldVersion === this.schemaVersion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// version 2 schema changes
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
|
||||||
|
if (old.services) {
|
||||||
|
if (old.services.filter) {
|
||||||
|
this.time = old.services.filter.time;
|
||||||
|
this.templating.list = old.services.filter.list || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
// rename panel type
|
||||||
|
if (panel.type === 'graphite') {
|
||||||
|
panel.type = 'graph';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.type !== 'graph') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; }
|
||||||
|
|
||||||
|
if (panel.grid) {
|
||||||
|
if (panel.grid.min) {
|
||||||
|
panel.grid.leftMin = panel.grid.min;
|
||||||
|
delete panel.grid.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.grid.max) {
|
||||||
|
panel.grid.leftMax = panel.grid.max;
|
||||||
|
delete panel.grid.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.y_format) {
|
||||||
|
panel.y_formats[0] = panel.y_format;
|
||||||
|
delete panel.y_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.y2_format) {
|
||||||
|
panel.y_formats[1] = panel.y2_format;
|
||||||
|
delete panel.y2_format;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema version 3 changes
|
||||||
|
if (oldVersion < 3) {
|
||||||
|
// ensure panel ids
|
||||||
|
var maxId = this.getNextPanelId();
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (!panel.id) {
|
||||||
|
panel.id = maxId;
|
||||||
|
maxId += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema version 4 changes
|
||||||
|
if (oldVersion < 4) {
|
||||||
|
// move aliasYAxis changes
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (panel.type !== 'graph') { return; }
|
||||||
|
_.each(panel.aliasYAxis, function(value, key) {
|
||||||
|
panel.seriesOverrides = [{ alias: key, yaxis: value }];
|
||||||
|
});
|
||||||
|
delete panel.aliasYAxis;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
// move pulldowns to new schema
|
||||||
|
var annotations = _.find(old.pulldowns, { type: 'annotations' });
|
||||||
|
|
||||||
|
if (annotations) {
|
||||||
|
this.annotations = {
|
||||||
|
list: annotations.annotations || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// update template variables
|
||||||
|
for (i = 0 ; i < this.templating.list.length; i++) {
|
||||||
|
var variable = this.templating.list[i];
|
||||||
|
if (variable.datasource === void 0) { variable.datasource = null; }
|
||||||
|
if (variable.type === 'filter') { variable.type = 'query'; }
|
||||||
|
if (variable.type === void 0) { variable.type = 'query'; }
|
||||||
|
if (variable.allFormat === void 0) { variable.allFormat = 'glob'; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 7) {
|
||||||
|
if (old.nav && old.nav.length) {
|
||||||
|
this.timepicker = old.nav[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure query refIds
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
_.each(panel.targets, function(target) {
|
||||||
|
if (!target.refId) {
|
||||||
|
target.refId = this.getNextQueryLetter(panel);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 8) {
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
_.each(panel.targets, function(target) {
|
||||||
|
// update old influxdb query schema
|
||||||
|
if (target.fields && target.tags && target.groupBy) {
|
||||||
|
if (target.rawQuery) {
|
||||||
|
delete target.fields;
|
||||||
|
delete target.fill;
|
||||||
|
} else {
|
||||||
|
target.select = _.map(target.fields, function(field) {
|
||||||
|
var parts = [];
|
||||||
|
parts.push({type: 'field', params: [field.name]});
|
||||||
|
parts.push({type: field.func, params: []});
|
||||||
|
if (field.mathExpr) {
|
||||||
|
parts.push({type: 'math', params: [field.mathExpr]});
|
||||||
|
}
|
||||||
|
if (field.asExpr) {
|
||||||
|
parts.push({type: 'alias', params: [field.asExpr]});
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
});
|
||||||
|
delete target.fields;
|
||||||
|
_.each(target.groupBy, function(part) {
|
||||||
|
if (part.type === 'time' && part.interval) {
|
||||||
|
part.params = [part.interval];
|
||||||
|
delete part.interval;
|
||||||
|
}
|
||||||
|
if (part.type === 'tag' && part.key) {
|
||||||
|
part.params = [part.key];
|
||||||
|
delete part.key;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target.fill) {
|
||||||
|
target.groupBy.push({type: 'fill', params: [target.fill]});
|
||||||
|
delete target.fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema version 9 changes
|
||||||
|
if (oldVersion < 9) {
|
||||||
|
// move aliasYAxis changes
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; }
|
||||||
|
|
||||||
|
if (panel.thresholds) {
|
||||||
|
var k = panel.thresholds.split(",");
|
||||||
|
|
||||||
|
if (k.length >= 3) {
|
||||||
|
k.shift();
|
||||||
|
panel.thresholds = k.join(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema version 10 changes
|
||||||
|
if (oldVersion < 10) {
|
||||||
|
// move aliasYAxis changes
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (panel.type !== 'table') { return; }
|
||||||
|
|
||||||
|
_.each(panel.styles, function(style) {
|
||||||
|
if (style.thresholds && style.thresholds.length >= 3) {
|
||||||
|
var k = style.thresholds;
|
||||||
|
k.shift();
|
||||||
|
style.thresholds = k;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 12) {
|
||||||
|
// update template variables
|
||||||
|
_.each(this.templating.list, function(templateVariable) {
|
||||||
|
if (templateVariable.refresh) { templateVariable.refresh = 1; }
|
||||||
|
if (!templateVariable.refresh) { templateVariable.refresh = 0; }
|
||||||
|
if (templateVariable.hideVariable) {
|
||||||
|
templateVariable.hide = 2;
|
||||||
|
} else if (templateVariable.hideLabel) {
|
||||||
|
templateVariable.hide = 1;
|
||||||
|
} else {
|
||||||
|
templateVariable.hide = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 12) {
|
||||||
|
// update graph yaxes changes
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (panel.type !== 'graph') { return; }
|
||||||
|
if (!panel.grid) { return; }
|
||||||
|
|
||||||
|
if (!panel.yaxes) {
|
||||||
|
panel.yaxes = [
|
||||||
|
{
|
||||||
|
show: panel['y-axis'],
|
||||||
|
min: panel.grid.leftMin,
|
||||||
|
max: panel.grid.leftMax,
|
||||||
|
logBase: panel.grid.leftLogBase,
|
||||||
|
format: panel.y_formats[0],
|
||||||
|
label: panel.leftYAxisLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show: panel['y-axis'],
|
||||||
|
min: panel.grid.rightMin,
|
||||||
|
max: panel.grid.rightMax,
|
||||||
|
logBase: panel.grid.rightLogBase,
|
||||||
|
format: panel.y_formats[1],
|
||||||
|
label: panel.rightYAxisLabel,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
panel.xaxis = {
|
||||||
|
show: panel['x-axis'],
|
||||||
|
};
|
||||||
|
|
||||||
|
delete panel.grid.leftMin;
|
||||||
|
delete panel.grid.leftMax;
|
||||||
|
delete panel.grid.leftLogBase;
|
||||||
|
delete panel.grid.rightMin;
|
||||||
|
delete panel.grid.rightMax;
|
||||||
|
delete panel.grid.rightLogBase;
|
||||||
|
delete panel.y_formats;
|
||||||
|
delete panel.leftYAxisLabel;
|
||||||
|
delete panel.rightYAxisLabel;
|
||||||
|
delete panel['y-axis'];
|
||||||
|
delete panel['x-axis'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 13) {
|
||||||
|
// update graph yaxes changes
|
||||||
|
panelUpgrades.push(function(panel) {
|
||||||
|
if (panel.type !== 'graph') { return; }
|
||||||
|
|
||||||
|
panel.thresholds = [];
|
||||||
|
var t1: any = {}, t2: any = {};
|
||||||
|
|
||||||
|
if (panel.grid.threshold1 !== null) {
|
||||||
|
t1.value = panel.grid.threshold1;
|
||||||
|
if (panel.grid.thresholdLine) {
|
||||||
|
t1.line = true;
|
||||||
|
t1.lineColor = panel.grid.threshold1Color;
|
||||||
|
t1.colorMode = 'custom';
|
||||||
|
} else {
|
||||||
|
t1.fill = true;
|
||||||
|
t1.fillColor = panel.grid.threshold1Color;
|
||||||
|
t1.colorMode = 'custom';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.grid.threshold2 !== null) {
|
||||||
|
t2.value = panel.grid.threshold2;
|
||||||
|
if (panel.grid.thresholdLine) {
|
||||||
|
t2.line = true;
|
||||||
|
t2.lineColor = panel.grid.threshold2Color;
|
||||||
|
t2.colorMode = 'custom';
|
||||||
|
} else {
|
||||||
|
t2.fill = true;
|
||||||
|
t2.fillColor = panel.grid.threshold2Color;
|
||||||
|
t2.colorMode = 'custom';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isNumber(t1.value)) {
|
||||||
|
if (_.isNumber(t2.value)) {
|
||||||
|
if (t1.value > t2.value) {
|
||||||
|
t1.op = t2.op = 'lt';
|
||||||
|
panel.thresholds.push(t1);
|
||||||
|
panel.thresholds.push(t2);
|
||||||
|
} else {
|
||||||
|
t1.op = t2.op = 'gt';
|
||||||
|
panel.thresholds.push(t1);
|
||||||
|
panel.thresholds.push(t2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t1.op = 'gt';
|
||||||
|
panel.thresholds.push(t1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete panel.grid.threshold1;
|
||||||
|
delete panel.grid.threshold1Color;
|
||||||
|
delete panel.grid.threshold2;
|
||||||
|
delete panel.grid.threshold2Color;
|
||||||
|
delete panel.grid.thresholdLine;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelUpgrades.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < this.rows.length; i++) {
|
||||||
|
var row = this.rows[i];
|
||||||
|
for (j = 0; j < row.panels.length; j++) {
|
||||||
|
for (k = 0; k < panelUpgrades.length; k++) {
|
||||||
|
panelUpgrades[k].call(this, row.panels[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ describe('dashboardSrv', function() {
|
|||||||
var _dashboardSrv;
|
var _dashboardSrv;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_dashboardSrv = new DashboardSrv();
|
_dashboardSrv = new DashboardSrv({}, {}, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when creating new dashboard with defaults only', function() {
|
describe('when creating new dashboard with defaults only', function() {
|
||||||
|
|||||||
@@ -358,9 +358,15 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.getExpandedVariables = function(target, dimensionKey, variable) {
|
this.getExpandedVariables = function(target, dimensionKey, variable) {
|
||||||
|
/* if the all checkbox is marked we should add all values to the targets */
|
||||||
|
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
|
||||||
return _.chain(variable.options)
|
return _.chain(variable.options)
|
||||||
.filter(function(v) {
|
.filter(function(v) {
|
||||||
return v.selected;
|
if (allSelected) {
|
||||||
|
return v.text !== 'All';
|
||||||
|
} else {
|
||||||
|
return v.selected;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.map(function(v) {
|
.map(function(v) {
|
||||||
var t = angular.copy(target);
|
var t = angular.copy(target);
|
||||||
@@ -369,6 +375,10 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
|||||||
}).value();
|
}).value();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.containsVariable = function (str, variableName) {
|
||||||
|
return str.indexOf('$' + variableName) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
this.expandTemplateVariable = function(targets, templateSrv) {
|
this.expandTemplateVariable = function(targets, templateSrv) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return _.chain(targets)
|
return _.chain(targets)
|
||||||
@@ -379,7 +389,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
|||||||
|
|
||||||
if (dimensionKey) {
|
if (dimensionKey) {
|
||||||
var variable = _.find(templateSrv.variables, function(variable) {
|
var variable = _.find(templateSrv.variables, function(variable) {
|
||||||
return templateSrv.containsVariable(target.dimensions[dimensionKey], variable.name);
|
return self.containsVariable(target.dimensions[dimensionKey], variable.name);
|
||||||
});
|
});
|
||||||
return self.getExpandedVariables(target, dimensionKey, variable);
|
return self.getExpandedVariables(target, dimensionKey, variable);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label width-13">Default Region</label>
|
<label class="gf-form-label width-13">Default Region</label>
|
||||||
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
|
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
|
||||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select>
|
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2']"></select>
|
||||||
<info-popover mode="right-absolute">
|
<info-popover mode="right-absolute">
|
||||||
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
|
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
|
||||||
</info-popover>
|
</info-popover>
|
||||||
|
|||||||
@@ -478,7 +478,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "prometheus_evaluator_duration_milliseconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
|
"expr": "prometheus_evaluator_duration_seconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"intervalFactor": 2,
|
"intervalFactor": 2,
|
||||||
"legendFormat": "{{quantile}}",
|
"legendFormat": "{{quantile}}",
|
||||||
|
|||||||
@@ -392,17 +392,21 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
|||||||
position: 'BOTTOM',
|
position: 'BOTTOM',
|
||||||
markerSize: 5,
|
markerSize: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
types['$__ok'] = {
|
types['$__ok'] = {
|
||||||
color: 'rgba(11, 237, 50, 1)',
|
color: 'rgba(11, 237, 50, 1)',
|
||||||
position: 'BOTTOM',
|
position: 'BOTTOM',
|
||||||
markerSize: 5,
|
markerSize: 5,
|
||||||
};
|
};
|
||||||
types['$__nodata'] = {
|
|
||||||
|
types['$__no_data'] = {
|
||||||
color: 'rgba(150, 150, 150, 1)',
|
color: 'rgba(150, 150, 150, 1)',
|
||||||
position: 'BOTTOM',
|
position: 'BOTTOM',
|
||||||
markerSize: 5,
|
markerSize: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
types['$__execution_error'] = ['$__no_data'];
|
||||||
|
|
||||||
for (var i = 0; i < annotations.length; i++) {
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
var item = annotations[i];
|
var item = annotations[i];
|
||||||
if (item.newState) {
|
if (item.newState) {
|
||||||
|
|||||||
@@ -149,8 +149,6 @@ function ($, _) {
|
|||||||
|
|
||||||
seriesHtml = '';
|
seriesHtml = '';
|
||||||
|
|
||||||
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
|
|
||||||
|
|
||||||
// Dynamically reorder the hovercard for the current time point if the
|
// Dynamically reorder the hovercard for the current time point if the
|
||||||
// option is enabled, sort by yaxis by default.
|
// option is enabled, sort by yaxis by default.
|
||||||
if (panel.tooltip.sort === 2) {
|
if (panel.tooltip.sort === 2) {
|
||||||
@@ -161,13 +159,14 @@ function ($, _) {
|
|||||||
seriesHoverInfo.sort(function(a, b) {
|
seriesHoverInfo.sort(function(a, b) {
|
||||||
return a.value - b.value;
|
return a.value - b.value;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
seriesHoverInfo.sort(function(a, b) {
|
seriesHoverInfo.sort(function(a, b) {
|
||||||
return a.yaxis - b.yaxis;
|
return a.yaxis - b.yaxis;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var distance, time;
|
||||||
|
|
||||||
for (i = 0; i < seriesHoverInfo.length; i++) {
|
for (i = 0; i < seriesHoverInfo.length; i++) {
|
||||||
hoverInfo = seriesHoverInfo[i];
|
hoverInfo = seriesHoverInfo[i];
|
||||||
|
|
||||||
@@ -175,6 +174,11 @@ function ($, _) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! distance || hoverInfo.distance < distance) {
|
||||||
|
distance = hoverInfo.distance;
|
||||||
|
time = hoverInfo.time;
|
||||||
|
}
|
||||||
|
|
||||||
var highlightClass = '';
|
var highlightClass = '';
|
||||||
if (item && i === item.seriesIndex) {
|
if (item && i === item.seriesIndex) {
|
||||||
highlightClass = 'graph-tooltip-list-item--highlight';
|
highlightClass = 'graph-tooltip-list-item--highlight';
|
||||||
@@ -190,6 +194,7 @@ function ($, _) {
|
|||||||
plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);
|
plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
absoluteTime = dashboard.formatDate(time, tooltipFormat);
|
||||||
self.showTooltip(absoluteTime, seriesHtml, pos);
|
self.showTooltip(absoluteTime, seriesHtml, pos);
|
||||||
}
|
}
|
||||||
// single series tooltip
|
// single series tooltip
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-tab-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-sidemenu-aside {
|
.edit-sidemenu-aside {
|
||||||
width: 16rem;
|
width: 16rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ describe("rangeUtil", () => {
|
|||||||
expect(info.from).to.be('now-13h')
|
expect(info.from).to.be('now-13h')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle non default future amount', () => {
|
||||||
|
var info = rangeUtil.describeTextRange('+3h');
|
||||||
|
expect(info.display).to.be('Next 3 hours')
|
||||||
|
expect(info.from).to.be('now')
|
||||||
|
expect(info.to).to.be('now+3h')
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle now/d', () => {
|
it('should handle now/d', () => {
|
||||||
var info = rangeUtil.describeTextRange('now/d');
|
var info = rangeUtil.describeTextRange('now/d');
|
||||||
expect(info.display).to.be('Today so far');
|
expect(info.display).to.be('Today so far');
|
||||||
|
|||||||
Reference in New Issue
Block a user