mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user