/**
* Icinga.UI
*
* Our user interface
*/
(function(Icinga, $) {
'use strict';
Icinga.UI = function (icinga) {
this.icinga = icinga;
this.currentLayout = 'default';
this.debug = false;
this.debugTimer = null;
this.timeCounterTimer = null;
};
Icinga.UI.prototype = {
initialize: function () {
$('html').removeClass('no-js').addClass('js');
this.enableTimeCounters();
this.triggerWindowResize();
this.fadeNotificationsAway();
},
fadeNotificationsAway: function()
{
var icinga = this.icinga;
$('#notifications li')
.not('.fading-out')
.not('.persist')
.addClass('fading-out')
.delay(7000)
.fadeOut('slow',
function() {
icinga.ui.fixControls();
$(this).remove();
});
},
enableDebug: function () {
this.debug = true;
this.debugTimer = this.icinga.timer.register(
this.refreshDebug,
this,
1000
);
this.fixDebugVisibility();
return this;
},
fixDebugVisibility: function () {
if (this.debug) {
$('#responsive-debug').css({display: 'block'});
} else {
$('#responsive-debug').css({display: 'none'});
}
return this;
},
disableDebug: function () {
if (this.debug === false) { return; }
this.debug = false;
this.icinga.timer.unregister(this.debugTimer);
this.debugTimer = null;
this.fixDebugVisibility();
return this;
},
reloadCss: function () {
var icinga = this.icinga;
$('link').each(function() {
var $oldLink = $(this);
if ($oldLink.attr('type').indexOf('css') > -1) {
var $newLink = $oldLink.clone().attr(
'href',
icinga.utils.addUrlParams(
$(this).attr('href'),
{ id: new Date().getTime() }
)
).on('load', function() {
icinga.ui.fixControls();
$oldLink.remove();
});
$newLink.appendTo($('head'));
}
});
},
enableTimeCounters: function () {
this.timeCounterTimer = this.icinga.timer.register(
this.refreshTimeSince,
this,
1000
);
return this;
},
disableTimeCounters: function () {
this.icinga.timer.unregister(this.timeCounterTimer);
this.timeCounterTimer = null;
return this;
},
moveToLeft: function () {
var col2 = this.cutContainer($('#col2'));
var kill = this.cutContainer($('#col1'));
this.pasteContainer($('#col1'), col2);
this.fixControls();
},
cutContainer: function ($col) {
var props = {
'elements': $('#' + $col.attr('id') + ' > div').detach(),
'data': {
'data-icinga-url': $col.data('icingaUrl'),
'data-icinga-refresh': $col.data('icingaRefresh'),
'data-last-update': $col.data('lastUpdate'),
'data-icinga-module': $col.data('icingaModule')
},
'class': $col.attr('class')
};
this.icinga.loader.stopPendingRequestsFor($col);
$col.removeData('icingaUrl');
$col.removeData('icingaRefresh');
$col.removeData('lastUpdate');
$col.removeData('icingaModule');
$col.removeAttr('class').attr('class', 'container');
return props;
},
pasteContainer: function ($col, backup) {
backup['elements'].appendTo($col);
$col.attr('class', backup['class']); // TODO: ie memleak? remove first?
$col.data('icingaUrl', backup['data']['data-icinga-url']);
$col.data('icingaRefresh', backup['data']['data-icinga-refresh']);
$col.data('lastUpdate', backup['data']['data-last-update']);
$col.data('icingaModule', backup['data']['data-icinga-module']);
},
scrollContainerToAnchor: function ($container, anchorName) {
// TODO: Generic issue -> we probably should escape attribute value selectors!?
var $anchor = $("a[name='" + anchorName.replace(/'/, '\\\'') + "']", $container);
if ($anchor.length) {
$container.scrollTop(0);
$container.scrollTop($anchor.first().position().top);
this.icinga.logger.debug('Scrolling ', $container, ' to ', anchorName);
} else {
this.icinga.logger.info('Anchor "' + anchorName + '" not found in ', $container);
}
},
triggerWindowResize: function () {
this.onWindowResize({data: {self: this}});
},
/**
* Our window got resized, let's fix our UI
*/
onWindowResize: function (event) {
var self = event.data.self;
if (self.layoutHasBeenChanged()) {
self.icinga.logger.info(
'Layout change detected, switching to',
self.currentLayout
);
}
self.fixControls();
self.refreshDebug();
},
layoutHasBeenChanged: function () {
var layout = $('html').css('fontFamily').replace(/['",]/g, '');
var matched;
if (null !== (matched = layout.match(/^([a-z]+)-layout$/))) {
if (matched[1] === this.currentLayout &&
$('#layout').hasClass(layout)
) {
return false;
} else {
$('#layout').removeClass(this.currentLayout + '-layout').addClass(layout);
this.currentLayout = matched[1];
if (this.currentLayout === 'poor' || this.currentLayout === 'minimal') {
this.layout1col();
}
return true;
}
}
this.icinga.logger.error(
'Someone messed up our responsiveness hacks, html font-family is',
layout
);
return false;
},
layout1col: function () {
if (! $('#layout').hasClass('twocols')) { return; }
var $col2 = $('#col2');
icinga.logger.debug('Switching to single col');
$('#layout').removeClass('twocols');
$col2.removeData('icingaUrl');
$col2.removeData('icingaRefresh');
$col2.removeData('lastUpdate');
$col2.removeData('icingaModule');
this.icinga.loader.stopPendingRequestsFor($col2);
$col2.html('');
this.fixControls();
},
layout2col: function () {
if ($('#layout').hasClass('twocols')) { return; }
icinga.logger.debug('Switching to double col');
$('#layout').addClass('twocols');
this.fixControls();
},
getAvailableColumnSpace: function () {
return $('#main').width() / this.getDefaultFontSize();
},
setColumnCount: function (count) {
if (count === 3) {
$('#main > .container').css({
width: '33.33333%'
});
} else if (count === 2) {
$('#main > .container').css({
width: '50%'
});
} else {
$('#main > .container').css({
width: '100%'
});
}
},
setTitle: function (title) {
document.title = title;
return this;
},
getColumnCount: function () {
return $('#main > .container').length;
},
prepareContainers: function () {
var icinga = this.icinga;
$('.container').each(function(idx, el) {
icinga.events.applyHandlers($(el));
icinga.ui.initializeControls($(el));
});
/*
$('#icinga-main').attr(
'icingaurl',
window.location.pathname + window.location.search
);
*/
},
/**
* Prepare all multiselectable tables for multi-selection by
* removing the regular text selection.
*/
prepareMultiselectTables: function () {
var $rows = $('table.multiselect tr[href]');
$rows.find('td').attr('unselectable', 'on')
.css('user-select', 'none')
.css('-webkit-user-select', 'none')
.css('-moz-user-select', 'none')
.css('-ms-user-select', 'none');
},
/**
* Add the given table-row to the selection of the closest
* table and deselect all other rows of the closest table.
*
* @param $tr {jQuery} The selected table row.
* @returns {boolean} If the selection was changed.
*/
setTableRowSelection: function ($tr) {
var $table = $tr.closest('table.multiselect');
if ($tr.hasClass('active')) {
return false;
}
$table.find('tr[href].active').removeClass('active');
$tr.addClass('active');
return true;
},
/**
* Toggle the given table row to "on" when not selected, or to "off" when
* currently selected.
*
* @param $tr {jQuery} The table row.
* @returns {boolean} If the selection was changed.
*/
toogleTableRowSelection: function ($tr) {
// multi selection
if ($tr.hasClass('active')) {
$tr.removeClass('active');
} else {
$tr.addClass('active');
}
return true;
},
/**
* Add a new selection range to the closest table, using the selected row as
* range target.
*
* @param $tr {jQuery} The target of the selected range.
* @returns {boolean} If the selection was changed.
*/
addTableRowRangeSelection: function ($tr) {
var $table = $tr.closest('table.multiselect');
var $rows = $table.find('tr[href]'),
from, to;
var selected = $tr.first().get(0);
$rows.each(function(i, el) {
if ($(el).hasClass('active') || el === selected) {
if (!from) {
from = el;
}
to = el;
}
});
var inRange = false;
$rows.each(function(i, el){
if (el === from) {
inRange = true;
}
if (inRange) {
$(el).addClass('active');
}
if (el === to) {
inRange = false;
}
});
return false;
},
/**
* Focus the given table by deselecting all selections on all other tables.
*
* Focusing a table is important for environments with multiple tables like
* the dashboard. It should only be possible to select rows at one table at a time,
* when a user selects a row on a table all rows that are not child of the given table
* will be removed from the selection.
*
* @param table {htmlElement} The table to focus.
*/
focusTable: function (table) {
$('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active');
},
refreshDebug: function () {
var size = this.getDefaultFontSize().toString();
var winWidth = $( window ).width();
var winHeight = $( window ).height();
var loading = '';
$.each(this.icinga.loader.requests, function (el, req) {
if (loading === '') {
loading = '
Loading:
';
}
loading += el + ' => ' + req.url;
});
$('#responsive-debug').html(
' Time: ' +
this.icinga.utils.formatHHiiss(new Date()) +
'
1em: ' +
size +
'px
Win: ' +
winWidth +
'x'+
winHeight +
'px
' +
' Layout: ' +
this.currentLayout +
loading
);
},
refreshTimeSince: function () {
$('.timesince').each(function (idx, el) {
var m = el.innerHTML.match(/^(-?\d+)m\s(-?\d+)s/);
if (m !== null) {
var nm = parseInt(m[1]);
var ns = parseInt(m[2]);
if (ns < 59) {
ns++;
} else {
ns = 0;
nm++;
}
$(el).html(nm + 'm ' + ns + 's');
}
});
$('.timeunless').each(function (idx, el) {
var m = el.innerHTML.match(/^(-?\d+)m\s(-?\d+)s/);
if (m !== null) {
var nm = parseInt(m[1]);
var ns = parseInt(m[2]);
var signed = '';
var sec = 0;
if (nm < 0) {
signed = '-';
nm = nm * -1;
sec = nm * 60 + ns;
sec++;
} else if (nm == 0 && ns == 0) {
signed = '-';
sec = 1;
} else if (nm == 0 && m[1][0] == '-') {
signed = '-';
sec = ns;
sec++;
} else if (nm == 0 && m[1][0] != '-') {
sec = ns;
sec--;
} else {
signed = '';
sec = nm * 60 + ns;
sec--;
}
nm = Math.floor(sec/60);
ns = sec - nm * 60;
$(el).html(signed + nm + 'm ' + ns + 's');
}
});
},
createFontSizeCalculator: function () {
var $el = $('