Allow selecting multiple label values when creating silence.

This commit turns label checkboxes into multiselect dropdowns that are populated from all labels of all instances of current alert (matched by alertname label). There is also a cocollapsible preview box that allows user to inspect raw silence JSON object we will send to the Alertmanager API. Fixes #69
This commit is contained in:
Łukasz Mierzwa
2017-04-23 09:02:27 -07:00
parent 9a5419efb1
commit 087458b767
12 changed files with 274 additions and 69 deletions
+3
View File
@@ -166,6 +166,9 @@ assets: bootstrap-tagsinput/0.8.0/bootstrap-tagsinput-typeahead.css
# datepicker widget for bootstrap3
assets: bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js
assets: bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css
# multiselect widget for bootstrap3, used for silence form
assets: bootstrap-select/1.12.2/js/bootstrap-select.min.js
assets: bootstrap-select/1.12.2/css/bootstrap-select.min.css
# loaders.css, for animated spinners
assets: loaders.css/0.1.2/loaders.css.min.js
assets: loaders.css/0.1.2/loaders.min.css
+26
View File
@@ -388,3 +388,29 @@ span.alert-group-link > a {
.silence-result-icon {
font-size: 12em;
}
.silence-label-select {
background-color: inherit;
}
.bootstrap-select > button {
padding: 0;
}
.silence-label-select > .bs-caret {
margin-left: -6px;
}
.silence-label-select:hover, .silence-label-picker:hover,
.silence-label-select:active, .silence-label-picker:active,
.silence-label-select:focus, .silence-label-picker:focus {
color: inherit;
}
.bootstrap-select > .dropdown-menu > .dropdown-menu > li > a > .label {
margin-right: 20px;
}
a[aria-expanded=true] .fa-chevron-right {
display: none;
}
a[aria-expanded=false] .fa-chevron-down {
display: none;
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -5,4 +5,5 @@
0.8.0-bootstrap-tagsinput.css
0.8.0-bootstrap-tagsinput-typeahead.css
4.17.47-bootstrap-datetimepicker.min.css
1.12.2-bootstrap-select.min.css
0.1.2-loaders.min.css
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@
0.8.0-bootstrap-tagsinput.min.js
1.1.1-typeahead.bundle.min.js
4.17.47-bootstrap-datetimepicker.min.js
1.12.2-bootstrap-select.min.js
0.1.2-loaders.css.min.js
2.1.3-js.cookie.min.js
1.8.3-underscore-min.js
File diff suppressed because one or more lines are too long
+1
View File
@@ -23,6 +23,7 @@ var Templates = (function(params) {
silenceForm: '#silence-form',
silenceFormSuccess: '#silence-form-success',
silenceFormError: '#silence-form-error',
silenceFormFatal: '#silence-form-fatal',
// label button
buttonLabel: '#label-button-filter',
+115 -53
View File
@@ -82,41 +82,123 @@ var UI = (function(params) {
}
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;
case "startsAt": case "endsAt":
payload[elem.name] = moment(elem.value);
break;
}
});
$.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;
}
silenceFormJSONRender = function() {
var d = "curl " + $("#silenceModal").data("silence-api")
+ "\n -X POST --data "
+ JSON.stringify(silenceFormData(), undefined, 2);
$("#silenceJSONBlob").html(d);
}
// modal form for creating new silences
setupSilenceForm = function() {
var modal = $("#silenceModal");
modal.on("show.bs.modal", function(event) {
Unsee.Pause();
var modal = $(this);
var elem = $(event.relatedTarget);
var labels = [];
var elemLabels = {};
$.each(elem.data("labels").split(","), function(i, l) {
labels.push({
key: l.split("=")[0],
value: l.split("=")[1],
attrs: Alerts.GetLabelAttrs(l.split("=")[0], l.split("=")[1])
});
elemLabels[l.split("=")[0]] = l.split("=")[1];
});
modal.find(".modal-body").html(
Templates.Render("silenceForm", {
labels: labels
})
);
$('.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'
$.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})
);
},
minDate: moment().subtract(1, 'minutes'),
sideBySide: true
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})
);
$(".selectpicker").selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
width: 'fit',
actionsBox: true,
selectAllText: 'All',
deselectAllText: 'None',
noneSelectedText: 'No label selected',
multipleSeparator: ' ',
selectedTextFormat: 'count > 2'
});
$('.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().subtract(1, 'minutes'),
sideBySide: true
});
}
});
});
@@ -125,34 +207,14 @@ var UI = (function(params) {
modal.find(".modal-body").children().remove();
Unsee.WaitForNextReload();
});
modal.on('show.bs.collapse, dp.change', function (e) {
silenceFormJSONRender();
});
modal.on('change', function (e) {
silenceFormJSONRender();
});
modal.submit(function(event) {
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;
case "startsAt": case "endsAt":
payload[elem.name] = moment(elem.value);
break;
default:
if (elem.value == "on") {
payload["matchers"].push({
name: elem.name.split("=")[0],
value: elem.name.split("=")[1],
isRegex: false
})
}
}
});
payload = silenceFormData();
if (payload["matchers"].length == 0) {
var errContent = Templates.Render("silenceFormError", {error: "Select at least on label"});
$("#newSilenceAlert").html(errContent).removeClass("hidden");
+1
View File
@@ -90,6 +90,7 @@
<span class="label label-list label-success cursor-pointer"
type="button"
data-labels="<%= labels.join(',') %>"
data-alertname="<%= alert.labels.alertname %>"
data-toggle="modal"
data-target="#silenceModal">
<i class="fa fa-bell-slash" title="Silence this alert" data-toggle="tooltip" data-placement="top" />
+34 -9
View File
@@ -2,17 +2,20 @@
<div id="newSilenceAlert" class="alert alert-danger hidden" role="alert"></div>
<form id="newSilenceForm">
<label class="control-label">Labels to match</label>
<% _.each(labels, function(label) { %>
<% _.each(labels, function(label_values, label_key) { %>
<div>
<label>
<input type="checkbox"
checked="checked"
name="<%= label.key %>=<%= label.value %>">
<span class="<%= label.attrs.class %>" style="<%= label.attrs.style %>">
<%- label.attrs.text %>
</span>
</label>
</div>
<select class="selectpicker silence-label-picker" data-label-key="<%= label_key %>" data-style="silence-label-select" multiple>
<% _.each(label_values, function(label, label_val) { %>
<option <% if (label.selected) { %>selected="selected"<% } %>
value="<%= label.value %>"
data-content="<span class='<%= label.attrs.class %>' style='<%= label.attrs.style %>'><%- label.attrs.text %></span>">
<%- label.attrs.text %>
</option>
<% }) %>
</select>
</label>
</div>
<% }) %>
<hr class="separator">
@@ -80,6 +83,16 @@
<hr class="separator">
<a class="text-muted" data-toggle="collapse" href="#silenceJSON" aria-expanded="false" aria-controls="silenceJSON">
<i class="fa fa-chevron-right"></i>
<i class="fa fa-chevron-down"></i>
</a>
<div class="collapse" id="silenceJSON">
<p class="text-muted">
</p>
<pre id="silenceJSONBlob"></pre>
</div>
<div class="text-center">
<button id="silenceSubmit" type="submit" class="btn btn-default">Create</button>
</div>
@@ -95,6 +108,18 @@
</p>
</script>
<script type="application/json" id="silence-form-fatal">
<div class="silence-result-icon text-center text-danger">
<i class="fa fa-exclamation-circle"></i>
</div>
<p class="text-center">
New silence form rendering failed.
</p>
<p class="text-center">
<%- error %>
</p>
</script>
<script type="application/json" id="silence-form-error">
<i class="fa fa-exclamation-circle"></i>
<%- error %>
+76 -7
View File
File diff suppressed because one or more lines are too long