Merge branch 'master' into feature/translation-plurals-6982

This commit is contained in:
Alexander Fuhr 2014-09-15 08:34:19 +02:00
commit f48094f01a
20 changed files with 259 additions and 306 deletions

View File

@ -26,12 +26,12 @@
%define revision 1 %define revision 1
%define configdir %{_sysconfdir}/icingaweb %define configdir %{_sysconfdir}/%{name}
%define sharedir %{_datadir}/icingaweb %define sharedir %{_datadir}/%{name}
%define prefixdir %{_datadir}/icingaweb %define prefixdir %{_datadir}/%{name}
%define logdir %{sharedir}/log
%define usermodparam -a -G %define usermodparam -a -G
%define logdir %{_localstatedir}/log/icingaweb %define logdir %{_localstatedir}/log/%{name}
%define docdir %{sharedir}/log
%if "%{_vendor}" == "suse" %if "%{_vendor}" == "suse"
%define phpname php5 %define phpname php5
@ -172,25 +172,26 @@ install -D -m0644 packages/rpm/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{ap
# install public, library, modules # install public, library, modules
%{__mkdir} -p %{buildroot}/%{sharedir} %{__mkdir} -p %{buildroot}/%{sharedir}
%{__mkdir} -p %{buildroot}/%{logdir} %{__mkdir} -p %{buildroot}/%{logdir}
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb %{__mkdir} -p %{buildroot}/%{docdir}
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/dashboard %{__mkdir} -p %{buildroot}/%{_sysconfdir}/dashboard
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules %{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring %{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules %{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/enabledModules
%{__cp} -r application library modules public %{buildroot}/%{sharedir}/ %{__cp} -r application doc library modules public %{buildroot}/%{sharedir}/
## config ## config
# authentication is db only # authentication is db only
install -D -m0644 packages/rpm/etc/icingaweb/authentication.ini %{buildroot}/%{_sysconfdir}/icingaweb/authentication.ini install -D -m0644 packages/rpm/etc/%{name}/authentication.ini %{buildroot}/%{_sysconfdir}/%{name}/authentication.ini
# custom resource paths # custom resource paths
install -D -m0644 packages/rpm/etc/icingaweb/resources.ini %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini install -D -m0644 packages/rpm/etc/%{name}/resources.ini %{buildroot}/%{_sysconfdir}/%{name}/resources.ini
# monitoring module (icinga2) # monitoring module (icinga2)
install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/backends.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/backends.ini install -D -m0644 packages/rpm/etc/%{name}/modules/monitoring/backends.ini %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring/backends.ini
install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/instances.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/instances.ini install -D -m0644 packages/rpm/etc/%{name}/modules/monitoring/instances.ini %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring/instances.ini
# enable the monitoring module by default # enable the monitoring module by default
ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/%{name}/enabledModules/monitoring
## config ## config
# install icingacli # install icingacli
@ -228,6 +229,8 @@ fi
%config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir} %config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir}
# logs # logs
%attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir} %attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir}
# shipped docs
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/doc
%files -n php-Icinga %files -n php-Icinga
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/application %attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/application

View File

@ -123,7 +123,7 @@ class Hook
*/ */
private static function assertValidHook($instance, $name) private static function assertValidHook($instance, $name)
{ {
$base_class = self::$BASE_NS . ucfirst($name); $base_class = self::$BASE_NS . ucfirst($name) . 'Hook';
if (strpos($base_class, self::$classSuffix) === false) { if (strpos($base_class, self::$classSuffix) === false) {
$base_class .= self::$classSuffix; $base_class .= self::$classSuffix;
@ -174,18 +174,6 @@ class Hook
} }
} }
/**
* Register your hook
*
* Alias for Hook::registerClass()
*
* @see Hook::registerClass()
*/
public static function register($name, $key, $class)
{
self::registerClass($name, $key, $class);
}
/** /**
* Register a class * Register a class
* *
@ -194,45 +182,12 @@ class Hook
* @param string $class Your class name, must inherit one of the * @param string $class Your class name, must inherit one of the
* classes in the Icinga/Web/Hook folder * classes in the Icinga/Web/Hook folder
*/ */
public static function registerClass($name, $key, $class) public static function register($name, $key, $class)
{ {
if (!class_exists($class)) {
throw new ProgrammingError(
'"%s" is not an existing class',
$class
);
}
if (!isset(self::$hooks[$name])) { if (!isset(self::$hooks[$name])) {
self::$hooks[$name] = array(); self::$hooks[$name] = array();
} }
self::$hooks[$name][$key] = $class; self::$hooks[$name][$key] = $class;
} }
/**
* Register an object
*
* @param string $name One of the predefined hook names
* @param string $key The identifier of a specific subtype
* @param object $object The instantiated hook to register
*
* @throws ProgrammingError
*/
public static function registerObject($name, $key, $object)
{
if (!is_object($object)) {
throw new ProgrammingError(
'"%s" is not an instantiated class',
$object
);
}
if (!isset(self::$instances[$name])) {
self::$instances[$name] = array();
}
self::$instances[$name][$key] = $object;
self::registerClass($name, $key, get_class($object));
}
} }

View File

@ -18,6 +18,7 @@ class JavaScript
'js/icinga/ui.js', 'js/icinga/ui.js',
'js/icinga/timer.js', 'js/icinga/timer.js',
'js/icinga/loader.js', 'js/icinga/loader.js',
'js/icinga/eventlistener.js',
'js/icinga/events.js', 'js/icinga/events.js',
'js/icinga/history.js', 'js/icinga/history.js',
'js/icinga/module.js', 'js/icinga/module.js',

View File

@ -17,7 +17,7 @@ class Response extends Zend_Controller_Response_Http
$url->getParams()->setSeparator('&'); $url->getParams()->setSeparator('&');
if (Icinga::app()->getFrontController()->getRequest()->isXmlHttpRequest()) { if (Icinga::app()->getFrontController()->getRequest()->isXmlHttpRequest()) {
$this->setHeader('X-Icinga-Redirect', rawurlencode($url)); $this->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()));
} else { } else {
$this->setRedirect($url->getAbsoluteUrl()); $this->setRedirect($url->getAbsoluteUrl());
} }

View File

@ -15,11 +15,15 @@ use DateInterval;
*/ */
class HistoryColorGrid extends AbstractWidget { class HistoryColorGrid extends AbstractWidget {
const ORIENTATION_VERTICAL = 'vertical'; const CAL_GROW_INTO_PAST = 'past';
const CAL_GROW_INTO_PRESENT = 'present';
const ORIENTATION_VERTICAL = 'vertical';
const ORIENTATION_HORIZONTAL = 'horizontal'; const ORIENTATION_HORIZONTAL = 'horizontal';
public $weekFlow = self::CAL_GROW_INTO_PAST;
public $orientation = self::ORIENTATION_VERTICAL; public $orientation = self::ORIENTATION_VERTICAL;
public $weekStartMonday = true;
private $maxValue = 1; private $maxValue = 1;
@ -158,7 +162,7 @@ class HistoryColorGrid extends AbstractWidget {
$html = '<table class="historycolorgrid">'; $html = '<table class="historycolorgrid">';
$html .= '<tr>'; $html .= '<tr>';
for ($i = 0; $i < 7; $i++) { for ($i = 0; $i < 7; $i++) {
$html .= '<th>' . $this->weekdayName($i) . "</th>"; $html .= '<th>' . $this->weekdayName($this->weekStartMonday ? $i + 1 : $i) . "</th>";
} }
$html .= '</tr>'; $html .= '</tr>';
$old = -1; $old = -1;
@ -192,7 +196,9 @@ class HistoryColorGrid extends AbstractWidget {
*/ */
private function renderWeekdayHorizontal($weekday, &$weeks) private function renderWeekdayHorizontal($weekday, &$weeks)
{ {
$html = '<tr><td class="weekday">' . $this->weekdayName($weekday) . '</td>'; $html = '<tr><td class="weekday">'
. $this->weekdayName($this->weekStartMonday ? $weekday + 1 : $weekday)
. '</td>';
foreach ($weeks as $week) { foreach ($weeks as $week) {
if (array_key_exists($weekday, $week)) { if (array_key_exists($weekday, $week)) {
$html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>'; $html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
@ -219,6 +225,10 @@ class HistoryColorGrid extends AbstractWidget {
$month = intval(date('n', $start)); $month = intval(date('n', $start));
$day = intval(date('j', $start)); $day = intval(date('j', $start));
$weekday = intval(date('w', $start)); $weekday = intval(date('w', $start));
if ($this->weekStartMonday) {
// 0 => monday, 6 => sunday
$weekday = $weekday === 0 ? 6 : $weekday - 1;
}
$date = $this->toDateStr($day, $month, $year); $date = $this->toDateStr($day, $month, $year);
$weeks[0][$weekday] = $date; $weeks[0][$weekday] = $date;
@ -229,8 +239,11 @@ class HistoryColorGrid extends AbstractWidget {
if ($weekday > 6) { if ($weekday > 6) {
$weekday = 0; $weekday = 0;
$weeks[] = array(); $weeks[] = array();
// PRESENT => The last day of week determines the month
if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
$months[$week] = $month;
}
$week++; $week++;
$months[$week] = $month;
} }
if ($day > cal_days_in_month(CAL_GREGORIAN, $month, $year)) { if ($day > cal_days_in_month(CAL_GREGORIAN, $month, $year)) {
$month++; $month++;
@ -240,10 +253,22 @@ class HistoryColorGrid extends AbstractWidget {
} }
$day = 1; $day = 1;
} }
if ($weekday === 0) {
// PAST => The first day of each week determines the month
if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
$months[$week] = $month;
}
}
$date = $this->toDateStr($day, $month, $year); $date = $this->toDateStr($day, $month, $year);
$weeks[$week][$weekday] = $date; $weeks[$week][$weekday] = $date;
}; };
$months[$week] = $month; $months[$week] = $month;
if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
return array(
'weeks' => array_reverse($weeks),
'months' => array_reverse($months)
);
}
return array( return array(
'weeks' => $weeks, 'weeks' => $weeks,
'months' => $months 'months' => $months

View File

@ -42,8 +42,8 @@ Decide whether to use MySQL or PostgreSQL.
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
quit quit
mysql -u root -p icingaweb < /usr/share/doc/icingaweb2-*/schema/accounts.mysql.sql mysql -u root -p icingaweb < /usr/share/doc/icingaweb2*/schema/accounts.mysql.sql
mysql -u root -p icingaweb < /usr/share/doc/icingaweb2-*/schema/preferences.mysql.sql mysql -u root -p icingaweb < /usr/share/doc/icingaweb2*/schema/preferences.mysql.sql
### PostgreSQL ### PostgreSQL
@ -62,8 +62,8 @@ in `/var/lib/pgsql/data/pg_hba.conf` and restart the PostgreSQL server.
Now install the `icingaweb` schema Now install the `icingaweb` schema
bash$ psql -U icingaweb -a -f /usr/share/doc/icingaweb2-*/schema/accounts.pgsql.sql bash$ psql -U icingaweb -a -f /usr/share/doc/icingaweb2*/schema/accounts.pgsql.sql
bash$ psql -U icingaweb -a -f /usr/share/doc/icingaweb2-*/schema/preferences.pgsql.sql bash$ psql -U icingaweb -a -f /usr/share/doc/icingaweb2*/schema/preferences.pgsql.sql
## Configuration ## Configuration
@ -74,16 +74,16 @@ The monitoring module is enabled by default.
### Backend configuration ### Backend configuration
`/etc/icingaweb/resources.ini` contains the database backend information. `/etc/icingaweb2/resources.ini` contains the database backend information.
By default the Icinga 2 DB IDO is used by the monitoring module in By default the Icinga 2 DB IDO is used by the monitoring module in
`/etc/icingaweb/modules/monitoring/backends.ini` `/etc/icingaweb2/modules/monitoring/backends.ini`
The external command pipe is required for sending commands The external command pipe is required for sending commands
and configured for Icinga 2 in and configured for Icinga 2 in
`/etc/icingaweb/modules/monitoring/instances.ini` `/etc/icingaweb2/modules/monitoring/instances.ini`
### Authentication configuration ### Authentication configuration
The `/etc/icingaweb/authentication.ini` file uses the internal database as The `/etc/icingaweb2/authentication.ini` file uses the internal database as
default. This requires the database being installed properly before default. This requires the database being installed properly before
allowing users to login via web console. allowing users to login via web console.

View File

@ -1,6 +1,6 @@
Alias /icingaweb "/usr/share/icingaweb/public" Alias /icingaweb "/usr/share/icingaweb2/public"
<Directory "/usr/share/icingaweb/public"> <Directory "/usr/share/icingaweb2/public">
Options SymLinksIfOwnerMatch Options SymLinksIfOwnerMatch
AllowOverride None AllowOverride None
@ -17,7 +17,7 @@ Alias /icingaweb "/usr/share/icingaweb/public"
Allow from all Allow from all
</IfModule> </IfModule>
SetEnv ICINGAWEB_CONFIGDIR /etc/icingaweb SetEnv ICINGAWEB_CONFIGDIR /etc/icingaweb2
EnableSendfile Off EnableSendfile Off

View File

@ -22,7 +22,7 @@ socket = /var/run/icinga2/cmd/livestatus
[logfile] [logfile]
type = file type = file
filename = "/var/log/icingaweb/icingaweb.log" filename = "/var/log/icingaweb2/icingaweb2.log"
fields = "/^(?<datetime>[0-9]{4}(-[0-9]{2}){2}T[0-9]{2}(:[0-9]{2}){2}(\\+[0-9]{2}:[0-9]{2})?) - (?<loglevel>[A-Za-z]+) - (?<message>.*)$/" fields = "/^(?<datetime>[0-9]{4}(-[0-9]{2}){2}T[0-9]{2}(:[0-9]{2}){2}(\\+[0-9]{2}:[0-9]{2})?) - (?<loglevel>[A-Za-z]+) - (?<message>.*)$/"
; format: PCRE ; format: PCRE
; ;

View File

@ -2,5 +2,5 @@
<?php <?php
use Icinga\Application\Cli; use Icinga\Application\Cli;
require_once '/usr/share/icingaweb/library/Icinga/Application/Cli.php'; require_once '/usr/share/icingaweb2/library/Icinga/Application/Cli.php';
Cli::start()->dispatch(); Cli::start()->dispatch();

View File

@ -10,14 +10,24 @@
Icinga.Behaviors = Icinga.Behaviors || {}; Icinga.Behaviors = Icinga.Behaviors || {};
var Navigation = function (icinga) { var Navigation = function (icinga) {
this.icinga = icinga; Icinga.EventListener.call(this, icinga);
this.on('click', '#menu a', this.linkClicked, this);
this.on('click', '#menu tr[href]', this.linkClicked, this);
this.on('mouseenter', 'li.dropdown', this.dropdownHover, this);
this.on('mouseleave', 'li.dropdown', this.dropdownLeave, this);
this.on('mouseenter', '#menu > ul > li', this.menuTitleHovered, this);
this.on('mouseleave', '#sidebar', this.leaveSidebar, this);
this.on('rendered', this.onRendered);
}; };
Navigation.prototype = new Icinga.EventListener();
Navigation.prototype.apply = function(el) { Navigation.prototype.onRendered = function(evt) {
// get original source element of the rendered-event
var el = evt.target;
// restore old menu state // restore old menu state
if (activeMenuId) { if (activeMenuId) {
$('[role="navigation"] li.active', el).removeClass('active'); $('[role="navigation"] li.active', el).removeClass('active');
var $selectedMenu = $('#' + activeMenuId, el).addClass('active'); var $selectedMenu = $('#' + activeMenuId).addClass('active');
var $outerMenu = $selectedMenu.parent().closest('li'); var $outerMenu = $selectedMenu.parent().closest('li');
if ($outerMenu.size()) { if ($outerMenu.size()) {
$outerMenu.addClass('active'); $outerMenu.addClass('active');
@ -31,24 +41,6 @@
} }
}; };
Navigation.prototype.bind = function() {
$(document).on('click', '#menu a', { self: this }, this.linkClicked);
$(document).on('click', '#menu tr[href]', { self: this }, this.linkClicked);
$(document).on('mouseenter', 'li.dropdown', this.dropdownHover);
$(document).on('mouseleave', 'li.dropdown', {self: this}, this.dropdownLeave);
$(document).on('mouseenter', '#menu > ul > li', { self: this }, this.menuTitleHovered);
$(document).on('mouseleave', '#sidebar', { self: this }, this.leaveSidebar);
};
Navigation.prototype.unbind = function() {
$(document).off('click', '#menu a', this.linkClicked);
$(document).off('click', '#menu tr[href]', this.linkClicked);
$(document).off('mouseenter', 'li.dropdown', this.dropdownHover);
$(document).off('mouseleave', 'li.dropdown', this.dropdownLeave);
$(document).off('mouseenter', '#menu > ul > li', this.menuTitleHovered);
$(document).on('mouseleave', '#sidebar', this.leaveSidebar);
};
Navigation.prototype.linkClicked = function(event) { Navigation.prototype.linkClicked = function(event) {
var $a = $(this); var $a = $(this);
var href = $a.attr('href'); var href = $a.attr('href');
@ -82,6 +74,23 @@
$menu.data('icinga-url', menuDataUrl); $menu.data('icinga-url', menuDataUrl);
}; };
/**
* Change the active menu element
*
* @param $el {jQuery} A selector pointing to the active element
*/
Navigation.prototype.setActive = function($el) {
$el.closest('li').addClass('active');
$el.parents('li').addClass('active');
activeMenuId = $el.closest('li').attr('id');
};
Navigation.prototype.resetActive = function() {
$('#menu .active').removeClass('active');
activeMenuId = null;
};
Navigation.prototype.menuTitleHovered = function(event) { Navigation.prototype.menuTitleHovered = function(event) {
var $li = $(this), var $li = $(this),
delay = 800, delay = 800,

View File

@ -8,10 +8,14 @@
Icinga.Behaviors = Icinga.Behaviors || {}; Icinga.Behaviors = Icinga.Behaviors || {};
var Sparkline = function (icinga) { var Sparkline = function (icinga) {
this.icinga = icinga; Icinga.EventListener.call(this, icinga);
this.on('rendered', this.onRendered, this);
}; };
Sparkline.prototype = new Icinga.EventListener();
Sparkline.prototype.onRendered = function(evt) {
var el = evt.target;
Sparkline.prototype.apply = function(el) {
$('span.sparkline', el).each(function(i, element) { $('span.sparkline', el).each(function(i, element) {
// read custom options // read custom options
var $spark = $(element); var $spark = $(element);
@ -45,12 +49,6 @@
}); });
}; };
Sparkline.prototype.bind = function() {
};
Sparkline.prototype.unbind = function() {
};
Icinga.Behaviors.Sparkline = Sparkline; Icinga.Behaviors.Sparkline = Sparkline;
}) (Icinga, jQuery); }) (Icinga, jQuery);

View File

@ -8,15 +8,23 @@
Icinga.Behaviors = Icinga.Behaviors || {}; Icinga.Behaviors = Icinga.Behaviors || {};
var Tooltip = function (icinga) { var Tooltip = function (icinga) {
this.icinga = icinga; Icinga.EventListener.call(this, icinga);
this.mouseX = 0; this.mouseX = 0;
this.mouseY = 0; this.mouseY = 0;
this.on('mousemove', this.onMousemove, this);
this.on('rendered', this.onRendered, this);
};
Tooltip.prototype = new Icinga.EventListener();
Tooltip.prototype.onMousemove = function(event) {
event.data.self.mouseX = event.pageX;
event.data.self.mouseY = event.pageY;
}; };
Tooltip.prototype.apply = function(el) { Tooltip.prototype.onRendered = function(evt) {
var self = this, icinga = this.icinga; var self = evt.data.self, icinga = evt.data.icinga, el = evt.target;
$('[title]').each(function () { $('[title]', el).each(function () {
var $el = $(this); var $el = $(this);
$el.attr('title', $el.data('title-rich') || $el.attr('title')); $el.attr('title', $el.data('title-rich') || $el.attr('title'));
}); });
@ -49,18 +57,6 @@
}); });
}; };
Tooltip.prototype.bind = function() {
var self = this;
$(document).on('mousemove', function (event) {
self.mouseX = event.pageX;
self.mouseY = event.pageY;
});
};
Tooltip.prototype.unbind = function() {
$(document).off('mousemove');
};
// Export // Export
Icinga.Behaviors.Tooltip = Tooltip; Icinga.Behaviors.Tooltip = Tooltip;

View File

@ -8,21 +8,10 @@
Icinga.Behaviors = Icinga.Behaviors || {}; Icinga.Behaviors = Icinga.Behaviors || {};
var Tristate = function (icinga) { var Tristate = function (icinga) {
this.icinga = icinga; Icinga.EventListener.call(this, icinga);
}; this.on('click', 'div.tristate .tristate-dummy', this.clickTriState, this);
Tristate.prototype.apply = function(el) {
var self = this, icinga = this.icinga;
};
Tristate.prototype.bind = function() {
// Toggle all triStateButtons
$(document).on('click', 'div.tristate .tristate-dummy', { self: this }, this.clickTriState);
};
Tristate.prototype.unbind = function() {
$(document).off('click', 'div.tristate .tristate-dummy', this.clickTriState);
}; };
Tristate.prototype = new Icinga.EventListener();
Tristate.prototype.clickTriState = function (event) { Tristate.prototype.clickTriState = function (event) {
var self = event.data.self; var self = event.data.self;

View File

@ -0,0 +1,74 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
/**
* EventListener contains event handlers and can bind / and unbind them from
* event emitting objects
*/
(function(Icinga, $) {
"use strict";
var EventListener = function (icinga) {
this.icinga = icinga;
this.handlers = [];
};
/**
* Add an handler to this EventLister
*
* @param evt {String} The name of the triggering event
* @param cond {String} The filter condition
* @param fn {Function} The event handler to execute
* @param scope {Object} The optional 'this' of the called function
*/
EventListener.prototype.on = function(evt, cond, fn, scope) {
if (typeof cond === 'function') {
scope = fn;
fn = cond;
cond = 'body';
}
this.icinga.logger.debug('on: ' + evt + '(' + cond + ')');
this.handlers.push({ evt: evt, cond: cond, fn: fn, scope: scope });
};
/**
* Bind all listeners to the given event emitter
*
* All event handlers will be executed when the associated event is
* triggered on the given Emitter.
*
* @param emitter {String} An event emitter that supports the function
* 'on' to register listeners
*/
EventListener.prototype.bind = function (emitter) {
var self = this;
$.each(this.handlers, function(i, handler) {
self.icinga.logger.debug('bind: ' + handler.evt + '(' + handler.cond + ')');
emitter.on(
handler.evt, handler.cond,
{
self: handler.scope || emitter,
icinga: self.icinga
}, handler.fn
);
});
};
/**
* Unbind all listeners from the given event emitter
*
* @param emitter {String} An event emitter that supports the function
* 'off' to un-register listeners.
*/
EventListener.prototype.unbind = function (emitter) {
var self = this;
$.each(this.handlers, function(i, handler) {
self.icinga.logger.debug('unbind: ' + handler.evt + '(' + handler.cond + ')');
emitter.off(handler.evt, handler.cond, handler.fn);
});
};
Icinga.EventListener = EventListener;
}) (Icinga, jQuery);

View File

@ -29,20 +29,17 @@
*/ */
initialize: function () { initialize: function () {
this.applyGlobalDefaults(); this.applyGlobalDefaults();
this.applyHandlers($('#layout')); $('#layout').trigger('rendered');
//$('.container').trigger('rendered');
$('.container').each(function(idx, el) { $('.container').each(function(idx, el) {
icinga.events.applyHandlers($(el));
icinga.ui.initializeControls($(el)); icinga.ui.initializeControls($(el));
}); });
}, },
// TODO: What's this? // TODO: What's this?
applyHandlers: function (el) { applyHandlers: function (evt) {
$.each(this.icinga.behaviors, function (name, behavior) { var el = $(evt.target), self = evt.data.self;
behavior.apply(el); var icinga = self.icinga;
});
var icinga = this.icinga;
$('.dashboard > div', el).each(function(idx, el) { $('.dashboard > div', el).each(function(idx, el) {
var url = $(el).data('icingaUrl'); var url = $(el).data('icingaUrl');
@ -83,7 +80,7 @@
var searchField = $('#menu input.search', el); var searchField = $('#menu input.search', el);
// Remember initial search field value if any // Remember initial search field value if any
if (searchField.length && searchField.val().length) { if (searchField.length && searchField.val().length) {
this.searchValue = searchField.val(); self.searchValue = searchField.val();
} }
}, },
@ -92,9 +89,12 @@
*/ */
applyGlobalDefaults: function () { applyGlobalDefaults: function () {
$.each(self.icinga.behaviors, function (name, behavior) { $.each(self.icinga.behaviors, function (name, behavior) {
behavior.bind(); behavior.bind($(document));
}); });
// Apply element-specific behavior whenever the layout is rendered
$(document).on('rendered', { self: this }, this.applyHandlers);
// We catch resize events // We catch resize events
$(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize); $(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize);
@ -145,7 +145,7 @@
}, },
onLoad: function (event) { onLoad: function (event) {
$('.container').trigger('rendered'); //$('.container').trigger('rendered');
}, },
onUnload: function (event) { onUnload: function (event) {
@ -462,7 +462,7 @@
unbindGlobalHandlers: function () { unbindGlobalHandlers: function () {
$.each(self.icinga.behaviors, function (name, behavior) { $.each(self.icinga.behaviors, function (name, behavior) {
behavior.unbind(); behavior.unbind($(document));
}); });
$(window).off('resize', this.onWindowResize); $(window).off('resize', this.onWindowResize);
$(window).off('load', this.onLoad); $(window).off('load', this.onLoad);

View File

@ -345,7 +345,7 @@
$matches.each(function (idx, el) { $matches.each(function (idx, el) {
if ($(el).closest('#menu').length) { if ($(el).closest('#menu').length) {
$('#menu .active').removeClass('active'); self.icinga.behaviors.navigation.resetActive();
} else if ($(el).closest('table.action').length) { } else if ($(el).closest('table.action').length) {
$(el).closest('table.action').find('.active').removeClass('active'); $(el).closest('table.action').find('.active').removeClass('active');
} }
@ -357,8 +357,7 @@
if ($el.is('form')) { if ($el.is('form')) {
$('input', $el).addClass('active'); $('input', $el).addClass('active');
} else { } else {
$el.closest('li').addClass('active'); self.icinga.behaviors.navigation.setActive($el);
$el.parents('li').addClass('active');
} }
// Interrupt .each, only on menu item shall be active // Interrupt .each, only on menu item shall be active
return false; return false;
@ -540,7 +539,6 @@
}).addClass('active'); }).addClass('active');
} }
} }
req.$target.trigger('rendered');
}, },
/** /**
@ -726,8 +724,10 @@
} }
// TODO: this.icinga.events.refreshContainer(container); // TODO: this.icinga.events.refreshContainer(container);
$container.trigger('rendered');
var icinga = this.icinga; var icinga = this.icinga;
icinga.events.applyHandlers($container); //icinga.events.applyHandlers($container);
icinga.ui.initializeControls($container); icinga.ui.initializeControls($container);
icinga.ui.fixControls(); icinga.ui.fixControls();

View File

@ -2,23 +2,38 @@
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Hook;
use Icinga\Web\Hook;
class TestHook extends Hook {}
namespace Tests\Icinga\Web; namespace Tests\Icinga\Web;
use Mockery;
use Exception;
use Icinga\Web\Hook;
use Icinga\Test\BaseTestCase; use Icinga\Test\BaseTestCase;
use Icinga\Web\Hook;
use Icinga\Web\Hook\TestHook;
use Exception;
class ErrorProneHookImplementation class NoHook {}
class MyHook extends TestHook {}
class AnotherHook extends TestHook {}
class FailingHook extends TestHook
{ {
public function __construct() public function __construct()
{ {
throw new Exception(); throw new Exception("I'm failing");
} }
} }
class HookTest extends BaseTestCase class HookTest extends BaseTestCase
{ {
protected $invalidHook = '\\Tests\\Icinga\\Web\\NoHook';
protected $validHook = '\\Tests\\Icinga\\Web\\MyHook';
protected $anotherHook = '\\Tests\\Icinga\\Web\\AnotherHook';
protected $failingHook = '\\Tests\\Icinga\\Web\\FailingHook';
protected $testBaseClass = '\\Icinga\\Web\\Hook\\TestHook';
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
@ -31,83 +46,28 @@ class HookTest extends BaseTestCase
Hook::clean(); Hook::clean();
} }
public function testWhetherHasReturnsTrueIfGivenAKnownHook() public function testKnowsWhichHooksAreRegistered()
{ {
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook'))); Hook::register('test', __FUNCTION__, $this->validHook);
$this->assertTrue(Hook::has('test'));
$this->assertTrue(Hook::has('TestHook'), 'Hook::has does not return true if given a known hook'); $this->assertFalse(Hook::has('no_such_hook'));
} }
public function testWhetherHasReturnsFalseIfGivenAnUnknownHook() public function testCorrectlyHandlesMultipleInstances()
{ {
$this->assertFalse(Hook::has('not_existing'), 'Hook::has does not return false if given an unknown hook'); Hook::register('test', 'one', $this->validHook);
} Hook::register('test', 'two', $this->anotherHook);
public function testWhetherHooksCanBeRegisteredWithRegisterClass()
{
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
$this->assertTrue(Hook::has('TestHook'), 'Hook::registerClass does not properly register a given hook');
}
/**
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
*/
public function testWhetherMultipleHooksOfTheSameTypeCanBeRegisteredWithRegisterClass()
{
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
$secondHook = Mockery::mock('overload:' . get_class($firstHook));
Hook::registerClass('TestHook', 'one', get_class($firstHook));
Hook::registerClass('TestHook', 'two', get_class($secondHook));
$this->assertInstanceOf( $this->assertInstanceOf(
get_class($secondHook), $this->anotherHook,
Hook::createInstance('TestHook', 'two'), Hook::createInstance('test', 'two')
'Hook::registerClass is not able to register different hooks of the same type' );
$this->assertInstanceOf(
$this->validHook,
Hook::createInstance('test', 'one')
); );
} }
/** public function testReturnsNullForInvalidHooks()
* @expectedException Icinga\Exception\ProgrammingError
*/
public function testWhetherOnlyClassesCanBeRegisteredAsHookWithRegisterClass()
{
Hook::registerClass('TestHook', __FUNCTION__, 'nope');
}
public function testWhetherHooksCanBeRegisteredWithRegisterObject()
{
Hook::registerObject('TestHook', __FUNCTION__, Mockery::mock(Hook::$BASE_NS . 'TestHook'));
$this->assertTrue(Hook::has('TestHook'), 'Hook::registerObject does not properly register a given hook');
}
/**
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
*/
public function testWhetherMultipleHooksOfTheSameTypeCanBeRegisteredWithRegisterObject()
{
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
$secondHook = Mockery::mock('overload:' . get_class($firstHook));
Hook::registerObject('TestHook', 'one', $firstHook);
Hook::registerObject('TestHook', 'two', $secondHook);
$this->assertInstanceOf(
get_class($secondHook),
Hook::createInstance('TestHook', 'two'),
'Hook::registerObject is not able to register different hooks of the same type'
);
}
/**
* @expectedException Icinga\Exception\ProgrammingError
*/
public function testWhetherOnlyObjectsCanBeRegisteredAsHookWithRegisterObject()
{
Hook::registerObject('TestHook', __FUNCTION__, 'nope');
}
public function testWhetherCreateInstanceReturnsNullIfGivenAnUnknownHookName()
{ {
$this->assertNull( $this->assertNull(
Hook::createInstance('not_existing', __FUNCTION__), Hook::createInstance('not_existing', __FUNCTION__),
@ -115,98 +75,41 @@ class HookTest extends BaseTestCase
); );
} }
/** public function testReturnsNullForFailingHook()
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
*/
public function testWhetherCreateInstanceInitializesHooksInheritingFromAPredefinedAbstractHook()
{ {
$baseHook = Mockery::mock(Hook::$BASE_NS . 'TestHook'); Hook::register('test', __FUNCTION__, $this->failingHook);
Hook::registerClass( $this->assertNull(Hook::createInstance('test', __FUNCTION__));
'TestHook', }
__FUNCTION__,
get_class(Mockery::mock('overload:' . get_class($baseHook)))
);
public function testChecksWhetherCreatedInstancesInheritBaseClasses()
{
Hook::register('test', __FUNCTION__, $this->validHook);
$this->assertInstanceOf( $this->assertInstanceOf(
get_class($baseHook), $this->testBaseClass,
Hook::createInstance('TestHook', __FUNCTION__), Hook::createInstance('test', __FUNCTION__)
'Hook::createInstance does not initialize hooks inheriting from a predefined abstract hook'
); );
} }
/**
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
*/
public function testWhetherCreateInstanceDoesNotInitializeMultipleHooksForASpecificIdentifier()
{
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
$secondHook = Hook::createInstance('TestHook', __FUNCTION__);
$thirdHook = Hook::createInstance('TestHook', __FUNCTION__);
$this->assertSame(
$secondHook,
$thirdHook,
'Hook::createInstance initializes multiple hooks for a specific identifier'
);
}
/**
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
*/
public function testWhetherCreateInstanceReturnsNullIfHookCannotBeInitialized()
{
Hook::registerClass('TestHook', __FUNCTION__, 'Tests\Icinga\Web\ErrorProneHookImplementation');
$this->assertNull(Hook::createInstance('TestHook', __FUNCTION__));
}
/** /**
* @expectedException Icinga\Exception\ProgrammingError * @expectedException Icinga\Exception\ProgrammingError
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
*/ */
public function testWhetherCreateInstanceThrowsAnErrorIfGivenAHookNotInheritingFromAPredefinedAbstractHook() public function testThrowsErrorsForInstancesNotInheritingBaseClasses()
{ {
Mockery::mock(Hook::$BASE_NS . 'TestHook'); Hook::register('test', __FUNCTION__, $this->invalidHook);
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock('TestHook'))); Hook::createInstance('test', __FUNCTION__);
Hook::createInstance('TestHook', __FUNCTION__);
} }
/** public function testCreatesIdenticalInstancesOnlyOnce()
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
*/
public function testWhetherAllReturnsAllRegisteredHooks()
{ {
$hook = Mockery::mock(Hook::$BASE_NS . 'TestHook'); Hook::register('test', __FUNCTION__, $this->validHook);
Hook::registerObject('TestHook', 'one', $hook); $first = Hook::createInstance('test', __FUNCTION__);
Hook::registerObject('TestHook', 'two', $hook); $second = Hook::createInstance('test', __FUNCTION__);
Hook::registerObject('TestHook', 'three', $hook);
$this->assertCount(3, Hook::all('TestHook'), 'Hook::all does not return all registered hooks'); $this->assertSame($first, $second);
} }
public function testWhetherAllReturnsNothingIfGivenAnUnknownHookName() public function testReturnsAnEmptyArrayWithNoRegisteredHook()
{ {
$this->assertEmpty( $this->assertEquals(array(), Hook::all('not_existing'));
Hook::all('not_existing'),
'Hook::all does not return an empty array if given an unknown hook'
);
}
/**
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
*/
public function testWhetherFirstReturnsTheFirstRegisteredHook()
{
$firstHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
$secondHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
Hook::registerObject('TestHook', 'first', $firstHook);
Hook::registerObject('TestHook', 'second', $secondHook);
$this->assertSame(
$firstHook,
Hook::first('TestHook'),
'Hook::first does not return the first registered hook'
);
} }
} }