mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
Rewrite clientside-haml-js templates using underscore
Drop haml templates in favor of underscore. Haml templates are harder to maintain and require extra dependencies, we already have underscore.js included and it provides a fast templating engine. Rewrite all client side templates using it. Performance with underscore is pretty much the same as with haml (with 10k alerts).
This commit is contained in:
2
Makefile
2
Makefile
@@ -167,8 +167,6 @@ assets: loaders.css/0.1.2/loaders.min.css
|
||||
assets: js-cookie/2.1.3/js.cookie.min.js
|
||||
# underscore & haml, for template rendering in js
|
||||
assets: underscore.js/1.8.3/underscore-min.js
|
||||
assets: underscore.string/2.4.0/underscore.string.min.js
|
||||
assets: clientside-haml-js/5.4/haml.js
|
||||
# masonry, for grid layout
|
||||
assets: masonry/4.1.1/masonry.pkgd.min.js
|
||||
# copy to clipboard
|
||||
|
||||
@@ -11,7 +11,7 @@ var Alerts = (function() {
|
||||
}
|
||||
|
||||
Render() {
|
||||
return haml.compileHaml('groups')({
|
||||
return Templates.Render('alertGroup', {
|
||||
group: this,
|
||||
silences: silences,
|
||||
static_color_label: Colors.GetStaticLabels(),
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,6 @@
|
||||
0.1.2-loaders.css.min.js
|
||||
2.1.3-js.cookie.min.js
|
||||
1.8.3-underscore-min.js
|
||||
2.4.0-underscore.string.min.js
|
||||
5.4-haml.js
|
||||
4.1.1-masonry.pkgd.min.js
|
||||
1.5.16-clipboard.min.js
|
||||
3.9.1-raven.min.js
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
var Summary = (function() {
|
||||
|
||||
|
||||
var summary;
|
||||
var summary, templates;
|
||||
|
||||
render = function() {
|
||||
var top_tags = [];
|
||||
@@ -28,9 +28,7 @@ var Summary = (function() {
|
||||
tags.push(tag);
|
||||
});
|
||||
|
||||
return haml.compileHaml('breakdown-content')({
|
||||
tags: tags
|
||||
});
|
||||
return Templates.Render('breakdownContent', {tags: tags});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +41,7 @@ var Summary = (function() {
|
||||
placement: 'bottom',
|
||||
title: 'Top labels',
|
||||
content: render,
|
||||
template: haml.compileHaml('breakdown')()
|
||||
template: Templates.Render('breakdown', {})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
67
assets/static/templates.js
Normal file
67
assets/static/templates.js
Normal file
@@ -0,0 +1,67 @@
|
||||
var Templates = (function(params) {
|
||||
|
||||
|
||||
var templates = {},
|
||||
config = {
|
||||
// popover with the list of most common labels
|
||||
breakdown: '#breakdown',
|
||||
breakdownContent: '#breakdown-content',
|
||||
|
||||
// reload message if backend version bump is detected
|
||||
reloadNeeded: '#reload-needed',
|
||||
|
||||
// errors
|
||||
fatalError: '#fatal-error',
|
||||
internalError: '#internal-error',
|
||||
updateError: '#update-error',
|
||||
|
||||
// modal popup with label filters
|
||||
modalTitle: '#modal-title',
|
||||
modalBody: '#modal-body',
|
||||
|
||||
// label button
|
||||
buttonLabel: '#label-button-filter',
|
||||
|
||||
// alert group
|
||||
alertGroup: '#alert-group',
|
||||
alertGroupTitle: '#alert-group-title',
|
||||
alertGroupAnnotations: '#alert-group-annotations',
|
||||
alertGroupLabels: '#alert-group-labels',
|
||||
alertGroupElements: '#alert-group-elements',
|
||||
alertGroupSilence: '#alert-group-silence',
|
||||
alertGroupLabelMap: '#alert-group-label-map'
|
||||
}
|
||||
|
||||
|
||||
init = function() {
|
||||
$.each(config, function(name, selector) {
|
||||
try {
|
||||
templates[name] = _.template($(selector).html());
|
||||
} catch (err) {
|
||||
console.error('Failed to parse template ' + name + ' ' + selector);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
renderTemplate = function(name, context) {
|
||||
var t = templates[name];
|
||||
if (t == undefined) {
|
||||
console.error('Unknown template ' + name);
|
||||
return '<div class="jumbotron"><h1>Internal error: unknown template ' + name + '</h1></div>';
|
||||
}
|
||||
try {
|
||||
return t(context);
|
||||
} catch (err) {
|
||||
return '<div class="jumbotron">Failed to render template "' + name + '"<h1><p>' + err + '</p></h1></div>'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
Init: init,
|
||||
Render: renderTemplate
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -13,15 +13,15 @@ var UI = (function(params) {
|
||||
var attrs = Alerts.GetLabelAttrs(label_key, label_val);
|
||||
var counter = Summary.Get(label_key, label_val);
|
||||
modal.find('.modal-title').html(
|
||||
haml.compileHaml('modal-title')({
|
||||
attrs: attrs,
|
||||
counter: counter
|
||||
Templates.Render('modalTitle', {
|
||||
attrs: attrs,
|
||||
counter: counter
|
||||
})
|
||||
);
|
||||
var hints = Autocomplete.GenerateHints(label_key, label_val);
|
||||
modal.find('.modal-body').html(haml.compileHaml('modal-body')({
|
||||
hints: hints
|
||||
}));
|
||||
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');
|
||||
|
||||
@@ -70,7 +70,7 @@ var Unsee = (function(params) {
|
||||
Counter.Error()
|
||||
Grid.Clear();
|
||||
Grid.Hide();
|
||||
$(selectors.errors).html(haml.compileHaml(template)(context));
|
||||
$(selectors.errors).html(Templates.Render(template, context));
|
||||
$(selectors.errors).show();
|
||||
Counter.Unknown();
|
||||
Summary.Update({});
|
||||
@@ -84,7 +84,7 @@ var Unsee = (function(params) {
|
||||
if (window.console) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
renderError('internal-error', {
|
||||
renderError('internalError', {
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
raw: err
|
||||
@@ -96,7 +96,7 @@ var Unsee = (function(params) {
|
||||
|
||||
|
||||
upgrade = function() {
|
||||
renderError('reload-needed', {});
|
||||
renderError('reloadNeeded', {});
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
@@ -113,7 +113,7 @@ var Unsee = (function(params) {
|
||||
upgrade();
|
||||
} else if (resp['error']) {
|
||||
Counter.Unknown();
|
||||
renderError('update-error', {
|
||||
renderError('updateError', {
|
||||
error: 'Backend error',
|
||||
message: resp['error'],
|
||||
last_ts: Watchdog.GetLastUpdate()
|
||||
@@ -149,7 +149,7 @@ var Unsee = (function(params) {
|
||||
// if fatal error was already triggered we have error message
|
||||
// so don't add new one
|
||||
if (!Watchdog.IsFatal()) {
|
||||
renderError('update-error', {
|
||||
renderError('updateError', {
|
||||
error: 'Backend error',
|
||||
message: 'AJAX request failed',
|
||||
last_ts: Watchdog.GetLastUpdate()
|
||||
@@ -238,6 +238,7 @@ $(document).ready(function() {
|
||||
trigger: 'hover'
|
||||
});
|
||||
|
||||
Templates.Init();
|
||||
UI.Init();
|
||||
Unsee.Init();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ var Watchdog = (function() {
|
||||
|
||||
var now = moment().utc().unix();
|
||||
if (now - lastTs > maxLag) {
|
||||
$('#errors').html(haml.compileHaml('fatal-error')({
|
||||
$('#errors').html(Templates.Render('fatalError', {
|
||||
last_ts: lastTs,
|
||||
seconds_left: fatalCountdown
|
||||
})).show();
|
||||
|
||||
244
assets/templates/alertgroup.html
Normal file
244
assets/templates/alertgroup.html
Normal file
@@ -0,0 +1,244 @@
|
||||
<script type="application/json" id="alert-group-title">
|
||||
<% if (Object.keys(group.labels).length > 0) { %>
|
||||
<% var filters = [] %>
|
||||
<% _.each(group.labels, function(label_val, label_key) { filters.push(label_key + '=' + label_val) }) %>
|
||||
<% var groupLink = '?q=' + filters.join(',') %>
|
||||
<span class="pull-left alert-group-link">
|
||||
<a href="<%= groupLink %>" title="Link to this alert group", data-toggle="tooltip" data-placement="top">
|
||||
<i class="fa fa-link"/>
|
||||
</a>
|
||||
</span>
|
||||
<span class="badge pull-right">
|
||||
<%- group.alerts.length %>
|
||||
</span>
|
||||
<div class="panel-title">
|
||||
<% if (Object.keys(group.labels).length > 0) { %>
|
||||
<% _.each(Alerts.SortMapByKey(group.labels), function(label) { %>
|
||||
<% var attrs = Alerts.GetLabelAttrs(label.key, label.value) %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'div', elemClass: 'label label-list', attrs: attrs, label: label}) %>
|
||||
<% }) %>
|
||||
<% } else { %>
|
||||
<div class="label-list label"></div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group-annotations">
|
||||
<% _.each(Alerts.SortMapByKey(alert.annotations), function(annotation) { %>
|
||||
<div class="well well-sm annotation-well">
|
||||
<i class="fa fa-question-circle text-muted" title="<%= annotation.key %>" data-toggle="tooltip" data-placement="top"/>
|
||||
<% if (annotation.value) { %>
|
||||
<%- annotation.value %>
|
||||
<% } else { %>
|
||||
<span class="text-muted">
|
||||
[ missing annotation value ]
|
||||
</span>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }) %>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group-labels">
|
||||
<% _.each(Alerts.SortMapByKey(alert.labels), function(label) { %>
|
||||
<% if (group.labels[label.key] == undefined) { %>
|
||||
<% var attrs = Alerts.GetLabelAttrs(label.key, label.value) %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', attrs: attrs, label: label}) %>
|
||||
<% } %>
|
||||
<% }) %>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group-elements">
|
||||
<% var cls_indicator = 'incident-indicator-danger' %>
|
||||
<% if (alert.silenced) { cls_indicator = 'incident-indicator-success' } %>
|
||||
<div>
|
||||
<% if (alert.generatorURL) { %>
|
||||
<a class="label label-list label-default"
|
||||
href="<%= alert.generatorURL %>"
|
||||
target="_blank"
|
||||
title="Go to the alert source"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top">
|
||||
<i class="fa fa-external-link"/>
|
||||
source
|
||||
</a>
|
||||
<% } %>
|
||||
<% _.each(alert.links, function(url, text) { %>
|
||||
<a class="label label-list label-default"
|
||||
href="<%= url %>"
|
||||
target="_blank"
|
||||
title="<%= url %>"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top">
|
||||
<i class="fa fa-external-link"/>
|
||||
<%- text %>
|
||||
</a>
|
||||
<% }) %>
|
||||
<% if (alert.silenced) { %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-success', label: {key: '@silenced', value: 'true', text: '@silenced: true'}}) %>
|
||||
<% } else { %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-list label-danger', label: {key: '@silenced', value: 'false', text: '@silenced: false'}}) %>
|
||||
<% } %>
|
||||
<a class="label label-list label-default label-age label-ts"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
data-ts="<%= alert.startsAt %>">
|
||||
<span class="label-ts-span">
|
||||
<%- alert.startsAt %>
|
||||
</span>
|
||||
<div class="incident-indicator hidden <%= cls_indicator %>">
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group-silence">
|
||||
<div>
|
||||
<% var silence = silences[alert.silenced] %>
|
||||
<% if (silence) { %>
|
||||
<blockquote class="silence-comment">
|
||||
<% if (silence.jiraURL) { %>
|
||||
<a href="<%= silence.jiraURL %>" target="_blank">
|
||||
<i class="fa fa-external-link"/>
|
||||
<%- silence.comment %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<%- silence.comment %>
|
||||
<% } %>
|
||||
<footer>
|
||||
<cite>
|
||||
<abbr class="label-ts" data-toggle="tooltip" data-placement="top" data-ts="<%= silence.startsAt %>">
|
||||
<%- silence.createdBy %>
|
||||
</abbr>
|
||||
</cite>
|
||||
</footer>
|
||||
</blockquote>
|
||||
<% } %>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group-label-map">
|
||||
<% var labelArr = [] %>
|
||||
<% _.each(labelMap, function(label, text) { %>
|
||||
<% labelArr.push({text: text, key: label.key, value: label.value, hits: label.hits}) %>
|
||||
<% }) %>
|
||||
<% labelArr.sort(function(a, b) { %>
|
||||
<% if (a.hits < b.hits) return 1 %>
|
||||
<% if (a.hits > b.hits) return -1 %>
|
||||
<% if (a.text > b.text) return 1 %>
|
||||
<% if (a.text < b.text) return -1 %>
|
||||
<% return 0 %>
|
||||
<% }) %>
|
||||
|
||||
<div class="panel-body incident-group-separator">
|
||||
<div class="incident-group">
|
||||
<% var skippedLabel = '+' + skipped %>
|
||||
<% if (skipped == 1) { %>
|
||||
<% skippedLabel += " alert" %>
|
||||
<% } else { %>
|
||||
<% skippedLabel += " alerts" %>
|
||||
<% } %>
|
||||
<div class="incident-group">
|
||||
<span class="badge">
|
||||
<%- skippedLabel %>
|
||||
</span>
|
||||
<% var rendered = 0 %>
|
||||
<!--
|
||||
can't use underscore each() here as it doesn't support breaking the loop
|
||||
with 'return false', use jquery method
|
||||
-->
|
||||
<% $.each(labelArr, function(i, label) { %>
|
||||
<% if (rendered > 8 && labelArr.length > 10) { %>
|
||||
<span class="label label-list label-default">
|
||||
<% var skippedCount = labelArr.length - rendered %>
|
||||
<% if (skippedCount == 1) { %>
|
||||
+<%- skippedCount %> label
|
||||
<% } else { %>
|
||||
+<%- skippedCount %> labels
|
||||
<% } %>
|
||||
</span>
|
||||
<% return false %>
|
||||
<% } else { %>
|
||||
<% rendered++ %>
|
||||
<% var attrs = Alerts.GetLabelAttrs(label.key, label.value) %>
|
||||
<% if (label.hits > 1) { %>
|
||||
<div class="label-trim-group">
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', elemClass: 'label label-trim-tag', attrs: attrs, label: label}) %>
|
||||
<span class="label label-default label-trim-count">
|
||||
<%- label.hits %>
|
||||
</span>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<%= Templates.Render('buttonLabel', {elem: 'span', attrs: attrs, label: label}) %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="alert-group">
|
||||
<div class="incident" id="<%= group.id %>" data-hash="<%= group.hash %>">
|
||||
<% var cls_panel = 'panel-success' %>
|
||||
<% if (group.unsilencedCount > 0) { cls_panel = 'panel-danger' } %>
|
||||
<div class="panel <%= cls_panel %>">
|
||||
<div class="panel-heading text-center">
|
||||
<%= Templates.Render('alertGroupTitle', {group: group}) %>
|
||||
</div>
|
||||
<% var labelMap = {} %>
|
||||
<% var skipped = 0 %>
|
||||
<% _.each(group.alerts, function(alert, i) { %>
|
||||
<% if (i > alert_limit - 1) { %>
|
||||
<% skipped++ %>
|
||||
<% _.each(alert.labels, function(label_val, label_key) { %>
|
||||
<% var text = label_key + ': ' + label_val %>
|
||||
<% if (group.labels[label_key] == undefined) { %>
|
||||
<% if (labelMap[text] == undefined) { labelMap[text] = {key: label_key, value: label_val, hits: 0} } %>
|
||||
<% labelMap[text].hits++ %>
|
||||
<% } %>
|
||||
<% }) %>
|
||||
<% var silencedText = '@silenced: false' %>
|
||||
<% var isSilenced = 'false' %>
|
||||
<% if (alert.silenced) { %>
|
||||
<% silencedText = '@silenced: true' %>
|
||||
<% isSilenced = 'true' %>
|
||||
<% } %>
|
||||
<% if (labelMap[silencedText] == undefined) { labelMap[silencedText] = {key: '@silenced', value: isSilenced, hits: 0} } %>
|
||||
<% labelMap[silencedText].hits++ %>
|
||||
<% } else { %>
|
||||
<% var cls_body = '' %>
|
||||
<% if (i < group.alerts.length - 1) { cls_body = 'incident-group-separator' } %>
|
||||
<div class="panel-body <%= cls_body %>">
|
||||
<div class="incident-group">
|
||||
<%= Templates.Render('alertGroupAnnotations', {alert: alert}) %>
|
||||
<%= Templates.Render('alertGroupLabels', {alert: alert, group: group}) %>
|
||||
<%= Templates.Render('alertGroupElements', {alert: alert}) %>
|
||||
<% if (alert.silenced) { %>
|
||||
<%= Templates.Render('alertGroupSilence', {alert: alert, silences: silences}) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }) %>
|
||||
<% if (!$.isEmptyObject(labelMap)) { %>
|
||||
<%= Templates.Render('alertGroupLabelMap', {labelMap: labelMap, skipped: skipped}) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="label-button-filter">
|
||||
<% if (elemClass == undefined) { var elemClass = '' } %>
|
||||
<% if (attrs == undefined) { var attrs = {class: '', style: ''} } %>
|
||||
<<%= elem %> class="<%= elemClass %> <%= attrs.class %>"
|
||||
style="<%= attrs.style %>"
|
||||
type="button"
|
||||
data-label-type="filter"
|
||||
data-label-key="<%= label.key %>"
|
||||
data-label-val="<%= label.value %>"
|
||||
data-toggle="modal"
|
||||
data-target="#labelModal">
|
||||
<%- label.text %>
|
||||
</<%= elem %>>
|
||||
</script>
|
||||
@@ -1,58 +0,0 @@
|
||||
<script type="application/json" id="reload-needed">
|
||||
%div.jumbotron
|
||||
%h1.text-center
|
||||
New version detected, reloading ...
|
||||
%i.fa.fa-refresh.fa-spin
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="update-error">
|
||||
%div.jumbotron
|
||||
%h1.text-center
|
||||
=error
|
||||
%i.fa.fa-exclamation-circle.text-danger
|
||||
-if (message) {
|
||||
%div.text-center
|
||||
%p
|
||||
=message
|
||||
-if (last_ts > 0) {
|
||||
%div.text-center
|
||||
%p
|
||||
Last successful update was
|
||||
=moment.unix(last_ts).fromNow()
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="fatal-error">
|
||||
%div.jumbotron
|
||||
%h1.text-center
|
||||
Critical error
|
||||
%i.fa.fa-exclamation-circle.text-danger
|
||||
%div.text-center
|
||||
%p
|
||||
-var msg = 'Auto refresh is enabled but last update was ' + moment.unix(last_ts).fromNow();
|
||||
=msg
|
||||
%p
|
||||
This page will auto reload in
|
||||
%span#reload-counter
|
||||
=seconds_left
|
||||
seconds
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="internal-error">
|
||||
%div.jumbotron
|
||||
%h1.text-center
|
||||
Internal error
|
||||
%i.fa.fa-exclamation-circle.text-danger
|
||||
%div.text-center
|
||||
%p
|
||||
-if (name) {
|
||||
=name
|
||||
-if (message) {
|
||||
=message
|
||||
-if (!name && !message) {
|
||||
-var msg = raw.split('(');
|
||||
-if (msg.length > 0) {
|
||||
=msg[0]
|
||||
</script>
|
||||
78
assets/templates/errors.html
Normal file
78
assets/templates/errors.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<script type="application/json" id="reload-needed">
|
||||
<div class="jumbotron">
|
||||
<h1 class="text-center">
|
||||
New version detected, reloading ...
|
||||
<i class="fa fa-refresh fa-spin"/>
|
||||
</h1>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="update-error">
|
||||
<div class="jumbotron">
|
||||
<h1 class="text-center">
|
||||
<%- error %>
|
||||
<% if (message) { %>
|
||||
<div class="text-center">
|
||||
<p>
|
||||
<%- message %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (last_ts > 0) { %>
|
||||
<div class="text-center">
|
||||
<p>
|
||||
Last successful update was <%= moment.unix(last_ts).fromNow() %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</h1>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="fatal-error">
|
||||
<div class="jumbotron">
|
||||
<h1 class="text-center">
|
||||
Critical error
|
||||
<i class="fa fa-exclamation-circle text-danger"/>
|
||||
</h1>
|
||||
<div class="text-center">
|
||||
<p>
|
||||
Auto refresh is enabled but last update was <%= moment.unix(last_ts).fromNow() %>
|
||||
</p>
|
||||
<p>
|
||||
This page will auto reload in
|
||||
<span id='reload-counter'>
|
||||
<%- seconds_left %> seconds
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="application/json" id="internal-error">
|
||||
<div class="jumbotron">
|
||||
<h1 class="text-center">
|
||||
Internal error
|
||||
<i class="fa fa-exclamation-circle text-danger"/>
|
||||
</h1>
|
||||
<div class="text-center">
|
||||
<p>
|
||||
<% if (name) { %>
|
||||
<%- name %>
|
||||
<% } %>
|
||||
<% if (message) { %>
|
||||
<%- message %>
|
||||
<% } %>
|
||||
<% if (!name && !message) { %>
|
||||
<% var msg = raw.split('(') %>
|
||||
<% if (msg.length > 0) { %>
|
||||
<%- msg[0] %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
@@ -1,164 +0,0 @@
|
||||
<script type="application/json" id="groups">
|
||||
%div.incident{id: group.id, 'data-hash': group.hash}
|
||||
-var cls_panel = 'panel-success';
|
||||
-if (group.unsilencedCount > 0) {
|
||||
-cls_panel = 'panel-danger';
|
||||
|
||||
%div.panel{class: cls_panel}
|
||||
%div.panel-heading.text-center
|
||||
-if (Object.keys(group.labels).length > 0) {
|
||||
-var filters = [];
|
||||
-$.each(group.labels, function(label_key, label_val) {
|
||||
-filters.push(label_key + '=' + label_val);
|
||||
-var groupLink = '?q=' + filters.join(',');
|
||||
%span.pull-left.alert-group-link
|
||||
%a{href: groupLink, title: 'Link to this alert group', 'data-toggle': 'tooltip', 'data-placement': 'top'}
|
||||
%i.fa.fa-link
|
||||
%span.badge.pull-right
|
||||
=group.alerts.length
|
||||
%div.panel-title
|
||||
-if (Object.keys(group.labels).length > 0) {
|
||||
-$.each(Alerts.SortMapByKey(group.labels), function(i, label) {
|
||||
-var attrs = Alerts.GetLabelAttrs(label.key, label.value);
|
||||
%div.label-list.label{class: attrs.class, style: attrs.style, 'data-label-type': "filter", 'data-label-key': label.key, 'data-label-val': label.value, type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
=label.text
|
||||
-else {
|
||||
%div.label-list.label
|
||||
|
||||
-var labelMap = {};
|
||||
-var skipped = 0;
|
||||
|
||||
-$.each(group.alerts, function(i, alert) {
|
||||
|
||||
-if (i > alert_limit - 1) {
|
||||
-skipped++;
|
||||
-$.each(alert.labels, function(label_key, label_val) {
|
||||
-var text = label_key + ': ' + label_val;
|
||||
-if (group.labels[label_key] == undefined) {
|
||||
-if (labelMap[text] == undefined) {
|
||||
-labelMap[text] = {key: label_key, value: label_val, hits: 0};
|
||||
-labelMap[text].hits++;
|
||||
|
||||
-var silencedText = '@silenced: false';
|
||||
-var isSilenced = 'false';
|
||||
-if (alert.silenced) {
|
||||
-silencedText = '@silenced: true';
|
||||
-isSilenced = 'true';
|
||||
-if (labelMap[silencedText] == undefined) {
|
||||
-labelMap[silencedText] = {key: '@silenced', value: isSilenced, hits: 0};
|
||||
-labelMap[silencedText].hits++;
|
||||
|
||||
-else {
|
||||
-var cls_body = '';
|
||||
-var cls_age = '';
|
||||
|
||||
-if (i < group.alerts.length - 1) {
|
||||
-cls_body = 'incident-group-separator';
|
||||
|
||||
%div.panel-body{class: cls_body}
|
||||
%div.incident-group
|
||||
-var now = moment();
|
||||
|
||||
-$.each(Alerts.SortMapByKey(alert.annotations), function(i, annotation) {
|
||||
%div.well.well-sm.annotation-well
|
||||
%i.fa.fa-question-circle.text-muted{title: annotation.key, 'data-toggle': 'tooltip', 'data-placement': 'top'}
|
||||
-if (annotation.value == '') {
|
||||
%span.text-muted
|
||||
[ missing annotation value ]
|
||||
-else {
|
||||
=annotation.value
|
||||
|
||||
-$.each(Alerts.SortMapByKey(alert.labels), function(i, label) {
|
||||
-if (group.labels[label.key] == undefined) {
|
||||
-var attrs = Alerts.GetLabelAttrs(label.key, label.value);
|
||||
%span{class: attrs.class, style: attrs.style, 'data-label-type': "filter", 'data-label-key': label.key, 'data-label-val': label.value, type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
=label.text
|
||||
|
||||
-var cls_age = '';
|
||||
-var cls_body = '';
|
||||
|
||||
-var cls_indicator = 'incident-indicator-danger';
|
||||
-if (alert.silenced) {
|
||||
-cls_indicator = 'incident-indicator-success';
|
||||
|
||||
%div
|
||||
-if (alert.generatorURL) {
|
||||
%a.label.label-list.label-default{href: alert.generatorURL, target: '_blank', title: "Go to the alert source", 'data-toggle': 'tooltip', 'data-placement': 'top'}
|
||||
%i.fa.fa-external-link
|
||||
source
|
||||
-$.each(alert.links, function(k, url) {
|
||||
%a.label.label-list.label-default{href: url, target: '_blank', title: url, 'data-toggle': 'tooltip', 'data-placement': 'top'}
|
||||
%i.fa.fa-external-link
|
||||
=k
|
||||
-if (alert.silenced) {
|
||||
%span.label.label-list.label-success{'data-label-type': "filter", 'data-label-key': "@silenced", 'data-label-val': "true", type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
@silenced: true
|
||||
-else {
|
||||
%span.label.label-list.label-danger{'data-label-type': "filter", 'data-label-key': "@silenced", 'data-label-val': "false", type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
@silenced: false
|
||||
%a.label.label-list.label-default.label-age.label-ts{'data-toggle': 'tooltip', 'data-placement': 'top', 'data-ts': alert.startsAt}
|
||||
%span.label-ts-span
|
||||
=alert.startsAt
|
||||
%div.incident-indicator.hidden{class: cls_indicator}
|
||||
|
||||
-if (alert.silenced) {
|
||||
%div
|
||||
-var silence = silences[alert.silenced];
|
||||
-if(silence) {
|
||||
%blockquote.silence-comment
|
||||
-if (silence.jiraURL) {
|
||||
%a{target: '_blank', href: silence.jiraURL}
|
||||
%i.fa.fa-external-link
|
||||
=silence.comment
|
||||
-else {
|
||||
=silence.comment
|
||||
%footer
|
||||
%cite
|
||||
%abbr{'data-toggle': 'tooltip', 'data-placement': 'bottom', 'data-ts': silence.startsAt}
|
||||
=silence.createdBy
|
||||
|
||||
-if (!$.isEmptyObject(labelMap)) {
|
||||
-labelArr = [];
|
||||
-$.each(labelMap, function(text, label){
|
||||
-labelArr.push({text: text, key: label.key, value: label.value, hits: label.hits});
|
||||
-labelArr.sort(function(a, b) {
|
||||
-if (a.hits < b.hits) return 1;
|
||||
-if (a.hits > b.hits) return -1;
|
||||
-if (a.text > b.text) return 1;
|
||||
-if (a.text < b.text) return -1;
|
||||
-return 0;
|
||||
%div.panel-body.incident-group-separator
|
||||
%div.incident-group
|
||||
-var skippedLabel = '+' + skipped;
|
||||
-if (skipped == 1) {
|
||||
-skippedLabel += " alert";
|
||||
-else {
|
||||
-skippedLabel += " alerts";
|
||||
%span.badge
|
||||
=skippedLabel
|
||||
-var rendered = 0;
|
||||
-$.each(labelArr, function(i, label) {
|
||||
-if (rendered > 8 && labelArr.length > 10) {
|
||||
%span.label.label-list.label-default
|
||||
-var skippedCount = labelArr.length - rendered;
|
||||
-var text = "+" + skippedCount;
|
||||
-if (skippedCount == 1) {
|
||||
-text += ' label';
|
||||
-else {
|
||||
-text += ' labels';
|
||||
=text
|
||||
-return false;
|
||||
-} else {
|
||||
-rendered++;
|
||||
-var attrs = Alerts.GetLabelAttrs(label.key, label.value);
|
||||
-if (label.hits > 1) {
|
||||
%div.label-trim-group
|
||||
%span.label.label-trim-tag{class: attrs.class, style: attrs.style, 'data-label-type': "filter", 'data-label-key': label.key, 'data-label-val': label.value, type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
=label.text
|
||||
%span.label.label-default.label-trim-count
|
||||
=label.hits
|
||||
-else {
|
||||
%span{class: attrs.class, style: attrs.style, 'data-label-type': "filter", 'data-label-key': label.key, 'data-label-val': label.value, type: 'button', 'data-toggle': 'modal', 'data-target': '#labelModal'}
|
||||
=label.text
|
||||
|
||||
</script>
|
||||
@@ -163,7 +163,7 @@
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{ template "templates/groups.haml" }}
|
||||
{{ template "templates/summary.haml" }}
|
||||
{{ template "templates/errors.haml" }}
|
||||
{{ template "templates/modal.haml" }}
|
||||
{{ template "templates/alertgroup.html" }}
|
||||
{{ template "templates/summary.html" }}
|
||||
{{ template "templates/errors.html" }}
|
||||
{{ template "templates/modal.html" }}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/grid.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/progress.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/summary.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/templates.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/watchdog.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/querystring.js?_={{ .NowQ }}"></script>
|
||||
<script type="text/javascript" src="{{ .WebPrefix }}static/ui.js?_={{ .NowQ }}"></script>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<script type="application/json" id="modal-title">
|
||||
%button.close{type: "button", "data-dismiss": "modal"}
|
||||
%i.fa.fa-close
|
||||
%div.label-list.label{class: attrs.class, style: attrs.style}
|
||||
=attrs.text
|
||||
%span.badge.badge-primary
|
||||
=counter
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="modal-body">
|
||||
%table.table.table-striped
|
||||
%caption.text-center
|
||||
Quick filters
|
||||
%tbody
|
||||
-$.each(hints, function(i, hint){
|
||||
%tr
|
||||
%td.modal-row-filters
|
||||
=hint
|
||||
%td.modal-row-actions
|
||||
%button.btn.btn-sm.btn-primary.pull-right.modal-button-filter{type: "button", 'data-filter-append-value': hint}
|
||||
Apply
|
||||
</script>
|
||||
33
assets/templates/modal.html
Normal file
33
assets/templates/modal.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<script type="application/json" id="modal-title">
|
||||
<button class="close" type="button" data-dismiss="modal">
|
||||
<i class="fa fa-close"/>
|
||||
</button>
|
||||
<div class="label-list label <%= attrs.class %>" style="<%= attrs.style %>">
|
||||
<%- attrs.text %>
|
||||
</div>
|
||||
<span class="badge badge-primary">
|
||||
<%- counter %>
|
||||
</span>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="modal-body">
|
||||
<table class="table table-striped">
|
||||
<caption class="text-center">
|
||||
Quick filters
|
||||
</caption>
|
||||
<tbody>
|
||||
<% _.each(hints, function(hint) { %>
|
||||
<tr>
|
||||
<td class="modal-row-filters">
|
||||
<%- hint %>
|
||||
</td>
|
||||
<td class="modal-row-actions">
|
||||
<button class="btn btn-sm btn-primary pull-right modal-button-filter" type="button" data-filter-append-value="<%= hint %>">
|
||||
Apply
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<% }) %>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script type="application/json" id="breakdown">
|
||||
%div.popover
|
||||
%div.arrow
|
||||
%h1.popover-title.text-center
|
||||
%div.popover-content{id: 'breakdown-content'}
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="breakdown-content">
|
||||
-if (tags.length > 0) {
|
||||
%table.table
|
||||
-$.each(tags, function(i, tag) {
|
||||
%tr
|
||||
%td
|
||||
%span.label{class: tag.cls, style: tag.style}
|
||||
=tag.name
|
||||
%td{align: 'center'}
|
||||
%span.breakdown-badge
|
||||
=tag.val
|
||||
-else {
|
||||
%p.text-muted
|
||||
Empty
|
||||
</script>
|
||||
30
assets/templates/summary.html
Normal file
30
assets/templates/summary.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<script type="application/json" id="breakdown">
|
||||
<div class="popover">
|
||||
<div class="arrow"></div>
|
||||
<h1 class="popover-title text-center"></h1>
|
||||
<div class="popover-content" id="breakdown-content"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="application/json" id="breakdown-content">
|
||||
<% if(tags.length > 0) { %>
|
||||
<table class="table">
|
||||
<% _.each(tags, function(tag) { %>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="label <%= tag.cls %>" style="<%= tag.style %>">
|
||||
<%- tag.name %>
|
||||
</span>
|
||||
</td>
|
||||
<td align="center">
|
||||
<span class="breakdown-badge">
|
||||
<%- tag.val %>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<% }) %>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p class="text-muted">Empty</p>
|
||||
<% } %>
|
||||
</script>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user