Commit 6c7e227d by bergquist

Merge branch 'master' of github.com:grafana/grafana

parents 466e6296 a45a487a
...@@ -252,7 +252,7 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R ...@@ -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 { ...@@ -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")
}
...@@ -252,7 +252,7 @@ func Register(r *macaron.Macaron) { ...@@ -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))
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))
...@@ -269,6 +269,7 @@ func Register(r *macaron.Macaron) { ...@@ -269,6 +269,7 @@ func Register(r *macaron.Macaron) {
}, reqOrgAdmin) }, reqOrgAdmin)
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 { ...@@ -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"`
}
...@@ -102,8 +102,10 @@ func writePIDFile() { ...@@ -102,8 +102,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 {
......
...@@ -5,6 +5,7 @@ import "github.com/grafana/grafana/pkg/components/simplejson" ...@@ -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 { ...@@ -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 {
......
...@@ -46,13 +46,23 @@ func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error { ...@@ -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 { ...@@ -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 ...@@ -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 { ...@@ -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 ...@@ -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
})
}
...@@ -59,7 +59,7 @@ export class AlertTabCtrl { ...@@ -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 { ...@@ -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;
......
...@@ -125,7 +125,16 @@ ...@@ -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>&nbsp;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">
......
///<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);
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 { ...@@ -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 { ...@@ -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
......
...@@ -9,7 +9,7 @@ import {DashboardExporter} from '../export/exporter'; ...@@ -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 { ...@@ -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 { ...@@ -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() {
......
...@@ -6,7 +6,7 @@ describe('dashboardSrv', function() { ...@@ -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() {
......
...@@ -478,7 +478,7 @@ ...@@ -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) { ...@@ -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 ($, _) { ...@@ -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 ($, _) { ...@@ -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 ($, _) { ...@@ -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 ($, _) { ...@@ -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 @@ ...@@ -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;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment