Files
karma/assets/static/ui.js
Łukasz Mierzwa 9152aeff04 Prevent a chain of click events
Click events in some weird cases cause this function to be called twice, ensure that we stop processing this event after first pass
2017-04-25 19:14:18 -07:00

392 lines
16 KiB
JavaScript

var UI = (function(params) {
// when user click on any alert label modal popup with a list of possible
// filter will show, this function is used to setup that modal
setupModal = function() {
$("#labelModal").on("show.bs.modal", function(event) {
Unsee.Pause();
var modal = $(this);
var label = $(event.relatedTarget);
var label_key = label.data("label-key");
var label_val = label.data("label-val");
var attrs = Alerts.GetLabelAttrs(label_key, label_val);
var counter = Summary.Get(label_key, label_val);
modal.find(".modal-title").html(
Templates.Render("modalTitle", {
attrs: attrs,
counter: counter
})
);
var hints = Autocomplete.GenerateHints(label_key, label_val);
modal.find(".modal-body").html(
Templates.Render("modalBody", {hints: hints})
);
modal.on("click", ".modal-button-filter", function(elem) {
var filter = $(elem.target).data("filter-append-value");
$("#labelModal").modal("hide");
Filters.AddFilter(filter);
});
});
$("#labelModal").on("hidden.bs.modal", function(event) {
var modal = $(this);
modal.find(".modal-title").children().remove();
modal.find(".modal-body").children().remove();
Unsee.WaitForNextReload();
});
};
// each alert group have a link generated for it, but we hide it until
// user hovers over that group so it doesn"t trash the UI
setupGroupLinkHover = function(elem) {
$(elem).on("mouseenter", function() {
$(this).find(".alert-group-link > a").finish().animate({
opacity: 100
}, 200);
});
$(elem).on("mouseleave", function() {
$(this).find(".alert-group-link > a").finish().animate({
opacity: 0
}, 200);
});
};
// find all elements inside alert group panel that will use tooltips
// and setup those
setupGroupTooltips = function(groupElem) {
$.each(groupElem.find("[data-toggle=tooltip]"), function(i, elem) {
$(elem).tooltip({
animation: false, // slows down tooltip removal
delay: {
show: 500,
hide: 0
},
title: $(elem).attr("title") || $(elem).data("ts-title"),
trigger: "hover"
});
});
};
setupAlertGroupUI = function(elem) {
setupGroupLinkHover(elem);
setupGroupTooltips(elem);
};
init = function() {
setupModal();
setupSilenceForm();
};
silenceFormData = function() {
var values = $("#newSilenceForm").serializeArray();
var payload = {
matchers: [],
startsAt: "",
endsAt: "",
createdBy: "",
comment: ""
};
$.each(values, function(i, elem){
switch (elem.name) {
case "comment": case "createdBy":
payload[elem.name] = elem.value;
break;
}
});
if ($("#startsAt").data('DateTimePicker')) {
payload.startsAt = $("#startsAt").data('DateTimePicker').date();
}
if ($("#endsAt").data('DateTimePicker')) {
payload.endsAt = $("#endsAt").data('DateTimePicker').date();
}
$.each($("#newSilenceForm .selectpicker"), function(i, elem) {
var label_key = $(elem).data('label-key');
var values = $(elem).selectpicker('val');
if (values && values.length > 0) {
var pval;
isRegex = false;
if (values.length > 1) {
pval = "(" + values.join("|") + ")";
isRegex = true;
} else {
pval = values[0];
}
payload.matchers.push({
name: label_key,
value: pval,
isRegex: isRegex
});
}
});
return payload;
};
silenceFormCalculateDuration = function() {
// skip if datetimepicker isn't ready yet
if (!$("#startsAt").data('DateTimePicker') || !$("#endsAt").data('DateTimePicker')) return false;
var startsAt = $("#startsAt").data('DateTimePicker').date();
var endsAt = $("#endsAt").data('DateTimePicker').date();
var totalDays = (endsAt.diff(startsAt, 'days'));
var totalHours = (endsAt.diff(startsAt, 'hours')) % 24;
var totalMinutes = endsAt.diff(startsAt, 'minutes') % 60;
$("#silence-duration-days").html(totalDays);
$("#silence-duration-hours").html(totalHours);
$("#silence-duration-minutes").html(totalMinutes);
var startsAtDesc = moment().to(startsAt);
startsAtDesc = startsAtDesc.replace("in a few seconds", "now");
startsAtDesc = startsAtDesc.replace("a few seconds ago", "now");
$("#silence-start-description").html(startsAtDesc);
var endsAtDesc = moment().to(endsAt);
endsAtDesc = endsAtDesc.replace("in a few seconds", "now");
endsAtDesc = endsAtDesc.replace("a few seconds ago", "now");
$("#silence-end-description").html(endsAtDesc);
};
silenceFormJSONRender = function() {
var d = "curl " + $("#silenceModal").data("silence-api") +
"\n -X POST --data " +
JSON.stringify(silenceFormData(), undefined, 2);
$("#silenceJSONBlob").html(d);
};
silenceFormUpdateDuration = function(event) {
// skip if datetimepicker isn't ready yet
if (!$("#startsAt").data('DateTimePicker') || !$("#endsAt").data('DateTimePicker')) return false;
var startsAt = $("#startsAt").data('DateTimePicker').date();
var endsAt = $("#endsAt").data('DateTimePicker').date();
var endsAtMinDate = $("#endsAt").data('DateTimePicker').minDate();
var action = $(event.target).data("duration-action");
var unit = $(event.target).data("duration-unit");
var step = parseInt($(event.target).data("duration-step"));
// re-calculate step for low values
// if we have 5 minute step and current duration is 1 minute than clicking
// on the increment should give us 5 minute, not 6 minute duration
var totalValue = (endsAt.diff(startsAt, unit));
switch (unit) {
case "hours":
totalValue = totalValue % 24;
break;
case "minutes":
totalValue = totalValue % 60;
break;
}
if (action == "increment") {
// if step is 5 minute and current value is 3 than set 5 minutes, not 8
if (step > 1 && totalValue < step) {
step = step - totalValue;
}
endsAt.add(step, unit);
} else {
// if step is 5 minute and current value is 3 than set 0 minutes
if (totalValue > 0 && step > 1 && totalValue < step) {
step = totalValue;
}
endsAt.subtract(step, unit);
if (endsAt < endsAtMinDate) {
// if decrement would result in a timestamp lower than allowed minimum
// then just reset it to the minimum
endsAt = endsAtMinDate;
}
}
$("#endsAt").data('DateTimePicker').date(endsAt);
silenceFormCalculateDuration();
// prevent recursive events
event.stopPropagation();
};
// modal form for creating new silences
setupSilenceForm = function() {
var modal = $("#silenceModal");
modal.on("show.bs.modal", function(event) {
Unsee.Pause();
modal.find(".modal-body").html(
Templates.Render("silenceFormLoading", {})
);
var elem = $(event.relatedTarget);
var elemLabels = {};
$.each(elem.data("labels").split(","), function(i, l) {
elemLabels[l.split("=")[0]] = l.split("=")[1];
});
$.ajax({
url: 'alerts.json?q=alertname=' + elem.data('alertname'),
error: function(xhr, textStatus, errorThrown) {
var err = xhr.responseText || errorThrown || textStatus;
modal.find(".modal-body").html(
Templates.Render("silenceFormFatal", {error: err})
);
},
success: function(data) {
var modal = $("#silenceModal");
var labels = {};
$.each(data.groups, function(i, group) {
$.each(group.alerts, function(j, alert) {
$.each(alert.labels, function(label_key, label_val) {
if (labels[label_key] === undefined) {
labels[label_key] = {};
}
if (labels[label_key][label_val] === undefined) {
labels[label_key][label_val] = {
key: label_key,
value: label_val,
attrs: Alerts.GetLabelAttrs(label_key, label_val),
selected: elemLabels[label_key] == label_val
};
}
});
});
});
modal.find(".modal-body").html(
Templates.Render("silenceForm", {labels: labels})
);
$.each($(".selectpicker"), function(i, elem) {
$(elem).selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
width: 'fit',
selectAllText: '<i class="fa fa-check-square-o"></i>',
deselectAllText: '<i class="fa fa-square-o"></i>',
noneSelectedText: '<span class="label label-list label-default">' + $(this).data('label-key') + ": </span>",
multipleSeparator: ' ',
selectedTextFormat: 'count > 1',
countSelectedText: function (numSelected, numTotal) {
return '<span class="label label-list label-warning">' +
$(elem).data('label-key') + ": " + numSelected + " values selected</span>";
}
});
});
$('.datetime-picker').datetimepicker({
format: "YYYY-MM-DD HH:mm",
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-asterisk',
clear: 'fa fa-undo',
close: 'fa fa-close'
},
minDate: moment(),
sideBySide: true,
inline: true
});
setupGroupTooltips(modal);
$('.select-label-badge').on('click', function(e) {
var select = $(this).parent().parent().find('select');
if (select.selectpicker('val')) {
// if there's anything selected deselect all
select.selectpicker('deselectAll');
} else {
// else select all
select.selectpicker('selectAll');
}
});
// set endsAt minDate to now + 1 minute
$("#endsAt").data('DateTimePicker').minDate(moment().add(1, 'minute'));
// set endsAt time to +1 hour
$("#endsAt").data('DateTimePicker').date(moment().add(1, 'hours'));
// whenever startsAt changes set it as the minDate for endsAt
// we can't have endsAt < startsAt
modal.on("dp.change", "#startsAt", function(){
if (!$("#startsAt").data('DateTimePicker')) return false;
var startsAt = $("#startsAt").data('DateTimePicker').date();
// endsAt needs to be at least 1 minute after startsAt
startsAt.add(1, "minute");
$("#endsAt").data('DateTimePicker').minDate(startsAt);
});
modal.on("click", "a.silence-duration-btn", silenceFormUpdateDuration);
modal.on("mousedown", "a.silence-duration-btn", false);
silenceFormCalculateDuration();
}
});
});
modal.on("hidden.bs.modal", function(event) {
var modal = $(this);
modal.find(".modal-body").children().remove();
Unsee.WaitForNextReload();
});
modal.on('show.bs.collapse, dp.change', function (e) {
silenceFormJSONRender();
silenceFormCalculateDuration();
});
modal.on('change', function (e) {
silenceFormJSONRender();
});
modal.submit(function(event) {
payload = silenceFormData();
if (payload.matchers.length === 0) {
var errContent = Templates.Render("silenceFormError", {error: "Select at least on label"});
$("#newSilenceAlert").html(errContent).removeClass("hidden");
return false;
}
var url = modal.data("silence-api");
$.ajax({
type: "POST",
url: url,
data: JSON.stringify(payload),
error: function(xhr, textStatus, errorThrown) {
// default to whatever error text we can get
var err = xhr.responseText || errorThrown || textStatus;
if (xhr.responseText) {
// if we have a reponse text try to decode it as JSON
// it should be error from Alertmanager (it we were able to connect)
try {
var j = JSON.parse(xhr.responseText);
if (j.error !== undefined) {
err = j.error;
}
} catch (error) {
// can't parse json, do nothing
}
}
var errContent = Templates.Render("silenceFormError", {error: err});
$("#newSilenceAlert").html(errContent).removeClass("hidden");
},
success: function(data, textStatus, xhr) {
if (data.status == "success") {
$("#newSilenceAlert").addClass("hidden");
$('#newSilenceForm').html(Templates.Render("silenceFormSuccess", {
silenceID: data.data.silenceId
}));
} else {
var err = "Invalid response from Alertmanager API: " + JSON.stringify(data);
var errContent = Templates.Render("silenceFormError", {error: err});
$("#newSilenceAlert").html(errContent).removeClass("hidden");
}
},
dataType: "json"
});
event.preventDefault();
});
};
return {
Init: init,
SetupAlertGroupUI: setupAlertGroupUI
};
})();