From c9d7eb0a7a05338949b90dd0488be8365e3ff07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 09:33:16 +0200 Subject: [PATCH 01/24] feat(alerting): added clear history button to alert state history view, #6244 --- pkg/api/annotations.go | 16 +++++++++++++++ pkg/api/api.go | 1 + pkg/api/dtos/annotations.go | 6 ++++++ pkg/services/annotations/annotations.go | 7 +++++++ pkg/services/sqlstore/annotation.go | 14 +++++++++++++ .../app/features/alerting/alert_tab_ctrl.ts | 20 ++++++++++++++++++- .../features/alerting/partials/alert_tab.html | 11 +++++++++- public/app/plugins/panel/graph/graph.ts | 6 +++++- public/sass/components/edit_sidemenu.scss | 4 ++++ 9 files changed, 82 insertions(+), 3 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 2803aa46435..48bf6c327ad 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -44,3 +44,19 @@ func GetAnnotations(c *middleware.Context) Response { 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") +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 5609536e213..71adc7da063 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -269,6 +269,7 @@ func Register(r *macaron.Macaron) { }, reqOrgAdmin) r.Get("/annotations", wrap(GetAnnotations)) + r.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations)) // error test r.Get("/metrics/error", wrap(GenerateError)) diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go index a5d5823e1a4..45415978ee1 100644 --- a/pkg/api/dtos/annotations.go +++ b/pkg/api/dtos/annotations.go @@ -15,3 +15,9 @@ type Annotation struct { Data *simplejson.Json `json:"data"` } + +type DeleteAnnotationsCmd struct { + AlertId int64 `json:"alertId"` + DashboardId int64 `json:"dashboardId"` + PanelId int64 `json:"panelId"` +} diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index 189c3d823cf..3fc3bafe5c5 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -5,6 +5,7 @@ import "github.com/grafana/grafana/pkg/components/simplejson" type Repository interface { Save(item *Item) error Find(query *ItemQuery) ([]*Item, error) + Delete(params *DeleteParams) error } type ItemQuery struct { @@ -20,6 +21,12 @@ type ItemQuery struct { Limit int64 `json:"alertId"` } +type DeleteParams struct { + AlertId int64 `json:"alertId"` + DashboardId int64 `json:"dashboardId"` + PanelId int64 `json:"panelId"` +} + var repositoryInstance Repository func GetRepository() Repository { diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 3ea8647d3fa..e219f48d2fe 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -84,3 +84,17 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I 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 + }) +} diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index b7e0e4ac218..61c4d658ed1 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -59,7 +59,7 @@ export class AlertTabCtrl { this.panelCtrl.render(); }); - // build notification model + // build notification model this.notifications = []; this.alertNotifications = []; this.alertHistory = []; @@ -352,6 +352,24 @@ export class AlertTabCtrl { 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() { this.testing = true; diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 07ff28dcddb..4dd5516945b 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -125,7 +125,16 @@
-
State history (last 50 state changes)
+ +
+ State history (last 50 state changes) +
+ +
+
+ No state changes recorded +
+
  1. diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 7e19d00251f..6a3e49b8455 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -392,17 +392,21 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) { position: 'BOTTOM', markerSize: 5, }; + types['$__ok'] = { color: 'rgba(11, 237, 50, 1)', position: 'BOTTOM', markerSize: 5, }; - types['$__nodata'] = { + + types['$__no_data'] = { color: 'rgba(150, 150, 150, 1)', position: 'BOTTOM', markerSize: 5, }; + types['$__execution_error'] = ['$__no_data']; + for (var i = 0; i < annotations.length; i++) { var item = annotations[i]; if (item.newState) { diff --git a/public/sass/components/edit_sidemenu.scss b/public/sass/components/edit_sidemenu.scss index d6c56fcc521..aa6f1c3535d 100644 --- a/public/sass/components/edit_sidemenu.scss +++ b/public/sass/components/edit_sidemenu.scss @@ -4,6 +4,10 @@ flex-direction: row; } +.edit-tab-content { + flex-grow: 1; +} + .edit-sidemenu-aside { width: 16rem; } From 46bab0bb37d553736ef9e2645fb86dc71d480569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 09:43:30 +0200 Subject: [PATCH 02/24] feat(alerting): remove annotations when deleting alert, closes #6244 --- pkg/services/sqlstore/alert.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index 76ea268bc04..b755177d2a3 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -46,13 +46,23 @@ func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error { 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 { return inTransaction(func(sess *xorm.Session) error { - if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", cmd.AlertId); err != nil { - return err - } - - return nil + return deleteAlertByIdInternal(cmd.AlertId, "DeleteAlertCommand", sess) }) } @@ -110,12 +120,7 @@ func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error { sess.Where("dashboard_id = ?", dashboardId).Find(&alerts) for _, alert := range alerts { - _, err := sess.Exec("DELETE FROM alert WHERE id = ? ", alert.Id) - if err != nil { - return err - } - - sqlog.Debug("Alert deleted (due to dashboard deletion)", "name", alert.Name, "id", alert.Id) + deleteAlertByIdInternal(alert.Id, "Dashboard deleted", sess) } return nil @@ -195,12 +200,7 @@ func deleteMissingAlerts(alerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xorm } if missing { - _, err := sess.Exec("DELETE FROM alert WHERE id = ?", missingAlert.Id) - if err != nil { - return err - } - - sqlog.Debug("Alert deleted", "name", missingAlert.Name, "id", missingAlert.Id) + deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess) } } From 4c26c55fea9aca18f5ae717133a85c5eb82cc518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 09:58:22 +0200 Subject: [PATCH 03/24] fix(api): minor change to how pause alert was done --- pkg/api/api.go | 2 +- pkg/services/sqlstore/alert.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 71adc7da063..211b5b05e4d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -252,7 +252,7 @@ func Register(r *macaron.Macaron) { r.Group("/alerts", func() { 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)) r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert)) r.Get("/", wrap(GetAlerts)) r.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard)) diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index b755177d2a3..4824b000bcb 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -248,7 +248,9 @@ func PauseAlertRule(cmd *m.PauseAlertCommand) error { return inTransaction(func(sess *xorm.Session) error { 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 } else if !has { return fmt.Errorf("Could not find alert") From ec4260e14f63125484c39b2b2c1cd0c95463cb0d Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 14 Oct 2016 10:26:08 +0200 Subject: [PATCH 04/24] tech(build): remove commented build steps --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 22f0e5606dc..0913b57d520 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,10 +25,8 @@ install: build_script: - go run build.go build - grunt release - #- 7z a grafana.zip %APPVEYOR_BUILD_FOLDER%\dist\* - cp dist/* . artifacts: - path: grafana-*windows-ia32.zip - #- path: dist/* name: binzip From 466e62968e75553176092a41191be5fbf7e90ec5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 14 Oct 2016 10:37:31 +0200 Subject: [PATCH 05/24] tech(build): publish artifacts to s3 --- appveyor.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0913b57d520..0f2a340a769 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,3 +30,7 @@ build_script: artifacts: - path: grafana-*windows-ia32.zip name: binzip + +deploy: +provider: Environment + name: GrafanaBuildsS3 From 30645a6d3d757cc5862067eb6595f1827cc664e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 13:06:29 +0200 Subject: [PATCH 06/24] refactor(alerting): initial step for seperating alerting and dash model, #6273 --- pkg/api/alerting.go | 2 +- public/app/features/dashboard/alerting_srv.ts | 22 + public/app/features/dashboard/all.js | 1 + .../app/features/dashboard/dashboard_ctrl.ts | 2 + .../app/features/dashboard/dashboard_srv.ts | 670 +++--------------- .../app/features/dashboard/dashnav/dashnav.ts | 93 +-- public/app/features/dashboard/model.ts | 576 +++++++++++++++ 7 files changed, 701 insertions(+), 665 deletions(-) create mode 100644 public/app/features/dashboard/alerting_srv.ts create mode 100644 public/app/features/dashboard/model.ts diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 40b117835ec..e745f820aec 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -252,7 +252,7 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R return ApiSuccess("Test notification sent") } -//POST /api/:alertId/pause +//POST /api/alerts/:alertId/pause func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response { cmd := models.PauseAlertCommand{ OrgId: c.OrgId, diff --git a/public/app/features/dashboard/alerting_srv.ts b/public/app/features/dashboard/alerting_srv.ts new file mode 100644 index 00000000000..0e1c160b726 --- /dev/null +++ b/public/app/features/dashboard/alerting_srv.ts @@ -0,0 +1,22 @@ +/// + +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); + diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index b49b910f95f..3aa298001a5 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -1,5 +1,6 @@ define([ './dashboard_ctrl', + './alerting_srv', './dashboardLoaderSrv', './dashnav/dashnav', './submenu/submenu', diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 0e80598e758..649a04474cc 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -16,6 +16,7 @@ export class DashboardCtrl { dashboardKeybindings, timeSrv, variableSrv, + alertingSrv, dashboardSrv, unsavedChangesSrv, dynamicDashboardSrv, @@ -43,6 +44,7 @@ export class DashboardCtrl { // init services timeSrv.init(dashboard); + alertingSrv.init(dashboard, data.alerts); // template values service needs to initialize completely before // the rest of the dashboard can load diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts index 94026233134..96a3bb1af7b 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/dashboard_srv.ts @@ -1,595 +1,113 @@ /// -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'; import coreModule from 'app/core/core_module'; - -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]); - } - } - } - } -} - +import {DashboardModel} from './model'; export class DashboardSrv { - currentDashboard: any; + dash: any; + + /** @ngInject */ + constructor(private backendSrv, private $rootScope, private $location) { + } create(dashboard, meta) { return new DashboardModel(dashboard, meta); } setCurrent(dashboard) { - this.currentDashboard = dashboard; + this.dash = dashboard; } 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); diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 6ef324ccf44..929cc66a5b0 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -9,7 +9,7 @@ import {DashboardExporter} from '../export/exporter'; export class DashNavCtrl { /** @ngInject */ - constructor($scope, $rootScope, alertSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) { + constructor($scope, $rootScope, dashboardSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) { $scope.init = function() { $scope.onAppEvent('save-dashboard', $scope.saveDashboard); @@ -71,88 +71,14 @@ export class DashNavCtrl { $scope.makeEditable = function() { $scope.dashboard.editable = true; - var clone = $scope.dashboard.getSaveModelClone(); - - 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]); - + return dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(function() { // force refresh whole page window.location.href = window.location.href; - }, $scope.handleSaveDashError); + }); }; $scope.saveDashboard = function(options) { - if ($scope.dashboardMeta.canSave === false) { - 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}); - } - }); - } + return dashboardSrv.saveDashboard(options); }; $scope.deleteDashboard = function() { @@ -189,16 +115,7 @@ export class DashNavCtrl { }; $scope.saveDashboardAs = function() { - var newScope = $rootScope.$new(); - 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' - }); + return dashboardSrv.saveDashboardAs(); }; $scope.viewJson = function() { diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts new file mode 100644 index 00000000000..3149d76b4df --- /dev/null +++ b/public/app/features/dashboard/model.ts @@ -0,0 +1,576 @@ +/// + +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]); + } + } + } + } +} + From 04c23ef3220aca6b63539ce088f2c56b6897d3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 14:23:22 +0200 Subject: [PATCH 07/24] fix(SIGHUP): ignore SIGHUP syscall, fixes #6276 --- pkg/cmd/grafana-server/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index caf9d2cb56f..60d430b24b9 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -102,8 +102,10 @@ func writePIDFile() { func listenToSystemSignals(server models.GrafanaServer) { signalChan := make(chan os.Signal, 1) + ignoreChan := make(chan os.Signal, 1) code := 0 + signal.Notify(ignoreChan, syscall.SIGHUP) signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM) select { From c1567ae59533faccd271b81b5ab180cf2af07bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 15 Oct 2016 08:00:48 +0200 Subject: [PATCH 08/24] fix(build): fixes #6284 --- public/app/features/dashboard/specs/dashboard_srv_specs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/specs/dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dashboard_srv_specs.ts index 7dded76e079..b9426cc07fb 100644 --- a/public/app/features/dashboard/specs/dashboard_srv_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_srv_specs.ts @@ -6,7 +6,7 @@ describe('dashboardSrv', function() { var _dashboardSrv; beforeEach(() => { - _dashboardSrv = new DashboardSrv(); + _dashboardSrv = new DashboardSrv({}, {}, {}); }); describe('when creating new dashboard with defaults only', function() { From d1e8418c7470409559ce4a974041c7ac83a327f8 Mon Sep 17 00:00:00 2001 From: Ben RUBSON Date: Sat, 15 Oct 2016 08:02:07 +0200 Subject: [PATCH 09/24] Correct tooltip timestamp (#6283) Corrects #6275, follows #6211 --- public/app/plugins/panel/graph/graph_tooltip.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index da0080f0005..5ae03ccf813 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -149,8 +149,6 @@ function ($, _) { seriesHtml = ''; - absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat); - // Dynamically reorder the hovercard for the current time point if the // option is enabled, sort by yaxis by default. if (panel.tooltip.sort === 2) { @@ -161,13 +159,14 @@ function ($, _) { seriesHoverInfo.sort(function(a, b) { return a.value - b.value; }); - } - else { + } else { seriesHoverInfo.sort(function(a, b) { return a.yaxis - b.yaxis; }); } + var distance, time; + for (i = 0; i < seriesHoverInfo.length; i++) { hoverInfo = seriesHoverInfo[i]; @@ -175,6 +174,11 @@ function ($, _) { continue; } + if (! distance || hoverInfo.distance < distance) { + distance = hoverInfo.distance; + time = hoverInfo.time; + } + var highlightClass = ''; if (item && i === item.seriesIndex) { highlightClass = 'graph-tooltip-list-item--highlight'; @@ -190,6 +194,7 @@ function ($, _) { plot.highlight(hoverInfo.index, hoverInfo.hoverIndex); } + absoluteTime = dashboard.formatDate(time, tooltipFormat); self.showTooltip(absoluteTime, seriesHtml, pos); } // single series tooltip From a45a487ad3c38243270a2ec9291582d7a1fdf6cd Mon Sep 17 00:00:00 2001 From: Paul Traylor Date: Mon, 17 Oct 2016 15:37:59 +0900 Subject: [PATCH 10/24] Update query for "Rule Eval Duration" (#6294) This query was changed to use seconds in https://github.com/prometheus/prometheus/commit/f69e668fc468dfa2feb13603237810b8d748d87e --- .../datasource/prometheus/dashboards/prometheus_stats.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json b/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json index 0c2e0e85728..3b5b73ea6d4 100644 --- a/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json +++ b/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json @@ -478,7 +478,7 @@ "steppedLine": false, "targets": [ { - "expr": "prometheus_evaluator_duration_milliseconds{quantile!=\"0.01\", quantile!=\"0.05\"}", + "expr": "prometheus_evaluator_duration_seconds{quantile!=\"0.01\", quantile!=\"0.05\"}", "interval": "", "intervalFactor": 2, "legendFormat": "{{quantile}}", From b6d112f8d7414d6d88429f1459045b6a07209421 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 17 Oct 2016 09:34:58 +0200 Subject: [PATCH 11/24] tech(build): appveyor formating --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0f2a340a769..434e57448cd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,5 +32,5 @@ artifacts: name: binzip deploy: -provider: Environment + - provider: Environment name: GrafanaBuildsS3 From 977cdd593307043949d94c0c3b831d0100fe37d2 Mon Sep 17 00:00:00 2001 From: mkm85 Date: Mon, 17 Oct 2016 11:33:28 +0200 Subject: [PATCH 12/24] Fix cloudwatch multivalue (#6282) * Fixed missing contains variable problem * allow All option to be used in cloudwatch templating --- .../plugins/datasource/cloudwatch/datasource.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index c4f3a82f98e..fde9e8f09ea 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -358,9 +358,15 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { } 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) .filter(function(v) { - return v.selected; + if (allSelected) { + return v.text !== 'All'; + } else { + return v.selected; + } }) .map(function(v) { var t = angular.copy(target); @@ -369,6 +375,10 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { }).value(); }; + this.containsVariable = function (str, variableName) { + return str.indexOf('$' + variableName) !== -1; + }; + this.expandTemplateVariable = function(targets, templateSrv) { var self = this; return _.chain(targets) @@ -379,7 +389,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { if (dimensionKey) { 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); } else { From 0abc22f272b573bc4b68e3d3173ecd816161a7a3 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 17 Oct 2016 14:34:27 +0200 Subject: [PATCH 13/24] tech(build): make artifact path less strict --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 434e57448cd..2cf92ebcbcb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,7 @@ build_script: - cp dist/* . artifacts: - - path: grafana-*windows-ia32.zip + - path: grafana-*windows-*.zip name: binzip deploy: From 75a1ff1037631eac6fa9e5545bda4e0d78073240 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 17 Oct 2016 15:09:35 +0200 Subject: [PATCH 14/24] tech(build): dont override arch for windows --- Gruntfile.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1f96048746b..9496ccd163c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,11 +12,15 @@ module.exports = function (grunt) { platform: process.platform.replace('win32', 'windows'), }; - if (process.platform.match(/^win/)) { - config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86'; - } + if (grunt.option('arch')) { + 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'); From 97008f90c4dabc11c2ccd9c364c2c9ec180df712 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 17 Oct 2016 15:19:00 +0200 Subject: [PATCH 15/24] =?UTF-8?q?tech(build):=20semic=C3=B6l=C3=B6ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9496ccd163c..50ac332d894 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -13,9 +13,9 @@ module.exports = function (grunt) { }; if (grunt.option('arch')) { - config.arch = grunt.option('arch') + config.arch = grunt.option('arch'); } else { - config.arch = os.arch() + config.arch = os.arch(); if (process.platform.match(/^win/)) { config.arch = process.env.hasOwnProperty('ProgramFiles(x86)') ? 'x64' : 'x86'; From 888619d74e8123bf3ae2e3a896a703154f14bef3 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 18 Oct 2016 12:28:20 +0900 Subject: [PATCH 16/24] (cloudwatch) add region --- public/app/plugins/datasource/cloudwatch/partials/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index fb34a9fb92b..6f119964512 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -11,7 +11,7 @@
    - + Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region. From d016fc1da13e0c0af887c545d4803a77ecb029d5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 18 Oct 2016 10:54:17 +0200 Subject: [PATCH 17/24] tech(build): create sha files for windows build --- appveyor.yml | 1 + build.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 2cf92ebcbcb..48d3e7f314e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,7 @@ install: build_script: - go run build.go build - grunt release + - go run build.go sha1-dist - cp dist/* . artifacts: diff --git a/build.go b/build.go index a457a49a2c8..de828d3ef91 100644 --- a/build.go +++ b/build.go @@ -98,6 +98,9 @@ func main() { createDebPackages() sha1FilesInDist() + case "sha1-dist": + sha1FilesInDist() + case "latest": makeLatestDistCopies() sha1FilesInDist() From 2b55cf609c97735620c5cc9ebd40e420f9b6b75d Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 18 Oct 2016 11:42:25 +0200 Subject: [PATCH 18/24] tech(build): include sha1 as artifact --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 48d3e7f314e..756dbf8fba5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,7 +29,7 @@ build_script: - cp dist/* . artifacts: - - path: grafana-*windows-*.zip + - path: grafana-*windows-*.* name: binzip deploy: From a8dd44918b426b432e360e45a1e57a63aa46c754 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 18 Oct 2016 19:11:46 +0900 Subject: [PATCH 19/24] support future time range --- public/app/core/utils/rangeutil.ts | 18 ++++++++++++------ public/test/core/utils/rangeutil_specs.ts | 7 +++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 7f2f2fd72b1..1e564ee7ea6 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -82,8 +82,9 @@ function formatDate(date) { // now/d // if no to then to now is assumed export function describeTextRange(expr: any) { + let isLast = (expr.indexOf('+') !== 0); if (expr.indexOf('now') === -1) { - expr = 'now-' + expr; + expr = (isLast ? 'now-' : 'now') + expr; } let opt = rangeIndex[expr + ' to now']; @@ -91,15 +92,20 @@ export function describeTextRange(expr: any) { 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) { - let unit = parts[2]; - let amount = parseInt(parts[1]); + let unit = parts[3]; + let amount = parseInt(parts[2]); let span = spans[unit]; if (span) { - opt.display = 'Last ' + amount + ' ' + span.display; + opt.display = isLast ? 'Last ' : 'Next '; + opt.display += amount + ' ' + span.display; opt.section = span.section; if (amount > 1) { opt.display += 's'; diff --git a/public/test/core/utils/rangeutil_specs.ts b/public/test/core/utils/rangeutil_specs.ts index 36556fcbc4c..1deb708b1bc 100644 --- a/public/test/core/utils/rangeutil_specs.ts +++ b/public/test/core/utils/rangeutil_specs.ts @@ -31,6 +31,13 @@ describe("rangeUtil", () => { 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', () => { var info = rangeUtil.describeTextRange('now/d'); expect(info.display).to.be('Today so far'); From 9429434cb5422226bb061ae0cc6947aae2e2d724 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 18 Oct 2016 15:12:01 +0200 Subject: [PATCH 20/24] feat(influxdb): use database auth if provided closes #6310 --- docker/blocks/influxdb/config.toml | 75 ----------------------- docker/blocks/influxdb/fig | 3 +- docker/blocks/influxdb/influxdb.conf | 92 ++++++++++++++++++++++++++++ pkg/tsdb/influxdb/influxdb.go | 5 ++ 4 files changed, 99 insertions(+), 76 deletions(-) delete mode 100644 docker/blocks/influxdb/config.toml create mode 100644 docker/blocks/influxdb/influxdb.conf diff --git a/docker/blocks/influxdb/config.toml b/docker/blocks/influxdb/config.toml deleted file mode 100644 index 23834b22e3d..00000000000 --- a/docker/blocks/influxdb/config.toml +++ /dev/null @@ -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 diff --git a/docker/blocks/influxdb/fig b/docker/blocks/influxdb/fig index 7b83bb2bab4..8821c010a98 100644 --- a/docker/blocks/influxdb/fig +++ b/docker/blocks/influxdb/fig @@ -1,11 +1,12 @@ influxdb: - #image: influxdb/influxdb:1.0-alpine image: influxdb:latest container_name: influxdb ports: - "2004:2004" - "8083:8083" - "8086:8086" + volumes: + - ./blocks/influxdb/influxdb.conf:/etc/influxdb/influxdb.conf fake-influxdb-data: image: grafana/fake-data-gen diff --git a/docker/blocks/influxdb/influxdb.conf b/docker/blocks/influxdb/influxdb.conf new file mode 100644 index 00000000000..c0331ce7449 --- /dev/null +++ b/docker/blocks/influxdb/influxdb.conf @@ -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 + diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index 22f6230ed6d..b546a6ee3a9 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -124,10 +124,15 @@ func (e *InfluxDBExecutor) createRequest(query string) (*http.Request, error) { req.URL.RawQuery = params.Encode() req.Header.Set("User-Agent", "Grafana") + if e.BasicAuth { req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) } + if e.User != "" { + req.SetBasicAuth(e.User, e.Password) + } + glog.Debug("Influxdb request", "url", req.URL.String()) return req, nil } From d1eceedf55b0f6d1e88cffdeeb07b5502ad78743 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 18 Oct 2016 16:18:16 +0200 Subject: [PATCH 21/24] feat(webhook): add httpmethod to webhook closes #6255 --- pkg/models/notifications.go | 18 ++++++++------- pkg/services/alerting/notifiers/webhook.go | 19 ++++++++------- pkg/services/notifications/notifications.go | 18 ++++++++------- pkg/services/notifications/webhook.go | 17 +++++++++----- .../alerting/notification_edit_ctrl.ts | 2 +- .../alerting/partials/notification_edit.html | 23 +++++++++++-------- 6 files changed, 57 insertions(+), 40 deletions(-) diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index 759efe41a02..abbc6ec7e27 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -17,17 +17,19 @@ type SendEmailCommandSync struct { } type SendWebhook struct { - Url string - User string - Password string - Body string + Url string + User string + Password string + Body string + HttpMethod string } type SendWebhookSync struct { - Url string - User string - Password string - Body string + Url string + User string + Password string + Body string + HttpMethod string } type SendResetPasswordEmailCommand struct { diff --git a/pkg/services/alerting/notifiers/webhook.go b/pkg/services/alerting/notifiers/webhook.go index 979ce2e8a98..fb236c91c13 100644 --- a/pkg/services/alerting/notifiers/webhook.go +++ b/pkg/services/alerting/notifiers/webhook.go @@ -24,16 +24,18 @@ func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) { Url: url, User: model.Settings.Get("user").MustString(), Password: model.Settings.Get("password").MustString(), + HttpMethod: model.Settings.Get("httpMethod").MustString("POST"), log: log.New("alerting.notifier.webhook"), }, nil } type WebhookNotifier struct { NotifierBase - Url string - User string - Password string - log log.Logger + Url string + User string + Password string + HttpMethod string + log log.Logger } func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { @@ -59,10 +61,11 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { body, _ := bodyJSON.MarshalJSON() cmd := &m.SendWebhookSync{ - Url: this.Url, - User: this.User, - Password: this.Password, - Body: string(body), + Url: this.Url, + User: this.User, + Password: this.Password, + Body: string(body), + HttpMethod: this.HttpMethod, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index f7762e2d3d0..9aa30b94edd 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -61,19 +61,21 @@ func Init() error { func SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error { return sendWebRequestSync(ctx, &Webhook{ - Url: cmd.Url, - User: cmd.User, - Password: cmd.Password, - Body: cmd.Body, + Url: cmd.Url, + User: cmd.User, + Password: cmd.Password, + Body: cmd.Body, + HttpMethod: cmd.HttpMethod, }) } func sendWebhook(cmd *m.SendWebhook) error { addToWebhookQueue(&Webhook{ - Url: cmd.Url, - User: cmd.User, - Password: cmd.Password, - Body: cmd.Body, + Url: cmd.Url, + User: cmd.User, + Password: cmd.Password, + Body: cmd.Body, + HttpMethod: cmd.HttpMethod, }) return nil diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index d5b8a718d52..de1303d8131 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -15,10 +15,11 @@ import ( ) type Webhook struct { - Url string - User string - Password string - Body string + Url string + User string + Password string + Body string + HttpMethod string } var webhookQueue chan *Webhook @@ -44,13 +45,17 @@ func processWebhookQueue() { } 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{ 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 != "" { request.Header.Add("Authorization", util.GetBasicAuthHeader(webhook.User, webhook.Password)) } diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index 19b804f4697..c5f24650845 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -18,7 +18,7 @@ export class AlertNotificationEditCtrl { this.model = { type: 'email', settings: { - severityFilter: 'none' + httpMethod: 'POST' }, isDefault: false }; diff --git a/public/app/features/alerting/partials/notification_edit.html b/public/app/features/alerting/partials/notification_edit.html index 817035d21a3..c6c422b3598 100644 --- a/public/app/features/alerting/partials/notification_edit.html +++ b/public/app/features/alerting/partials/notification_edit.html @@ -32,19 +32,24 @@

    Webhook settings

    - Url + Url
    -
    -
    - Username - -
    -
    - Password - +
    + Http Method +
    +
    +
    + Username + +
    +
    + Password + +
    From 9468ddeed5c93a4de9ab245ddcabfff043d1c2b1 Mon Sep 17 00:00:00 2001 From: Eric Uldall Date: Tue, 18 Oct 2016 22:43:32 -0700 Subject: [PATCH 22/24] Issues/6317 empty google login (#6318) * added Login property to google_oauth model * fixed spacing * fixed google_oauth syntax error, missing comma before newline * set noexpandtab for commit --- pkg/social/google_oauth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index a2687d58f81..21543902a86 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -46,5 +46,6 @@ func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) { return &BasicUserInfo{ Name: data.Name, Email: data.Email, + Login: data.Email, }, nil } From b2d5b920f58d1ac1bb5ba3e310a9c689c45e39fc Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 19 Oct 2016 08:01:14 +0200 Subject: [PATCH 23/24] feat(alerting): allow editor to modify notifications closes #6314 --- pkg/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 211b5b05e4d..ed73f2dc76d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -252,7 +252,7 @@ func Register(r *macaron.Macaron) { r.Group("/alerts", func() { r.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest)) - r.Post("/:alertId/pause", bind(dtos.PauseAlertCommand{}), wrap(PauseAlert)) + r.Post("/:alertId/pause", bind(dtos.PauseAlertCommand{}), wrap(PauseAlert), reqEditorRole) r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert)) r.Get("/", wrap(GetAlerts)) r.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard)) @@ -266,7 +266,7 @@ func Register(r *macaron.Macaron) { r.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification)) r.Get("/:notificationId", wrap(GetAlertNotificationById)) r.Delete("/:notificationId", wrap(DeleteAlertNotification)) - }, reqOrgAdmin) + }, reqEditorRole) r.Get("/annotations", wrap(GetAnnotations)) r.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations)) From 20bfe443fe10f4ce4d8a674fa3f4a9b510d0a439 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 19 Oct 2016 08:57:58 +0200 Subject: [PATCH 24/24] feat(prometheus): trim labelname lookup key closes #6300 --- pkg/tsdb/prometheus/prometheus.go | 6 ++++-- pkg/tsdb/prometheus/prometheus_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 85f6b621c1c..6dc4146ad0e 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -84,8 +84,10 @@ func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { - ind := strings.Replace(strings.Replace(string(in), "{{", "", 1), "}}", "", 1) - if val, exists := metric[pmodel.LabelName(ind)]; exists { + labelName := strings.Replace(string(in), "{{", "", 1) + labelName = strings.Replace(labelName, "}}", "", 1) + labelName = strings.TrimSpace(labelName) + if val, exists := metric[pmodel.LabelName(labelName)]; exists { return []byte(val) } diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index f7489ae9afc..a4c38cae582 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -17,7 +17,7 @@ func TestPrometheus(t *testing.T) { } query := &PrometheusQuery{ - LegendFormat: "legend {{app}} {{device}} {{broken}}", + LegendFormat: "legend {{app}} {{ device }} {{broken}}", } So(formatLegend(metric, query), ShouldEqual, "legend backend mobile {{broken}}")