Merge branch 'master' into bugfix/commands-6593
This commit is contained in:
commit
8bf66425d1
|
@ -26,12 +26,12 @@
|
|||
|
||||
%define revision 1
|
||||
|
||||
%define configdir %{_sysconfdir}/icingaweb
|
||||
%define sharedir %{_datadir}/icingaweb
|
||||
%define prefixdir %{_datadir}/icingaweb
|
||||
%define logdir %{sharedir}/log
|
||||
%define configdir %{_sysconfdir}/%{name}
|
||||
%define sharedir %{_datadir}/%{name}
|
||||
%define prefixdir %{_datadir}/%{name}
|
||||
%define usermodparam -a -G
|
||||
%define logdir %{_localstatedir}/log/icingaweb
|
||||
%define logdir %{_localstatedir}/log/%{name}
|
||||
%define docdir %{sharedir}/log
|
||||
|
||||
%if "%{_vendor}" == "suse"
|
||||
%define phpname php5
|
||||
|
@ -172,25 +172,26 @@ install -D -m0644 packages/rpm/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{ap
|
|||
# install public, library, modules
|
||||
%{__mkdir} -p %{buildroot}/%{sharedir}
|
||||
%{__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}/icingaweb/modules
|
||||
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring
|
||||
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules
|
||||
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules
|
||||
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring
|
||||
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/%{name}/enabledModules
|
||||
|
||||
%{__cp} -r application library modules public %{buildroot}/%{sharedir}/
|
||||
%{__cp} -r application doc library modules public %{buildroot}/%{sharedir}/
|
||||
|
||||
## config
|
||||
# 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
|
||||
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)
|
||||
install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/backends.ini %{buildroot}/%{_sysconfdir}/icingaweb/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/backends.ini %{buildroot}/%{_sysconfdir}/%{name}/modules/monitoring/backends.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
|
||||
ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring
|
||||
ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/%{name}/enabledModules/monitoring
|
||||
## config
|
||||
|
||||
# install icingacli
|
||||
|
@ -228,6 +229,8 @@ fi
|
|||
%config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir}
|
||||
# logs
|
||||
%attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir}
|
||||
# shipped docs
|
||||
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/doc
|
||||
|
||||
%files -n php-Icinga
|
||||
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/application
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -194,45 +182,12 @@ class Hook
|
|||
* @param string $class Your class name, must inherit one of the
|
||||
* 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])) {
|
||||
self::$hooks[$name] = array();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,15 @@ class JavaScript
|
|||
'js/icinga/ui.js',
|
||||
'js/icinga/timer.js',
|
||||
'js/icinga/loader.js',
|
||||
'js/icinga/eventlistener.js',
|
||||
'js/icinga/events.js',
|
||||
'js/icinga/history.js',
|
||||
'js/icinga/module.js',
|
||||
'js/icinga/timezone.js',
|
||||
'js/icinga/behavior/tooltip.js',
|
||||
'js/icinga/behavior/sparkline.js',
|
||||
'js/icinga/behavior/tristate.js',
|
||||
'js/icinga/behavior/navigation.js'
|
||||
);
|
||||
|
||||
protected static $vendorFiles = array(
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Exception;
|
||||
use RecursiveIteratorIterator;
|
||||
use Icinga\Logger\Logger;
|
||||
|
||||
/**
|
||||
* A renderer to draw a menu with its sub-menus using an unordered html list
|
||||
|
@ -106,7 +108,11 @@ class MenuRenderer extends RecursiveIteratorIterator
|
|||
public function renderChild(Menu $child)
|
||||
{
|
||||
if ($child->getRenderer() !== null && $this->useCustomRenderer) {
|
||||
return $child->getRenderer()->render($child);
|
||||
try {
|
||||
return $child->getRenderer()->render($child);
|
||||
} catch (Exception $e) {
|
||||
Logger::error('Could not invoke custom renderer. Exception: '. $e->getMessage());
|
||||
}
|
||||
}
|
||||
return sprintf(
|
||||
'<a href="%s">%s%s</a>',
|
||||
|
|
|
@ -17,7 +17,7 @@ class Response extends Zend_Controller_Response_Http
|
|||
$url->getParams()->setSeparator('&');
|
||||
|
||||
if (Icinga::app()->getFrontController()->getRequest()->isXmlHttpRequest()) {
|
||||
$this->setHeader('X-Icinga-Redirect', rawurlencode($url));
|
||||
$this->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()));
|
||||
} else {
|
||||
$this->setRedirect($url->getAbsoluteUrl());
|
||||
}
|
||||
|
|
|
@ -90,8 +90,7 @@ $this->addHelperFunction('propertiesToString', function ($properties) use ($view
|
|||
return ' ' . implode(' ', $attributes);
|
||||
});
|
||||
|
||||
$this->addHelperFunction('attributeToString', function ($key, $value)
|
||||
{
|
||||
$this->addHelperFunction('attributeToString', function ($key, $value) use ($view) {
|
||||
// TODO: Doublecheck this!
|
||||
if (! preg_match('~^[a-zA-Z0-9-]+$~', $key)) {
|
||||
throw new ProgrammingError(
|
||||
|
@ -103,7 +102,7 @@ $this->addHelperFunction('attributeToString', function ($key, $value)
|
|||
return sprintf(
|
||||
'%s="%s"',
|
||||
$key,
|
||||
$value
|
||||
$view->escape($value)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -15,11 +15,15 @@ use DateInterval;
|
|||
*/
|
||||
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';
|
||||
|
||||
public $weekFlow = self::CAL_GROW_INTO_PAST;
|
||||
public $orientation = self::ORIENTATION_VERTICAL;
|
||||
public $weekStartMonday = true;
|
||||
|
||||
private $maxValue = 1;
|
||||
|
||||
|
@ -158,7 +162,7 @@ class HistoryColorGrid extends AbstractWidget {
|
|||
$html = '<table class="historycolorgrid">';
|
||||
$html .= '<tr>';
|
||||
for ($i = 0; $i < 7; $i++) {
|
||||
$html .= '<th>' . $this->weekdayName($i) . "</th>";
|
||||
$html .= '<th>' . $this->weekdayName($this->weekStartMonday ? $i + 1 : $i) . "</th>";
|
||||
}
|
||||
$html .= '</tr>';
|
||||
$old = -1;
|
||||
|
@ -192,7 +196,9 @@ class HistoryColorGrid extends AbstractWidget {
|
|||
*/
|
||||
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) {
|
||||
if (array_key_exists($weekday, $week)) {
|
||||
$html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
|
||||
|
@ -219,6 +225,10 @@ class HistoryColorGrid extends AbstractWidget {
|
|||
$month = intval(date('n', $start));
|
||||
$day = intval(date('j', $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);
|
||||
$weeks[0][$weekday] = $date;
|
||||
|
@ -229,8 +239,11 @@ class HistoryColorGrid extends AbstractWidget {
|
|||
if ($weekday > 6) {
|
||||
$weekday = 0;
|
||||
$weeks[] = array();
|
||||
// PRESENT => The last day of week determines the month
|
||||
if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
|
||||
$months[$week] = $month;
|
||||
}
|
||||
$week++;
|
||||
$months[$week] = $month;
|
||||
}
|
||||
if ($day > cal_days_in_month(CAL_GREGORIAN, $month, $year)) {
|
||||
$month++;
|
||||
|
@ -240,10 +253,22 @@ class HistoryColorGrid extends AbstractWidget {
|
|||
}
|
||||
$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);
|
||||
$weeks[$week][$weekday] = $date;
|
||||
};
|
||||
$months[$week] = $month;
|
||||
if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
|
||||
return array(
|
||||
'weeks' => array_reverse($weeks),
|
||||
'months' => array_reverse($months)
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'weeks' => $weeks,
|
||||
'months' => $months
|
||||
|
|
|
@ -6,7 +6,7 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl();
|
|||
|
||||
?>
|
||||
<h3 class="tinystatesummary" <?= $this->compact ? ' data-base-target="col1"' : '' ?>>
|
||||
<?= $this->qlink(sprintf($this->translate('%s service configured:'), $this->stats->services_total), $selfUrl) ?>
|
||||
<?= $this->qlink(sprintf($this->translate('%s configured services:'), $this->stats->services_total), $selfUrl) ?>
|
||||
<?php if ($this->stats->services_ok > 0): ?>
|
||||
<span class="state ok<?= $currentUrl === $selfUrl->with('service_state', 0)->getRelativeUrl() ? ' active' : '' ?>"><?= $this->qlink(
|
||||
$this->stats->services_ok,
|
||||
|
|
|
@ -38,8 +38,11 @@ class StatehistoryQuery extends IdoQuery
|
|||
{
|
||||
if ($col === 'UNIX_TIMESTAMP(sh.state_time)') {
|
||||
return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression));
|
||||
} elseif ($col === $this->columnMap['statehistory']['type']) {
|
||||
return 'sh.state_type ' . $sign . ' ' . $this->types[$expression];
|
||||
} elseif ($col === $this->columnMap['statehistory']['type']
|
||||
&& is_array($expression) === false
|
||||
&& array_key_exists($expression, $this->types) === true
|
||||
) {
|
||||
return 'sh.state_type ' . $sign . ' ' . $this->types[$expression];
|
||||
} else {
|
||||
return parent::whereToSql($col, $sign, $expression);
|
||||
}
|
||||
|
@ -58,4 +61,3 @@ class StatehistoryQuery extends IdoQuery
|
|||
$this->joinedVirtualTables = array('statehistory' => true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
# Introduction
|
||||
|
||||
Icinga Web 2 provides localization out of the box - for the core application and the modules, that means
|
||||
that you can with a lightness use existent localizations, update or even create you own localizations.
|
||||
|
||||
The chapters [Translation for Developers](Translation for Developers),
|
||||
[Translation for Translators](Translation for Translators) and [Testing Translations](Testing Translations) will
|
||||
introduce and explain you, how to take part on localizing Icinga Web 2 for different languages and how to use the
|
||||
`translation module` to make your life much easier.
|
||||
|
||||
# Translation for Developers
|
||||
|
||||
To make use of the built-in translations in your applications code or views, you should use the method
|
||||
`$this->translate('String to be translated')`, let's have a look at an example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class ExampleController extends Controller
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->title = $this->translate('Hello World');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
So if there a translation available for the `Hello World` string you will get an translated output, depends on the
|
||||
language which is setted in your configuration as the default language, if it is `de_DE` the output would be
|
||||
`Hallo Welt`.
|
||||
|
||||
The same works also for views:
|
||||
|
||||
```
|
||||
<h1><?= $this->title ?></h1>
|
||||
<p>
|
||||
<?= $this->translate('Hello World') ?>
|
||||
<?= $this->translate('String to be translated') ?>
|
||||
</p>
|
||||
```
|
||||
|
||||
If you need to provide placeholders in your messages, you should wrap the `$this->translate()` with `sprintf()` for e.g.
|
||||
sprintf($this->translate('Hello User: (%s)'), $user->getName())
|
||||
|
||||
# Translation for Translators
|
||||
|
||||
Icinga Web 2 internally uses the UNIX standard gettext tool to perform internationalization, this means translation
|
||||
files in the .po file format are supplied for text strings used in the code.
|
||||
|
||||
There are a lot of tools and techniques to work with .po localization files, you can choose what ever you prefer. We
|
||||
won't let you alone on your first steps and therefore we'll introduce you a nice tool, called Poedit.
|
||||
|
||||
### Poedit
|
||||
|
||||
First of all, you have to download and install Poedit from http://poedit.net, when you are done, you have to do some
|
||||
configuration under the Preferences.
|
||||
|
||||
#### Configuration
|
||||
|
||||
__Personalize__: Please provide your Name and E-Mail under Identity.
|
||||
|
||||

|
||||
|
||||
__Editor__: Under the Behavior the Automatically compile .mo files on save, should be disabled.
|
||||
|
||||

|
||||
|
||||
__Translations Memory__: Under the Database please add your languages, for which are you writing translations.
|
||||
|
||||

|
||||
|
||||
When you are done, just save your new settings.
|
||||
|
||||
#### Editing .po files
|
||||
|
||||
To work with Icinga Web 2 .po files, you can open for e.g. the german icinga.po file which is located under
|
||||
`application/locale/de_DE/LC_MESSAGES/icinga.po`, as shown below, you will get then a full list of all available
|
||||
translation strings for the core application. Each module names it's translation files `%module_name%.po`. For a
|
||||
module called __yourmodule__ the .po translation file will be named `yourmodule.po`.
|
||||
|
||||
|
||||

|
||||
|
||||
Now you can make changes and when there is no translation available, Poedit would mark it with a blue color, as shown
|
||||
below.
|
||||
|
||||

|
||||
|
||||
And when you want to test your changes, please read more about under the chapter
|
||||
[Testing Translations](Testing Translations).
|
||||
|
||||
# Testing Translations
|
||||
|
||||
If you want to try out your translation changes in Icinga Web 2, you can make use of the the CLI translations commands.
|
||||
|
||||
** NOTE: Please make sure that the gettext package is installed **
|
||||
|
||||
To get an easier development with translations, you can activate the `translation module` which provides CLI commands,
|
||||
after that you would be able to refresh and compile your .po files.
|
||||
|
||||
|
||||
** NOTE: the ll_CC stands for ll=language and CC=country code for e.g de_DE, fr_FR, ru_RU, it_IT etc. **
|
||||
|
||||
## Application
|
||||
|
||||
To refresh the __icinga.po__:
|
||||
|
||||
icingacli translation refresh icinga ll_CC
|
||||
|
||||
And to compile it:
|
||||
|
||||
icingacli translation compile icinga ll_CC
|
||||
|
||||
** NOTE: After a compile you need to restart the web server to get new translations available in your application. **
|
||||
|
||||
## Modules
|
||||
|
||||
Let's assume, we want to provide german translations for our just new created module __yourmodule__.
|
||||
|
||||
If we haven't yet any translations strings in our .po file or even the .po file, we can use the CLI command, to do the
|
||||
job for us:
|
||||
|
||||
icingacli translation refresh module development ll_CC
|
||||
|
||||
This will go through all .php and .phtml files inside the module and a look after `$this->translate()` if there is
|
||||
something to translate - if there is something and is not available in the __yourmodule.po__ it will updates this file
|
||||
for us with new
|
||||
strings.
|
||||
|
||||
Now you can open the __yourmodule.po__ and you will see something similar:
|
||||
|
||||
# Icinga Web 2 - Head for multiple monitoring backends.
|
||||
# Copyright (C) 2014 Icinga Development Team
|
||||
# This file is distributed under the same license as Development Module.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Development Module (0.0.1)\n"
|
||||
"Report-Msgid-Bugs-To: dev@icinga.org\n"
|
||||
"POT-Creation-Date: 2014-09-09 10:12+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ll_CC\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: /modules/yourmodule/configuration.php:6
|
||||
msgid "yourmodule"
|
||||
msgstr ""
|
||||
|
||||
Great, now you can adjust the file and provide the german `msgstr` for `yourmodule`.
|
||||
|
||||
#: /modules/yourmodule/configuration.php:6
|
||||
msgid "Dummy"
|
||||
msgstr "Attrappe"
|
||||
|
||||
The last step is to compile the __yourmodule.po__ to the __yourmodule.mo__:
|
||||
|
||||
icingacli translation compile module development ll_CC
|
||||
|
||||
At this moment, everywhere in the module where the `Dummy` should be translated, it would returns the translated
|
||||
string `Attrappe`.
|
|
@ -294,11 +294,12 @@ class GettextTranslationHelper
|
|||
$headerInfo['translator_name'] = $translatorInfo[1];
|
||||
$headerInfo['translator_mail'] = $translatorInfo[2];
|
||||
}
|
||||
$languageInfo = array();
|
||||
if (preg_match('@Language-Team: (.+) <(.+)>@', $content, $languageInfo)) {
|
||||
$headerInfo['language_team_name'] = $languageInfo[1];
|
||||
$headerInfo['language_team_url'] = $languageInfo[2];
|
||||
$languageTeamInfo = array();
|
||||
if (preg_match('@Language-Team: (.+) <(.+)>@', $content, $languageTeamInfo)) {
|
||||
$headerInfo['language_team_name'] = $languageTeamInfo[1];
|
||||
$headerInfo['language_team_url'] = $languageTeamInfo[2];
|
||||
}
|
||||
$languageInfo = array();
|
||||
if (preg_match('@Language: ([a-z]{2}_[A-Z]{2})@', $content, $languageInfo)) {
|
||||
$headerInfo['language'] = $languageInfo[1];
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
|
@ -42,8 +42,8 @@ Decide whether to use MySQL or PostgreSQL.
|
|||
FLUSH PRIVILEGES;
|
||||
quit
|
||||
|
||||
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/accounts.mysql.sql
|
||||
mysql -u root -p icingaweb < /usr/share/doc/icingaweb2*/schema/preferences.mysql.sql
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
|
@ -62,8 +62,8 @@ in `/var/lib/pgsql/data/pg_hba.conf` and restart the PostgreSQL server.
|
|||
|
||||
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/preferences.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
|
||||
|
||||
|
||||
## Configuration
|
||||
|
@ -74,16 +74,16 @@ The monitoring module is enabled by default.
|
|||
|
||||
### 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
|
||||
`/etc/icingaweb/modules/monitoring/backends.ini`
|
||||
`/etc/icingaweb2/modules/monitoring/backends.ini`
|
||||
|
||||
The external command pipe is required for sending commands
|
||||
and configured for Icinga 2 in
|
||||
`/etc/icingaweb/modules/monitoring/instances.ini`
|
||||
`/etc/icingaweb2/modules/monitoring/instances.ini`
|
||||
|
||||
### 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
|
||||
allowing users to login via web console.
|
||||
|
|
|
@ -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
|
||||
AllowOverride None
|
||||
|
||||
|
@ -17,7 +17,7 @@ Alias /icingaweb "/usr/share/icingaweb/public"
|
|||
Allow from all
|
||||
</IfModule>
|
||||
|
||||
SetEnv ICINGAWEB_CONFIGDIR /etc/icingaweb
|
||||
SetEnv ICINGAWEB_CONFIGDIR /etc/icingaweb2
|
||||
|
||||
EnableSendfile Off
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ socket = /var/run/icinga2/cmd/livestatus
|
|||
|
||||
[logfile]
|
||||
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>.*)$/"
|
||||
; format: PCRE
|
||||
;
|
|
@ -2,5 +2,5 @@
|
|||
<?php
|
||||
|
||||
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();
|
||||
|
|
|
@ -60,6 +60,11 @@
|
|||
*/
|
||||
this.utils = null;
|
||||
|
||||
/**
|
||||
* Additional site behavior
|
||||
*/
|
||||
this.behaviors = {};
|
||||
|
||||
/**
|
||||
* Loaded modules
|
||||
*/
|
||||
|
@ -90,6 +95,10 @@
|
|||
this.loader = new Icinga.Loader(this);
|
||||
this.events = new Icinga.Events(this);
|
||||
this.history = new Icinga.History(this);
|
||||
var self = this;
|
||||
$.each(Icinga.Behaviors, function(name, Behavior) {
|
||||
self.behaviors[name.toLowerCase()] = new Behavior(self);
|
||||
});
|
||||
|
||||
this.timezone.initialize();
|
||||
this.timer.initialize();
|
||||
|
@ -97,6 +106,7 @@
|
|||
this.history.initialize();
|
||||
this.ui.initialize();
|
||||
this.loader.initialize();
|
||||
|
||||
this.logger.info('Icinga is ready, running on jQuery ', $().jquery);
|
||||
this.initialized = true;
|
||||
},
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
(function(Icinga, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var activeMenuId;
|
||||
|
||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||
|
||||
var Navigation = function (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.onRendered = function(evt) {
|
||||
// get original source element of the rendered-event
|
||||
var el = evt.target;
|
||||
// restore old menu state
|
||||
if (activeMenuId) {
|
||||
$('[role="navigation"] li.active', el).removeClass('active');
|
||||
var $selectedMenu = $('#' + activeMenuId).addClass('active');
|
||||
var $outerMenu = $selectedMenu.parent().closest('li');
|
||||
if ($outerMenu.size()) {
|
||||
$outerMenu.addClass('active');
|
||||
}
|
||||
} else {
|
||||
// store menu state
|
||||
var $menus = $('[role="navigation"] li.active', el);
|
||||
if ($menus.size()) {
|
||||
activeMenuId = $menus[0].id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Navigation.prototype.linkClicked = function(event) {
|
||||
var $a = $(this);
|
||||
var href = $a.attr('href');
|
||||
var $li;
|
||||
var icinga = event.data.self.icinga;
|
||||
|
||||
if (href.match(/#/)) {
|
||||
// ...it may be a menu section without a dedicated link.
|
||||
// Switch the active menu item:
|
||||
$li = $a.closest('li');
|
||||
$('#menu .active').removeClass('active');
|
||||
$li.addClass('active');
|
||||
activeMenuId = $($li).attr('id');
|
||||
if ($li.hasClass('hover')) {
|
||||
$li.removeClass('hover');
|
||||
}
|
||||
if (href === '#') {
|
||||
// Allow to access dropdown menu by keyboard
|
||||
if ($a.hasClass('dropdown-toggle')) {
|
||||
$a.closest('li').toggleClass('hover');
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
activeMenuId = $(event.target).closest('li').attr('id');
|
||||
}
|
||||
// update target url of the menu container to the clicked link
|
||||
var $menu = $('#menu');
|
||||
var menuDataUrl = icinga.utils.parseUrl($menu.data('icinga-url'));
|
||||
menuDataUrl = icinga.utils.addUrlParams(menuDataUrl.path, { url: href });
|
||||
$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) {
|
||||
var $li = $(this),
|
||||
delay = 800,
|
||||
self = event.data.self;
|
||||
|
||||
if ($li.hasClass('active')) {
|
||||
$li.siblings().removeClass('hover');
|
||||
return;
|
||||
}
|
||||
if ($li.children('ul').children('li').length === 0) {
|
||||
return;
|
||||
}
|
||||
if ($('#menu').scrollTop() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('#layout').hasClass('hoveredmenu')) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if (! $li.is('li:hover')) {
|
||||
return;
|
||||
}
|
||||
if ($li.hasClass('active')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$li.siblings().each(function () {
|
||||
var $sibling = $(this);
|
||||
if ($sibling.is('li:hover')) {
|
||||
return;
|
||||
}
|
||||
if ($sibling.hasClass('hover')) {
|
||||
$sibling.removeClass('hover');
|
||||
}
|
||||
});
|
||||
|
||||
self.hoverElement($li);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
Navigation.prototype.leaveSidebar = function (event) {
|
||||
var $sidebar = $(this),
|
||||
$li = $sidebar.find('li.hover'),
|
||||
self = event.data.self;
|
||||
if (! $li.length) {
|
||||
$('#layout').removeClass('hoveredmenu');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if ($li.is('li:hover') || $sidebar.is('sidebar:hover') ) {
|
||||
return;
|
||||
}
|
||||
$li.removeClass('hover');
|
||||
$('#layout').removeClass('hoveredmenu');
|
||||
}, 500);
|
||||
};
|
||||
|
||||
Navigation.prototype.hoverElement = function ($li) {
|
||||
$('#layout').addClass('hoveredmenu');
|
||||
$li.addClass('hover');
|
||||
};
|
||||
|
||||
Navigation.prototype.dropdownHover = function () {
|
||||
$(this).addClass('hover');
|
||||
};
|
||||
|
||||
Navigation.prototype.dropdownLeave = function (event) {
|
||||
var $li = $(this),
|
||||
self = event.data.self;
|
||||
setTimeout(function () {
|
||||
// TODO: make this behave well together with keyboard navigation
|
||||
if (! $li.is('li:hover') /*&& ! $li.find('a:focus')*/) {
|
||||
$li.removeClass('hover');
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
Icinga.Behaviors.Navigation = Navigation;
|
||||
|
||||
}) (Icinga, jQuery);
|
|
@ -0,0 +1,54 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
(function(Icinga, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||
|
||||
var Sparkline = function (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;
|
||||
|
||||
$('span.sparkline', el).each(function(i, element) {
|
||||
// read custom options
|
||||
var $spark = $(element);
|
||||
var labels = $spark.attr('labels').split('|');
|
||||
var formatted = $spark.attr('formatted').split('|');
|
||||
var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
|
||||
var format = $spark.attr('tooltipformat');
|
||||
var hideEmpty = $spark.attr('hideEmptyLabel') === 'true';
|
||||
$spark.sparkline(
|
||||
'html',
|
||||
{
|
||||
enableTagOptions: true,
|
||||
tooltipFormatter: function (sparkline, options, fields) {
|
||||
var out = format;
|
||||
if (hideEmpty && fields.offset === 3) {
|
||||
return '';
|
||||
}
|
||||
var replace = {
|
||||
title: tooltipChartTitle,
|
||||
label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
|
||||
formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
|
||||
value: fields.value,
|
||||
percent: Math.round(fields.percent * 100) / 100
|
||||
};
|
||||
$.each(replace, function(key, value) {
|
||||
out = out.replace('{{' + key + '}}', value);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Icinga.Behaviors.Sparkline = Sparkline;
|
||||
|
||||
}) (Icinga, jQuery);
|
|
@ -0,0 +1,63 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
(function(Icinga, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||
|
||||
var Tooltip = function (icinga) {
|
||||
Icinga.EventListener.call(this, icinga);
|
||||
this.mouseX = 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.onRendered = function(evt) {
|
||||
var self = evt.data.self, icinga = evt.data.icinga, el = evt.target;
|
||||
|
||||
$('[title]', el).each(function () {
|
||||
var $el = $(this);
|
||||
$el.attr('title', $el.data('title-rich') || $el.attr('title'));
|
||||
});
|
||||
$('svg rect.chart-data[title]', el).tipsy({ gravity: 'se', html: true });
|
||||
$('.historycolorgrid a[title]', el).tipsy({ gravity: 's', offset: 2 });
|
||||
$('img.icon[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 });
|
||||
$('[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, delayIn: 500 });
|
||||
|
||||
// migrate or remove all orphaned tooltips
|
||||
$('.tipsy').each(function () {
|
||||
var arrow = $('.tipsy-arrow', this)[0];
|
||||
if (!icinga.utils.elementsOverlap(arrow, $('#main')[0])) {
|
||||
$(this).remove();
|
||||
return;
|
||||
}
|
||||
if (!icinga.utils.elementsOverlap(arrow, el)) {
|
||||
return;
|
||||
}
|
||||
var title = $(this).find('.tipsy-inner').html();
|
||||
var atMouse = document.elementFromPoint(self.mouseX, self.mouseY);
|
||||
var nearestTip = $(atMouse).closest('[original-title="' + title + '"]')[0];
|
||||
if (nearestTip) {
|
||||
var tipsy = $.data(nearestTip, 'tipsy');
|
||||
tipsy.$tip = $(this);
|
||||
$.data(this, 'tipsy-pointee', nearestTip);
|
||||
} else {
|
||||
// doesn't match delete
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Export
|
||||
Icinga.Behaviors.Tooltip = Tooltip;
|
||||
|
||||
}) (Icinga, jQuery);
|
|
@ -0,0 +1,51 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
(function(Icinga, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||
|
||||
var Tristate = function (icinga) {
|
||||
Icinga.EventListener.call(this, icinga);
|
||||
this.on('click', 'div.tristate .tristate-dummy', this.clickTriState, this);
|
||||
};
|
||||
Tristate.prototype = new Icinga.EventListener();
|
||||
|
||||
Tristate.prototype.clickTriState = function (event) {
|
||||
var self = event.data.self;
|
||||
var $tristate = $(this);
|
||||
var triState = parseInt($tristate.data('icinga-tristate'), 10);
|
||||
|
||||
// load current values
|
||||
var old = $tristate.data('icinga-old').toString();
|
||||
var value = $tristate.parent().find('input:radio:checked').first().prop('checked', false).val();
|
||||
|
||||
// calculate the new value
|
||||
if (triState) {
|
||||
// 1 => 0
|
||||
// 0 => unchanged
|
||||
// unchanged => 1
|
||||
value = value === '1' ? '0' : (value === '0' ? 'unchanged' : '1');
|
||||
} else {
|
||||
// 1 => 0
|
||||
// 0 => 1
|
||||
value = value === '1' ? '0' : '1';
|
||||
}
|
||||
|
||||
// update form value
|
||||
$tristate.parent().find('input:radio[value="' + value + '"]').prop('checked', true);
|
||||
// update dummy
|
||||
|
||||
if (value !== old) {
|
||||
$tristate.parent().find('b.tristate-changed').css('visibility', 'visible');
|
||||
} else {
|
||||
$tristate.parent().find('b.tristate-changed').css('visibility', 'hidden');
|
||||
}
|
||||
self.icinga.ui.setTriState(value.toString(), $tristate);
|
||||
};
|
||||
|
||||
Icinga.Behaviors.Tristate = Tristate;
|
||||
|
||||
}) (Icinga, jQuery);
|
|
@ -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);
|
|
@ -10,10 +10,6 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var activeMenuId;
|
||||
|
||||
var mouseX, mouseY;
|
||||
|
||||
Icinga.Events = function (icinga) {
|
||||
this.icinga = icinga;
|
||||
|
||||
|
@ -33,14 +29,17 @@
|
|||
*/
|
||||
initialize: function () {
|
||||
this.applyGlobalDefaults();
|
||||
this.applyHandlers($('#layout'));
|
||||
this.icinga.ui.prepareContainers();
|
||||
$('#layout').trigger('rendered');
|
||||
//$('.container').trigger('rendered');
|
||||
$('.container').each(function(idx, el) {
|
||||
icinga.ui.initializeControls($(el));
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: What's this?
|
||||
applyHandlers: function (el) {
|
||||
|
||||
var icinga = this.icinga;
|
||||
applyHandlers: function (evt) {
|
||||
var el = $(evt.target), self = evt.data.self;
|
||||
var icinga = self.icinga;
|
||||
|
||||
$('.dashboard > div', el).each(function(idx, el) {
|
||||
var url = $(el).data('icingaUrl');
|
||||
|
@ -78,93 +77,10 @@
|
|||
|
||||
$('input.autofocus', el).focus();
|
||||
|
||||
// replace all sparklines
|
||||
$('span.sparkline', el).each(function(i, element) {
|
||||
// read custom options
|
||||
var $spark = $(element);
|
||||
var labels = $spark.attr('labels').split('|');
|
||||
var formatted = $spark.attr('formatted').split('|');
|
||||
var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
|
||||
var format = $spark.attr('tooltipformat');
|
||||
var hideEmpty = $spark.attr('hideEmptyLabel') === 'true';
|
||||
$spark.sparkline(
|
||||
'html',
|
||||
{
|
||||
enableTagOptions: true,
|
||||
tooltipFormatter: function (sparkline, options, fields) {
|
||||
var out = format;
|
||||
if (hideEmpty && fields.offset === 3) {
|
||||
return '';
|
||||
}
|
||||
var replace = {
|
||||
title: tooltipChartTitle,
|
||||
label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
|
||||
formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
|
||||
value: fields.value,
|
||||
percent: Math.round(fields.percent * 100) / 100
|
||||
};
|
||||
$.each(replace, function(key, value) {
|
||||
out = out.replace('{{' + key + '}}', value);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
});
|
||||
});
|
||||
var searchField = $('#menu input.search', el);
|
||||
// Remember initial search field value if any
|
||||
if (searchField.length && searchField.val().length) {
|
||||
this.searchValue = searchField.val();
|
||||
}
|
||||
|
||||
$('[title]').each(function () {
|
||||
var $el = $(this);
|
||||
$el.attr('title', $el.data('title-rich') || $el.attr('title'));
|
||||
});
|
||||
$('svg rect.chart-data[title]', el).tipsy({ gravity: 'se', html: true });
|
||||
$('.historycolorgrid a[title]', el).tipsy({ gravity: 's', offset: 2 });
|
||||
$('img.icon[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 });
|
||||
$('[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, delayIn: 500 });
|
||||
|
||||
// migrate or remove all orphaned tooltips
|
||||
$('.tipsy').each(function () {
|
||||
var arrow = $('.tipsy-arrow', this)[0];
|
||||
if (!icinga.utils.elementsOverlap(arrow, $('#main')[0])) {
|
||||
$(this).remove();
|
||||
return;
|
||||
}
|
||||
if (!icinga.utils.elementsOverlap(arrow, el)) {
|
||||
return;
|
||||
}
|
||||
var title = $(this).find('.tipsy-inner').html();
|
||||
var atMouse = document.elementFromPoint(mouseX, mouseY);
|
||||
var nearestTip = $(atMouse)
|
||||
.closest('[original-title="' + title + '"]')[0];
|
||||
if (nearestTip) {
|
||||
var tipsy = $.data(nearestTip, 'tipsy');
|
||||
tipsy.$tip = $(this);
|
||||
$.data(this, 'tipsy-pointee', nearestTip);
|
||||
} else {
|
||||
// doesn't match delete
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
// restore menu state
|
||||
if (activeMenuId) {
|
||||
$('li.active', el).removeClass('active');
|
||||
|
||||
var $selectedMenu = $('#' + activeMenuId, el);
|
||||
var $outerMenu = $selectedMenu.parent().closest('li');
|
||||
if ($outerMenu.size()) {
|
||||
$selectedMenu = $outerMenu;
|
||||
}
|
||||
$selectedMenu.addClass('active');
|
||||
} else {
|
||||
// store menu state
|
||||
var $menus = $('[role="navigation"] li.active', el);
|
||||
if ($menus.size()) {
|
||||
activeMenuId = $menus[0].id;
|
||||
}
|
||||
self.searchValue = searchField.val();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -172,6 +88,13 @@
|
|||
* Global default event handlers
|
||||
*/
|
||||
applyGlobalDefaults: function () {
|
||||
$.each(self.icinga.behaviors, function (name, behavior) {
|
||||
behavior.bind($(document));
|
||||
});
|
||||
|
||||
// Apply element-specific behavior whenever the layout is rendered
|
||||
$(document).on('rendered', { self: this }, this.applyHandlers);
|
||||
|
||||
// We catch resize events
|
||||
$(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize);
|
||||
|
||||
|
@ -203,103 +126,12 @@
|
|||
|
||||
$(document).on('keyup', '#menu input.search', {self: this}, this.autoSubmitSearch);
|
||||
|
||||
$(document).on('mouseenter', '.historycolorgrid td', this.historycolorgridHover);
|
||||
$(document).on('mouseleave', '.historycolorgrid td', this.historycolorgidUnhover);
|
||||
$(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);
|
||||
$(document).on('click', '.tree .handle', { self: this }, this.treeNodeToggle);
|
||||
|
||||
// Toggle all triStateButtons
|
||||
$(document).on('click', 'div.tristate .tristate-dummy', { self: this }, this.clickTriState);
|
||||
|
||||
// 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);
|
||||
|
||||
$(document).on('mousemove', function (event) {
|
||||
mouseX = event.pageX;
|
||||
mouseY = event.pageY;
|
||||
});
|
||||
},
|
||||
|
||||
menuTitleHovered: function (event) {
|
||||
var $li = $(this),
|
||||
delay = 800,
|
||||
self = event.data.self;
|
||||
|
||||
if ($li.hasClass('active')) {
|
||||
$li.siblings().removeClass('hover');
|
||||
return;
|
||||
}
|
||||
if ($li.children('ul').children('li').length === 0) {
|
||||
return;
|
||||
}
|
||||
if ($('#menu').scrollTop() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('#layout').hasClass('hoveredmenu')) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if (! $li.is('li:hover')) {
|
||||
return;
|
||||
}
|
||||
if ($li.hasClass('active')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$li.siblings().each(function () {
|
||||
var $sibling = $(this);
|
||||
if ($sibling.is('li:hover')) {
|
||||
return;
|
||||
}
|
||||
if ($sibling.hasClass('hover')) {
|
||||
$sibling.removeClass('hover');
|
||||
}
|
||||
});
|
||||
|
||||
$('#layout').addClass('hoveredmenu');
|
||||
$li.addClass('hover');
|
||||
}, delay);
|
||||
},
|
||||
|
||||
leaveSidebar: function (event) {
|
||||
var $sidebar = $(this),
|
||||
$li = $sidebar.find('li.hover'),
|
||||
self = event.data.self;
|
||||
if (! $li.length) {
|
||||
$('#layout').removeClass('hoveredmenu');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if ($li.is('li:hover') || $sidebar.is('sidebar:hover') ) {
|
||||
return;
|
||||
}
|
||||
$li.removeClass('hover');
|
||||
$('#layout').removeClass('hoveredmenu');
|
||||
}, 500);
|
||||
},
|
||||
|
||||
dropdownHover: function () {
|
||||
$(this).addClass('hover');
|
||||
},
|
||||
|
||||
dropdownLeave: function (event) {
|
||||
var $li = $(this),
|
||||
self = event.data.self;
|
||||
setTimeout(function () {
|
||||
// TODO: make this behave well together with keyboard navigation
|
||||
if (! $li.is('li:hover') /*&& ! $li.find('a:focus')*/) {
|
||||
$li.removeClass('hover');
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
|
||||
treeNodeToggle: function () {
|
||||
|
@ -313,7 +145,7 @@
|
|||
},
|
||||
|
||||
onLoad: function (event) {
|
||||
$('.container').trigger('rendered');
|
||||
//$('.container').trigger('rendered');
|
||||
},
|
||||
|
||||
onUnload: function (event) {
|
||||
|
@ -330,14 +162,6 @@
|
|||
icinga.ui.fixControls();
|
||||
},
|
||||
|
||||
historycolorgridHover: function () {
|
||||
$(this).addClass('hover');
|
||||
},
|
||||
|
||||
historycolorgidUnhover: function() {
|
||||
$(this).removeClass('hover');
|
||||
},
|
||||
|
||||
autoSubmitSearch: function(event) {
|
||||
var self = event.data.self;
|
||||
if ($('#menu input.search').val() === self.searchValue) {
|
||||
|
@ -351,39 +175,6 @@
|
|||
return event.data.self.submitForm(event, true);
|
||||
},
|
||||
|
||||
clickTriState: function (event) {
|
||||
var self = event.data.self;
|
||||
var $tristate = $(this);
|
||||
var triState = parseInt($tristate.data('icinga-tristate'), 10);
|
||||
|
||||
// load current values
|
||||
var old = $tristate.data('icinga-old').toString();
|
||||
var value = $tristate.parent().find('input:radio:checked').first().prop('checked', false).val();
|
||||
|
||||
// calculate the new value
|
||||
if (triState) {
|
||||
// 1 => 0
|
||||
// 0 => unchanged
|
||||
// unchanged => 1
|
||||
value = value === '1' ? '0' : (value === '0' ? 'unchanged' : '1');
|
||||
} else {
|
||||
// 1 => 0
|
||||
// 0 => 1
|
||||
value = value === '1' ? '0' : '1';
|
||||
}
|
||||
|
||||
// update form value
|
||||
$tristate.parent().find('input:radio[value="' + value + '"]').prop('checked', true);
|
||||
// update dummy
|
||||
|
||||
if (value !== old) {
|
||||
$tristate.parent().find('b.tristate-changed').css('visibility', 'visible');
|
||||
} else {
|
||||
$tristate.parent().find('b.tristate-changed').css('visibility', 'hidden');
|
||||
}
|
||||
self.icinga.ui.setTriState(value.toString(), $tristate);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -527,9 +318,7 @@
|
|||
var $a = $(this);
|
||||
var href = $a.attr('href');
|
||||
var linkTarget = $a.attr('target');
|
||||
var $li;
|
||||
var $target;
|
||||
var isMenuLink = $a.closest('#menu').length > 0;
|
||||
var formerUrl;
|
||||
var remote = /^(?:[a-z]+:)\/\//;
|
||||
if (href.match(/^(mailto|javascript):/)) {
|
||||
|
@ -580,26 +369,9 @@
|
|||
|
||||
// If link has hash tag...
|
||||
if (href.match(/#/)) {
|
||||
// ...it may be a menu section without a dedicated link.
|
||||
// Switch the active menu item:
|
||||
if (isMenuLink) {
|
||||
$li = $a.closest('li');
|
||||
$('#menu .active').removeClass('active');
|
||||
$li.addClass('active');
|
||||
activeMenuId = $($li).attr('id');
|
||||
if ($li.hasClass('hover')) {
|
||||
$li.removeClass('hover');
|
||||
}
|
||||
}
|
||||
if (href === '#') {
|
||||
// Allow to access dropdown menu by keyboard
|
||||
if ($a.hasClass('dropdown-toggle')) {
|
||||
$a.closest('li').toggleClass('hover');
|
||||
}
|
||||
// Ignore link, no action
|
||||
return false;
|
||||
}
|
||||
|
||||
$target = self.getLinkTargetFor($a);
|
||||
|
||||
formerUrl = $target.data('icingaUrl');
|
||||
|
@ -612,21 +384,13 @@
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
if (isMenuLink) {
|
||||
activeMenuId = $(event.target).closest('li').attr('id');
|
||||
}
|
||||
$target = self.getLinkTargetFor($a);
|
||||
}
|
||||
|
||||
// Load link URL
|
||||
icinga.loader.loadUrl(href, $target);
|
||||
|
||||
if (isMenuLink) {
|
||||
// update target url of the menu container to the clicked link
|
||||
var menuDataUrl = icinga.utils.parseUrl($('#menu').data('icinga-url'));
|
||||
menuDataUrl = icinga.utils.addUrlParams(menuDataUrl.path, { url: href });
|
||||
$('#menu').data('icinga-url', menuDataUrl);
|
||||
|
||||
if ($a.closest('#menu').length > 0) {
|
||||
// Menu links should remove all but the first layout column
|
||||
icinga.ui.layout1col();
|
||||
}
|
||||
|
@ -697,6 +461,9 @@
|
|||
*/
|
||||
|
||||
unbindGlobalHandlers: function () {
|
||||
$.each(self.icinga.behaviors, function (name, behavior) {
|
||||
behavior.unbind($(document));
|
||||
});
|
||||
$(window).off('resize', this.onWindowResize);
|
||||
$(window).off('load', this.onLoad);
|
||||
$(window).off('unload', this.onUnload);
|
||||
|
@ -708,12 +475,6 @@
|
|||
$(document).off('submit', 'form', this.submitForm);
|
||||
$(document).off('click', 'button', this.submitForm);
|
||||
$(document).off('change', 'form select.autosubmit', this.submitForm);
|
||||
$(document).off('mouseenter', '.historycolorgrid td', this.historycolorgridHover);
|
||||
$(document).off('mouseleave', '.historycolorgrid td', this.historycolorgidUnhover);
|
||||
$(document).off('mouseenter', 'li.dropdown', this.dropdownHover);
|
||||
$(document).off('mouseleave', 'li.dropdown', this.dropdownLeave);
|
||||
$(document).off('click', 'div.tristate .tristate-dummy', this.clickTriState);
|
||||
$(document).off('mousemove');
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
|
||||
$matches.each(function (idx, el) {
|
||||
if ($(el).closest('#menu').length) {
|
||||
$('#menu .active').removeClass('active');
|
||||
self.icinga.behaviors.navigation.resetActive();
|
||||
} else if ($(el).closest('table.action').length) {
|
||||
$(el).closest('table.action').find('.active').removeClass('active');
|
||||
}
|
||||
|
@ -357,8 +357,7 @@
|
|||
if ($el.is('form')) {
|
||||
$('input', $el).addClass('active');
|
||||
} else {
|
||||
$el.closest('li').addClass('active');
|
||||
$el.parents('li').addClass('active');
|
||||
self.icinga.behaviors.navigation.setActive($el);
|
||||
}
|
||||
// Interrupt .each, only on menu item shall be active
|
||||
return false;
|
||||
|
@ -540,7 +539,6 @@
|
|||
}).addClass('active');
|
||||
}
|
||||
}
|
||||
req.$target.trigger('rendered');
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -726,8 +724,10 @@
|
|||
}
|
||||
|
||||
// TODO: this.icinga.events.refreshContainer(container);
|
||||
$container.trigger('rendered');
|
||||
|
||||
var icinga = this.icinga;
|
||||
icinga.events.applyHandlers($container);
|
||||
//icinga.events.applyHandlers($container);
|
||||
icinga.ui.initializeControls($container);
|
||||
icinga.ui.fixControls();
|
||||
|
||||
|
|
|
@ -282,20 +282,6 @@
|
|||
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
|
||||
);
|
||||
*/
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the given table-row to the selection of the closest
|
||||
* table and deselect all other rows of the closest table.
|
||||
|
|
|
@ -2,23 +2,38 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Web\Hook;
|
||||
|
||||
use Icinga\Web\Hook;
|
||||
|
||||
class TestHook extends Hook {}
|
||||
|
||||
namespace Tests\Icinga\Web;
|
||||
|
||||
use Mockery;
|
||||
use Exception;
|
||||
use Icinga\Web\Hook;
|
||||
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()
|
||||
{
|
||||
throw new Exception();
|
||||
throw new Exception("I'm failing");
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
parent::setUp();
|
||||
|
@ -31,83 +46,28 @@ class HookTest extends BaseTestCase
|
|||
Hook::clean();
|
||||
}
|
||||
|
||||
public function testWhetherHasReturnsTrueIfGivenAKnownHook()
|
||||
public function testKnowsWhichHooksAreRegistered()
|
||||
{
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock(Hook::$BASE_NS . 'TestHook')));
|
||||
|
||||
$this->assertTrue(Hook::has('TestHook'), 'Hook::has does not return true if given a known hook');
|
||||
Hook::register('test', __FUNCTION__, $this->validHook);
|
||||
$this->assertTrue(Hook::has('test'));
|
||||
$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');
|
||||
}
|
||||
|
||||
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));
|
||||
Hook::register('test', 'one', $this->validHook);
|
||||
Hook::register('test', 'two', $this->anotherHook);
|
||||
$this->assertInstanceOf(
|
||||
get_class($secondHook),
|
||||
Hook::createInstance('TestHook', 'two'),
|
||||
'Hook::registerClass is not able to register different hooks of the same type'
|
||||
$this->anotherHook,
|
||||
Hook::createInstance('test', 'two')
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
$this->validHook,
|
||||
Hook::createInstance('test', 'one')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()
|
||||
public function testReturnsNullForInvalidHooks()
|
||||
{
|
||||
$this->assertNull(
|
||||
Hook::createInstance('not_existing', __FUNCTION__),
|
||||
|
@ -115,98 +75,41 @@ class HookTest extends BaseTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceInitializesHooksInheritingFromAPredefinedAbstractHook()
|
||||
public function testReturnsNullForFailingHook()
|
||||
{
|
||||
$baseHook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerClass(
|
||||
'TestHook',
|
||||
__FUNCTION__,
|
||||
get_class(Mockery::mock('overload:' . get_class($baseHook)))
|
||||
);
|
||||
Hook::register('test', __FUNCTION__, $this->failingHook);
|
||||
$this->assertNull(Hook::createInstance('test', __FUNCTION__));
|
||||
}
|
||||
|
||||
public function testChecksWhetherCreatedInstancesInheritBaseClasses()
|
||||
{
|
||||
Hook::register('test', __FUNCTION__, $this->validHook);
|
||||
$this->assertInstanceOf(
|
||||
get_class($baseHook),
|
||||
Hook::createInstance('TestHook', __FUNCTION__),
|
||||
'Hook::createInstance does not initialize hooks inheriting from a predefined abstract hook'
|
||||
$this->testBaseClass,
|
||||
Hook::createInstance('test', __FUNCTION__)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterClass
|
||||
*/
|
||||
public function testWhetherCreateInstanceThrowsAnErrorIfGivenAHookNotInheritingFromAPredefinedAbstractHook()
|
||||
public function testThrowsErrorsForInstancesNotInheritingBaseClasses()
|
||||
{
|
||||
Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerClass('TestHook', __FUNCTION__, get_class(Mockery::mock('TestHook')));
|
||||
|
||||
Hook::createInstance('TestHook', __FUNCTION__);
|
||||
Hook::register('test', __FUNCTION__, $this->invalidHook);
|
||||
Hook::createInstance('test', __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherHooksCanBeRegisteredWithRegisterObject
|
||||
*/
|
||||
public function testWhetherAllReturnsAllRegisteredHooks()
|
||||
public function testCreatesIdenticalInstancesOnlyOnce()
|
||||
{
|
||||
$hook = Mockery::mock(Hook::$BASE_NS . 'TestHook');
|
||||
Hook::registerObject('TestHook', 'one', $hook);
|
||||
Hook::registerObject('TestHook', 'two', $hook);
|
||||
Hook::registerObject('TestHook', 'three', $hook);
|
||||
Hook::register('test', __FUNCTION__, $this->validHook);
|
||||
$first = Hook::createInstance('test', __FUNCTION__);
|
||||
$second = Hook::createInstance('test', __FUNCTION__);
|
||||
|
||||
$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(
|
||||
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'
|
||||
);
|
||||
$this->assertEquals(array(), Hook::all('not_existing'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue