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,200 +9,151 @@
* });
* </code>
*/
(function() {
(function(window, $) {
var Icinga = function(config) {
'use strict';
/**
* Our config object
*/
this.config = config;
var Icinga = function (config) {
/**
* Icinga.Logger
*/
this.logger = null;
/**
* Our config object
*/
this.config = config;
/**
* Icinga.UI
*/
this.ui = null;
/**
* Icinga.Logger
*/
this.logger = null;
/**
* Icinga.Loader
*/
this.loader = null;
/**
* Icinga.UI
*/
this.ui = null;
/**
* Icinga.Events
*/
this.events = null;
/**
* Icinga.Loader
*/
this.loader = null;
/**
* Icinga.Timer
*/
this.timer = null;
/**
* Icinga.Events
*/
this.events = null;
/**
* Icinga.Utils
*/
this.utils = null;
/**
* Icinga.Timer
*/
this.timer = null;
/**
* Loaded modules
*/
this.modules = {};
/**
* Icinga.History
*/
this.history = null;
var self = this;
$(document).ready(function() {
self.initialize();
self = null;
});
};
/**
* Icinga.Utils
*/
this.utils = null;
Icinga.prototype = {
/**
* Loaded modules
*/
this.modules = {};
/**
* Icinga startup, will be triggerd once the document is ready
*/
initialize: function()
{
$('html').removeClass('no-js').addClass('js');
var self = this;
$(document).ready(function () {
self.initialize();
self = null;
});
};
this.utils = new Icinga.Utils(this);
this.logger = new Icinga.Logger(this);
this.timer = new Icinga.Timer(this);
this.ui = new Icinga.UI(this);
this.loader = new Icinga.Loader(this);
this.events = new Icinga.Events(this);
Icinga.prototype = {
this.timer.initialize();
this.events.initialize();
this.ui.initialize();
this.loader.initialize();
this.logger.setLevel('info');
this.logger.info('Icinga is ready');
this.timer.register(this.refreshTimeSince, this, 1000);
},
/**
* Icinga startup, will be triggerd once the document is ready
*/
initialize: function () {
toggleFullscreen: function()
{
$('#layout').toggleClass('fullscreen');
this.ui.fixControls();
},
this.utils = new Icinga.Utils(this);
this.logger = new Icinga.Logger(this);
this.timer = new Icinga.Timer(this);
this.ui = new Icinga.UI(this);
this.loader = new Icinga.Loader(this);
this.events = new Icinga.Events(this);
this.history = new Icinga.History(this);
flipContent: function()
{
var col1 = $('#col1 > div').detach();
var col2 = $('#col2 > div').detach();
$('#col2').html('');
$('#col1').html('');
this.timer.initialize();
this.events.initialize();
this.history.initialize();
this.ui.initialize();
this.loader.initialize();
this.logger.info('Icinga is ready');
},
col1.appendTo('#col2');
col2.appendTo('#col1');
this.ui.fixControls();
},
/**
* Load a given module by name
*/
loadModule: function (name) {
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');
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 'undefined' !== typeof this.modules[name] ||
'undefined' !== typeof Icinga.availableModules[name];
},
/**
* Get a module by name
*/
module: function (name) {
if ('undefined' === typeof this.modules[name]) {
if ('undefined' !== typeof Icinga.availableModules[name]) {
this.modules[name] = new Icinga.Module(
this,
name,
Icinga.availableModules[name]
);
}
}
return this.modules[name];
},
/**
* Clean up and unload all Icinga components
*/
destroy: function () {
$.each(this.modules, function (name, module) {
module.destroy();
});
this.timer.destroy();
this.events.destroy();
this.loader.destroy();
this.ui.destroy();
this.logger.debug('Icinga has been destroyed');
this.logger.destroy();
this.utils.destroy();
this.modules = [];
this.timer = this.events = this.loader = this.ui = this.logger =
this.utils = null;
}
});
},
};
getWindowId: function()
{
var res = window.name.match(/^Icinga_([a-zA-Z0-9])$/);
if (res) {
return res[1];
}
return null;
},
window.Icinga = Icinga;
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)
{
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';
},
/**
* Get a module by name
*/
module: function(name)
{
if (typeof this.modules[name] === 'undefined') {
if (typeof Icinga.availableModules[name] !== 'undefined') {
this.modules[name] = new Icinga.Module(
this,
name,
Icinga.availableModules[name]
);
}
}
return this.modules[name];
},
/**
* Clean up and unload all Icinga components
*/
destroy: function()
{
$.each(this.modules, function(name, module) {
module.destroy();
});
this.timer.destroy();
this.events.destroy();
this.loader.destroy();
this.ui.destroy();
this.logger.debug('Icinga has been destroyed');
this.logger.destroy();
this.utils.destroy();
this.modules = [];
this.timer = this.events = this.loader = this.ui = this.logger = this.utils = null;
}
};
window.Icinga = Icinga;
Icinga.availableModules = {};
})(window);
Icinga.availableModules = {};
})(window, window.jQuery);

View File

@ -1,266 +1,272 @@
(function(Icinga) {
/**
* Icinga.Events
*
* Event handlers
*/
(function (Icinga, $) {
Icinga.Events = function(icinga) {
this.icinga = icinga;
};
'use strict';
Icinga.Events.prototype = {
Icinga.Events = function (icinga) {
this.icinga = icinga;
};
/**
* Icinga will call our initialize() function once it's ready
*/
initialize: function()
{
this.applyGlobalDefaults();
this.icinga.ui.prepareContainers();
},
Icinga.Events.prototype = {
// TODO: What's this?
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();
if ($a.length) {
$(el).attr('href', $a.attr('href'));
}
});
$('.icinga-module', el).each(function(idx, mod) {
$mod = $(mod);
var moduleName = $mod.data('icinga-module');
if (icinga.hasModule(moduleName)) {
var module = icinga.module(moduleName);
// NOT YET, the applyOnloadDings: module.applyEventHandlers(mod);
/**
* Icinga will call our initialize() function once it's ready
*/
initialize: function () {
this.applyGlobalDefaults();
this.applyHandlers($('#layout'));
this.icinga.ui.prepareContainers();
},
// TODO: What's this?
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();
if ($a.length) {
$(el).attr('href', $a.attr('href'));
}
});
$('.icinga-module', el).each(function(idx, mod) {
var $mod = $(mod);
var moduleName = $mod.data('icinga-module');
if (icinga.hasModule(moduleName)) {
var module = icinga.module(moduleName);
// NOT YET, the applyOnloadDings: module.applyEventHandlers(mod);
}
});
$('input.autofocus', el).focus();
$('.inlinepie', el).sparkline('html', {
type: 'pie',
sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'],
width: '2em',
height: '2em',
});
},
/**
* Global default event handlers
*/
applyGlobalDefaults: function () {
// We catch resize events
$(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);
$( window ).on('beforeunload', { self: this }, this.onUnload);
// We catch scroll events in our containers
$('.container').on('scroll', icinga.events.onContainerScroll);
// We want to catch each link click
$(document).on('click', 'a', { self: this }, this.linkClicked);
// We treat tr's with a href attribute like links
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
// We catch all form submit events
$(document).on('submit', 'form', { self: this }, this.submitForm);
// We support an 'autosubmit' class on dropdown form elements
$(document).on('change', 'form select.autosubmit', { self: this }, this.submitForm);
$(document).on('keyup', '#menu input.search', {self: this}, this.submitForm);
// TBD: a global autocompletion handler
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
// $(document).on('change', 'form.auto input', this.formChanged);
// $(document).on('change', 'form.auto select', this.submitForm);
},
onUnload: function (event) {
var icinga = event.data.self.icinga;
icinga.logger.info('Unloading Icinga');
icinga.destroy();
},
/**
* A scroll event happened in one of our containers
*/
onContainerScroll: function (event) {
// Ugly. And PLEASE, not so often
icinga.ui.fixControls();
},
/**
*
*/
submitForm: function (event) {
var icinga = event.data.self.icinga;
event.stopPropagation();
event.preventDefault();
// .closest is not required unless subelements to trigger this
var $form = $(event.currentTarget).closest('form');
var url = $form.attr('action');
var method = $form.attr('method');
var data = $form.serializeArray();
// TODO: Check button
data.push({ name: 'btn_submit', value: 'yesss' });
icinga.logger.debug('Submitting form: ' + method + ' ' + url);
// We should move this to a generic target-finder:
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;
}
});
$('.inlinepie', el).sparkline('html', {
type: 'pie',
sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'],
width: '2em',
height: '2em',
});
icinga.loader.loadUrl(url, $target, data, method);
// TODO: Do we really need to return false with stop/preventDefault?
return false;
},
},
/**
* Global default event handlers
*/
applyGlobalDefaults: function()
{
// We catch resize events
$(window).on('resize', { self: this }, this.onWindowResize);
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();
},
// Destroy Icinga, clean up and interrupt pending requests on unload
$( window ).on('unload', { self: this }, this.onUnload);
$( window ).on('beforeunload', { self: this }, this.onUnload);
layout2col: function () {
if ($('#layout').hasClass('twocols')) { return; }
icinga.logger.debug('Switching to double col');
$('#layout').addClass('twocols');
this.icinga.ui.fixControls();
},
// We catch scroll events in our containers
$('.container').on('scroll', icinga.events.onContainerScroll);
/**
* Someone clicked a link or tr[href]
*/
linkClicked: function (event) {
var icinga = event.data.self.icinga;
// We want to catch each link click
$(document).on('click', 'a', { self: this }, this.linkClicked);
var $a = $(this);
var href = $a.attr('href');
var $li;
if ($a.attr('target') === '_blank') {
return true;
}
event.stopPropagation();
event.preventDefault();
// We treat tr's with a href attribute like links
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
// If link is hash tag...
if (href === '#') {
if ($a.closest('#menu')) {
$li = $a.closest('li');
$('#menu .active').removeClass('active');
$li.addClass('active');
}
return;
}
var $target = $('#col1');
var $container = $a.closest('.container');
if ($container.length) {
$target = $container;
}
// We catch all form submit events
$(document).on('submit', 'form', { self: this }, this.submitForm);
if ($a.closest('table').length) {
$target = $('#col2');
icinga.events.layout2col();
}
if ($a.closest('[data-base-target]').length) {
$target = $('#' + $a.closest('[data-base-target]').data('baseTarget'));
icinga.events.layout2col();
}
if ($a.closest('.tree').length) {
$li = $a.closest('li');
if ($li.find('li').length) {
if ($li.hasClass('collapsed')) {
$li.removeClass('collapsed');
} else {
$li.addClass('collapsed');
$li.find('li').addClass('collapsed');
}
return false;
} else {
$target = $('#col2');
icinga.events.layout2col();
}
}
icinga.loader.loadUrl(href, $target);
event.stopPropagation();
event.preventDefault();
if ($a.closest('#menu').length) {
icinga.events.layout1col();
return false;
}
if ($a.closest('table.action').length) {
if ($('#layout').hasClass('twocols')) {
if ($target.attr('id') === 'col2') {
return;
}
icinga.events.layout1col();
} else {
icinga.events.layout2col();
}
return false;
}
},
// We support an 'autosubmit' class on dropdown form elements
$(document).on('change', 'form select.autosubmit', { self: this }, this.submitForm);
/*
hrefIsHashtag: function(href) {
// WARNING: IE gives full URL :(
// Also it doesn't support negativ indexes in substr
return href.substr(href.length - 1, 1) == '#';
},
*/
$(window).on('popstate', { self: this }, this.historyChanged);
unbindGlobalHandlers: function () {
$(window).off('resize', this.onWindowResize);
$(window).off('unload', this.onUnload);
$(window).off('beforeunload', this.onUnload);
$(document).off('scroll', '.container', this.onContainerScroll);
$(document).off('click', 'a', this.linkClicked);
$(document).off('click', 'tr[href]', this.linkClicked);
$(document).off('submit', 'form', this.submitForm);
$(document).off('change', 'form select.autosubmit', this.submitForm);
},
// TBD: a global autocompletion handler
// $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
// $(document).on('change', 'form.auto input', this.formChanged);
// $(document).on('change', 'form.auto select', this.submitForm);
},
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
icinga.ui.fixControls();
},
/**
*
*/
submitForm: function (event)
{
var icinga = event.data.self.icinga;
event.stopPropagation();
event.preventDefault();
// .closest is not required unless subelements to trigger this
var $form = $(event.currentTarget).closest('form');
var url = $form.attr('action');
var method = $form.attr('method');
var data = $form.serializeArray();
// TODO: Check button
data.push({ name: 'btn_submit', value: 'yesss' });
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');
destroy: function() {
// This is gonna be hard, clean up the mess
this.unbindGlobalHandlers();
this.icinga = null;
}
};
icinga.loader.loadUrl(url, $target, data, method);
// TODO: Do we really need to return false with stop/preventDefault?
return false;
},
/**
* Someone clicked a link or tr[href]
*/
linkClicked: function(event)
{
var icinga = event.data.self.icinga;
var $a = $(this);
var href = $a.attr('href');
if ($a.attr('target') === '_blank') {
return true;
}
event.stopPropagation();
event.preventDefault();
if (href === '#') {
if ($a.closest('#menu')) {
var $li = $a.closest('li');
$li.siblings('li.active').removeClass('active');
$li.addClass('active');
}
return;
}
var $target = $('#col1');
var $container = $a.closest('.container');
if ($container.length) {
$target = $container;
}
// If link is hash tag...
if ($a.closest('table').length) {
$target = $('#col2');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
}
if ($a.closest('[data-base-target]').length) {
$target = $('#' + $a.closest('[data-base-target]').data('baseTarget'));
$('#layout').addClass('twocols');
icinga.ui.fixControls();
}
if ($a.closest('.tree').length) {
var $li = $a.closest('li');
if ($li.find('li').length) {
if ($li.hasClass('collapsed')) {
$li.removeClass('collapsed');
} else {
$li.addClass('collapsed');
$li.find('li').addClass('collapsed');
}
return false;
} else {
$target = $('#col2');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
}
}
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();
return false;
}
if ($a.closest('table').length) {
if ($('#layout').hasClass('twocols')) {
if ($target.attr('id') === 'col2') return;
icinga.logger.debug('Switching to single col');
$('#layout').removeClass('twocols');
icinga.ui.fixControls();
} else {
icinga.logger.debug('Switching to double col');
$('#layout').addClass('twocols');
icinga.ui.fixControls();
}
return false;
}
},
/*
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);
$(window).off('resize', this.onWindowResize);
$(window).off('unload', this.onUnload);
$(window).off('beforeunload', this.onUnload);
$(document).off('scroll', '.container', this.onContainerScroll);
$(document).off('click', 'a', this.linkClicked);
$(document).off('click', 'tr[href]', this.linkClicked);
$(document).off('submit', 'form', this.submitForm);
$(document).off('change', 'form select.autosubmit', this.submitForm);
},
destroy: function() {
// This is gonna be hard, clean up the mess
this.unbindGlobalHandlers();
this.icinga = null;
}
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -3,336 +3,499 @@
*
* This is where we take care of XHR requests, responses and failures.
*/
(function(Icinga) {
(function(Icinga, $) {
Icinga.Loader = function(icinga) {
'use strict';
/**
* YES, we need Icinga
*/
this.icinga = icinga;
Icinga.Loader = function (icinga) {
/**
* Our base url
*/
this.baseUrl = icinga.config.baseUrl;
/**
* YES, we need Icinga
*/
this.icinga = icinga;
this.failureNotice = null;
/**
* Our base url
*/
this.baseUrl = icinga.config.baseUrl;
this.exception = null;
this.failureNotice = null;
/**
* Pending requests
*/
this.requests = {};
this.exception = null;
this.autorefreshEnabled = true;
};
/**
* Pending requests
*/
this.requests = {};
Icinga.Loader.prototype = {
this.autorefreshEnabled = true;
};
initialize: function()
{
this.icinga.timer.register(this.autorefresh, this, 10000);
},
Icinga.Loader.prototype = {
/**
* Load the given URL to the given target
*
* @param {string} url URL to be loaded
* @param {object} target Target jQuery element
* @param {object} data Optional parameters, usually for POST requests
* @param {string} method HTTP method, default is 'GET'
*/
loadUrl: function (url, $target, data, method)
{
var id = null;
initialize: function () {
this.icinga.timer.register(this.autorefresh, this, 500);
},
// Default method is GET
if (typeof method === 'undefined') {
method = 'GET';
}
/**
* Load the given URL to the given target
*
* @param {string} url URL to be loaded
* @param {object} target Target jQuery element
* @param {object} data Optional parameters, usually for POST requests
* @param {string} method HTTP method, default is 'GET'
*/
loadUrl: function (url, $target, data, method) {
var id = null;
this.icinga.logger.debug('Loading ', url, ' to ', $target);
// We should do better and ignore requests without target and/or id
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) {
$target.removeAttr('data-icinga-url');
$target.removeAttr('data-icinga-refresh');
$target.removeData('icingaUrl');
$target.removeData('icingaRefresh');
}
}
// If we have a pending request for the same target...
if (id in this.requests) {
// ...ignore the new request if it is already pending with the same URL
if (this.requests[id].url === url) {
this.icinga.logger.debug('Request to ', url, ' is already running for ', $target);
return this.requests[id];
}
// ...or abort the former request otherwise
this.icinga.logger.debug('Aborting pending request loading ', url, ' to ', $target);
this.requests[id].abort();
}
// Not sure whether we need this Accept-header
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();
} else {
headers['X-Icinga-WindowId'] = 'undefined';
}
var self = this;
var req = $.ajax({
type : method,
url : url,
data : data,
headers: headers,
context: self
});
req.$target = $target;
req.url = url;
req.done(this.onResponse);
req.fail(this.onFailure);
req.historyTriggered = false;
req.autorefresh = false;
if (id) {
this.requests[id] = req;
}
return req;
},
/**
* Create an URL relative to the Icinga base Url, still unused
*
* @param {string} url Relative url
*/
url: function(url)
{
if (typeof url === 'undefined') {
return this.baseUrl;
}
return this.baseUrl + url;
},
autorefresh: function()
{
var self = this;
if (self.autorefreshEnabled !== true) {
return;
}
$('.container[data-icinga-refresh]').each(function(idx, el) {
var $el = $(el);
self.loadUrl($el.data('icingaUrl'), $el).autorefresh = true;
el = null;
});
},
disableAutorefresh: function()
{
this.autorefreshEnabled = false;
},
enableAutorefresh: function()
{
this.autorefreshEnabled = true;
},
/**
* Handle successful XHR response
*/
onResponse: function (data, textStatus, req)
{
if (this.failureNotice !== null) {
this.failureNotice.remove();
this.failureNotice = null;
}
if (this.exception !== null) {
this.exception.remove();
this.exception = null;
req.$target.removeClass('impact');
}
var url = req.url;
var targetId = req.$target.attr('id');
this.icinga.logger.debug('Got response for ', req.$target, ', URL was ' + url);
if (! req.autorefresh) {
// TODO: Hook for response/url?
var $matches = $('[href="' + url + '"]');
$matches.each(function(idx, el) {
if ($(el).closest('#menu').length) {
$(el).closest('#menu').find('li.active').removeClass('active');
} else if ($(el).closest('table.action').length) {
$(el).closest('table.action').find('.active').removeClass('active');
// Default method is GET
if ('undefined' === typeof method) {
method = 'GET';
}
});
$matches.each(function(idx, el) {
if ($(el).closest('#menu').length) {
$(el).closest('li').addClass('active');
$(el).parents('li').addClass('active');
} else if ($(el).closest('table.action').length) {
$(el).addClass('active');
this.icinga.logger.debug('Loading ', url, ' to ', $target);
// We should do better and ignore requests without target and/or id
if (typeof $target !== 'undefined' && $target.attr('id')) {
id = $target.attr('id');
}
});
}
delete this.requests[targetId];
req.$target.attr('icingaurl', this.url);
//
var target = req.getResponseHeader('X-Icinga-Container');
if (target) {
req.$target = $('body');
}
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);
}
// 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);
}
// Update history when necessary. Don't do so for requests triggered
// by history or autorefresh events
if (! req.historyTriggered && ! req.autorefresh) {
// We only want to care about top-level containers
if (req.$target.parent().closest('.container').length === 0) {
this.icinga.logger.debug('Pushing ', req.url, ' to history');
window.history.pushState({icinga: true}, null, req.url);
}
}
$resp = $(req.responseText);
/* Should we try to fiddle with responses containing full HTML? */
/*
if ($('body', $resp).length) {
req.responseText = $('script', $('body', $resp).html()).remove();
}
*/
this.renderContentToContainer(req.responseText, req.$target);
},
/**
* Handle failed XHR response
*/
onFailure: function (req, textStatus, errorThrown)
{
var url = req.url;
delete this.requests[req.$target.attr('id')];
if (req.status === 500) {
if (this.exception === null) {
req.$target.addClass('impact');
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();
}
} else if (req.status > 0) {
this.icinga.logger.debug(req.responseText.slice(0, 100));
this.renderContentToContainer(
'<h1>' + req.status + ' ' + errorThrown + '</h1> ' +
req.responseText,
req.$target
);
// Header example:
// Icinga.debug(req.getResponseHeader('X-Icinga-Redirect'));
} else {
if (errorThrown === 'abort') {
this.icinga.logger.info('Request to ', url, ' has been aborted for ', req.$target);
} else {
if (this.failureNotice === null) {
this.failureNotice = this.createNotice(
'error',
'The connection to the Icinga web server has been lost at ' +
this.icinga.utils.timeShort() +
'.'
);
this.icinga.ui.fixControls();
if (typeof $target !== 'undefined') {
// TODO: We shouldn't use data but keep this information somewhere else.
if ($target.data('icingaUrl') !== url) {
$target.removeAttr('data-icinga-url');
$target.removeAttr('data-icinga-refresh');
$target.removeData('icingaUrl');
$target.removeData('icingaRefresh');
}
this.icinga.logger.error(
'Failed to contact web server loading ',
url,
' for ',
req.$target
);
}
// If we have a pending request for the same target...
if (id in this.requests) {
// ...ignore the new request if it is already pending with the same URL
if (this.requests[id].url === url) {
this.icinga.logger.debug('Request to ', url, ' is already running for ', $target);
return this.requests[id];
}
// ...or abort the former request otherwise
this.icinga.logger.debug(
'Aborting pending request loading ',
url,
' to ',
$target
);
this.requests[id].abort();
}
// Not sure whether we need this Accept-header
var headers = { 'X-Icinga-Accept': 'text/html' };
// Ask for a new window id in case we don't already have one
if (this.icinga.ui.hasWindowId()) {
headers['X-Icinga-WindowId'] = this.icinga.ui.getWindowId();
} else {
headers['X-Icinga-WindowId'] = 'undefined';
}
var self = this;
var req = $.ajax({
type : method,
url : url,
data : data,
headers: headers,
context: self
});
req.$target = $target;
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;
},
/**
* Create an URL relative to the Icinga base Url, still unused
*
* @param {string} url Relative url
*/
url: function (url) {
if (typeof url === 'undefined') {
return this.baseUrl;
}
return this.baseUrl + url;
},
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) {
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 () {
this.autorefreshEnabled = false;
},
enableAutorefresh: function () {
this.autorefreshEnabled = true;
},
/**
* Handle successful XHR response
*/
onResponse: function (data, textStatus, req) {
var self = this;
if (this.failureNotice !== null) {
this.failureNotice.remove();
this.failureNotice = null;
}
if (this.exception !== null) {
this.exception.remove();
this.exception = null;
req.$target.removeClass('impact');
}
var url = req.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 $forms = $('[action="' + url + '"]');
var $matches = $.merge($('[href="' + url + '"]'), $forms);
$matches.each(function (idx, el) {
if ($(el).closest('#menu').length) {
$('#menu .active').removeClass('active');
} else if ($(el).closest('table.action').length) {
$(el).closest('table.action').find('.active').removeClass('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');
}
});
} else {
// TODO: next container url
active = $('[href].active', req.$target).attr('href');
}
req.$target.attr('data-icinga-url', url);
//
var target = req.getResponseHeader('X-Icinga-Container');
var newBody = false;
if (target) {
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-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.ui.setWindowId(windowId);
}
// Update history when necessary. Don't do so for requests triggered
// by history or autorefresh events
if (! req.historyTriggered && ! req.autorefresh) {
// 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);
}
*/
}
}
// 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? */
/*
if ($('body', $resp).length) {
req.responseText = $('script', $('body', $resp).html()).remove();
}
*/
/*
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) {
var url = req.url;
if (req.status === 500) {
if (this.exception === null) {
req.$target.addClass('impact');
this.exception = this.createNotice(
'error',
$('h1', $(req.responseText)).first().html()
);
this.icinga.ui.fixControls();
}
} else if (req.status > 0) {
this.icinga.logger.debug(req.responseText.slice(0, 100));
this.renderContentToContainer(
'<h1>' + req.status + ' ' + errorThrown + '</h1> ' +
req.responseText,
req.$target
);
// Header example:
// Icinga.debug(req.getResponseHeader('X-Icinga-Redirect'));
} else {
if (errorThrown === 'abort') {
this.icinga.logger.info(
'Request to ' + url + ' has been aborted for ',
req.$target
);
} else {
if (this.failureNotice === null) {
this.failureNotice = this.createNotice(
'error',
'The connection to the Icinga web server has been lost at ' +
this.icinga.utils.timeShort() +
'.'
);
this.icinga.ui.fixControls();
}
this.icinga.logger.error(
'Failed to contact web server loading ',
url,
' for ',
req.$target
);
}
}
},
createNotice: function (severity, message) {
return $(
'<li class="' + severity + '">' + message + '</li>'
).appendTo($('#notifications'));
},
/**
* Smoothly render given HTML to given container
*/
renderContentToContainer: function (content, $container) {
// Disable all click events while rendering
$('*').click(function (event) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
});
// Container update happens here
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;
icinga.events.applyHandlers($container);
icinga.ui.initializeControls($container);
icinga.ui.fixControls();
// Re-enable all click events
$('*').off('click');
},
/**
* On shutdown we kill all pending requests
*/
destroy: function() {
$.each(this.requests, function(id, request) {
request.abort();
});
this.icinga = null;
this.requests = {};
}
},
createNotice: function(severity, message) {
return $('<li class="' + severity + '">' + message + '</li>').appendTo($('#notifications'));
},
};
/**
* Smoothly render given HTML to given container
*/
renderContentToContainer: function (content, $container)
{
// Disable all click events while rendering
$('*').click(function( event ) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
});
// Container update happens here
var scrollPos = $container.scrollTop();
$container.html(content);
$container.scrollTop(scrollPos);
// TODO: this.icinga.events.refreshContainer(container);
var icinga = this.icinga;
icinga.events.applyHandlers($container);
icinga.ui.initializeControls($container);
icinga.ui.fixControls();
// Re-enable all click events
$('*').off('click');
},
/**
* On shutdown we kill all pending requests
*/
destroy: function() {
$.each(this.requests, function(id, request) {
request.abort();
});
this.icinga = null;
this.requests = {};
}
};
}(Icinga));
}(Icinga, jQuery));

View File

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

View File

@ -1,112 +1,124 @@
/**
* This is how we bootstrap JS code in our modules
*/
(function(Icinga) {
(function(Icinga, $) {
Icinga.Module = function(icinga, name, prototyp) {
'use strict';
// The Icinga instance
this.icinga = icinga;
Icinga.Module = function (icinga, name, prototyp) {
// Event handlers registered by this module
this.handlers = [];
// The Icinga instance
this.icinga = icinga;
this.registeredHandlers = {};
// Event handlers registered by this module
this.handlers = [];
// The module name
this.name = name;
this.registeredHandlers = {};
// The JS prototype for this module
this.prototyp = prototyp;
// The module name
this.name = name;
// Once initialized, this will be an instance of the modules prototype
this.object = {};
// The JS prototype for this module
this.prototyp = prototyp;
// Initialize this module
this.initialize();
};
// Once initialized, this will be an instance of the modules prototype
this.object = {};
Icinga.Module.prototype = {
// Initialize this module
this.initialize();
};
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);
return false;
}
Icinga.Module.prototype = {
// That's all, the module is ready
this.icinga.logger.debug('Module ' + this.name + ' has been initialized');
return true;
},
initialize: function () {
/**
* Globally register this modules event handlers
*/
registerEventHandlers: function(handlers)
{
this.registeredHandlers = handlers;
return this;
},
try {
applyRegisteredEventHandlers: function()
{
var self = this;
$.each(this.registeredHandlers, function(filter, events) {
$.each(events, function (event, handler) {
// TODO: if (event[1] === 'each') {
// $(event[0], $(el)).each(event[2]);
self.bindEventHandler(
event,
'.module-' + self.name + ' ' + filter,
handler
);
});
});
self = null;
return this;
},
// 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
);
/**
* Effectively bind the given event handler
*/
bindEventHandler: function(event, filter, handler)
{
var self = this;
this.icinga.logger.debug('Bound ' + filter + ' .' + event + '()');
this.handlers.push([event, filter, handler]);
$(document).on(event, filter, handler.bind(self.object));
},
return false;
}
/**
* Unbind all event handlers bound by this module
*/
unbindEventHandlers: function()
{
$.each(this.handlers, function(idx, handler) {
$(document).off(handler[0], handler[1], handler[2]);
});
},
// That's all, the module is ready
this.icinga.logger.debug(
'Module ' + this.name + ' has been initialized'
);
/**
* Allow to destroy and clean up this module
*/
destroy: function()
{
this.unbindEventHandlers();
if (typeof this.object.destroy === 'function') {
this.object.destroy();
}
this.object = null;
this.icinga = null;
this.prototyp = null;
}
return true;
},
};
/**
* Globally register this modules event handlers
*/
registerEventHandlers: function (handlers) {
this.registeredHandlers = handlers;
return this;
},
}(Icinga));
applyRegisteredEventHandlers: function () {
var self = this;
$.each(this.registeredHandlers, function (filter, events) {
$.each(events, function (event, handler) {
// TODO: if (event[1] === 'each') {
// $(event[0], $(el)).each(event[2]);
self.bindEventHandler(
event,
'.module-' + self.name + ' ' + filter,
handler
);
});
});
self = null;
return this;
},
/**
* Effectively bind the given event handler
*/
bindEventHandler: function (event, filter, handler) {
var self = this;
this.icinga.logger.debug('Bound ' + filter + ' .' + event + '()');
this.handlers.push([event, filter, handler]);
$(document).on(event, filter, handler.bind(self.object));
},
/**
* Unbind all event handlers bound by this module
*/
unbindEventHandlers: function () {
$.each(this.handlers, function (idx, handler) {
$(document).off(handler[0], handler[1], handler[2]);
});
},
/**
* Allow to destroy and clean up this module
*/
destroy: function () {
this.unbindEventHandlers();
if (typeof this.object.destroy === 'function') {
this.object.destroy();
}
this.object = null;
this.icinga = null;
this.prototyp = null;
}
};
}(Icinga, jQuery));

View File

@ -4,144 +4,164 @@
* 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';
/**
* We keep a reference to the Icinga instance even if we don't need it
*/
this.icinga = icinga;
Icinga.Timer = function (icinga) {
/**
* The Interval object
*/
this.ticker = null;
/**
* We keep a reference to the Icinga instance even if we don't need it
*/
this.icinga = icinga;
/**
* Fixed default interval is 250ms
*/
this.interval = 250;
/**
* The Interval object
*/
this.ticker = null;
/**
* Our registerd observers
*/
this.observers = [];
/**
* Fixed default interval is 250ms
*/
this.interval = 250;
/**
* Counter
*/
this.stepCounter = 0;
/**
* Our registerd observers
*/
this.observers = [];
this.start = (new Date()).getTime();
/**
* Counter
*/
this.stepCounter = 0;
this.start = (new Date()).getTime();
this.lastRuntime = [];
};
this.lastRuntime = [];
};
Icinga.Timer.prototype = {
Icinga.Timer.prototype = {
/**
* The initialization function starts our ticker
*/
initialize: function(icinga)
{
var self = this;
this.ticker = setInterval(function() { self.tick(); }, this.interval);
},
/**
* The initialization function starts our ticker
*/
initialize: function () {
var self = this;
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()
{
var icinga = this.icinga;
$.each(this.observers, function(idx, observer) {
if (observer.isDue()) {
observer.run();
} else {
// Not due
/**
* We will trigger our tick function once a second. It will call each
* registered observer.
*/
tick: function () {
var icinga = this.icinga;
$.each(this.observers, function (idx, observer) {
if (observer.isDue()) {
observer.run();
} else {
// Not due
}
});
icinga = null;
},
/**
* Register a given callback function to be run within an optional scope.
*/
register: function (callback, scope, interval) {
var observer;
try {
if (typeof scope === 'undefined') {
observer = new Icinga.Timer.Interval(callback, interval);
} else {
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 () {
if (this.ticker !== null) {
clearInterval(this.ticker);
}
this.icinga = null;
$.each(this.observers, function (idx, observer) {
observer.destroy();
});
this.observers = [];
}
});
icinga = null;
},
};
/**
* Register a given callback function to be run within an optional scope.
*/
register: function(callback, scope, interval)
{
try {
if (typeof scope === 'undefined') {
this.observers.push(new Icinga.Timer.Interval(callback, interval));
} else {
this.observers.push(
new Icinga.Timer.Interval(
callback.bind(scope),
interval
)
);
Icinga.Timer.Interval = function (callback, interval) {
if ('undefined' === typeof interval) {
throw 'Timer interval is required';
}
} catch(err) {
this.icinga.logger.error(err);
}
},
/**
* Our destroy function will clean up everything. Unused right now.
*/
destroy: function()
{
if (this.ticker !== null) {
clearInterval(this.ticker);
}
this.icinga = null;
$.each(this.observers, function(idx, observer) {
observer.destroy();
});
this.observers = [];
}
};
if (interval < 100) {
throw 'Timer interval cannot be less than 100ms, got ' + interval;
}
Icinga.Timer.Interval = function(callback, interval) {
if (typeof interval === 'undefined') {
throw 'Timer interval is required';
}
if (interval < 100) {
throw 'Timer interval cannot be less than 100ms, got ' + interval;
}
this.lastRun = (new Date()).getTime();
this.interval = interval;
this.scheduledNextRun = this.lastRun + interval;
this.callback = callback;
};
Icinga.Timer.Interval.prototype = {
isDue: function()
{
return this.scheduledNextRun < (new Date()).getTime();
},
run: function()
{
this.lastRun = (new Date()).getTime();
while (this.scheduledNextRun < this.lastRun) {
this.scheduledNextRun += this.interval;
this.interval = interval;
this.scheduledNextRun = this.lastRun + interval;
this.callback = callback;
};
Icinga.Timer.Interval.prototype = {
isDue: function () {
return this.scheduledNextRun < (new Date()).getTime();
},
run: function () {
this.lastRun = (new Date()).getTime();
while (this.scheduledNextRun < this.lastRun) {
this.scheduledNextRun += this.interval;
}
this.callback();
},
destroy: function () {
this.callback = null;
}
this.callback();
},
};
destroy: function()
{
this.callback = null;
}
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -1,117 +1,335 @@
(function(Icinga) {
/**
* Icinga.UI
*
* Our user interface
*/
(function(Icinga, $) {
Icinga.UI = function(icinga) {
this.icinga = icinga;
};
'use strict';
Icinga.UI.prototype = {
initialize: function()
{
this.icinga.timer.register(this.refreshDebug, this, 1000);
this.refreshDebug();
},
Icinga.UI = function (icinga) {
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();
var winWidth = $( window ).width();
var winHeight = $( window ).height();
$('#responsive-debug').html(
'Time: ' +
this.icinga.ui.formatHHiiss(new Date) +
'<br />&nbsp;1em: ' +
size +
'px<br />&nbsp;Win: ' +
winWidth +
'x'+
winHeight +
'px<br />'
).css({display: 'block'});
},
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;
},
createFontSizeCalculator: function()
{
var $el = $('<div id="fontsize-calc">&nbsp;</div>');
$('#main').append($el);
return $el;
},
getDefaultFontSize: function()
{
var $calc = $('#fontsize-calc');
if (! $calc.length) {
$calc = this.createFontSizeCalculator();
this.icinga = icinga;
this.currentLayout = 'default';
this.debug = false;
this.debugTimer = null;
};
Icinga.UI.prototype = {
initialize: function () {
$('html').removeClass('no-js').addClass('js');
this.triggerWindowResize();
},
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.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.utils.formatHHiiss(new Date()) +
'<br /> 1em: ' +
size +
'px<br /> Win: ' +
winWidth +
'x'+
winHeight +
'px<br />' +
' 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]);
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 () {
var $el = $('<div id="fontsize-calc">&nbsp;</div>');
$('#layout').append($el);
return $el;
},
getDefaultFontSize: function () {
var $calc = $('#fontsize-calc');
if (! $calc.length) {
$calc = this.createFontSizeCalculator();
}
return $calc.width() / 1000;
},
initializeControls: function (parent) {
var self = this;
$('.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')
});
$el.after(newdiv);
}
});
this.fixControls(parent);
},
fixControls: function ($parent) {
var self = this;
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) {
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')
});
$fake.css({
height : $el.css('height'),
display : 'block'
});
});
},
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;
}
return $calc.width() / 1000;
},
initializeControls: function(parent)
{
var self = this;
$('.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')
});
$el.after(newdiv);
}
});
this.fixControls(parent);
},
fixControls: function($parent)
{
var self = this;
if (typeof $parent === 'undefined') {
$('.container').each(function(idx, container) {
self.fixControls($(container));
});
return;
}
self.icinga.logger.debug('Fixing controls for ', $parent);
$('.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')
});
$fake.css({
height: $el.css('height'),
display: 'block'
});
});
},
destroy: function() {
// This is gonna be hard, clean up the mess
this.icinga = null;
}
};
};
}(Icinga));
}(Icinga, jQuery));

View File

@ -3,95 +3,113 @@
*/
(function(Icinga) {
Icinga.Utils = function(icinga) {
'use strict';
/**
* Utility functions may need access to their Icinga instance
*/
this.icinga = icinga;
Icinga.Utils = function (icinga) {
/**
* We will use this to create an URL helper only once
*/
this.url_helper = null;
};
/**
* Utility functions may need access to their Icinga instance
*/
this.icinga = icinga;
Icinga.Utils.prototype = {
/**
* We will use this to create an URL helper only once
*/
this.urlHelper = null;
};
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;
},
Icinga.Utils.prototype = {
timeShort: function(now)
{
if (typeof now === 'undefined') {
now = new Date();
}
return now.toLocaleTimeString().replace(/:\d{2}$/, '');
},
timeWithMs: function (now) {
/**
* Parse a given Url and return an object
*/
parseUrl: function(url)
{
if (this.url_helper === null) {
this.url_helper = document.createElement('a');
}
var a = this.url_helper;
a.href = url;
if (typeof now === 'undefined') {
now = new Date();
}
var result = {
source : url,
protocol: a.protocol.replace(':', ''),
host : a.hostname,
port : a.port,
query : a.search,
file : (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash : a.hash.replace('#',''),
path : a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/'),
params : this.parseParams(a),
};
a = null;
var ms = now.getMilliseconds() + '';
while (ms.length < 3) {
ms = '0' + ms;
}
return result;
},
return now.toLocaleTimeString() + '.' + ms;
},
/**
* Parse url params
*/
parseParams: function(a) {
var params = {},
segment = a.search.replace(/^\?/,'').split('&'),
len = segment.length,
i = 0,
s;
for (; i < len; i++) {
if (! segment[i]) { continue; }
s = segment[i].split('=');
params[s[0]] = decodeURIComponent(s[1]);
}
return params;
},
timeShort: function (now) {
/**
* Cleanup
*/
destroy: function()
{
this.url_helper = null;
this.icinga = null;
}
};
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.urlHelper === null) {
this.urlHelper = document.createElement('a');
}
var a = this.urlHelper;
a.href = url;
var result = {
source : url,
protocol: a.protocol.replace(':', ''),
host : a.hostname,
port : a.port,
query : a.search,
file : (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash : a.hash.replace('#',''),
path : a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/'),
params : this.parseParams(a),
};
a = null;
return result;
},
/**
* Parse url params
*/
parseParams: function (a) {
var params = {},
segment = a.search.replace(/^\?/,'').split('&'),
len = segment.length,
i = 0,
s;
for (; i < len; i++) {
if (!segment[i]) {
continue;
}
s = segment[i].split('=');
params[s[0]] = decodeURIComponent(s[1]);
}
return params;
},
/**
* Cleanup
*/
destroy: function () {
this.urlHelper = null;
this.icinga = null;
}
};
}(Icinga));