Skip to content

Commit

Permalink
use same dialog to acknowledge and disable tests
Browse files Browse the repository at this point in the history
  • Loading branch information
daduke committed Jul 12, 2018
1 parent 6214e66 commit 1c7ef47
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 34 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -6,14 +6,14 @@ For monitoring large scale computing environments, we love <a href="https://www.
[<img src="img/screenshot_th.png">](img/screenshot.png)

* for each test, we have a two-dimensional coordinate defined by the test's priority (P1 to P3 or none) and the result of the test (red, yellow, green..). So it makes sense to display the test results in a matrix with the most important stuff in the upper left corner and the least important items in the lower right. We decided on prio for the x axis and color for the y axis
* if a hosts has multiple non-green entries (say P2/yellow and P1/red), we only want to see it once in our matrix, at P1/red. The P2/yellow entry will be added to the P1/red and "jumps back" to P2/yello once P1/red has been resolved or acknowledged
* we want to make acknowledgements fast and easy in order to encourage team members to let their colleagues know they're working on something and get the corresponding entry off of everybody's matrix. You can acknowledge individual tests or all tests of a host. Tests will also be acknowledged on the critical view
* if a hosts has multiple non-green entries (say P2/yellow and P1/red), we only want to see it once in our matrix, at P1/red. The P2/yellow entry will be added to the P1/red and "jumps back" to P2/yello once P1/red has been resolved, disabled or acknowledged
* we want to make acknowledging and disabling tests fast and easy in order to encourage team members to let their colleagues know they're working on something and get the corresponding entry off of everybody's matrix. You can acknowledge and disable individual tests or all tests of a host. Tests will also be acknowledged on the critical view
* we want to show as much information as possible as unobtrusively as possible. Hence we use mouse-over tooltips a lot
* if you're like us, you're looking at the monitoring interface a lot of times during the day. There might be tests that shouldn't be acknowledged, but you can't get rid of them right now either. In order not to have to "mentally parse" the whole matrix each time, you can hit the 'mark all as seen' button which will fade out all currently present tests so that all new ones will be very hard to miss
* there's a dynamic favicon and you can turn on desktop notifications
* you can override the URL parameters used to fetch the JSON data in order to modify which tests to fetch (see `xymon2json` for details)
* the interface should be usable on mobile devices ("responsive")
* everything is configurable: which colors and prios to see, display acknowledged tests or not, conditions for the overall background color etc
* everything is configurable: which colors and prios to see, display acknowledged/disabled tests or not, conditions for the overall background color etc
* you can search for hosts and display all their tests
* keyboard shortcuts: `r` for reload, `m` for mark as seen, `s` for search

Expand Down
3 changes: 3 additions & 0 deletions config.ini.example
Expand Up @@ -16,6 +16,9 @@ XYMONURL="/xymon-cgi/svcstatus.sh"
# relative URL of xymon-ack on your monitoring server
XYMONACKURL="/xymondash/cgi/xymon-ack"

# relative URL of xymon-disable on your monitoring server
XYMONDISURL="/xymondash/cgi/xymon-disable"

# relative URL of xymon2json on your monitoring server
XYMONJSONURL="/xymondash/cgi/xymon2json"

Expand Down
2 changes: 2 additions & 0 deletions config.sh
Expand Up @@ -4,6 +4,7 @@ source config.ini

#configure Python
sed -E -i "s#(XYMONCLI += ')(.+?)(')#\1$XYMONCLI\3#" cgi/xymon-ack
sed -E -i "s#(XYMONCLI += ')(.+?)(')#\1$XYMONCLI\3#" cgi/xymon-disable
sed -E -i "s#(XYMONACKINFOSH += ')(.+?)(')#\1$XYMONACKINFOSH\3#" cgi/xymon-ack
sed -E -i "s#(XYMONSVCSTATUSURL += ')(.+?)(')#\1$XYMONSVCSTATUSURL\3#" cgi/xymon-ack
sed -E -i "s#(XYMONCLI += ')(.+?)(')#\1$XYMONCLI\3#" cgi/xymon2json
Expand All @@ -12,6 +13,7 @@ sed -E -i "s#(CRITICAL += ')(.+?)(')#\1$CRITICAL\3#" cgi/xymon2json
#configure JS
sed -E -i "s#(XYMONURL += ')(.+?)(')#\1$XYMONURL\3#" js/main.js
sed -E -i "s#(XYMONACKURL += ')(.+?)(')#\1$XYMONACKURL\3#" js/main.js
sed -E -i "s#(XYMONDISURL += ')(.+?)(')#\1$XYMONDISURL\3#" js/main.js
sed -E -i "s#(XYMONJSONURL += ')(.+?)(')#\1$XYMONJSONURL\3#" js/main.js

#configure HTML
Expand Down
7 changes: 7 additions & 0 deletions css/main.css
Expand Up @@ -203,6 +203,13 @@ div#flash {
white-space: pre-line;
}

span#disableOK {
padding-left: 5px;
line-height: 3.5em;
font-size: 80%;
color: #ccc;
}

/* popup form CSS */
label, input {
display:block;
Expand Down
5 changes: 3 additions & 2 deletions index.html
Expand Up @@ -159,7 +159,7 @@ <h1 class="text-white" id="t">ISG D-PHYS xymon dashboard</h1>
</table>
</div>

<div id="dialog-form" title="Acknowledge test">
<div id="dialog-form" title="Modify test">
<form>
<fieldset>
<label for="duration">Duration</label>
Expand All @@ -170,8 +170,9 @@ <h1 class="text-white" id="t">ISG D-PHYS xymon dashboard</h1>
<option value="hours">hours</option>
<option value="days">days</option>
</select>
<span id="disableOK"> (-1 for disable until OK)</span>
</div>
<label for="ackmsg">Acknowledge text</label>
<label for="ackmsg">Message</label>
<input type="text" name="MESSAGE" id="message" value="working on it" class="text ui-widget-content ui-corner-all">
<input type="hidden" name="NUMBER" id="number">
<input type="hidden" name="HOST" id="host">
Expand Down
130 changes: 101 additions & 29 deletions js/main.js
Expand Up @@ -7,6 +7,7 @@

const XYMONURL = '/xymon-cgi/svcstatus.sh';
const XYMONACKURL = '/xymondash/cgi/xymon-ack';
const XYMONDISURL = '/xymondash/cgi/xymon-disable';
const XYMONJSONURL = '/xymondash/cgi/xymon2json';

let availableColors = ['red', 'purple', 'yellow', 'blue', 'green'];
Expand Down Expand Up @@ -72,10 +73,6 @@ $(document).ready(function() {
width: 350,
modal: true,
buttons: {
"Acknowledge test": ackTest,
Cancel: function() {
dialogForm.dialog("close");
}
},
open: function() {
let options = $("#dialog-form").dialog("option");
Expand All @@ -85,6 +82,15 @@ $(document).ready(function() {
$("#host").val(host);
let service = options.service;
$("#service").val(service);
let buttons = dialogForm.dialog('option', 'buttons');
if (options.actions.match(/a/)) {
$.extend(buttons, { "Acknowledge": ackTest });
}
if (options.actions.match(/d/)) {
$.extend(buttons, { "Disable": disableTest });
}
dialogForm.dialog('option', 'buttons', buttons);

paused = true; //no refresh while ack dialog is open
},
close: function() {
Expand All @@ -93,7 +99,6 @@ $(document).ready(function() {
});
$("#dialog-form").on( "submit", function( event ) {
event.preventDefault();
ackTest();
});

$("#period").selectmenu();
Expand Down Expand Up @@ -248,7 +253,7 @@ function processData() { //callback when JSON data is ready

let xymonData = this.response;
xymonData.forEach(function(entry) { //loop thru data and process all tests into entries object
let ackmsg, acktime, cookie;
let ackmsg, acktime, dismsg, distime, cookie;
let host = entry.hostname.trim();
let test = entry.testname.trim();
let color = entry.color.trim();
Expand All @@ -265,6 +270,13 @@ function processData() { //callback when JSON data is ready
ackmsg = 'empty';
acktime = '';
}
if (entry.dismsg) {
dismsg = entry.dismsg;
distime = (entry.disabletime == '-1')?'disabled until OK':entry.disabletime;
} else {
dismsg = 'empty';
distime = '';
}
if (entry.cookie) {
cookie = entry.cookie;
} else {
Expand All @@ -277,6 +289,8 @@ function processData() { //callback when JSON data is ready
if (!entries[color][prio][host][test]) entries[color][prio][host][test] = {};
entries[color][prio][host][test]['ackmsg'] = ackmsg;
entries[color][prio][host][test]['acktime'] = acktime;
entries[color][prio][host][test]['dismsg'] = dismsg;
entries[color][prio][host][test]['distime'] = distime;
entries[color][prio][host][test]['cookie'] = cookie;
entries[color][prio][host][test]['msg'] = msg;
lowestPos[host] = {};
Expand Down Expand Up @@ -319,13 +333,16 @@ function processData() { //callback when JSON data is ready
for (let test in entries[color][prio][host]) {
let ackmsg = entries[color][prio][host][test]['ackmsg'];
let acktime = entries[color][prio][host][test]['acktime'];
let dismsg = entries[color][prio][host][test]['dismsg'];
let distime = entries[color][prio][host][test]['distime'];
let msg = entries[color][prio][host][test]['msg'];
let cookie = entries[color][prio][host][test]['cookie'];
let lowestX = lowestPos[host]['x'];
let lowestY = lowestPos[host]['y'];
let lowestPosHost = lowestX + 10*lowestY;
let selector;
let seenSel = host + '_' + test + '_' + color;
let modifySel = host + '_' + test;
if (config['testState'][seenSel] != 'seen') {
allSeen[host] = false;
}
Expand All @@ -336,25 +353,37 @@ function processData() { //callback when JSON data is ready
lowestPos[host]['x'] = x;
lowestPos[host]['y'] = y;
}
let ackClass = (ackmsg != 'empty')?' acked':'';
let ackClass = (ackmsg != 'empty' || dismsg != 'empty')?' acked':'';
ackmsg = ackmsg.replace(/\\n/ig, "<br />");
dismsg = dismsg.replace(/\\n/ig, "<br />");
let d = new Date(acktime*1000);
acktime = "acked until " + dateFormat(d, "HH:MM, mmmm d (dddd)");
if (distime != 'disabled until OK') {
let d = new Date(distime*1000);
distime = "disabled until " + dateFormat(d, "HH:MM, mmmm d (dddd)");
}
let ackIcon;
if (cookie == 'empty') {
if (cookie == 'empty' && dismsg == 'empty') {
ackIcon = '&nbsp;&nbsp;';
} else {
ackIcon = "<i class='ack"+ackClass+" fas fa-check' id='"+cookie+"'></i>";
ackIcon = "<i class='ack"+ackClass+" fas fa-check' id='"+modifySel+"'></i>";
ackTests[host]++;
}
acktime = "acked until " + dateFormat(d, "HH:MM, mmmm d (dddd)");
ackmsg = '<b>'+ackmsg+'</b><br /><br />'+acktime;
let popupmsg = 'empty';
let actions = 'd';
if (cookie != 'empty') actions += ',a';
if (ackmsg != 'empty') {
popupmsg = '<b>'+ackmsg+'</b><br /><br />'+acktime;
} else if (dismsg != 'empty') {
popupmsg = '<b>'+dismsg+'</b><br /><br />'+distime;
}
if (numTests[host] == 0 || showSearch) { //new host or search result -> we need a host entry first
$("#" + selector).append(
"<div class='msg' data-host='"+host+"'>"+
"<span class='info'>"+host+": </span>"+
"<div class='tests'>"+
"<span class='test"+ackClass+"' data-test='"+test+"' data-color='" +color+
"' data-ackmsg='" +escape(ackmsg)+"' data-cookie='"+cookie+"'>"+test+
"<span class='test"+ackClass+"' data-test='"+test+"' data-color='" +color+"' data-cookie='"+cookie+"' data-actions='"+actions+"'>"+
test+
"</span>"+
ackIcon+
"</div>"+
Expand All @@ -363,16 +392,16 @@ function processData() { //callback when JSON data is ready
} else { //host already exists -> just add another test
$('[data-host="'+host+'"]').append(
"<div class='tests'>"+
"<span class='test"+ackClass+"' data-test='"+test+"' data-color='" +color+
"' data-ackmsg='"+escape(ackmsg)+"' data-cookie='"+cookie+"' >"+test+
"<span class='test"+ackClass+"' data-test='"+test+"' data-color='" +color+"' data-cookie='"+cookie+"' data-actions='"+actions+"' >"+
test+
"</span>"+
ackIcon+
"</div>");
}
let entry = $('[data-host="'+host+'"]').find('div.tests').children('span.test[data-test="' + test + '"][data-color="'+color+'"]');
$(entry).attr('tooltip', msg);
if (ackmsg != 'empty') {
$('i#'+cookie).attr('tooltip', ackmsg);
if (popupmsg != 'empty') {
$('i#'+modifySel).attr('tooltip', popupmsg);
}
if (numEntries++ > 500) {
showFlash('Your settings yield too many tests! Please choose fewer colors or prios.');
Expand Down Expand Up @@ -469,16 +498,22 @@ function processData() { //callback when JSON data is ready
});
$("i.ack").click(function(){
dialogForm.dialog("option", "hostname", $(this).parent().parent().data("host"));
dialogForm.dialog("option", "service", $(this).parent().children("span.test").data("test"));
if ($(this).hasClass('ackall')) { //ack all tests of this host TODO same for service
if ($(this).hasClass('ackall')) { //ack all tests of this host
let cookies = [];
let tests = [];
let actions = [];
$(this).parent().parent().find("span.test").each(function(e) {
let cookie = $(this).data("cookie");
cookies.push(cookie);
cookies.push($(this).data("cookie"));
tests.push($(this).data("test"));
actions.push($(this).data("actions"));
});
dialogForm.dialog("option", "cookie", cookies.join(','));
dialogForm.dialog("option", "cookie", cookies.join('#'));
dialogForm.dialog("option", "service", tests.join('#'));
dialogForm.dialog("option", "actions", actions.join('#'));
} else { //single test
dialogForm.dialog("option", "cookie", $(this).parent().children("span.test").data("cookie"));
dialogForm.dialog("option", "service", $(this).parent().children("span.test").data("test"));
dialogForm.dialog("option", "actions", $(this).parent().children("span.test").data("actions"));
}
dialogForm.dialog("open");
});
Expand Down Expand Up @@ -514,20 +549,17 @@ function ackTest() {
vals[field] = $("#"+field).val().trim();
});
if (!vals['message']) vals['message'] = '-';
if (vals['period'] == 'hours') {
vals['delay'] *= 60;
} else if (vals['period'] == 'days') {
vals['delay'] *= 60*24;
}
let min = calcMins(vals['delay'], vals['period']);

let i = 0;
let numbers = vals['number'].split(',');
let numbers = vals['number'].split('#');
let services = vals['service'].split('#');
numbers.forEach(function(number) {
if (number != 'empty') {
$.ajax({
type: "POST",
url: XYMONACKURL,
data: { number: number, min: vals['delay'], msg: vals['message'], host: vals['host'], test: vals['service'] },
data: { number: number, min: min, msg: vals['message'], host: vals['host'], test: services[i] },
success: function(data) {
if (++i == numbers.length) {
dialogForm.dialog( "close" );
Expand All @@ -543,6 +575,46 @@ function ackTest() {
});
}

function disableTest() {
let fields = ['delay', 'period', 'message', 'host', 'service'];
let vals = {};
fields.forEach(function(field) {
vals[field] = $("#"+field).val().trim();
});
if (!vals['message']) vals['message'] = '-';
let min = calcMins(vals['delay'], vals['period']);

let i = 0;
let services = vals['service'].split('#');
services.forEach(function(service) {
$.ajax({
type: "POST",
url: XYMONDISURL,
data: { min: min, msg: vals['message'], host: vals['host'], test: service },
success: function(data) {
if (++i == services.length) {
dialogForm.dialog( "close" );
triggerUpdate();
}
},
error: function(data) {
console.log(data);
alert('could not disable test!');
},
});
});
}

function calcMins(min, period) {
if (min == -1) return -1;
if (period == 'hours') {
min *= 60;
} else if (period == 'days') {
min *= 60*24;
}
return min;
}

function keys(obj) {
let keys = [];
for(let key in obj) {
Expand Down

0 comments on commit 1c7ef47

Please sign in to comment.