Lot's of JS changes

This commit is contained in:
Thomas Gelf 2014-03-04 13:08:29 +00:00
parent 9531dbb869
commit 9a485df81a
8 changed files with 1624 additions and 1215 deletions

View File

@ -9,9 +9,11 @@
* });
* </code>
*/
(function() {
(function(window, $) {
var Icinga = function(config) {
'use strict';
var Icinga = function (config) {
/**
* Our config object
@ -43,6 +45,11 @@
*/
this.timer = null;
/**
* Icinga.History
*/
this.history = null;
/**
* Icinga.Utils
*/
@ -54,7 +61,7 @@
this.modules = {};
var self = this;
$(document).ready(function() {
$(document).ready(function () {
self.initialize();
self = null;
});
@ -65,9 +72,7 @@
/**
* Icinga startup, will be triggerd once the document is ready
*/
initialize: function()
{
$('html').removeClass('no-js').addClass('js');
initialize: function () {
this.utils = new Icinga.Utils(this);
this.logger = new Icinga.Logger(this);
@ -75,100 +80,44 @@
this.ui = new Icinga.UI(this);
this.loader = new Icinga.Loader(this);
this.events = new Icinga.Events(this);
this.history = new Icinga.History(this);
this.timer.initialize();
this.events.initialize();
this.history.initialize();
this.ui.initialize();
this.loader.initialize();
this.logger.setLevel('info');
this.logger.info('Icinga is ready');
this.timer.register(this.refreshTimeSince, this, 1000);
},
toggleFullscreen: function()
{
$('#layout').toggleClass('fullscreen');
this.ui.fixControls();
},
flipContent: function()
{
var col1 = $('#col1 > div').detach();
var col2 = $('#col2 > div').detach();
$('#col2').html('');
$('#col1').html('');
col1.appendTo('#col2');
col2.appendTo('#col1');
this.ui.fixControls();
},
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');
}
});
},
getWindowId: function()
{
var res = window.name.match(/^Icinga_([a-zA-Z0-9])$/);
if (res) {
return res[1];
}
return null;
},
hasWindowId: function()
{
var res = window.name.match(/^Icinga_([a-zA-Z0-9])$/);
return typeof res === 'object';
},
setWindowId: function(id)
{
window.name = 'Icinga_' + id;
},
/**
* Load a given module by name
*/
loadModule: function(name)
{
loadModule: function (name) {
if (this.hasModule(name)) {
this.logger.error('Cannot load module ' + name + ' twice');
return;
}
this.modules[name] = new Icinga.Module(this, name);
},
/**
* Whether a module matching the given name exists
*/
hasModule: function(name)
{
return typeof this.modules[name] !== 'undefined' ||
typeof Icinga.availableModules[name] !== 'undefined';
hasModule: function (name) {
return 'undefined' !== typeof this.modules[name] ||
'undefined' !== typeof Icinga.availableModules[name];
},
/**
* Get a module by name
*/
module: function(name)
{
if (typeof this.modules[name] === 'undefined') {
if (typeof Icinga.availableModules[name] !== 'undefined') {
module: function (name) {
if ('undefined' === typeof this.modules[name]) {
if ('undefined' !== typeof Icinga.availableModules[name]) {
this.modules[name] = new Icinga.Module(
this,
name,
@ -176,17 +125,19 @@
);
}
}
return this.modules[name];
},
/**
* Clean up and unload all Icinga components
*/
destroy: function()
{
$.each(this.modules, function(name, module) {
destroy: function () {
$.each(this.modules, function (name, module) {
module.destroy();
});
this.timer.destroy();
this.events.destroy();
this.loader.destroy();
@ -196,7 +147,8 @@
this.utils.destroy();
this.modules = [];
this.timer = this.events = this.loader = this.ui = this.logger = this.utils = null;
this.timer = this.events = this.loader = this.ui = this.logger =
this.utils = null;
}
};
@ -204,5 +156,4 @@
Icinga.availableModules = {};
})(window);
})(window, window.jQuery);

View File

@ -1,6 +1,13 @@
(function(Icinga) {
/**
* Icinga.Events
*
* Event handlers
*/
(function (Icinga, $) {
Icinga.Events = function(icinga) {
'use strict';
Icinga.Events = function (icinga) {
this.icinga = icinga;
};
@ -9,21 +16,23 @@
/**
* Icinga will call our initialize() function once it's ready
*/
initialize: function()
{
initialize: function () {
this.applyGlobalDefaults();
this.applyHandlers($('#layout'));
this.icinga.ui.prepareContainers();
},
// TODO: What's this?
applyHandlers: function(el)
{
applyHandlers: function (el) {
var icinga = this.icinga;
$('.dashboard > div', el).each(function(idx, el) {
var url = $(el).attr('data-icinga-url');
if (typeof url === 'undefined') return;
icinga.loader.loadUrl(url, $(el)).autorefresh = true;
});
// Set first links href in a action table tr as row href:
$('table.action tr', el).each(function(idx, el) {
var $a = $('a[href]', el).first();
@ -31,8 +40,9 @@
$(el).attr('href', $a.attr('href'));
}
});
$('.icinga-module', el).each(function(idx, mod) {
$mod = $(mod);
var $mod = $(mod);
var moduleName = $mod.data('icinga-module');
if (icinga.hasModule(moduleName)) {
var module = icinga.module(moduleName);
@ -40,6 +50,8 @@
}
});
$('input.autofocus', el).focus();
$('.inlinepie', el).sparkline('html', {
type: 'pie',
sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'],
@ -48,13 +60,13 @@
});
},
/**
* Global default event handlers
*/
applyGlobalDefaults: function()
{
applyGlobalDefaults: function () {
// We catch resize events
$(window).on('resize', { self: this }, this.onWindowResize);
$(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize);
// Destroy Icinga, clean up and interrupt pending requests on unload
$( window ).on('unload', { self: this }, this.onUnload);
@ -75,7 +87,7 @@
// We support an 'autosubmit' class on dropdown form elements
$(document).on('change', 'form select.autosubmit', { self: this }, this.submitForm);
$(window).on('popstate', { self: this }, this.historyChanged);
$(document).on('keyup', '#menu input.search', {self: this}, this.submitForm);
// TBD: a global autocompletion handler
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
@ -83,50 +95,24 @@
// $(document).on('change', 'form.auto select', this.submitForm);
},
onUnload: function(event)
{
onUnload: function (event) {
var icinga = event.data.self.icinga;
icinga.logger.info('Unloading Icinga');
icinga.destroy();
},
historyChanged: function(event)
{
var icinga = event.data.self.icinga;
if (event.originalEvent.state === null) {
icinga.logger.debug('No more history steps available');
} else {
icinga.logger.debug(event.originalEvent.state);
}
icinga.loader.loadUrl(
document.location.pathname + document.location.search,
$('#col1')
).historyTriggered = true;
},
/**
* Our window got resized, let's fix our UI
*/
onWindowResize: function(event)
{
var icinga = event.data.self.icinga;
icinga.ui.fixControls();
},
/**
* A scroll event happened in one of our containers
*/
onContainerScroll: function(event)
{
// Yet ugly. And PLEASE, not so often
onContainerScroll: function (event) {
// Ugly. And PLEASE, not so often
icinga.ui.fixControls();
},
/**
*
*/
submitForm: function (event)
{
submitForm: function (event) {
var icinga = event.data.self.icinga;
event.stopPropagation();
event.preventDefault();
@ -142,38 +128,65 @@
icinga.logger.debug('Submitting form: ' + method + ' ' + url);
// We should move this to a generic target-finder:
var $target = $form.closest('.container');
if ($target.length == 0) {
$target = $('#body');
var $target = null;
if ($form.closest('[data-base-target]').length) {
$target = $(
'#' + $form.closest('[data-base-target]').data('baseTarget')
);
} else if ($form.closest('.container').length) {
$target = $form.closest('.container');
} else {
icinga.logger.error('No form target found, stopping here');
return false;
}
icinga.loader.loadUrl(url, $target, data, method);
// TODO: Do we really need to return false with stop/preventDefault?
return false;
},
layout1col: function () {
if (! $('#layout').hasClass('twocols')) { return; }
var $col2 = $('#col2');
icinga.logger.debug('Switching to single col');
$('#layout').removeClass('twocols');
$col2.removeAttr('data-icinga-url');
$col2.removeAttr('data-icinga-refresh');
$col2.removeData('icingaUrl');
$col2.removeData('icingaRefresh');
this.icinga.loader.stopPendingRequestsFor($col2);
$col2.html('');
this.icinga.ui.fixControls();
},
layout2col: function () {
if ($('#layout').hasClass('twocols')) { return; }
icinga.logger.debug('Switching to double col');
$('#layout').addClass('twocols');
this.icinga.ui.fixControls();
},
/**
* Someone clicked a link or tr[href]
*/
linkClicked: function(event)
{
linkClicked: function (event) {
var icinga = event.data.self.icinga;
var $a = $(this);
var href = $a.attr('href');
var $li;
if ($a.attr('target') === '_blank') {
return true;
}
event.stopPropagation();
event.preventDefault();
// If link is hash tag...
if (href === '#') {
if ($a.closest('#menu')) {
var $li = $a.closest('li');
$li.siblings('li.active').removeClass('active');
$li = $a.closest('li');
$('#menu .active').removeClass('active');
$li.addClass('active');
}
return;
@ -183,19 +196,17 @@
if ($container.length) {
$target = $container;
}
// If link is hash tag...
if ($a.closest('table').length) {
$target = $('#col2');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
icinga.events.layout2col();
}
if ($a.closest('[data-base-target]').length) {
$target = $('#' + $a.closest('[data-base-target]').data('baseTarget'));
$('#layout').addClass('twocols');
icinga.ui.fixControls();
icinga.events.layout2col();
}
if ($a.closest('.tree').length) {
var $li = $a.closest('li');
$li = $a.closest('li');
if ($li.find('li').length) {
if ($li.hasClass('collapsed')) {
$li.removeClass('collapsed');
@ -206,46 +217,41 @@
return false;
} else {
$target = $('#col2');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
icinga.events.layout2col();
}
}
icinga.loader.loadUrl(href, $target);
event.stopPropagation();
event.preventDefault();
if ($a.closest('#menu').length) {
$('#layout').removeClass('twocols');
$('#col2').html('<ul class="tabs"></ul>');
icinga.ui.fixControls();
icinga.events.layout1col();
return false;
}
if ($a.closest('table').length) {
if ($a.closest('table.action').length) {
if ($('#layout').hasClass('twocols')) {
if ($target.attr('id') === 'col2') return;
icinga.logger.debug('Switching to single col');
$('#layout').removeClass('twocols');
icinga.ui.fixControls();
if ($target.attr('id') === 'col2') {
return;
}
icinga.events.layout1col();
} else {
icinga.logger.debug('Switching to double col');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
icinga.events.layout2col();
}
return false;
}
},
/*
hrefIsHashtag: function(href)
{
/*
hrefIsHashtag: function(href) {
// WARNING: IE gives full URL :(
// Also it doesn't support negativ indexes in substr
return href.substr(href.length - 1, 1) == '#';
},
*/
*/
unbindGlobalHandlers: function()
{
$(window).off('popstate', this.historyChanged);
unbindGlobalHandlers: function () {
$(window).off('resize', this.onWindowResize);
$(window).off('unload', this.onUnload);
$(window).off('beforeunload', this.onUnload);
@ -263,4 +269,4 @@
}
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -3,9 +3,11 @@
*
* This is where we take care of XHR requests, responses and failures.
*/
(function(Icinga) {
(function(Icinga, $) {
Icinga.Loader = function(icinga) {
'use strict';
Icinga.Loader = function (icinga) {
/**
* YES, we need Icinga
@ -31,9 +33,8 @@
Icinga.Loader.prototype = {
initialize: function()
{
this.icinga.timer.register(this.autorefresh, this, 10000);
initialize: function () {
this.icinga.timer.register(this.autorefresh, this, 500);
},
/**
@ -44,12 +45,11 @@
* @param {object} data Optional parameters, usually for POST requests
* @param {string} method HTTP method, default is 'GET'
*/
loadUrl: function (url, $target, data, method)
{
loadUrl: function (url, $target, data, method) {
var id = null;
// Default method is GET
if (typeof method === 'undefined') {
if ('undefined' === typeof method) {
method = 'GET';
}
@ -59,6 +59,7 @@
if (typeof $target !== 'undefined' && $target.attr('id')) {
id = $target.attr('id');
}
if (typeof $target !== 'undefined') {
// TODO: We shouldn't use data but keep this information somewhere else.
if ($target.data('icingaUrl') !== url) {
@ -77,7 +78,13 @@
return this.requests[id];
}
// ...or abort the former request otherwise
this.icinga.logger.debug('Aborting pending request loading ', url, ' to ', $target);
this.icinga.logger.debug(
'Aborting pending request loading ',
url,
' to ',
$target
);
this.requests[id].abort();
}
@ -85,8 +92,8 @@
var headers = { 'X-Icinga-Accept': 'text/html' };
// Ask for a new window id in case we don't already have one
if (this.icinga.hasWindowId()) {
headers['X-Icinga-WindowId'] = this.icinga.getWindowId();
if (this.icinga.ui.hasWindowId()) {
headers['X-Icinga-WindowId'] = this.icinga.ui.getWindowId();
} else {
headers['X-Icinga-WindowId'] = 'undefined';
}
@ -104,11 +111,13 @@
req.url = url;
req.done(this.onResponse);
req.fail(this.onFailure);
req.complete(this.onComplete);
req.historyTriggered = false;
req.autorefresh = false;
if (id) {
this.requests[id] = req;
}
this.icinga.ui.refreshDebug();
return req;
},
@ -117,43 +126,86 @@
*
* @param {string} url Relative url
*/
url: function(url)
{
url: function (url) {
if (typeof url === 'undefined') {
return this.baseUrl;
}
return this.baseUrl + url;
},
autorefresh: function()
{
stopPendingRequestsFor: function ($el) {
var id;
if (typeof $el !== 'undefined' || ! (id = $el.attr('id'))) {
return;
}
if (id in this.requests) {
this.requests[id].abort();
}
},
autorefresh: function () {
var self = this;
if (self.autorefreshEnabled !== true) {
return;
}
$('.container[data-icinga-refresh]').each(function(idx, el) {
$('.container[data-icinga-refresh]').each(function (idx, el) {
var $el = $(el);
var id = $el.attr('id');
if (id in self.requests) {
self.icinga.logger.debug('No refresh, request pending', id);
return;
}
var interval = $el.data('icingaRefresh');
var lastUpdate = $el.data('lastUpdate');
if (typeof interval === 'undefined' || ! interval) {
self.icinga.logger.info('No interval, setting default', id);
interval = 10;
}
if (typeof lastUpdate === 'undefined' || ! lastUpdate) {
self.icinga.logger.info('No lastUpdate, setting one', id);
$el.data('lastUpdate',(new Date()).getTime());
return;
}
interval = interval * 1000;
// TODO:
if ((lastUpdate + interval) > (new Date()).getTime()) {
// self.icinga.logger.info(
// 'Skipping refresh',
// id,
// lastUpdate,
// interval,
// (new Date()).getTime()
// );
return;
}
self.icinga.logger.info(
'Autorefreshing ' + id + ' ' + interval + ' ms passed'
);
self.loadUrl($el.data('icingaUrl'), $el).autorefresh = true;
el = null;
});
},
disableAutorefresh: function()
{
disableAutorefresh: function () {
this.autorefreshEnabled = false;
},
enableAutorefresh: function()
{
enableAutorefresh: function () {
this.autorefreshEnabled = true;
},
/**
* Handle successful XHR response
*/
onResponse: function (data, textStatus, req)
{
onResponse: function (data, textStatus, req) {
var self = this;
if (this.failureNotice !== null) {
this.failureNotice.remove();
this.failureNotice = null;
@ -166,51 +218,76 @@
}
var url = req.url;
var targetId = req.$target.attr('id');
this.icinga.logger.debug('Got response for ', req.$target, ', URL was ' + url);
this.icinga.logger.debug(
'Got response for ', req.$target, ', URL was ' + url
);
var $resp = $(req.responseText);
var active = false;
if (! req.autorefresh) {
// TODO: Hook for response/url?
var $matches = $('[href="' + url + '"]');
$matches.each(function(idx, el) {
var $forms = $('[action="' + url + '"]');
var $matches = $.merge($('[href="' + url + '"]'), $forms);
$matches.each(function (idx, el) {
if ($(el).closest('#menu').length) {
$(el).closest('#menu').find('li.active').removeClass('active');
$('#menu .active').removeClass('active');
} else if ($(el).closest('table.action').length) {
$(el).closest('table.action').find('.active').removeClass('active');
}
});
$matches.each(function(idx, el) {
if ($(el).closest('#menu').length) {
$(el).closest('li').addClass('active');
$(el).parents('li').addClass('active');
$matches.each(function (idx, el) {
var $el = $(el);
if ($el.closest('#menu').length) {
if ($el.is('form')) {
$('input', $el).addClass('active');
} else {
$el.closest('li').addClass('active');
$el.parents('li').addClass('active');
}
} else if ($(el).closest('table.action').length) {
$(el).addClass('active');
$el.addClass('active');
}
});
} else {
// TODO: next container url
active = $('[href].active', req.$target).attr('href');
}
delete this.requests[targetId];
req.$target.attr('icingaurl', this.url);
req.$target.attr('data-icinga-url', url);
//
var target = req.getResponseHeader('X-Icinga-Container');
var newBody = false;
if (target) {
req.$target = $('body');
req.$target = $('#' + target);
newBody = true;
}
var title = req.getResponseHeader('X-Icinga-Title');
if (title && req.$target.closest('.dashboard').length === 0) {
this.icinga.ui.setTitle(title);
}
var refresh = req.getResponseHeader('X-Icinga-Refresh');
if (refresh) {
// Hmmmm... .data() doesn't work here?
req.$target.attr('data-icinga-refresh', refresh);
req.$target.attr('data-icinga-url', req.url);
req.$target.attr('data-last-update', (new Date()).getTime());
req.$target.data('lastUpdate', (new Date()).getTime());
req.$target.data('icingaRefresh', refresh);
} else {
req.$target.removeAttr('data-icinga-refresh');
req.$target.removeAttr('data-last-update');
req.$target.removeData('icingaRefresh');
req.$target.removeData('lastUpdate');
}
// Set a window identifier if the server asks us to do so
var windowId = req.getResponseHeader('X-Icinga-WindowId');
if (windowId) {
this.icinga.setWindowId(windowId);
this.icinga.ui.setWindowId(windowId);
}
// Update history when necessary. Don't do so for requests triggered
@ -219,11 +296,43 @@
// We only want to care about top-level containers
if (req.$target.parent().closest('.container').length === 0) {
this.icinga.history.pushCurrentState();
/*
this.icinga.logger.debug('Pushing ', req.url, ' to history');
if (typeof window.history.pushState !== 'undefined') {
window.history.pushState({icinga: true}, null, req.url);
}
*/
}
$resp = $(req.responseText);
}
// Handle search requests, still hardcoded
if (req.url === '/search' &&
req.$target.data('icingaUrl') === '/search')
{
// TODO: We need dashboard pane and container identifiers (not ids)
var targets = [];
$('.dashboard .container').each(function (idx, el) {
targets.push($(el));
});
var i = 0;
$('.dashboard .container', $resp).each(function (idx, el) {
var $el = $(el);
var url = $el.data('icingaUrl');
targets[i].data('icingaUrl', url);
var title = $('h1', $el).first();
$('h1', targets[i]).first().replaceWith(title);
self.loadUrl(url, targets[i]);
i++;
});
return;
}
req.$target.attr('data-icinga-url', req.url);
req.$target.data('icingaUrl', req.url);
/* Should we try to fiddle with responses containing full HTML? */
/*
@ -231,17 +340,41 @@
req.responseText = $('script', $('body', $resp).html()).remove();
}
*/
/*
this.renderContentToContainer(req.responseText, req.$target);
var containers = [];
$('.dashboard .container').each(function(idx, el) {
urls.push($(el).data('icingaUrl'));
});
console.log(urls);
$('.container[data-icinga-refresh]').each(function(idx, el) {
var $el = $(el);
self.loadUrl($el.data('icingaUrl'), $el).autorefresh = true;
el = null;
});
*/
this.renderContentToContainer($resp, req.$target);
if (newBody) {
this.icinga.ui.fixDebugVisibility().triggerWindowResize();
}
if (active) {
$('[href="' + active + '"]', req.$target).addClass('active');
}
},
onComplete: function (req, textStatus) {
delete this.requests[req.$target.attr('id')];
this.icinga.ui.refreshDebug();
},
/**
* Handle failed XHR response
*/
onFailure: function (req, textStatus, errorThrown)
{
onFailure: function (req, textStatus, errorThrown) {
var url = req.url;
delete this.requests[req.$target.attr('id')];
if (req.status === 500) {
if (this.exception === null) {
@ -250,10 +383,6 @@
this.exception = this.createNotice(
'error',
$('h1', $(req.responseText)).first().html()
/* 'The connection to the Icinga web server has been lost at ' +
this.icinga.utils.timeShort() +
'.'
*/
);
this.icinga.ui.fixControls();
}
@ -269,7 +398,10 @@
// Icinga.debug(req.getResponseHeader('X-Icinga-Redirect'));
} else {
if (errorThrown === 'abort') {
this.icinga.logger.info('Request to ', url, ' has been aborted for ', req.$target);
this.icinga.logger.info(
'Request to ' + url + ' has been aborted for ',
req.$target
);
} else {
if (this.failureNotice === null) {
this.failureNotice = this.createNotice(
@ -281,6 +413,7 @@
this.icinga.ui.fixControls();
}
this.icinga.logger.error(
'Failed to contact web server loading ',
url,
@ -291,26 +424,56 @@
}
},
createNotice: function(severity, message) {
return $('<li class="' + severity + '">' + message + '</li>').appendTo($('#notifications'));
createNotice: function (severity, message) {
return $(
'<li class="' + severity + '">' + message + '</li>'
).appendTo($('#notifications'));
},
/**
* Smoothly render given HTML to given container
*/
renderContentToContainer: function (content, $container)
{
renderContentToContainer: function (content, $container) {
// Disable all click events while rendering
$('*').click(function( event ) {
$('*').click(function (event) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
});
// Container update happens here
var scrollPos = $container.scrollTop();
var scrollPos = false;
var containerId = $container.attr('id');
if (typeof containerId !== 'undefined') {
scrollPos = $container.scrollTop();
}
var origFocus = document.activeElement;
var $content = $(content);
if (false &&
$('.dashboard', $content).length > 0 &&
$('.dashboard', $container).length === 0
) {
// $('.dashboard', $content)
// $container.html(content);
} else {
if ($container.closest('.dashboard').length &&
! $('h1', $content).length
) {
var title = $('h1', $container).first().detach();
$('h1', $content).first().detach();
$container.html(title).append(content);
} else {
$container.html(content);
}
}
if (scrollPos !== false) {
$container.scrollTop(scrollPos);
}
if (origFocus) {
origFocus.focus();
}
// TODO: this.icinga.events.refreshContainer(container);
var icinga = this.icinga;
@ -335,4 +498,4 @@
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -1,8 +1,14 @@
(function(Icinga) {
/**
* Icinga.Logger
*
* Well, log output. Rocket science.
*/
(function (Icinga) {
Icinga.Logger = function(icinga) {
'use strict';
Icinga.Logger = function (icinga) {
// Well... we don't really need Icinga right now
this.icinga = icinga;
this.logLevel = 'info';
@ -14,80 +20,95 @@
'error': 3
};
// Let's get started
this.initialize();
};
Icinga.Logger.prototype = {
/**
* Logger initialization
* Whether the browser has a console object
*/
initialize: function()
{
hasConsole: function () {
return 'undefined' !== typeof console;
},
/**
* Whether the browser has a console object
* Raise or lower current log level
*
* Messages blow this threshold will be silently discarded
*/
hasConsole: function()
{
return typeof console !== 'undefined';
},
debug: function(msg)
{
this.writeToConsole('debug', arguments);
},
setLevel: function(level)
{
if (this.numericLevel(level) !== 'undefined') {
setLevel: function (level) {
if ('undefined' !== typeof this.numericLevel(level)) {
this.logLevel = level;
}
return this;
},
info: function()
{
this.writeToConsole('info', arguments);
/**
* Log a debug message
*/
debug: function () {
return this.writeToConsole('debug', arguments);
},
warn: function()
{
this.writeToConsole('warn', arguments);
/**
* Log an informational message
*/
info: function () {
return this.writeToConsole('info', arguments);
},
error: function()
{
this.writeToConsole('error', arguments);
/**
* Log a warning message
*/
warn: function () {
return this.writeToConsole('warn', arguments);
},
writeToConsole: function(level, args) {
/**
* Log an error message
*/
error: function () {
return this.writeToConsole('error', arguments);
},
/**
* Write a log message with the given level to the console
*/
writeToConsole: function (level, args) {
args = Array.prototype.slice.call(args);
// We want our log messages to carry precise timestamps
args.unshift(this.icinga.utils.timeWithMs());
if (this.hasConsole() && this.hasLogLevel(level)) {
console[level].apply(console, args);
}
return this;
},
numericLevel: function(level)
{
/**
* Return the numeric identifier fot a given log level
*/
numericLevel: function (level) {
var ret = this.logLevels[level];
if (typeof ret === 'undefined') {
if ('undefined' === typeof ret) {
throw 'Got invalid log level ' + level;
}
return ret;
},
hasLogLevel: function(level)
{
/**
* Whether a given log level exists
*/
hasLogLevel: function (level) {
return this.numericLevel(level) >= this.numericLevel(this.logLevel);
},
/**
* There isn't much to clean up here
*/
destroy: function() {
destroy: function () {
this.enabled = false;
this.icinga = null;
}

View File

@ -1,9 +1,11 @@
/**
* This is how we bootstrap JS code in our modules
*/
(function(Icinga) {
(function(Icinga, $) {
Icinga.Module = function(icinga, name, prototyp) {
'use strict';
Icinga.Module = function (icinga, name, prototyp) {
// The Icinga instance
this.icinga = icinga;
@ -28,36 +30,45 @@
Icinga.Module.prototype = {
initialize: function()
{
initialize: function () {
try {
// The constructor of the modules prototype must be prepared to get an
// instance of Icinga.Module
this.object = new this.prototyp(this);
this.applyRegisteredEventHandlers();
} catch(e) {
this.icinga.logger.error('Failed to load module ', this.name, ': ', e);
this.icinga.logger.error(
'Failed to load module ' + this.name + ': ',
e
);
return false;
}
// That's all, the module is ready
this.icinga.logger.debug('Module ' + this.name + ' has been initialized');
this.icinga.logger.debug(
'Module ' + this.name + ' has been initialized'
);
return true;
},
/**
* Globally register this modules event handlers
*/
registerEventHandlers: function(handlers)
{
registerEventHandlers: function (handlers) {
this.registeredHandlers = handlers;
return this;
},
applyRegisteredEventHandlers: function()
{
applyRegisteredEventHandlers: function () {
var self = this;
$.each(this.registeredHandlers, function(filter, events) {
$.each(this.registeredHandlers, function (filter, events) {
$.each(events, function (event, handler) {
// TODO: if (event[1] === 'each') {
// $(event[0], $(el)).each(event[2]);
@ -69,14 +80,14 @@
});
});
self = null;
return this;
},
/**
* Effectively bind the given event handler
*/
bindEventHandler: function(event, filter, handler)
{
bindEventHandler: function (event, filter, handler) {
var self = this;
this.icinga.logger.debug('Bound ' + filter + ' .' + event + '()');
this.handlers.push([event, filter, handler]);
@ -86,9 +97,8 @@
/**
* Unbind all event handlers bound by this module
*/
unbindEventHandlers: function()
{
$.each(this.handlers, function(idx, handler) {
unbindEventHandlers: function () {
$.each(this.handlers, function (idx, handler) {
$(document).off(handler[0], handler[1], handler[2]);
});
},
@ -96,12 +106,14 @@
/**
* Allow to destroy and clean up this module
*/
destroy: function()
{
destroy: function () {
this.unbindEventHandlers();
if (typeof this.object.destroy === 'function') {
this.object.destroy();
}
this.object = null;
this.icinga = null;
this.prototyp = null;
@ -109,4 +121,4 @@
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -4,9 +4,11 @@
* Timer events are triggered once a second. Runs all reegistered callback
* functions and is able to preserve a desired scope.
*/
(function(Icinga) {
(function(Icinga, $) {
Icinga.Timer = function(icinga) {
'use strict';
Icinga.Timer = function (icinga) {
/**
* We keep a reference to the Icinga instance even if we don't need it
@ -44,20 +46,20 @@
/**
* The initialization function starts our ticker
*/
initialize: function(icinga)
{
initialize: function () {
var self = this;
this.ticker = setInterval(function() { self.tick(); }, this.interval);
this.ticker = setInterval(function () { self.tick(); }, this.interval);
},
/**
* We will trigger our tick function once a second. It will call each
* registered observer.
*/
tick: function()
{
tick: function () {
var icinga = this.icinga;
$.each(this.observers, function(idx, observer) {
$.each(this.observers, function (idx, observer) {
if (observer.isDue()) {
observer.run();
} else {
@ -70,43 +72,61 @@
/**
* Register a given callback function to be run within an optional scope.
*/
register: function(callback, scope, interval)
{
register: function (callback, scope, interval) {
var observer;
try {
if (typeof scope === 'undefined') {
this.observers.push(new Icinga.Timer.Interval(callback, interval));
observer = new Icinga.Timer.Interval(callback, interval);
} else {
this.observers.push(
new Icinga.Timer.Interval(
observer = new Icinga.Timer.Interval(
callback.bind(scope),
interval
)
);
}
this.observers.push(observer);
} catch(err) {
this.icinga.logger.error(err);
}
return observer;
},
unregister: function (observer) {
var idx = $.inArray(observer, this.observers);
if (idx > -1) {
this.observers.splice(idx, 1);
}
return this;
},
/**
* Our destroy function will clean up everything. Unused right now.
*/
destroy: function()
{
destroy: function () {
if (this.ticker !== null) {
clearInterval(this.ticker);
}
this.icinga = null;
$.each(this.observers, function(idx, observer) {
$.each(this.observers, function (idx, observer) {
observer.destroy();
});
this.observers = [];
}
};
Icinga.Timer.Interval = function(callback, interval) {
Icinga.Timer.Interval = function (callback, interval) {
if (typeof interval === 'undefined') {
if ('undefined' === typeof interval) {
throw 'Timer interval is required';
}
@ -124,24 +144,24 @@
};
Icinga.Timer.Interval.prototype = {
isDue: function()
{
isDue: function () {
return this.scheduledNextRun < (new Date()).getTime();
},
run: function()
{
run: function () {
this.lastRun = (new Date()).getTime();
while (this.scheduledNextRun < this.lastRun) {
this.scheduledNextRun += this.interval;
}
this.callback();
},
destroy: function()
{
destroy: function () {
this.callback = null;
}
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -1,77 +1,259 @@
(function(Icinga) {
/**
* Icinga.UI
*
* Our user interface
*/
(function(Icinga, $) {
'use strict';
Icinga.UI = function (icinga) {
Icinga.UI = function(icinga) {
this.icinga = icinga;
this.currentLayout = 'default';
this.debug = false;
this.debugTimer = null;
};
Icinga.UI.prototype = {
initialize: function()
{
this.icinga.timer.register(this.refreshDebug, this, 1000);
this.refreshDebug();
initialize: function () {
$('html').removeClass('no-js').addClass('js');
this.triggerWindowResize();
},
prepareContainers: function ()
{
enableDebug: function () {
this.debug = true;
this.debugTimer = this.icinga.timer.register(
this.refreshDebug,
this,
1000
);
this.icinga.timer.register(this.refreshTimeSince, 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;
},
flipContent: function () {
var col1 = $('#col1 > div').detach();
var col2 = $('#col2 > div').detach();
$('#col2').html('');
$('#col1').html('');
col1.appendTo('#col2');
col2.appendTo('#col1');
this.fixControls();
},
triggerWindowResize: function () {
this.onWindowResize({data: {self: this}});
},
/**
* Our window got resized, let's fix our UI
*/
onWindowResize: function (event) {
var self = event.data.self;
self.fixControls();
if (self.layoutHasBeenChanged()) {
self.icinga.logger.info(
'Layout change detected, switching to',
self.currentLayout
);
}
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').attr('class', layout);
this.currentLayout = matched[1];
return true;
}
}
this.icinga.logger.error(
'Someone messed up our responsiveness hacks, html font-family is',
layout
);
return false;
},
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
);
*/
*/
},
refreshDebug: function()
{
var size = this.icinga.ui.getDefaultFontSize().toString();
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 = '<br />Loading:<br />';
}
loading += el + ' => ' + req.url;
});
$('#responsive-debug').html(
'Time: ' +
this.icinga.ui.formatHHiiss(new Date) +
'<br />&nbsp;1em: ' +
' Time: ' +
this.icinga.utils.formatHHiiss(new Date()) +
'<br /> 1em: ' +
size +
'px<br />&nbsp;Win: ' +
'px<br /> Win: ' +
winWidth +
'x'+
winHeight +
'px<br />'
).css({display: 'block'});
'px<br />' +
' Layout: ' +
this.currentLayout +
loading
);
},
formatHHiiss: function(date)
{
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if (hours < 10) hours = '0' + hours;
if (minutes < 10) minutes = '0' + minutes;
if (seconds < 10) seconds = '0' + seconds;
return hours + ':' + minutes + ':' + seconds;
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]);
if (nm >= 0) {
if (ns > 0) {
ns--;
} else {
ns = 59;
nm--;
}
} else {
if (ns < 59) {
ns++;
} else {
ns = 0;
nm--;
}
}
$(el).html(nm + 'm ' + ns + 's');
}
});
},
createFontSizeCalculator: function()
{
createFontSizeCalculator: function () {
var $el = $('<div id="fontsize-calc">&nbsp;</div>');
$('#main').append($el);
$('#layout').append($el);
return $el;
},
getDefaultFontSize: function()
{
getDefaultFontSize: function () {
var $calc = $('#fontsize-calc');
if (! $calc.length) {
$calc = this.createFontSizeCalculator();
}
return $calc.width() / 1000;
},
initializeControls: function(parent)
{
initializeControls: function (parent) {
var self = this;
$('.controls', parent).each(function(idx, el) {
$('.controls', parent).each(function (idx, el) {
var $el = $(el);
if (! $el.next('.fake-controls').length) {
var newdiv = $('<div class="fake-controls"></div>');
newdiv.css({
height: $el.css('height')
@ -79,39 +261,75 @@
$el.after(newdiv);
}
});
this.fixControls(parent);
},
fixControls: function($parent)
{
fixControls: function ($parent) {
var self = this;
if (typeof $parent === 'undefined') {
$('.container').each(function(idx, container) {
if ('undefined' === typeof $parent) {
$('#header').css({height: 'auto'});
$('#main').css({top: $('#header').css('height')});
$('#sidebar').css({top: $('#header').height() + 'px'});
$('#header').css({height: $('#header').height() + 'px'});
$('#inner-layout').css({top: $('#header').css('height')});
$('.container').each(function (idx, container) {
self.fixControls($(container));
});
return;
}
self.icinga.logger.debug('Fixing controls for ', $parent);
$('.controls', $parent).each(function(idx, el) {
$('.controls', $parent).each(function (idx, el) {
var $el = $(el);
var $fake = $el.next('.fake-controls');
var y = $parent.scrollTop();
$el.css({
position: 'fixed',
top: $parent.offset().top,
width: $fake.css('width')
position : 'fixed',
top : $parent.offset().top,
width : $fake.css('width')
});
$fake.css({
height: $el.css('height'),
display: 'block'
height : $el.css('height'),
display : 'block'
});
});
},
destroy: function() {
toggleFullscreen: function () {
$('#layout').toggleClass('fullscreen-layout');
this.fixControls();
},
getWindowId: function () {
var res = window.name.match(/^Icinga_([a-zA-Z0-9])$/);
if (res) {
return res[1];
}
return null;
},
hasWindowId: function () {
var res = window.name.match(/^Icinga_([a-zA-Z0-9])$/);
return typeof res === 'object';
},
setWindowId: function (id) {
window.name = 'Icinga_' + id;
},
destroy: function () {
// This is gonna be hard, clean up the mess
this.icinga = null;
}
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -3,7 +3,9 @@
*/
(function(Icinga) {
Icinga.Utils = function(icinga) {
'use strict';
Icinga.Utils = function (icinga) {
/**
* Utility functions may need access to their Icinga instance
@ -13,40 +15,54 @@
/**
* We will use this to create an URL helper only once
*/
this.url_helper = null;
this.urlHelper = null;
};
Icinga.Utils.prototype = {
timeWithMs: function(now)
{
timeWithMs: function (now) {
if (typeof now === 'undefined') {
now = new Date();
}
var ms = now.getMilliseconds() + '';
while (ms.length < 3) {
ms = '0' + ms;
}
return now.toLocaleTimeString() + '.' + ms;
},
timeShort: function(now)
{
timeShort: function (now) {
if (typeof now === 'undefined') {
now = new Date();
}
return now.toLocaleTimeString().replace(/:\d{2}$/, '');
},
formatHHiiss: function (date) {
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if (hours < 10) hours = '0' + hours;
if (minutes < 10) minutes = '0' + minutes;
if (seconds < 10) seconds = '0' + seconds;
return hours + ':' + minutes + ':' + seconds;
},
/**
* Parse a given Url and return an object
*/
parseUrl: function(url)
{
if (this.url_helper === null) {
this.url_helper = document.createElement('a');
parseUrl: function (url) {
if (this.urlHelper === null) {
this.urlHelper = document.createElement('a');
}
var a = this.url_helper;
var a = this.urlHelper;
a.href = url;
var result = {
@ -70,14 +86,17 @@
/**
* Parse url params
*/
parseParams: function(a) {
parseParams: function (a) {
var params = {},
segment = a.search.replace(/^\?/,'').split('&'),
len = segment.length,
i = 0,
s;
for (; i < len; i++) {
if (! segment[i]) { continue; }
if (!segment[i]) {
continue;
}
s = segment[i].split('=');
params[s[0]] = decodeURIComponent(s[1]);
}
@ -87,9 +106,8 @@
/**
* Cleanup
*/
destroy: function()
{
this.url_helper = null;
destroy: function () {
this.urlHelper = null;
this.icinga = null;
}
};