Acknowledges: use tooltip instead inner table.

This commit is contained in:
Alexander Zobnin
2016-07-31 11:17:22 +03:00
parent 78ca95a8de
commit 24969cd560
5 changed files with 185 additions and 98 deletions

View File

@@ -47,7 +47,6 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
}, },
// Handle API errors // Handle API errors
function(error) { function(error) {
console.log('Zabbix error: '+error.data);
if (isNotAuthorized(error.data)) { if (isNotAuthorized(error.data)) {
return self.loginOnce().then( return self.loginOnce().then(
function() { function() {
@@ -114,12 +113,14 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
//////////////////////////////// ////////////////////////////////
// Zabbix API method wrappers // // Zabbix API method wrappers //
//////////////////////////////// ////////////////////////////////
acknowledgeEvent(eventid,message){
acknowledgeEvent(eventid, message) {
var params = { var params = {
eventids:eventid, eventids: eventid,
message:message message: message
}; };
return this.request('event.acknowledge',params);
return this.request('event.acknowledge', params);
} }
getGroups() { getGroups() {

View File

@@ -0,0 +1,113 @@
import angular from 'angular';
import $ from 'jquery';
import Drop from 'tether-drop';
/** @ngInject */
angular
.module('grafana.directives')
.directive('ackTooltip', function($sanitize, $compile) {
let buttonTemplate = '<a bs-tooltip="\'Acknowledges ({{trigger.acknowledges.length}})\'"' +
'<i ng-class="' +
"{'fa fa-comments': trigger.acknowledges.length, " +
"'fa fa-comments-o': !trigger.acknowledges.length, " +
'}"></i></a>';
return {
scope: {
ack: "=",
trigger: "=",
onAck: "=",
context: "="
},
link: function(scope, element) {
let acknowledges = scope.ack;
let $button = $(buttonTemplate);
$button.appendTo(element);
$button.click(function() {
let tooltip = '<div>';
if (acknowledges && acknowledges.length) {
tooltip += '<table class="table"><thead><tr>' +
'<th class="ack-time">Time</th>' +
'<th class="ack-user">User</th>' +
'<th class="ack-comments">Comments</th>' +
'</tr></thead><tbody>';
for (let ack of acknowledges) {
tooltip += '<tr><td>' + ack.time + '</td>' +
'<td>' + ack.user + '</td>' +
'<td>' + ack.message + '</td></tr>';
}
tooltip += '</tbody></table>';
} else {
tooltip += 'Add acknowledge';
}
let addAckButtonTemplate = '<div class="ack-add-button">' +
'<button id="add-acknowledge-btn"' +
'class="btn btn-mini btn-inverse gf-form-button">' +
'<i class="fa fa-plus"></i>' +
'</button></div>';
tooltip += addAckButtonTemplate;
tooltip += '</div>';
let drop = new Drop({
target: element[0],
content: tooltip,
position: "bottom left",
classes: 'drop-popover ack-tooltip',
openOn: 'hover',
hoverCloseDelay: 500,
tetherOptions: {
constraints: [{to: 'window', pin: true, attachment: "both"}]
}
});
drop.open();
drop.on('close', closeDrop);
$('#add-acknowledge-btn').on('click', onAddAckButtonClick);
function onAddAckButtonClick() {
let inputTemplate = '<div class="ack-input-group">' +
'<input type="text" id="ack-message">' +
'<button id="send-ack-button"' +
'class="btn btn-mini btn-inverse gf-form-button">' +
'Acknowledge </button>' +
'<button id="cancel-ack-button"' +
'class="btn btn-mini btn-inverse gf-form-button">' +
'Cancel' +
'</button></input></div>';
let $input = $(inputTemplate);
let $addAckButton = $('.ack-tooltip .ack-add-button');
$addAckButton.replaceWith($input);
$('.ack-tooltip #cancel-ack-button').on('click', onAckCancelButtonClick);
$('.ack-tooltip #send-ack-button').on('click', onAckSendlButtonClick);
}
function onAckCancelButtonClick() {
$('.ack-tooltip .ack-input-group').replaceWith(addAckButtonTemplate);
$('#add-acknowledge-btn').on('click', onAddAckButtonClick);
}
function onAckSendlButtonClick() {
let message = $('.ack-tooltip #ack-message')[0].value;
let onAck = scope.onAck.bind(scope.context);
onAck(scope.trigger, message).then(() => {
closeDrop();
});
}
function closeDrop() {
setTimeout(function() {
drop.destroy();
});
}
});
$compile(element.contents())(scope);
}
};
});

View File

@@ -31,21 +31,25 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="trigger in ctrl.triggerList"> <tr ng-repeat="trigger in ctrl.triggerList">
<td ng-if="ctrl.panel.hostField"> <td ng-if="ctrl.panel.hostField">
<div> <div>
<span><strong>{{trigger.host}}</strong></span> <span><strong>{{trigger.host}}</strong></span>
</div> </div>
</td> </td>
<td ng-if="ctrl.panel.statusField" style="background-color: {{trigger.color}}; color: white"> <td ng-if="ctrl.panel.statusField" style="background-color: {{trigger.color}}; color: white">
<div> <div>
{{ctrl.triggerStatusMap[trigger.value]}} {{ctrl.triggerStatusMap[trigger.value]}}
</div> </div>
</td> </td>
<td ng-if="ctrl.panel.severityField" style="background-color: {{trigger.color}}; color: white"> <td ng-if="ctrl.panel.severityField" style="background-color: {{trigger.color}}; color: white">
<div> <div>
{{trigger.severity}} {{trigger.severity}}
</div> </div>
</td> </td>
<td style="background-color: {{trigger.color}}; color: white"> <td style="background-color: {{trigger.color}}; color: white">
<div> <div>
{{trigger.description}} {{trigger.description}}
@@ -68,52 +72,16 @@
<small>{{trigger.comments}}</small> <small>{{trigger.comments}}</small>
</div> </div>
</div> </div>
<!-- Trigger acknowledges -->
<div class="collapse"
id="acknowledges-{{trigger.triggerid}}"
ng-if="trigger.showAcknowledges">
<div style="padding-top: 12px;">
<table class="table table-condensed">
<thead>
<tr>
<th><small>Time</small></th>
<th><small>User</small></th>
<th><small>Comments</small></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ack in trigger.acknowledges">
<td>
<small>{{ack.time}}</small>
</td>
<td>
<small>{{ack.user}}</small>
</td>
<td>
<small>{{ack.message}}</small>
</td>
</tr>
<tr ng-if="trigger.newAct">
<td>
<small>{{trigger.newAct.time}}</small>
</td>
<td>
<small>{{trigger.newAct.user}}</small>
</td>
<td><input ng-model="trigger.newAct.message" size="80" ng-blur="ctrl.acknowledgeTrigger({keyCode:13},trigger,trigger.newAct)" ng-keyup="ctrl.acknowledgeTrigger($event,trigger,trigger.newAct)"/></td>
</tr>
</tbody>
</table>
</div>
</div>
</td> </td>
<td ng-if="ctrl.panel.lastChangeField"> <td ng-if="ctrl.panel.lastChangeField">
{{trigger.lastchange}} {{trigger.lastchange}}
</td> </td>
<td ng-if="ctrl.panel.ageField"> <td ng-if="ctrl.panel.ageField">
{{trigger.age}} {{trigger.age}}
</td> </td>
<td ng-if="ctrl.panel.infoField"> <td ng-if="ctrl.panel.infoField">
<!-- Trigger Url --> <!-- Trigger Url -->
@@ -130,20 +98,12 @@
</span> </span>
<!-- Trigger acknowledges --> <!-- Trigger acknowledges -->
<a ng-if="trigger.acknowledges" <ack-tooltip
role="button" ack="trigger.acknowledges"
ng-click="ctrl.switchAcknowledges(trigger)" trigger="trigger"
bs-tooltip="'Acknowledges ({{trigger.acknowledges.length}})'"> on-ack="ctrl.acknowledgeTrigger"
<i class="fa fa-comments"></i> context="ctrl">
</a> </ack-tooltip>
<!-- Acknowledge events -->
<a ng-if="trigger.acknowledges === undefined"
role="button"
ng-click="ctrl.addAcknowledgeMessage(trigger)"
bs-tooltip="'acknowledge this event'">
<i class="fa fa-comments-o"></i>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -16,6 +16,7 @@ import moment from 'moment';
import * as utils from '../datasource-zabbix/utils'; import * as utils from '../datasource-zabbix/utils';
import {MetricsPanelCtrl} from 'app/plugins/sdk'; import {MetricsPanelCtrl} from 'app/plugins/sdk';
import {triggerPanelEditor} from './editor'; import {triggerPanelEditor} from './editor';
import './ack-tooltip.directive';
import './css/panel_triggers.css!'; import './css/panel_triggers.css!';
var defaultSeverity = [ var defaultSeverity = [
@@ -59,7 +60,7 @@ var defaultTimeFormat = "DD MMM YYYY HH:mm:ss";
class TriggerPanelCtrl extends MetricsPanelCtrl { class TriggerPanelCtrl extends MetricsPanelCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv,contextSrv) { constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv, contextSrv) {
super($scope, $injector); super($scope, $injector);
this.datasourceSrv = datasourceSrv; this.datasourceSrv = datasourceSrv;
this.templateSrv = templateSrv; this.templateSrv = templateSrv;
@@ -123,11 +124,11 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
showEvents) showEvents)
.then(triggers => { .then(triggers => {
return _.map(triggers, trigger => { return _.map(triggers, trigger => {
var triggerObj = trigger; let triggerObj = trigger;
// Format last change and age // Format last change and age
trigger.lastchangeUnix = Number(trigger.lastchange); trigger.lastchangeUnix = Number(trigger.lastchange);
var timestamp = moment.unix(trigger.lastchangeUnix); let timestamp = moment.unix(trigger.lastchangeUnix);
if (self.panel.customLastChangeFormat) { if (self.panel.customLastChangeFormat) {
// User defined format // User defined format
triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat);
@@ -172,8 +173,12 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
if (event) { if (event) {
trigger.acknowledges = _.map(event.acknowledges, ack => { trigger.acknowledges = _.map(event.acknowledges, ack => {
var time = new Date(+ack.clock * 1000); let timestamp = moment.unix(ack.clock);
ack.time = time.toLocaleString(); if (self.panel.customLastChangeFormat) {
ack.time = timestamp.format(self.panel.lastChangeFormat);
} else {
ack.time = timestamp.format(self.defaultTimeFormat);
}
ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
return ack; return ack;
}); });
@@ -225,41 +230,15 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
trigger.showComment = !trigger.showComment; trigger.showComment = !trigger.showComment;
} }
switchAcknowledges(trigger) { acknowledgeTrigger(trigger, message) {
trigger.showAcknowledges = !trigger.showAcknowledges; let self = this;
} let eventid = trigger.lastEvent.eventid;
addAcknowledgeMessage(trigger){ let grafana_user = this.contextSrv.user.name;
trigger.showAcknowledges = true; let ack_message = grafana_user + ' (Grafana): ' + message;
trigger.newAct={ return this.datasourceSrv.get(this.panel.datasource).then(datasource => {
time:new Date(), let zabbix = datasource.zabbixAPI;
user:this.contextSrv.user.name+'(Grafana)', return zabbix.acknowledgeEvent(eventid, ack_message).then(() => {
eventid:trigger.lastEvent.eventid, self.refresh();
message:''
};
}
acknowledgeTrigger($event,trigger,newAct){
if($event.keyCode!=13) return;
if(newAct.message.trim() === ""){
delete trigger.newAct;
trigger.showAcknowledges = false;
return;
}
this.datasourceSrv.get(this.panel.datasource).then(datasource => {
var zabbix = datasource.zabbixAPI;
zabbix.acknowledgeEvent(newAct.eventid,newAct.user+': '+newAct.message)
.then(rs=>{
zabbix.getAcknowledges(rs.eventids).then(events => {
_.each(events, event => {
trigger.acknowledges = _.map(event.acknowledges, ack => {
var time = new Date(+ack.clock * 1000);
ack.time = time.toLocaleString();
ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
return ack;
});
});
delete trigger.newAct;
console.log('event id '+ rs.eventids.join() + ' new message added: ' + ack.message );
});
}); });
}); });
} }

View File

@@ -106,3 +106,37 @@ $grafanaListAccent: lighten($dark-2, 2%);
height: 0px; height: 0px;
line-height: 0px; line-height: 0px;
} }
.ack-tooltip {
.drop-content {
// Rewrite tooltip width
max-width: 70rem !important;
min-width: 30rem !important;
}
.ack-comments {
width: 60%;
}
.ack-add-button {
padding-top: 1rem;
}
table td, th {
padding-right: 1rem;
}
.ack-input-group {
padding-top: 1rem;
input {
border: 1px solid;
border-radius: 2px;
width: 50%;
}
button {
margin-left: 1rem;
}
}
}