diff --git a/VERSION b/VERSION index 2f8540190..a00bf2444 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.0-beta2 +v2.0.0-beta3 diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 491a33ef3..77b5b66d9 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -11,14 +11,14 @@ use Icinga\Forms\Config\GeneralConfigForm; use Icinga\Forms\Config\ResourceConfigForm; use Icinga\Forms\ConfirmRemovalForm; use Icinga\Security\SecurityException; -use Icinga\Web\Controller\ActionController; +use Icinga\Web\Controller; use Icinga\Web\Notification; use Icinga\Web\Widget; /** * Application and module configuration */ -class ConfigController extends ActionController +class ConfigController extends Controller { /** * The first allowed config action according to the user's permissions @@ -128,8 +128,15 @@ class ConfigController extends ActionController $this->view->modules = Icinga::app()->getModuleManager()->select() ->from('modules') ->order('enabled', 'desc') - ->order('name') - ->paginate(); + ->order('name'); + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->modules); + // TODO: Not working + /*$this->setupSortControl(array( + 'name' => $this->translate('Modulename'), + 'path' => $this->translate('Installation Path'), + 'enabled' => $this->translate('State') + ));*/ } public function moduleAction() diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index a77d36f4f..b1343dc81 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -34,7 +34,11 @@ class ErrorController extends ActionController $path = preg_split('~/~', $path); $path = array_shift($path); $this->getResponse()->setHttpResponseCode(404); - $this->view->message = $this->translate('Page not found.'); + $title = preg_replace('/\r?\n.*$/s', '', $exception->getMessage()); + $this->view->title = 'Server error: ' . $title; + if ($this->getInvokeArg('displayExceptions')) { + $this->view->stackTrace = $exception->getTraceAsString(); + } if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { $this->view->message .= ' ' . sprintf( $this->translate('Enabling the "%s" module might help!'), diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php index feaf13921..4271e385a 100644 --- a/application/controllers/ListController.php +++ b/application/controllers/ListController.php @@ -3,6 +3,8 @@ use Icinga\Module\Monitoring\Controller; use Icinga\Web\Url; +use Icinga\Web\Widget\Tabextension\DashboardAction; +use Icinga\Web\Widget\Tabextension\OutputFormat; use Icinga\Application\Config; use Icinga\Application\Logger; use Icinga\Data\ConfigObject; @@ -29,7 +31,7 @@ class ListController extends Controller 'list/' . str_replace(' ', '', $action) ) - ))->activate($action); + ))->extend(new OutputFormat())->extend(new DashboardAction())->activate($action); } /** @@ -42,15 +44,16 @@ class ListController extends Controller } $this->addTitleTab('application log'); - $pattern = '/^(?[0-9]{4}(-[0-9]{2}){2}' // date - . 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time - . ' - (?[A-Za-z]+)' // loglevel - . ' - (?.*)$/'; $resource = new FileReader(new ConfigObject(array( 'filename' => Config::app()->get('logging', 'file'), - 'fields' => $pattern + 'fields' => '/(?[0-9]{4}(?:-[0-9]{2}){2}' // date + . 'T[0-9]{2}(?::[0-9]{2}){2}(?:[\+\-][0-9]{2}:[0-9]{2})?)' // time + . ' - (?[A-Za-z]+) - (?.*)(?!.)/msS' // loglevel, message ))); - $this->view->logData = $resource->select()->order('DESC')->paginate(); + $this->view->logData = $resource->select()->order('DESC'); + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->logData); } } diff --git a/application/forms/Config/AuthenticationBackendReorderForm.php b/application/forms/Config/AuthenticationBackendReorderForm.php index e4ac90c12..34f20d851 100644 --- a/application/forms/Config/AuthenticationBackendReorderForm.php +++ b/application/forms/Config/AuthenticationBackendReorderForm.php @@ -51,7 +51,7 @@ class AuthenticationBackendReorderForm extends ConfigForm try { if ($configForm->move($backendName, $position)->save()) { - Notification::success($this->translate('Authentication order updated!')); + Notification::success($this->translate('Authentication order updated')); } else { return false; } diff --git a/application/forms/ConfirmRemovalForm.php b/application/forms/ConfirmRemovalForm.php index 985413907..357928cd0 100644 --- a/application/forms/ConfirmRemovalForm.php +++ b/application/forms/ConfirmRemovalForm.php @@ -11,11 +11,11 @@ use Icinga\Web\Form; class ConfirmRemovalForm extends Form { /** - * Initalize this form + * Initialize this form */ public function init() { $this->setName('form_confirm_removal'); - $this->setSubmitLabel($this->translate('Confirm Removal')); + $this->getSubmitLabel() ?: $this->setSubmitLabel($this->translate('Confirm Removal')); } } diff --git a/application/locale/it_IT/LC_MESSAGES/icinga.po b/application/locale/it_IT/LC_MESSAGES/icinga.po index 2b867ca14..73d616bfa 100644 --- a/application/locale/it_IT/LC_MESSAGES/icinga.po +++ b/application/locale/it_IT/LC_MESSAGES/icinga.po @@ -199,8 +199,8 @@ msgid "Authentication backend name missing" msgstr "Nome del Backend di autenticazione non specificato" #: /usr/share/icingaweb2/application/forms/Config/AuthenticationBackendReorderForm.php:54 -msgid "Authentication order updated!" -msgstr "Ordine di autenticazione aggiornato!" +msgid "Authentication order updated" +msgstr "Ordine di autenticazione aggiornato" #: /usr/share/icingaweb2/application/forms/AutoRefreshForm.php:44 msgid "Auto refresh successfully disabled" diff --git a/application/locale/pt_BR/LC_MESSAGES/icinga.po b/application/locale/pt_BR/LC_MESSAGES/icinga.po index 01dc18a9f..16aba2378 100644 --- a/application/locale/pt_BR/LC_MESSAGES/icinga.po +++ b/application/locale/pt_BR/LC_MESSAGES/icinga.po @@ -130,8 +130,8 @@ msgid "Authentication backend name missing" msgstr "Falta o nome do backend de autenticação" #: /usr/local/icingaweb/application/forms/Config/AuthenticationBackendReorderForm.php:55 -msgid "Authentication order updated!" -msgstr "Ordem da autenticação atualizada!" +msgid "Authentication order updated" +msgstr "Ordem da autenticação atualizada" #: /usr/local/icingaweb/application/forms/Config/AuthenticationBackendConfigForm.php:307 msgid "Autologin" diff --git a/application/views/scripts/config/modules.phtml b/application/views/scripts/config/modules.phtml index 9121de405..2abf16fc6 100644 --- a/application/views/scripts/config/modules.phtml +++ b/application/views/scripts/config/modules.phtml @@ -1,8 +1,12 @@ +compact): ?>
-tabs ?> -paginationControl($modules) ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
diff --git a/application/views/scripts/list/applicationlog.phtml b/application/views/scripts/list/applicationlog.phtml index 583fde516..00102e855 100644 --- a/application/views/scripts/list/applicationlog.phtml +++ b/application/views/scripts/list/applicationlog.phtml @@ -1,9 +1,12 @@ +compact): ?>
- tabs->render($this) ?> -
- logData ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
logData !== null): ?>
@@ -16,7 +19,7 @@ escape($value->loglevel) ?> diff --git a/doc/accessibility/link-labels.html b/doc/accessibility/link-labels.html index a65b909de..439adb85e 100644 --- a/doc/accessibility/link-labels.html +++ b/doc/accessibility/link-labels.html @@ -8,7 +8,7 @@ - localhost diff --git a/doc/authentication.md b/doc/authentication.md index 604c85a2e..13a821441 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -107,7 +107,7 @@ Icinga Web 2 uses the MD5 based BSD password algorithm. For generating a passwor command: ```` -openssl passwd -1 "password" +openssl passwd -1 password ```` > Note: The switch to `openssl passwd` is the **number one** (`-1`) for using the MD5 based BSD password algorithm. diff --git a/doc/installation.md b/doc/installation.md index ef968974a..133f6cb98 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -3,21 +3,133 @@ The preferred way of installing Icinga Web 2 is to use the official package repositories depending on which operating system and distribution you are running. But it is also possible to install Icinga Web 2 directly from source. -## Installing Requirements +## Installing Requirements * A web server, e.g. Apache or nginx -* PHP >= 5.3.0 w/ gettext and OpenSSL support -* MySQL or PostgreSQL PHP libraries when using a database for authentication or storing user preferences into a database +* PHP >= 5.3.0 w/ gettext, intl and OpenSSL support +* MySQL or PostgreSQL PHP libraries when using a database for authentication or for storing preferences into a database * LDAP PHP library when using Active Directory or LDAP for authentication -* Icinga 1.x w/ Livestatus or IDO, Icinga 2 w/ Livestatus or IDO feature enabled +* Icinga 1.x w/ Livestatus or IDO; Icinga 2.x w/ Livestatus or IDO feature enabled +* MySQL or PostgreSQL PHP libraries when using IDO -## Installing Icinga Web 2 from Package +## Installing Icinga Web 2 from Package -A guide on how to install Icinga Web 2 from package will follow shortly. +Below is a list of official package repositories for installing Icinga Web 2 for various operating systems. -## Installing Icinga Web 2 from Source +Distribution | Repository +------------------------|--------------------------- +Debian | [debmon](http://debmon.org/packages/debmon-wheezy/icingaweb2), [Icinga Repository](http://packages.icinga.org/debian/) +Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/) +RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/) +openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/) +SLES | [Icinga Repository](http://packages.icinga.org/SUSE/) +Gentoo | - +FreeBSD | - +ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2) -**Step 1: Getting the Source** +Packages for distributions other than the ones listed above may also be available. +Please contact your distribution packagers. + +### Setting up Package Repositories + +You need to add the Icinga repository to your package management configuration for installing Icinga Web 2. +Below is a list with examples for various distributions. + +Debian (debmon): +```` +wget -O - http://debmon.org/debmon/repo.key 2>/dev/null | apt-key add - +echo 'deb http://debmon.org/debmon debmon-wheezy main' >/etc/apt/sources.list.d/debmon.list +apt-get update +```` + +Ubuntu Trusty: +```` +wget -O - http://packages.icinga.org/icinga.key | apt-key add - +add-apt-repository 'deb http://packages.icinga.org/ubuntu icinga-trusty main' +apt-get update +```` + +For other Ubuntu versions just replace trusty with your distribution's code name. + +RHEL and CentOS: +```` +rpm --import http://packages.icinga.org/icinga.key +curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/epel/ICINGA-release.repo +yum makecache +```` + +Fedora: +```` +rpm --import http://packages.icinga.org/icinga.key +curl -o /etc/yum.repos.d/ICINGA-release.repo http://packages.icinga.org/fedora/ICINGA-release.repo +yum makecache +```` + +SLES 11: +```` +zypper ar http://packages.icinga.org/SUSE/ICINGA-release-11.repo +zypper ref +```` + +SLES 12: +```` +zypper ar http://packages.icinga.org/SUSE/ICINGA-release.repo +zypper ref +```` + +openSUSE: +```` +zypper ar http://packages.icinga.org/openSUSE/ICINGA-release.repo +zypper ref +```` + +The packages for RHEL/CentOS depend on other packages which are distributed as part of the +[EPEL repository](http://fedoraproject.org/wiki/EPEL). Please make sure to enable this repository by following +[these instructions](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F). + +### Installing Icinga Web 2 + +You can install Icinga Web 2 by using your distribution's package manager to install the `icingaweb2` package. +Below is a list with examples for various distributions. + +Debian and Ubuntu: +```` +apt-get install icingaweb2 +```` + +RHEL, CentOS and Fedora: +```` +yum install icingaweb2 +```` + +SLES and openSUSE: +```` +zypper install icingaweb2 +```` + +### Preparing Web Setup + +You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time +you visit Icinga Web 2 in your browser. When using the web setup you are required to authenticate using a token. +In order to generate a token use the `icingacli`: +```` +icingacli setup token create +```` + +In case you do not remember the token you can show it using the `icingacli`: +```` +icingacli setup token show +```` + +Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation: +`/icingaweb2/setup`. + +## Installing Icinga Web 2 from Source + +Although the preferred way of installing Icinga Web 2 is to use packages, it is also possible to install Icinga Web 2 +directly from source. + +### Getting the Source First of all, you need to download the sources. Icinga Web 2 is available through a Git repository. You can clone this repository either via git or http protocol using the following URLs: @@ -33,7 +145,7 @@ This version also offers snapshots for easy download which you can use if you do git clone git://git.icinga.org/icingaweb2.git ```` -**Step 2: Install the Source** +### Installing Icinga Web 2 Choose a target directory and move Icinga Web 2 there. @@ -41,7 +153,7 @@ Choose a target directory and move Icinga Web 2 there. mv icingaweb2 /usr/share/icingaweb2 ```` -**Step 3: Configuring the Web Server** +### Configuring the Web Server Use `icingacli` to generate web server configuration for either Apache or nginx. @@ -57,13 +169,15 @@ nginx: Save the output as new file in your webserver's configuration directory. -Example for Apache on RHEL/CentOS: +Example for Apache on RHEL or CentOS: ```` ./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/httpd/conf.d/icingaweb2.conf ```` +### Preparing Web Setup -**Step 4: Preparing Web Setup** +You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time +you visit Icinga Web 2 in your browser. Please follow the steps listed below for preparing the web setup. Because both web and CLI must have access to configuration and logs, permissions will be managed using a special system group. The web server user and CLI user have to be added to this system group. @@ -102,6 +216,7 @@ Use `icingacli` to create the configuration directory which defaults to **/etc/i ./bin/icingacli setup config directory ```` + When using the web setup you are required to authenticate using a token. In order to generate a token use the `icingacli`: ```` @@ -113,11 +228,10 @@ In case you do not remember the token you can show it using the `icingacli`: ./bin/icingacli setup token show ```` -**Step 5: Web Setup** +Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation: +`/icingaweb2/setup`. -Visit Icinga Web 2 in your browser and complete installation using the web setup: /icingaweb2/setup - -## Upgrading to Icinga Web 2 Beta 2 +## Upgrading to Icinga Web 2 Beta 2 Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2, you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following @@ -134,7 +248,7 @@ If you delegated authentication to your web server using the `autologin` backend authentication backend to be able to log in again. The new name better reflects what’s going on. A similar change affects environments that opted for not storing preferences, your new backend is `none`. -## Upgrading to Icinga Web 2 Beta 3 +## Upgrading to Icinga Web 2 Beta 3 Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your configuration files after upgrading to Icinga Web 2 Beta 3. diff --git a/library/Icinga/Application/Logger/Writer/SyslogWriter.php b/library/Icinga/Application/Logger/Writer/SyslogWriter.php index e18c5eeec..4494c9971 100644 --- a/library/Icinga/Application/Logger/Writer/SyslogWriter.php +++ b/library/Icinga/Application/Logger/Writer/SyslogWriter.php @@ -67,6 +67,6 @@ class SyslogWriter extends LogWriter public function log($level, $message) { openlog($this->ident, LOG_PID, $this->facility); - syslog(static::$severityMap[$level], $message); + syslog(static::$severityMap[$level], str_replace("\n", ' ', $message)); } } diff --git a/library/Icinga/Application/Modules/Manager.php b/library/Icinga/Application/Modules/Manager.php index 8f48d7b77..e3e5ec6f2 100644 --- a/library/Icinga/Application/Modules/Manager.php +++ b/library/Icinga/Application/Modules/Manager.php @@ -12,7 +12,6 @@ use Icinga\Exception\ConfigurationError; use Icinga\Exception\SystemPermissionException; use Icinga\Exception\ProgrammingError; use Icinga\Exception\NotReadableError; -use Icinga\Exception\NotFoundError; /** * Module manager that handles detecting, enabling and disabling of modules @@ -102,6 +101,16 @@ class Manager */ private function detectEnabledModules() { + if (! file_exists($parent = dirname($this->enableDir))) { + return; + } + if (! is_readable($parent)) { + throw new NotReadableError( + 'Cannot read enabled modules. Module directory\'s parent directory "%s" is not readable', + $parent + ); + } + if (! file_exists($this->enableDir)) { return; } @@ -199,7 +208,6 @@ class Manager * * @return $this * @throws ConfigurationError When trying to enable a module that is not installed - * @throws NotFoundError In case the "enabledModules" directory does not exist * @throws SystemPermissionException When insufficient permissions for the application exist */ public function enableModule($name) @@ -218,14 +226,15 @@ class Manager if (! is_dir($this->enableDir) && !@mkdir($this->enableDir, 02770, true)) { $error = error_get_last(); throw new SystemPermissionException( - 'Failed to create enabledModule directory "%s" (%s)', + 'Failed to create enabledModules directory "%s" (%s)', $this->enableDir, $error['message'] ); } elseif (! is_writable($this->enableDir)) { throw new SystemPermissionException( - 'Cannot enable module "%s". Insufficient system permissions for enabling modules.', - $name + 'Cannot enable module "%s". Check the permissions for the enabledModules directory: %s', + $name, + $this->enableDir ); } @@ -237,10 +246,11 @@ class Manager $error = error_get_last(); if (strstr($error["message"], "File exists") === false) { throw new SystemPermissionException( - 'Could not enable module "%s" due to file system errors. ' + 'Cannot enable module "%s" at %s due to file system errors. ' . 'Please check path and mounting points because this is not a permission error. ' . 'Primary error was: %s', $name, + $this->enableDir, $error['message'] ); } @@ -259,32 +269,37 @@ class Manager * @return $this * * @throws ConfigurationError When the module is not installed or it's not a symlink - * @throws SystemPermissionException When the module can't be disabled + * @throws SystemPermissionException When insufficient permissions for the application exist */ public function disableModule($name) { if (! $this->hasEnabled($name)) { return $this; } + if (! is_writable($this->enableDir)) { throw new SystemPermissionException( - 'Could not disable module. Module path is not writable.' + 'Cannot disable module "%s". Check the permissions for the enabledModules directory: %s', + $name, + $this->enableDir ); } + $link = $this->enableDir . DIRECTORY_SEPARATOR . $name; if (! file_exists($link)) { throw new ConfigurationError( - 'Could not disable module. The module %s was not found.', + 'Cannot disable module "%s". Module is not installed.', $name ); } if (! is_link($link)) { throw new ConfigurationError( - 'Could not disable module. The module "%s" is not a symlink. ' + 'Cannot disable module %s at %s. ' . 'It looks like you have installed this module manually and moved it to your module folder. ' . 'In order to dynamically enable and disable modules, you have to create a symlink to ' - . 'the enabled_modules folder.', - $name + . 'the enabledModules folder.', + $name, + $this->enableDir ); } @@ -292,10 +307,11 @@ class Manager if (! @unlink($link)) { $error = error_get_last(); throw new SystemPermissionException( - 'Could not disable module "%s" due to file system errors. ' + 'Cannot enable module "%s" at %s due to file system errors. ' . 'Please check path and mounting points because this is not a permission error. ' . 'Primary error was: %s', $name, + $this->enableDir, $error['message'] ); } diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php index a392fa682..858fa742e 100644 --- a/library/Icinga/Authentication/Backend/LdapUserBackend.php +++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php @@ -286,7 +286,6 @@ class LdapUserBackend extends UserBackend $users[] = $row->{$this->userNameAttribute}; } } - return $users; } } diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php index 507c50e3b..9e30cf19e 100644 --- a/library/Icinga/Cli/Params.php +++ b/library/Icinga/Cli/Params.php @@ -167,7 +167,7 @@ class Params * * @throws MissingParameterException If the parameter was not given */ - public function req($name, $strict = true) + public function getRequired($name, $strict = true) { if ($this->has($name)) { $value = $this->get($name); @@ -258,6 +258,30 @@ class Params return $result; } + /** + * Require and remove a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function shiftRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + $this->shift($name); + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + /** * Put the given value onto the argument stack * diff --git a/library/Icinga/Data/Browsable.php b/library/Icinga/Data/Browsable.php deleted file mode 100644 index a734a2680..000000000 --- a/library/Icinga/Data/Browsable.php +++ /dev/null @@ -1,20 +0,0 @@ -column = $column; $this->sign = $sign; $this->expression = $expression; diff --git a/library/Icinga/Data/QueryInterface.php b/library/Icinga/Data/QueryInterface.php index 4d43d1059..5a6d5d5ab 100644 --- a/library/Icinga/Data/QueryInterface.php +++ b/library/Icinga/Data/QueryInterface.php @@ -5,4 +5,4 @@ namespace Icinga\Data; use Countable; -interface QueryInterface extends Browsable, Fetchable, Filterable, Limitable, Sortable, Countable {}; +interface QueryInterface extends Fetchable, Filterable, Limitable, Sortable, Countable {}; diff --git a/library/Icinga/Data/SimpleQuery.php b/library/Icinga/Data/SimpleQuery.php index 16f82cbe4..bf9360799 100644 --- a/library/Icinga/Data/SimpleQuery.php +++ b/library/Icinga/Data/SimpleQuery.php @@ -3,14 +3,12 @@ namespace Icinga\Data; -use Icinga\Application\Icinga; +use ArrayIterator; +use IteratorAggregate; use Icinga\Data\Filter\Filter; -use Icinga\Web\Paginator\Adapter\QueryAdapter; -use Zend_Paginator; -use Exception; use Icinga\Exception\IcingaException; -class SimpleQuery implements QueryInterface, Queryable +class SimpleQuery implements QueryInterface, Queryable, IteratorAggregate { /** * Query data source @@ -91,6 +89,16 @@ class SimpleQuery implements QueryInterface, Queryable */ protected function init() {} + /** + * Return a iterable for this query's result + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->fetchAll()); + } + /** * Get the data source * @@ -319,35 +327,6 @@ class SimpleQuery implements QueryInterface, Queryable return $this->limitOffset; } - /** - * Paginate data - * - * Auto-detects pagination parameters from request when unset - * - * @param int $itemsPerPage Number of items per page - * @param int $pageNumber Current page number - * - * @return Zend_Paginator - */ - public function paginate($itemsPerPage = null, $pageNumber = null) - { - if ($itemsPerPage === null || $pageNumber === null) { - // Detect parameters from request - $request = Icinga::app()->getFrontController()->getRequest(); - if ($itemsPerPage === null) { - $itemsPerPage = $request->getParam('limit', 25); - } - if ($pageNumber === null) { - $pageNumber = $request->getParam('page', 0); - } - } - $this->limit($itemsPerPage, $pageNumber * $itemsPerPage); - $paginator = new Zend_Paginator(new QueryAdapter($this)); - $paginator->setItemCountPerPage($itemsPerPage); - $paginator->setCurrentPageNumber($pageNumber); - return $paginator; - } - /** * Retrieve an array containing all rows of the result set * diff --git a/library/Icinga/File/Csv.php b/library/Icinga/File/Csv.php index ee88262c3..5d9975bc7 100644 --- a/library/Icinga/File/Csv.php +++ b/library/Icinga/File/Csv.php @@ -3,15 +3,13 @@ namespace Icinga\File; -use Icinga\Data\Browsable; - class Csv { protected $query; protected function __construct() {} - public static function fromQuery(Browsable $query) + public static function fromQuery($query) { $csv = new Csv(); $csv->query = $query; diff --git a/library/Icinga/Protocol/File/FileReader.php b/library/Icinga/Protocol/File/FileReader.php index aaef31840..2c43d390a 100644 --- a/library/Icinga/Protocol/File/FileReader.php +++ b/library/Icinga/Protocol/File/FileReader.php @@ -26,6 +26,13 @@ class FileReader implements Selectable, Countable */ protected $filename; + /** + * Cache for static::count() + * + * @var int + */ + protected $count = null; + /** * Create a new reader * @@ -51,7 +58,7 @@ class FileReader implements Selectable, Countable */ public function iterate() { - return new FileIterator($this->filename, $this->fields); + return new LogFileIterator($this->filename, $this->fields); } /** @@ -71,7 +78,10 @@ class FileReader implements Selectable, Countable */ public function count() { - return iterator_count($this->iterate()); + if ($this->count === null) { + $this->count = iterator_count($this->iterate()); + } + return $this->count; } /** diff --git a/library/Icinga/Protocol/File/LogFileIterator.php b/library/Icinga/Protocol/File/LogFileIterator.php new file mode 100644 index 000000000..0815a5376 --- /dev/null +++ b/library/Icinga/Protocol/File/LogFileIterator.php @@ -0,0 +1,155 @@ +file = new SplFileObject($filename); + $this->file->setFlags( + SplFileObject::DROP_NEW_LINE | + SplFileObject::READ_AHEAD + ); + $this->fields = $fields; + } + + public function rewind() + { + $this->file->rewind(); + $this->index = 0; + $this->nextMessage(); + } + + public function next() + { + $this->file->next(); + ++$this->index; + $this->nextMessage(); + } + + /** + * @return string + */ + public function current() + { + return $this->current; + } + + /** + * @return int + */ + public function key() + { + return $this->index; + } + + /** + * @return boolean + */ + public function valid() + { + return $this->valid; + } + + protected function nextMessage() + { + $message = $this->next === null ? array() : array($this->next); + $this->valid = null; + while ($this->file->valid()) { + if (false === ($res = preg_match( + $this->fields, $current = $this->file->current() + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if (empty($message)) { + if ($res === 1) { + $message[] = $current; + } + } else if ($res === 1) { + $this->next = $current; + $this->valid = true; + break; + } else { + $message[] = $current; + } + + $this->file->next(); + } + if ($this->valid === null) { + $this->next = null; + $this->valid = ! empty($message); + } + + if ($this->valid) { + while (! empty($message)) { + $matches = array(); + if (false === ($res = preg_match( + $this->fields, implode(PHP_EOL, $message), $matches + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if ($res === 1) { + $this->current = $matches; + return; + } + array_pop($message); + } + $this->valid = false; + } + } +} diff --git a/library/Icinga/Protocol/Ldap/Capability.php b/library/Icinga/Protocol/Ldap/Capability.php index 625dc98bb..e467884d3 100644 --- a/library/Icinga/Protocol/Ldap/Capability.php +++ b/library/Icinga/Protocol/Ldap/Capability.php @@ -9,7 +9,8 @@ namespace Icinga\Protocol\Ldap; * Provides information about the available encryption mechanisms (StartTLS), the supported * LDAP protocol (v2/v3), vendor-specific extensions or protocols controls and extensions. */ -class Capability { +class Capability +{ const LDAP_SERVER_START_TLS_OID = '1.3.6.1.4.1.1466.20037'; diff --git a/library/Icinga/Protocol/Ldap/Query.php b/library/Icinga/Protocol/Ldap/Query.php index f82a40511..004c2e43e 100644 --- a/library/Icinga/Protocol/Ldap/Query.php +++ b/library/Icinga/Protocol/Ldap/Query.php @@ -281,31 +281,6 @@ class Query return $this->sort_columns; } - /** - * Return a pagination adapter for the current query - * - * @return \Zend_Paginator - */ - public function paginate($limit = null, $page = null) - { - if ($page === null || $limit === null) { - $request = \Zend_Controller_Front::getInstance()->getRequest(); - if ($page === null) { - $page = $request->getParam('page', 0); - } - if ($limit === null) { - $limit = $request->getParam('limit', 20); - } - } - $paginator = new \Zend_Paginator( - // TODO: Adapter doesn't fit yet: - new \Icinga\Web\Paginator\Adapter\QueryAdapter($this) - ); - $paginator->setItemCountPerPage($limit); - $paginator->setCurrentPageNumber($page); - return $paginator; - } - /** * Add a filter expression to this query * diff --git a/library/Icinga/User.php b/library/Icinga/User.php index 18d44af96..f9df0d664 100644 --- a/library/Icinga/User.php +++ b/library/Icinga/User.php @@ -413,30 +413,30 @@ class User /** * Whether the user has a given permission * - * @param string $permission + * @param string $requiredPermission * * @return bool */ - public function can($permission) + public function can($requiredPermission) { - if (isset($this->permissions['*']) || isset($this->permissions[$permission])) { + if (isset($this->permissions['*']) || isset($this->permissions[$requiredPermission])) { return true; } // If the permission to check contains a wildcard, grant the permission if any permit related to the permission // matches - $any = strpos($permission, '*'); - foreach ($this->permissions as $permitted) { - if ($any !== false) { + $any = strpos($requiredPermission, '*'); + foreach ($this->permissions as $grantedPermission) { + if ($any !== false && strpos($grantedPermission, '*') === false) { $wildcard = $any; } else { // If the permit contains a wildcard, grant the permission if it's related to the permit - $wildcard = strpos($permitted, '*'); + $wildcard = strpos($grantedPermission, '*'); } if ($wildcard !== false) { - if (substr($permission, 0, $wildcard) === substr($permitted, 0, $wildcard)) { + if (substr($requiredPermission, 0, $wildcard) === substr($grantedPermission, 0, $wildcard)) { return true; } - } elseif ($permission === $permitted) { + } elseif ($requiredPermission === $grantedPermission) { return true; } } diff --git a/library/Icinga/Util/Color.php b/library/Icinga/Util/Color.php index 5bc2f2815..616bb308a 100644 --- a/library/Icinga/Util/Color.php +++ b/library/Icinga/Util/Color.php @@ -6,7 +6,8 @@ namespace Icinga\Util; /** * Provide functions to change and convert colors. */ -class Color { +class Color +{ /** * Convert a given color string to an rgb-array containing * each color as a decimal value. diff --git a/library/Icinga/Util/String.php b/library/Icinga/Util/String.php index 9fb3cb223..0d42d0e76 100644 --- a/library/Icinga/Util/String.php +++ b/library/Icinga/Util/String.php @@ -79,4 +79,59 @@ class String return $matches; } + + /** + * Check if a string ends with a different string + * + * @param $haystack The string to search for matches + * @param $needle The string to match at the start of the haystack + * + * @return bool Whether or not needle is at the beginning of haystack + */ + public static function endsWith($haystack, $needle) + { + return $needle === '' || + (($temp = strlen($haystack) - strlen($needle)) >= 0 && false !== strpos($haystack, $needle, $temp)); + } + + /** + * Generates an array of strings that constitutes the cartesian product of all passed sets, with all + * string combinations concatenated using the passed join-operator. + * + *
+     *  cartesianProduct(
+     *      array(array('foo', 'bar'), array('mumble', 'grumble', null)),
+     *      '_'
+     *  );
+     *     => array('foo_mumble', 'foo_grumble', 'bar_mumble', 'bar_grumble', 'foo', 'bar')
+     * 
+ * + * @param array $sets An array of arrays containing all sets for which the cartesian + * product should be calculated. + * @param string $glue The glue used to join the strings, defaults to ''. + * + * @returns array The cartesian product in one array of strings. + */ + public static function cartesianProduct(array $sets, $glue = '') + { + $product = null; + foreach ($sets as $set) { + if (! isset($product)) { + $product = $set; + } else { + $newProduct = array(); + foreach ($product as $strA) { + foreach ($set as $strB) { + if ($strB === null) { + $newProduct []= $strA; + } else { + $newProduct []= $strA . $glue . $strB; + } + } + } + $product = $newProduct; + } + } + return $product; + } } diff --git a/library/Icinga/Web/Controller.php b/library/Icinga/Web/Controller.php index e7a1d51f5..0975d4c13 100644 --- a/library/Icinga/Web/Controller.php +++ b/library/Icinga/Web/Controller.php @@ -3,7 +3,12 @@ namespace Icinga\Web; +use Zend_Controller_Action_Exception; +use Icinga\Data\Sortable; +use Icinga\Data\QueryInterface; use Icinga\Web\Controller\ModuleActionController; +use Icinga\Web\Widget\Limiter; +use Icinga\Web\Widget\Paginator; use Icinga\Web\Widget\SortBox; /** @@ -14,17 +19,140 @@ use Icinga\Web\Widget\SortBox; class Controller extends ModuleActionController { /** - * Create a sort control box at the 'sortControl' view parameter - * - * @param array $columns An array containing the sort columns, with the - * submit value as the key and the label as the value + * @see ActionController::init */ - protected function setupSortControl(array $columns) + public function init() { - $req = $this->getRequest(); - $this->view->sortControl = SortBox::create( - 'sortbox-' . $req->getActionName(), - $columns - )->applyRequest($req); + parent::init(); + $this->handleSortControlSubmit(); + } + + /** + * Check whether the sort control has been submitted and redirect using GET parameters + */ + protected function handleSortControlSubmit() + { + $request = $this->getRequest(); + if (! $request->isPost()) { + return; + } + + if (($sort = $request->getPost('sort'))) { + $url = Url::fromRequest(); + $url->setParam('sort', $sort); + if (($dir = $request->getPost('dir'))) { + $url->setParam('dir', $dir); + } else { + $url->removeParam('dir'); + } + + $this->redirectNow($url); + } + } + + /** + * Immediately respond w/ HTTP 404 + * + * @param $message + * + * @throws Zend_Controller_Action_Exception + */ + public function httpNotFound($message) + { + throw new Zend_Controller_Action_Exception($message, 404); + } + + /** + * Create a SortBox widget and apply its sort rules on the given query + * + * The widget is set on the `sortBox' view property only if the current view has not been requested as compact + * + * @param array $columns An array containing the sort columns, with the + * submit value as the key and the label as the value + * @param Sortable $query Query to apply the user chosen sort rules on + * + * @return $this + */ + protected function setupSortControl(array $columns, Sortable $query = null) + { + $request = $this->getRequest(); + $sortBox = SortBox::create('sortbox-' . $request->getActionName(), $columns); + $sortBox->setRequest($request); + + if ($query) { + $sortBox->setQuery($query); + $sortBox->handleRequest($request); + } + + if (! $this->view->compact) { + $this->view->sortBox = $sortBox; + } + + return $this; + } + + /** + * Create a Limiter widget at the `limiter' view property + * + * In case the current view has been requested as compact this method does nothing. + * + * @param int $itemsPerPage Default number of items per page + * + * @return $this + */ + protected function setupLimitControl($itemsPerPage = 25) + { + if (! $this->view->compact) { + $this->view->limiter = new Limiter(); + $this->view->limiter->setDefaultLimit($itemsPerPage); + } + + return $this; + } + + /** + * Apply the given page limit and number on the given query and setup a paginator for it + * + * The $itemsPerPage and $pageNumber parameters are only applied if not available in the current request. + * The paginator is set on the `paginator' view property only if the current view has not been requested as compact. + * + * @param QueryInterface $query The query to create a paginator for + * @param int $itemsPerPage Default number of items per page + * @param int $pageNumber Default page number + * + * @return $this + */ + protected function setupPaginationControl(QueryInterface $query, $itemsPerPage = 25, $pageNumber = 0) + { + $request = $this->getRequest(); + $limit = $request->getParam('limit', $itemsPerPage); + $page = $request->getParam('page', $pageNumber); + $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0); + + if (! $this->view->compact) { + $paginator = new Paginator(); + $paginator->setQuery($query); + $this->view->paginator = $paginator; + } + + return $this; + } + + /** + * Set the view property `filterEditor' to the given FilterEditor + * + * In case the current view has been requested as compact this method does nothing. + * + * @param Form $editor The FilterEditor + * + * @return $this + */ + protected function setupFilterControl($editor) + { + if (! $this->view->compact) { + $this->view->filterEditor = $editor; + } + + return $this; } } diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index 26e0e452a..b78090a7f 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -87,6 +87,7 @@ class ActionController extends Zend_Controller_Action $this->_helper->layout()->isIframe = $request->getUrl()->shift('isIframe'); $this->_helper->layout()->moduleName = false; + $this->view->compact = $request->getParam('view') === 'compact'; if ($this->rerenderLayout = $request->getUrl()->shift('renderLayout')) { $this->xhrLayout = 'body'; } diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index faf77770c..48f7acdb7 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -38,6 +38,28 @@ class Form extends Zend_Form */ const DEFAULT_SUFFIX = '_default'; + /** + * The type of the notification for the error + */ + const NOTIFICATION_ERROR = 0; + + /** + * The type of the notification for the warning + */ + const NOTIFICATION_WARNING = 2; + + /** + * The type of the notification for the info + */ + const NOTIFICATION_INFO = 4; + + /** + * The notifications of the form + * + * @var array + */ + protected $notifications = array(); + /** * Whether this form has been created * @@ -1009,6 +1031,7 @@ class Form extends Zend_Form } $this->addDecorator('FormErrors', array('onlyCustomFormErrors' => true)) + ->addDecorator('FormNotifications') ->addDecorator('FormDescriptions') ->addDecorator('FormElements') //->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')) @@ -1208,4 +1231,57 @@ class Form extends Zend_Form throw new SecurityException('No permission for %s', $permission); } } + + /** + * Return all form notifications + * + * @return array + */ + public function getNotifications() + { + return $this->notifications; + } + + /** + * Add a typed message to the notifications + * + * @param string $message The message which would be displayed to the user + * + * @param int $type The type of the message notification + */ + public function addNotification($message, $type = self::NOTIFICATION_ERROR) + { + $this->notifications[$message] = $type; + $this->markAsError(); + } + + /** + * Add a error message to notifications + * + * @param string $message + */ + public function error($message) + { + $this->addNotification($message, $type = self::NOTIFICATION_ERROR); + } + + /** + * Add a warning message to notifications + * + * @param string $message + */ + public function warning($message) + { + $this->addNotification($message, $type = self::NOTIFICATION_WARNING); + } + + /** + * Add a info message to notifications + * + * @param string $message + */ + public function info($message) + { + $this->addNotification($message, $type = self::NOTIFICATION_INFO); + } } diff --git a/library/Icinga/Web/Form/Decorator/FormNotifications.php b/library/Icinga/Web/Form/Decorator/FormNotifications.php new file mode 100644 index 000000000..ed725e625 --- /dev/null +++ b/library/Icinga/Web/Form/Decorator/FormNotifications.php @@ -0,0 +1,95 @@ +getElement(); + if (! $form instanceof Form) { + return $content; + } + + $view = $form->getView(); + if ($view === null) { + return $content; + } + + $notifications = $this->recurseForm($form); + + if (empty($notifications)) { + return $content; + } + + $html = '
    '; + + asort($notifications); + foreach ($notifications as $message => $type) { + $html .= '
  • ' . $view->escape($message) . '
  • '; + } + + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $html . '
'; + case self::PREPEND: + return $html . '' . $content; + } + } + + /** + * Recurse the given form and return the notifications for it and all of its subforms + * + * @param Form $form The form to recurse + * + * @return array + */ + protected function recurseForm(Form $form) + { + $notifications = $form->getNotifications(); + + foreach ($form->getSubForms() as $subForm) { + $notifications = $notifications + $this->recurseForm($subForm); + } + + return $notifications; + } + + /** + * Get the readable type name of the notification + * + * @param $type Type of the message + * + * @return string + */ + public static function getNotificationTypeName($type) + { + switch ($type) { + case Form::NOTIFICATION_ERROR: + return 'error'; + break; + case Form::NOTIFICATION_WARNING: + return 'warning'; + break; + case Form::NOTIFICATION_INFO: + return 'info'; + break; + default: + return 'unknown'; + } + } +} diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index e95f14c88..476d73879 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -117,13 +117,18 @@ class Menu implements RecursiveIterator foreach ($props as $key => $value) { $method = 'set' . implode('', array_map('ucfirst', explode('_', strtolower($key)))); if ($key === 'renderer') { - $class = '\Icinga\Web\Menu\\' . $value; - if (!class_exists($class)) { - throw new ConfigurationError( - sprintf('ItemRenderer with class "%s" does not exist', $class) - ); + $value = '\\' . ltrim($value, '\\'); + if (class_exists($value)) { + $value = new $value; + } else { + $class = '\Icinga\Web\Menu' . $value; + if (!class_exists($class)) { + throw new ConfigurationError( + sprintf('ItemRenderer with class "%s" does not exist', $class) + ); + } + $value = new $class; } - $value = new $class; } if (method_exists($this, $method)) { $this->{$method}($value); @@ -229,7 +234,8 @@ class Menu implements RecursiveIterator $section = $this->add(t('System'), array( 'icon' => 'wrench', - 'priority' => 200 + 'priority' => 200, + 'renderer' => 'ProblemMenuItemRenderer' )); $section->add(t('Configuration'), array( 'url' => 'config', @@ -464,6 +470,26 @@ class Menu implements RecursiveIterator return $this->permission; } + /** + * Get parent menu + * + * @return \Icinga\Web\Menu + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get submenus + * + * @return array + */ + public function getSubMenus() + { + return $this->subMenus; + } + /** * Set permission a user is required to have granted to display the menu item * diff --git a/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php b/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php index 2ed418bf3..b898b4d08 100644 --- a/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php +++ b/library/Icinga/Web/Menu/ForeignMenuItemRenderer.php @@ -9,15 +9,9 @@ use Icinga\Web\Url; /** * A menu item with a link that surpasses the regular navigation link behavior */ -class ForeignMenuItemRenderer implements MenuItemRenderer { - - public function render(Menu $menu) - { - return sprintf( - '%s%s', - $menu->getUrl() ?: '#', - $menu->getIcon() ? ' ' : '', - htmlspecialchars($menu->getTitle()) - ); - } +class ForeignMenuItemRenderer extends MenuItemRenderer +{ + protected $attributes = array( + 'target' => '_self' + ); } diff --git a/library/Icinga/Web/Menu/MenuItemRenderer.php b/library/Icinga/Web/Menu/MenuItemRenderer.php index 15e6b2930..d40650d91 100644 --- a/library/Icinga/Web/Menu/MenuItemRenderer.php +++ b/library/Icinga/Web/Menu/MenuItemRenderer.php @@ -3,11 +3,108 @@ namespace Icinga\Web\Menu; +use Icinga\Application\Icinga; use Icinga\Web\Menu; +use Icinga\Web\Url; +use Icinga\Web\View; /** - * Renders the html content of a single menu item + * Default MenuItemRenderer class */ -interface MenuItemRenderer { - public function render(Menu $menu); +class MenuItemRenderer +{ + /** + * Contains element specific attributes + * + * @var array + */ + protected $attributes = array(); + + /** + * View + * + * @var View|null + */ + protected $view; + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Renders the html content of a single menu item + * + * @param Menu $menu + * + * @return string + */ + public function render(Menu $menu) + { + return $this->createLink($menu); + } + + /** + * Creates a menu item link element + * + * @param Menu $menu + * + * @return string + */ + public function createLink(Menu $menu) + { + if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) { + return sprintf( + '%s', + $menu->getUrl() ? : '#', + $this->getAttributes(), + $menu->getIcon(), + $this->getView()->escape($menu->getTitle()) + ); + } + + return sprintf( + '%s%s', + $menu->getUrl() ? : '#', + $this->getAttributes(), + $menu->getIcon() ? ' ' : '', + $this->getView()->escape($menu->getTitle()) + ); + } + + /** + * Returns element specific attributes if present + * + * @return string + */ + protected function getAttributes() + { + $attributes = ''; + $view = $this->getView(); + foreach ($this->attributes as $attribute => $value) { + $attributes .= ' ' . $view->escape($attribute) . '="' . $view->escape($value) . '"'; + } + return $attributes; + } } diff --git a/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php b/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php index c72d7718e..010bdc034 100644 --- a/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php +++ b/library/Icinga/Web/Menu/ProblemMenuItemRenderer.php @@ -3,10 +3,62 @@ namespace Icinga\Web\Menu; -class ProblemMenuItemRenderer extends MonitoringMenuItemRenderer +use Icinga\Web\Menu; + +class ProblemMenuItemRenderer extends MenuItemRenderer { - protected $columns = array( - 'hosts_down_unhandled', - 'services_critical_unhandled' - ); + /** + * Set of summarized problems from submenus + * + * @var array + */ + protected $summary = array(); + + /** + * Renders the html content of a single menu item and summarizes submenu problems + * + * @param Menu $menu + * + * @return string + */ + public function render(Menu $menu) + { + if ($menu->getParent() !== null && $menu->hasSubMenus()) { + /** @var $submenu Menu */ + foreach ($menu->getSubMenus() as $submenu) { + $renderer = $submenu->getRenderer(); + if (method_exists($renderer, 'getSummary')) { + if ($renderer->getSummary() !== null) { + $this->summary[] = $renderer->getSummary(); + } + } + } + } + return $this->getBadge() . $this->createLink($menu); + } + + /** + * Get the problem badge + * + * @return string + */ + protected function getBadge() + { + if (count($this->summary) > 0) { + $problems = 0; + $titles = array(); + + foreach ($this->summary as $summary) { + $problems += $summary['problems']; + $titles[] = $summary['title']; + } + + return sprintf( + '
%s
', + implode(', ', $titles), + $problems + ); + } + return ''; + } } diff --git a/library/Icinga/Web/MenuRenderer.php b/library/Icinga/Web/MenuRenderer.php index 7d2cd5ad3..4dec9404e 100644 --- a/library/Icinga/Web/MenuRenderer.php +++ b/library/Icinga/Web/MenuRenderer.php @@ -4,6 +4,7 @@ namespace Icinga\Web; use Exception; +use Icinga\Web\Menu\MenuItemRenderer; use RecursiveIteratorIterator; use Icinga\Application\Logger; use Icinga\Web\Menu\PermittedMenuItemFilter; @@ -32,6 +33,11 @@ class MenuRenderer extends RecursiveIteratorIterator */ protected $useCustomRenderer = false; + /** + * @var MenuItemRenderer + */ + protected $defaultRenderer; + /** * Create a new MenuRenderer * @@ -45,6 +51,7 @@ class MenuRenderer extends RecursiveIteratorIterator } else { $this->url = Url::fromPath($url); } + $this->defaultRenderer = new MenuItemRenderer(); parent::__construct(new PermittedMenuItemFilter($menu), RecursiveIteratorIterator::CHILD_FIRST); } @@ -114,22 +121,8 @@ class MenuRenderer extends RecursiveIteratorIterator Logger::error('Could not invoke custom renderer. Exception: '. $e->getMessage()); } } - if ($child->getIcon() && strpos($child->getIcon(), '.') === false) { - return sprintf( - '
%s', - $child->getUrl() ?: '#', - $child->getIcon(), - htmlspecialchars($child->getTitle()) - ); - } - return sprintf( - '%s%s', - $child->getUrl() ?: '#', - $child->getIcon() - ? ' ' - : '', - htmlspecialchars($child->getTitle()) - ); + + return $this->defaultRenderer->render($child); } /** diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php index f4aa72fa3..7f47763c0 100644 --- a/library/Icinga/Web/Session/PhpSession.php +++ b/library/Icinga/Web/Session/PhpSession.php @@ -77,7 +77,7 @@ class PhpSession extends Session } } - $sessionSavePath = session_save_path(); + $sessionSavePath = session_save_path() ?: sys_get_temp_dir(); if (session_module_name() === 'files' && !is_writable($sessionSavePath)) { throw new ConfigurationError("Can't save session, path '$sessionSavePath' is not writable."); } diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php index 3f1e2b2a7..ab36d78c5 100644 --- a/library/Icinga/Web/StyleSheet.php +++ b/library/Icinga/Web/StyleSheet.php @@ -26,7 +26,8 @@ class StyleSheet 'css/icinga/pagination.less', 'css/icinga/monitoring-colors.less', 'css/icinga/selection-toolbar.less', - 'css/icinga/login.less' + 'css/icinga/login.less', + 'css/icinga/controls.less' ); public static function compileForPdf() diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php index e25fa7e6c..d97f83b39 100644 --- a/library/Icinga/Web/UrlParams.php +++ b/library/Icinga/Web/UrlParams.php @@ -54,7 +54,7 @@ class UrlParams * * @throws MissingParameterException If the parameter was not given */ - public function req($name, $strict = true) + public function getRequired($name, $strict = true) { if ($this->has($name)) { $value = $this->get($name); @@ -138,6 +138,30 @@ class UrlParams return $ret; } + /** + * Require and remove a parameter + * + * @param string $name Name of the parameter + * @param bool $strict Whether the parameter's value must not be the empty string + * + * @return mixed + * + * @throws MissingParameterException If the parameter was not given + */ + public function shiftRequired($name, $strict = true) + { + if ($this->has($name)) { + $value = $this->get($name); + if (! $strict || strlen($value) > 0) { + $this->shift($name); + return $value; + } + } + $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name); + $e->setParameter($name); + throw $e; + } + public function addEncoded($param, $value = true) { $this->params[] = array($param, $this->cleanupValue($value)); diff --git a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php index efaa02a5b..0eb41c574 100644 --- a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php +++ b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php @@ -127,7 +127,8 @@ class HistoryColorGrid extends AbstractWidget { . ' opacity: ' . $this->opacity . ';" ' . 'aria-label="' . $entry['caption'] . '" ' . 'title="' . $entry['caption'] . '" ' . - 'href="' . $entry['url'] . '"' . + 'href="' . $entry['url'] . '" ' . + 'data-tooltip-delay="0"' . '>'; } else { return ' $value) { + if (String::endsWith($key, '_handled') || String::endsWith($key, '_unhandled')) { + $handledUnhandledStates[$key] = $value; + } + } + $chart = new self(array_values($handledUnhandledStates), $title, $colors); + return $chart + ->setSize(50) + ->setTitle('') + ->setSparklineClass('sparkline-multi'); + } } diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php index ad9b1b5b2..2d64e4c2b 100644 --- a/library/Icinga/Web/Widget/FilterEditor.php +++ b/library/Icinga/Web/Widget/FilterEditor.php @@ -6,9 +6,11 @@ namespace Icinga\Web\Widget; use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\FilterExpression; use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filter\FilterOr; use Icinga\Web\Url; use Icinga\Application\Icinga; use Icinga\Exception\ProgrammingError; +use Icinga\Web\Notification; use Exception; /** @@ -39,6 +41,8 @@ class FilterEditor extends AbstractWidget protected $ignoreParams = array(); + protected $searchColumns = null; + /** * @var string */ @@ -73,6 +77,19 @@ class FilterEditor extends AbstractWidget return $this->filter; } + /** + * Set columns to search in + * + * @param array $searchColumns + * + * @return $this + */ + public function setSearchColumns(array $searchColumns) + { + $this->searchColumns = $searchColumns; + return $this; + } + public function setUrl($url) { $this->url = $url; @@ -147,6 +164,26 @@ class FilterEditor extends AbstractWidget return $filter; } + protected function resetSearchColumns(Filter &$filter) + { + if ($filter->isChain()) { + $filters = &$filter->filters(); + if (!($empty = empty($filters))) { + foreach ($filters as $k => &$f) { + if (false === $this->resetSearchColumns($f)) { + unset($filters[$k]); + } + } + } + return $empty || !empty($filters); + } + return $filter->isExpression() ? !( + in_array($filter->getColumn(), $this->searchColumns) + && + $filter->getSign() === '=' + ) : true; + } + public function handleRequest($request) { $this->setUrl($request->getUrl()->without($this->ignoreParams)); @@ -179,33 +216,25 @@ class FilterEditor extends AbstractWidget $filter = $this->getFilter(); if ($search !== null) { - if (strpos($search, '=') === false) { - // TODO: Ask the view for (multiple) search columns - switch($request->getActionName()) { - case 'services': - $searchCol = 'service'; - break; - case 'hosts': - $searchCol = 'host'; - break; - case 'hostgroups': - $searchCol = 'hostgroup'; - break; - case 'servicegroups': - $searchCol = 'servicegroup'; - break; - default: - $searchCol = null; + if (empty($this->searchColumns)) { + if (strpos($search, '=') === false) { + Notification::error(mt('monitoring', 'Cannot search here')); + return $this; + } else { + list($k, $v) = preg_split('/=/', $search); + $filter = $this->mergeRootExpression($filter, trim($k), '=', ltrim($v)); } - - if ($searchCol === null) { - throw new Exception('Cannot search here'); - } - $filter = $this->mergeRootExpression($filter, $searchCol, '=', "*$search*"); - } else { - list($k, $v) = preg_split('/=/', $search); - $filter = $this->mergeRootExpression($filter, $k, '=', $v); + if (false === $this->resetSearchColumns($filter)) { + $filter = Filter::matchAll(); + } + + $filters = array(); + $search = ltrim($search); + foreach ($this->searchColumns as $searchColumn) { + $filters[] = Filter::expression($searchColumn, '=', "*$search*"); + } + $filter->andFilter(new FilterOr($filters)); } $url = $this->url()->setQueryString( @@ -405,10 +434,10 @@ class FilterEditor extends AbstractWidget if ($this->addTo && $this->addTo === $filter->getId()) { return preg_replace( - '/ class="autosubmit"/', - ' class="autofocus"', - $this->selectOperator() - ) + '/ class="autosubmit"/', + ' class="autofocus"', + $this->selectOperator() + ) . '
  • ' . $this->selectColumn($filter) . $this->selectSign($filter) @@ -649,13 +678,13 @@ class FilterEditor extends AbstractWidget public function renderSearch() { - $html = '
    '; - if ($this->filter->isEmpty()) { + if ($this->filter->isEmpty()) { $title = t('Filter this list'); } else { $title = t('Modify this filter'); @@ -678,20 +707,22 @@ class FilterEditor extends AbstractWidget public function render() { if (! $this->preservedUrl()->getParam('modifyFilter')) { - return $this->renderSearch() . $this->shorten($this->filter, 50); + return '
    ' . $this->renderSearch() . $this->shorten($this->filter, 50) . '
    '; } - return $this->renderSearch() - . '
    ' - . '
    • ' - . $this->renderFilter($this->filter) - . '
    ' - . '
    ' - . '' - . '' - . '
    ' - . ''; + return '
    ' + . $this->renderSearch() + . '
    ' + . '
    • ' + . $this->renderFilter($this->filter) + . '
    ' + . '
    ' + . '' + . '' + . '
    ' + . '' + . '
    '; } protected function shorten($string, $length) diff --git a/library/Icinga/Web/Widget/Limiter.php b/library/Icinga/Web/Widget/Limiter.php index 9febf6f63..1c6c82f62 100644 --- a/library/Icinga/Web/Widget/Limiter.php +++ b/library/Icinga/Web/Widget/Limiter.php @@ -21,6 +21,8 @@ class Limiter extends AbstractWidget private $pages; + private $default; + public function setUrl(Url $url) { $this->url = $url; @@ -39,13 +41,19 @@ class Limiter extends AbstractWidget return $this; } + public function setDefaultLimit($limit) + { + $this->default = $limit; + return $this; + } + public function render() { if ($this->url === null) { $this->url = Url::fromRequest(); } - $currentLimit = (int) $this->url->getParam('limit', 25); // Default?? + $currentLimit = (int) $this->url->getParam('limit', $this->default); $availableLimits = array( 10 => '10', 25 => '25', diff --git a/library/Icinga/Web/Widget/Paginator.php b/library/Icinga/Web/Widget/Paginator.php new file mode 100644 index 000000000..dc014e371 --- /dev/null +++ b/library/Icinga/Web/Widget/Paginator.php @@ -0,0 +1,168 @@ +query = $query; + return $this; + } + + /** + * Set the view script to use + * + * @param string|array $script + * + * @return $this + */ + public function setViewScript($script) + { + $this->viewScript = $script; + return $this; + } + + /** + * Render this paginator + */ + public function render() + { + if ($this->query === null) { + throw new ProgrammingError('Need a query to create the paginator widget for'); + } + + $itemCountPerPage = $this->query->getLimit(); + if (! $itemCountPerPage) { + return ''; // No pagination required + } + + $totalItemCount = count($this->query); + $pageCount = (int) ceil($totalItemCount / $itemCountPerPage); + $currentPage = $this->query->hasOffset() ? ($this->query->getOffset() / $itemCountPerPage) + 1 : 1; + $pagesInRange = $this->getPages($pageCount, $currentPage); + $variables = array( + 'totalItemCount' => $totalItemCount, + 'pageCount' => $pageCount, + 'itemCountPerPage' => $itemCountPerPage, + 'first' => 1, + 'current' => $currentPage, + 'last' => $pageCount, + 'pagesInRange' => $pagesInRange, + 'firstPageInRange' => min($pagesInRange), + 'lastPageInRange' => max($pagesInRange) + ); + + if ($currentPage > 1) { + $variables['previous'] = $currentPage - 1; + } + + if ($currentPage < $pageCount) { + $variables['next'] = $currentPage + 1; + } + + if (is_array($this->viewScript)) { + if ($this->viewScript[1] !== null) { + return $this->view()->partial($this->viewScript[0], $this->viewScript[1], $variables); + } + + return $this->view()->partial($this->viewScript[0], $variables); + } + + return $this->view()->partial($this->viewScript, $variables); + } + + /** + * Returns an array of "local" pages given the page count and current page number + * + * @return array + */ + protected function getPages($pageCount, $currentPage) + { + $range = array(); + + if ($pageCount < 10) { + // Show all pages if we have less than 10 + for ($i = 1; $i < 10; $i++) { + if ($i > $pageCount) { + break; + } + + $range[$i] = $i; + } + } else { + // More than 10 pages: + foreach (array(1, 2) as $i) { + $range[$i] = $i; + } + + if ($currentPage < 6 ) { + // We are on page 1-5 from + for ($i = 1; $i <= 7; $i++) { + $range[$i] = $i; + } + } else { + // Current page > 5 + $range[] = '...'; + + if (($pageCount - $currentPage) < 5) { + // Less than 5 pages left + $start = 5 - ($pageCount - $currentPage); + } else { + $start = 1; + } + + for ($i = $currentPage - $start; $i < ($currentPage + (4 - $start)); $i++) { + if ($i > $pageCount) { + break; + } + + $range[$i] = $i; + } + } + + if ($currentPage < ($pageCount - 2)) { + $range[] = '...'; + } + + foreach (array($pageCount - 1, $pageCount) as $i) { + $range[$i] = $i; + } + + } + + if (empty($range)) { + $range[] = 1; + } + + return $range; + } +} diff --git a/library/Icinga/Web/Widget/SortBox.php b/library/Icinga/Web/Widget/SortBox.php index be96b614e..022efdf32 100644 --- a/library/Icinga/Web/Widget/SortBox.php +++ b/library/Icinga/Web/Widget/SortBox.php @@ -3,64 +3,66 @@ namespace Icinga\Web\Widget; -use Zend_Form_Element_Submit; use Icinga\Web\Form; use Icinga\Web\Request; -use Icinga\Web\Form\Decorator\ConditionalHidden; +use Icinga\Data\Sortable; +use Icinga\Application\Icinga; /** - * Sortbox widget + * SortBox widget * - * The "SortBox" Widget allows you to create a generic sort input for sortable views. - * It automatically creates a form containing a select box with all sort options and a - * dropbox with the sort direction. It also handles automatic submission of sorting changes and draws an additional - * submit button when JavaScript is disabled. + * The "SortBox" Widget allows you to create a generic sort input for sortable views. It automatically creates a form + * containing a select box with all sort options and a dropbox with the sort direction. It also handles automatic + * submission of sorting changes and draws an additional submit button when JavaScript is disabled. * - * The constructor takes an string for the component name ad an array containing the select options, where the key is - * the value to be submitted and the value is the label that will be shown. You then should call applyRequest in order - * to make sure the form is correctly populated when a request with a sort parameter is being made. + * The constructor takes an string for the component name and an array containing the select options, where the key is + * the value to be submitted and the value is the label that will be shown. You then should call setRequest in order + * to make sure the form is correctly populated when a request with a sort parameter is being made. * - * Example: - *
    
    + * Example:
    + *  
    
      *      $this->view->sortControl = new SortBox(
    - *      $this->getRequest()->getActionName(),
    + *          $this->getRequest()->getActionName(),
      *          $columns
      *      );
    - *      $this->view->sortControl->applyRequest($this->getRequest());
    - *      
    - * By default the sortBox uses the GET parameter 'sort' for the sorting key and 'dir' for the sorting direction - * + * $this->view->sortControl->setRequest($this->getRequest()); + *
    */ class SortBox extends AbstractWidget { - /** * An array containing all sort columns with their associated labels * * @var array */ - private $sortFields; + protected $sortFields; /** * The name of the form that will be created * * @var string */ - private $name; + protected $name; /** * A request object used for initial form population * - * @var \Icinga\Web\Request + * @var Request */ - private $request; + protected $request; + + /** + * What to apply sort parameters on + * + * @var Sortable + */ + protected $query = null; /** * Create a SortBox with the entries from $sortFields * - * @param string $name The name of the sort form - * @param array $sortFields An array containing the columns and their labels to be displayed - * in the sort select box + * @param string $name The name for the SortBox + * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox */ public function __construct($name, array $sortFields) { @@ -69,13 +71,12 @@ class SortBox extends AbstractWidget } /** - * Create a SortBox with the entries from $sortFields + * Create a SortBox * - * @param string $name The name of the sort form - * @param array $sortFields An array containing the columns and their labels to be displayed - * in the sort select box + * @param string $name The name for the SortBox + * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox * - * @return static + * @return SortBox */ public static function create($name, array $sortFields) { @@ -85,71 +86,84 @@ class SortBox extends AbstractWidget /** * Apply the parameters from the given request on this SortBox * - * @param Request $request The request to use for populating the form + * @param Request $request The request to use for populating the form * - * @return $this + * @return $this */ - public function applyRequest($request) + public function setRequest($request) { $this->request = $request; return $this; } /** - * Create a submit button that is hidden via the ConditionalDecorator - * in order to allow sorting changes to be submitted in a JavaScript-less environment + * @param Sortable $query * - * @return Zend_Form_Element_Submit The submit button that is hidden by default - * @see ConditionalDecorator + * @return $this */ - private function createFallbackSubmitButton() + public function setQuery(Sortable $query) { - $manualSubmitButton = new Zend_Form_Element_Submit( - array( - 'name' => 'submit_' . $this->name, - 'label' => 'Sort', - 'class' => '', - 'condition' => 0, - 'value' => '{{SUBMIT_ICON}}' - ) - ); - $manualSubmitButton->addDecorator(new ConditionalHidden()); - $manualSubmitButton->setAttrib('addLabelPlaceholder', true); - return $manualSubmitButton; + $this->query = $query; + return $this; + } + + public function handleRequest(Request $request = null) + { + if ($this->query !== null) { + if ($request === null) { + $request = Icinga::app()->getFrontController()->getRequest(); + } + if ($sort = $request->getParam('sort')) { + $this->query->order($sort, $request->getParam('dir')); + } + } + return $this; } /** - * Renders this widget via the given view and returns the - * HTML as a string + * Render this SortBox as HTML * * @return string */ public function render() { $form = new Form(); - $form->setAttrib('class', 'inline'); - $form->setMethod('POST'); $form->setTokenDisabled(); $form->setName($this->name); - $form->addElement('select', 'sort', array( - 'label' => 'Sort By', - 'multiOptions' => $this->sortFields, - 'style' => 'width: 12em', - 'autosubmit' => true + $form->setAttrib('class', 'sort-control inline'); + + $form->addElement( + 'select', + 'sort', + array( + 'autosubmit' => true, + 'label' => $this->view()->translate('Sort by'), + 'multiOptions' => $this->sortFields + ) + ); + $form->getElement('sort')->setDecorators(array( + array('ViewHelper'), + array('Label') )); - $form->addElement('select', 'dir', array( - 'multiOptions' => array( - 'asc' => 'Asc', - 'desc' => 'Desc', - ), - 'style' => 'width: 5em', - 'autosubmit' => true - )); - $sort = $form->getElement('sort')->setDecorators(array('ViewHelper')); - $dir = $form->getElement('dir')->setDecorators(array('ViewHelper')); + $form->addElement( + 'select', + 'dir', + array( + 'autosubmit' => true, + 'multiOptions' => array( + 'asc' => 'Asc', + 'desc' => 'Desc', + ), + 'decorators' => array( + array('ViewHelper') + ) + ) + ); + if ($this->request) { $form->populate($this->request->getParams()); } + return $form; } } diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php index ca9bc99be..569bc6f67 100644 --- a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php +++ b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php @@ -24,7 +24,7 @@ class DashboardAction implements Tabextension 'dashboard', array( 'icon' => 'dashboard', - 'label' => 'Add To Dashboard', + 'label' => t('Add To Dashboard'), 'url' => Url::fromPath('dashboard/new-dashlet'), 'urlParams' => array( 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl()) diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php index f05786a0c..f6b2cad2c 100644 --- a/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php +++ b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php @@ -22,7 +22,7 @@ class DashboardSettings implements Tabextension 'dashboard_add', array( 'icon' => 'img/icons/dashboard.png', - 'label' => t('Add To Dashboard'), + 'label' => t('Add New Pane Or Dashlet'), 'url' => Url::fromPath('dashboard/new-dashlet') ) ); diff --git a/library/Icinga/Web/Widget/Tabs.php b/library/Icinga/Web/Widget/Tabs.php index 770f5093f..aa5e7cda6 100644 --- a/library/Icinga/Web/Widget/Tabs.php +++ b/library/Icinga/Web/Widget/Tabs.php @@ -49,7 +49,7 @@ EOT; */ private $closeTpl = <<< 'EOT'
  • EOT; diff --git a/modules/doc/application/views/scripts/index/index.phtml b/modules/doc/application/views/scripts/index/index.phtml index 0b98623ac..67ee52ac1 100644 --- a/modules/doc/application/views/scripts/index/index.phtml +++ b/modules/doc/application/views/scripts/index/index.phtml @@ -1,6 +1,7 @@ -
    +
    showOnlyCloseButton(); ?>

    translate('Available documentations'); ?>

    +
    • qlink( diff --git a/modules/monitoring/application/controllers/AlertsummaryController.php b/modules/monitoring/application/controllers/AlertsummaryController.php index 0d3917e90..1dc3b137b 100644 --- a/modules/monitoring/application/controllers/AlertsummaryController.php +++ b/modules/monitoring/application/controllers/AlertsummaryController.php @@ -6,6 +6,7 @@ use Icinga\Chart\Unit\LinearUnit; use Icinga\Chart\Unit\StaticAxis; use Icinga\Module\Monitoring\Controller; use Icinga\Module\Monitoring\Web\Widget\SelectBox; +use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Url; class Monitoring_AlertsummaryController extends Controller @@ -44,7 +45,7 @@ class Monitoring_AlertsummaryController extends Controller 'label' => $this->translate('Alert Summary'), 'url' => Url::fromRequest() ) - )->activate('alertsummary'); + )->extend(new DashboardAction())->activate('alertsummary'); $this->view->title = $this->translate('Alert Summary'); $this->view->intervalBox = $this->createIntervalBox(); @@ -69,8 +70,10 @@ class Monitoring_AlertsummaryController extends Controller 'notification_state' ) ); + $this->view->notifications = $query; - $this->view->notifications = $query->paginate(); + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); } /** @@ -490,7 +493,7 @@ class Monitoring_AlertsummaryController extends Controller $query->order('notification_start_time', 'desc'); - return $query->paginate(5); + return $query->limit(5); } /** diff --git a/modules/monitoring/application/controllers/ChartController.php b/modules/monitoring/application/controllers/ChartController.php index 08f9334ee..d2e9f990c 100644 --- a/modules/monitoring/application/controllers/ChartController.php +++ b/modules/monitoring/application/controllers/ChartController.php @@ -16,11 +16,6 @@ use Icinga\Chart\Unit\LinearUnit; class Monitoring_ChartController extends Controller { - public function init() - { - $this->view->compact = $this->_request->getParam('view') === 'compact'; - } - private function drawLogChart1() { $chart = new GridChart(); @@ -168,7 +163,7 @@ class Monitoring_ChartController extends Controller public function hostgroupAction() { $query = $this->backend->select()->from( - 'groupsummary', + 'hostgroupsummary', array( 'hostgroup', 'hosts_up', @@ -199,7 +194,7 @@ class Monitoring_ChartController extends Controller public function servicegroupAction() { $query = $this->backend->select()->from( - 'groupsummary', + 'servicegroupsummary', array( 'servicegroup', 'services_ok', diff --git a/modules/monitoring/application/controllers/CommentController.php b/modules/monitoring/application/controllers/CommentController.php new file mode 100644 index 000000000..c3fcd18f8 --- /dev/null +++ b/modules/monitoring/application/controllers/CommentController.php @@ -0,0 +1,100 @@ +params->get('comment_id'); + + $this->comment = $this->backend->select()->from('comment', array( + 'id' => 'comment_internal_id', + 'objecttype' => 'comment_objecttype', + 'comment' => 'comment_data', + 'author' => 'comment_author_name', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type', + 'persistent' => 'comment_is_persistent', + 'expiration' => 'comment_expiration', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->where('comment_internal_id', $commentId)->getQuery()->fetchRow(); + + if (false === $this->comment) { + throw new Zend_Controller_Action_Exception($this->translate('Comment not found')); + } + + $this->getTabs()->add( + 'comment', + array( + 'title' => $this->translate( + 'Display detailed information about a comment.' + ), + 'icon' => 'comment', + 'label' => $this->translate('Comment'), + 'url' =>'monitoring/comments/show' + ) + )->activate('comment')->extend(new DashboardAction()); + } + + /** + * Display comment detail view + */ + public function showAction() + { + $listCommentsLink = Url::fromPath('monitoring/list/comments') + ->setQueryString('comment_type=(comment|ack)'); + + $this->view->comment = $this->comment; + if ($this->hasPermission('monitoring/command/comment/delete')) { + $this->view->delCommentForm = $this->createDelCommentForm(); + $this->view->delCommentForm->populate( + array( + 'redirect' => $listCommentsLink, + 'comment_id' => $this->comment->id, + 'comment_is_service' => isset($this->comment->service_description) + ) + ); + } + } + + /** + * Create a command form to delete a single comment + * + * @return DeleteCommentsCommandForm + */ + private function createDelCommentForm() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $delCommentForm = new DeleteCommentCommandForm(); + $delCommentForm->setAction( + Url::fromPath('monitoring/comment/show') + ->setParam('comment_id', $this->comment->id) + ); + $delCommentForm->handleRequest(); + return $delCommentForm; + } +} diff --git a/modules/monitoring/application/controllers/CommentsController.php b/modules/monitoring/application/controllers/CommentsController.php new file mode 100644 index 000000000..e1092b033 --- /dev/null +++ b/modules/monitoring/application/controllers/CommentsController.php @@ -0,0 +1,100 @@ +filter = Filter::fromQueryString(str_replace( + 'comment_id', + 'comment_internal_id', + (string)$this->params + )); + $this->comments = $this->backend->select()->from('comment', array( + 'id' => 'comment_internal_id', + 'objecttype' => 'comment_objecttype', + 'comment' => 'comment_data', + 'author' => 'comment_author_name', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type', + 'persistent' => 'comment_is_persistent', + 'expiration' => 'comment_expiration', + 'host_name', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->addFilter($this->filter)->getQuery()->fetchAll(); + + if (false === $this->comments) { + throw new Zend_Controller_Action_Exception($this->translate('Comment not found')); + } + + $this->getTabs()->add( + 'comments', + array( + 'title' => $this->translate( + 'Display detailed information about multiple comments.' + ), + 'icon' => 'comment', + 'label' => $this->translate('Comments') . sprintf(' (%d)', count($this->comments)), + 'url' =>'monitoring/comments/show' + ) + )->activate('comments'); + } + + /** + * Display the detail view for a comment list + */ + public function showAction() + { + $this->view->comments = $this->comments; + $this->view->listAllLink = Url::fromPath('monitoring/list/comments') + ->setQueryString($this->filter->toQueryString()); + $this->view->removeAllLink = Url::fromPath('monitoring/comments/delete-all') + ->setParams($this->params); + } + + /** + * Display the form for removing a comment list + */ + public function deleteAllAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $listCommentsLink = Url::fromPath('monitoring/list/comments') + ->setQueryString('comment_type=(comment|ack)'); + $delCommentForm = new DeleteCommentsCommandForm(); + $delCommentForm->setTitle($this->view->translate('Remove all Comments')); + $delCommentForm->addDescription(sprintf( + $this->translate('Confirm removal of %d comments.'), + count($this->comments) + )); + $delCommentForm->setComments($this->comments) + ->setRedirectUrl($listCommentsLink) + ->handleRequest(); + $this->view->delCommentForm = $delCommentForm; + $this->view->comments = $this->comments; + $this->view->listAllLink = Url::fromPath('monitoring/list/comments') + ->setQueryString($this->filter->toQueryString()); + } +} diff --git a/modules/monitoring/application/controllers/DowntimeController.php b/modules/monitoring/application/controllers/DowntimeController.php new file mode 100644 index 000000000..c06e0311d --- /dev/null +++ b/modules/monitoring/application/controllers/DowntimeController.php @@ -0,0 +1,141 @@ +params->get('downtime_id'); + + $this->downtime = $this->backend->select()->from('downtime', array( + 'id' => 'downtime_internal_id', + 'objecttype' => 'downtime_objecttype', + 'comment' => 'downtime_comment', + 'author_name' => 'downtime_author_name', + 'start' => 'downtime_start', + 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', + 'end' => 'downtime_end', + 'duration' => 'downtime_duration', + 'is_flexible' => 'downtime_is_flexible', + 'is_fixed' => 'downtime_is_fixed', + 'is_in_effect' => 'downtime_is_in_effect', + 'entry_time' => 'downtime_entry_time', + 'host_state' => 'downtime_host_state', + 'service_state' => 'downtime_service_state', + 'host_name', + 'host', + 'service', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->where('downtime_internal_id', $downtimeId)->getQuery()->fetchRow(); + + if (false === $this->downtime) { + throw new Zend_Controller_Action_Exception($this->translate('Downtime not found')); + } + + if (isset($this->downtime->service_description)) { + $this->isService = true; + } else { + $this->isService = false; + } + + $this->getTabs() + ->add( + 'downtime', + array( + 'title' => $this->translate( + 'Display detailed information about a downtime.' + ), + 'icon' => 'plug', + 'label' => $this->translate('Downtime'), + 'url' =>'monitoring/downtimes/show' + ) + )->activate('downtime')->extend(new DashboardAction()); + } + + /** + * Display the detail view for a downtime + */ + public function showAction() + { + $this->view->downtime = $this->downtime; + $this->view->isService = $this->isService; + $this->view->stateName = isset($this->downtime->service_description) ? + Service::getStateText($this->downtime->service_state) : + Host::getStateText($this->downtime->host_state); + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes'); + $this->view->showHostLink = Url::fromPath('monitoring/host/show') + ->setParam('host', $this->downtime->host); + $this->view->showServiceLink = Url::fromPath('monitoring/service/show') + ->setParam('host', $this->downtime->host) + ->setParam('service', $this->downtime->service_description); + if ($this->hasPermission('monitoring/command/downtime/delete')) { + $this->view->delDowntimeForm = $this->createDelDowntimeForm(); + $this->view->delDowntimeForm->populate( + array( + 'redirect' => Url::fromPath('monitoring/list/downtimes'), + 'downtime_id' => $this->downtime->id, + 'downtime_is_service' => $this->isService + ) + ); + } + } + + /** + * Receive DeleteDowntimeCommandForm post from other controller + */ + public function removeAction() + { + $this->assertHttpMethod('POST'); + $this->createDelDowntimeForm(); + } + + /** + * Create a command form to delete a single comment + * + * @return DeleteDowntimeCommandForm + */ + private function createDelDowntimeForm() + { + $this->assertPermission('monitoring/command/downtime/delete'); + $delDowntimeForm = new DeleteDowntimeCommandForm(); + $delDowntimeForm->setAction( + Url::fromPath('monitoring/downtime/show') + ->setParam('downtime_id', $this->downtime->id) + ); + $delDowntimeForm->handleRequest(); + return $delDowntimeForm; + } +} diff --git a/modules/monitoring/application/controllers/DowntimesController.php b/modules/monitoring/application/controllers/DowntimesController.php new file mode 100644 index 000000000..4ba872bbd --- /dev/null +++ b/modules/monitoring/application/controllers/DowntimesController.php @@ -0,0 +1,130 @@ +filter = Filter::fromQueryString(str_replace( + 'downtime_id', + 'downtime_internal_id', + (string)$this->params + )); + $this->downtimes = $this->backend->select()->from('downtime', array( + 'id' => 'downtime_internal_id', + 'objecttype' => 'downtime_objecttype', + 'comment' => 'downtime_comment', + 'author_name' => 'downtime_author_name', + 'start' => 'downtime_start', + 'scheduled_start' => 'downtime_scheduled_start', + 'scheduled_end' => 'downtime_scheduled_end', + 'end' => 'downtime_end', + 'duration' => 'downtime_duration', + 'is_flexible' => 'downtime_is_flexible', + 'is_fixed' => 'downtime_is_fixed', + 'is_in_effect' => 'downtime_is_in_effect', + 'entry_time' => 'downtime_entry_time', + 'host_state' => 'downtime_host_state', + 'service_state' => 'downtime_service_state', + 'host_name', + 'host', + 'service', + 'service_description', + 'host_display_name', + 'service_display_name' + ))->addFilter($this->filter)->getQuery()->fetchAll(); + + if (false === $this->downtimes) { + throw new Zend_Controller_Action_Exception( + $this->translate('Downtime not found') + ); + } + + $this->getTabs()->add( + 'downtimes', + array( + 'title' => $this->translate( + 'Display detailed information about multiple downtimes.' + ), + 'icon' => 'plug', + 'label' => $this->translate('Downtimes') . sprintf(' (%d)', count($this->downtimes)), + 'url' =>'monitoring/downtimes/show' + ) + )->activate('downtimes'); + + foreach ($this->downtimes as $downtime) { + if (isset($downtime->service_description)) { + $downtime->isService = true; + } else { + $downtime->isService = false; + } + + if ($downtime->isService) { + $downtime->stateText = Service::getStateText($downtime->service_state); + } else { + $downtime->stateText = Host::getStateText($downtime->host_state); + } + } + } + + /** + * Display the detail view for a downtime list + */ + public function showAction() + { + $this->view->downtimes = $this->downtimes; + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString($this->filter->toQueryString()); + $this->view->removeAllLink = Url::fromPath('monitoring/downtimes/delete-all') + ->setParams($this->params); + } + + /** + * Display the form for removing a downtime list + */ + public function deleteAllAction() + { + $this->assertPermission('monitoring/command/downtime/delete'); + $this->view->downtimes = $this->downtimes; + $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString($this->filter->toQueryString()); + $delDowntimeForm = new DeleteDowntimesCommandForm(); + $delDowntimeForm->setTitle($this->view->translate('Remove all Downtimes')); + $delDowntimeForm->addDescription(sprintf( + $this->translate('Confirm removal of %d downtimes.'), + count($this->downtimes) + )); + $delDowntimeForm->setRedirectUrl(Url::fromPath('monitoring/list/downtimes')); + $delDowntimeForm->setDowntimes($this->downtimes)->handleRequest(); + $this->view->delDowntimeForm = $delDowntimeForm; + } +} \ No newline at end of file diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php index 5b336a26b..7693023eb 100644 --- a/modules/monitoring/application/controllers/HostController.php +++ b/modules/monitoring/application/controllers/HostController.php @@ -1,6 +1,7 @@ backend, $this->params->get('host_name')); + if ($this->params->get('host') === null) { + throw new MissingParameterException( + $this->translate('Required parameter \'%s\' is missing'), + 'host' + ); + } + + $host = new Host($this->backend, $this->params->get('host')); $this->applyRestriction('monitoring/hosts/filter', $host); if ($host->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Host not found')); + throw new Zend_Controller_Action_Exception( + sprintf($this->translate('Host \'%s\' not found'), $this->params->get('host')), + 404 + ); } $this->object = $host; $this->createTabs(); $this->getTabs()->activate('host'); } + /** + * Get host actions from hook + * + * @return array + */ protected function getHostActions() { $urls = array(); @@ -56,7 +72,7 @@ class Monitoring_HostController extends MonitoredObjectController */ public function showAction() { - $this->view->hostActions = $this->getHostActions(); + $this->view->actions = $this->getHostActions(); parent::showAction(); } diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 27d26e51b..ac55ade9b 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -2,6 +2,7 @@ /* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterEqual; use Icinga\Module\Monitoring\Controller; use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm; @@ -10,11 +11,11 @@ use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostCheckCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm; -use Icinga\Module\Monitoring\Object\Host; use Icinga\Module\Monitoring\Object\HostList; use Icinga\Web\Url; -use Icinga\Web\Widget\Chart\InlinePie; +use Icinga\Web\Widget\Tabextension\DashboardAction; class Monitoring_HostsController extends Controller { @@ -25,51 +26,14 @@ class Monitoring_HostsController extends Controller public function init() { + // Support switching from service-view using the host and service selection. The filter would error + // on any occurrence of a filter based on service. + $filterString = preg_replace('/(service=[^)&]*)/', '', (string)$this->params); + $hostList = new HostList($this->backend); - $hostList->setFilter(Filter::fromQueryString((string) $this->params)); + $hostList->setFilter(Filter::fromQueryString((string) $this->params->without('view'))); $this->hostList = $hostList; - } - protected function handleCommandForm(ObjectsCommandForm $form) - { - $this->hostList->setColumns(array( - 'host_name', - 'host_state', - 'host_problem', - 'host_handled', - 'host_acknowledged', - 'host_in_downtime' - )); - - $form - ->setObjects($this->hostList) - ->setRedirectUrl(Url::fromPath('monitoring/hosts/show')->setParams($this->params)) - ->handleRequest(); - - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->hostList as $host) { - ++$hostStates[$host::getStateText($host->state)]; - } - - $this->view->form = $form; - $this->view->objects = $this->hostList; - $this->view->hostStates = $hostStates; - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') - ); - $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); - return $form; - } - - public function showAction() - { $this->getTabs()->add( 'show', array( @@ -77,10 +41,53 @@ class Monitoring_HostsController extends Controller $this->translate('Show summarized information for %u hosts'), count($this->hostList) ), - 'label' => $this->translate('Hosts'), - 'url' => Url::fromRequest() + 'label' => $this->translate('Hosts') . sprintf(' (%d)', count($this->hostList)), + 'url' => Url::fromRequest(), + 'icon' => 'host' ) - )->activate('show'); + )->extend(new DashboardAction())->activate('show'); + + $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts')->setQueryString($filterString); + } + + protected function handleCommandForm(ObjectsCommandForm $form) + { + $this->hostList->setColumns(array( + 'host_icon_image', + 'host_name', + 'host_state', + 'host_problem', + 'host_handled', + 'host_acknowledged', + 'host_in_downtime', + 'host_last_ack', + 'host_is_flapping', + 'host_last_comment', + 'host_output', + 'host_notifications_enabled', + 'host_active_checks_enabled', + 'host_passive_checks_enabled' + )); + + $form + ->setObjects($this->hostList) + ->setRedirectUrl(Url::fromPath('monitoring/hosts/show')->setParams($this->params)) + ->handleRequest(); + + $this->view->form = $form; + $this->view->objects = $this->hostList; + $this->view->stats = $this->hostList->getStateSummary(); + $this->view->hostStatesPieChart = InlinePie::createFromStateSummary( + $this->view->stats, + $this->translate('Host State'), + InlinePie::$colorsHostStatesHandledUnhandled + ); + $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); + return $form; + } + + public function showAction() + { $this->setAutorefreshInterval(15); $checkNowForm = new CheckNowCommandForm(); $checkNowForm @@ -88,45 +95,26 @@ class Monitoring_HostsController extends Controller ->handleRequest(); $this->view->checkNowForm = $checkNowForm; $this->hostList->setColumns(array( + 'host_icon_image', 'host_name', 'host_state', 'host_problem', 'host_handled', 'host_acknowledged', - 'host_in_downtime'/*, - 'host_passive_checks_enabled', + 'host_in_downtime', + 'host_last_ack', + 'host_is_flapping', + 'host_last_comment', + 'host_output', 'host_notifications_enabled', - 'host_event_handler_enabled', - 'host_flap_detection_enabled', 'host_active_checks_enabled', + 'host_passive_checks_enabled' + /*'host_event_handler_enabled', + 'host_flap_detection_enabled', 'host_obsessing'*/ )); - $unhandledObjects = array(); - $unhandledFilterExpressions = array(); - $acknowledgedObjects = array(); - $objectsInDowntime = array(); - $downtimeFilterExpressions = array(); - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->hostList as $host) { - /** @var Host $host */ - if ((bool) $host->problem === true && (bool) $host->handled === false) { - $unhandledObjects[] = $host; - $unhandledFilterExpressions[] = Filter::where('host', $host->getName()); - } - if ((bool) $host->acknowledged === true) { - $acknowledgedObjects[] = $host; - } - if ((bool) $host->in_downtime === true) { - $objectsInDowntime[] = $host; - $downtimeFilterExpressions[] = Filter::where('host_name', $host->getName()); - } - ++$hostStates[$host::getStateText($host->state)]; - } + + $acknowledgedObjects = $this->hostList->getAcknowledgedObjects(); if (! empty($acknowledgedObjects)) { $removeAckForm = new RemoveAcknowledgementCommandForm(); $removeAckForm @@ -134,43 +122,68 @@ class Monitoring_HostsController extends Controller ->handleRequest(); $this->view->removeAckForm = $removeAckForm; } + + $hostStates = (object)$this->hostList->getStateSummary(); + $this->setAutorefreshInterval(15); - $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts'); $this->view->rescheduleAllLink = Url::fromRequest()->setPath('monitoring/hosts/reschedule-check'); $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/hosts/schedule-downtime'); $this->view->processCheckResultAllLink = Url::fromRequest()->setPath('monitoring/hosts/process-check-result'); - $this->view->hostStates = $hostStates; + $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/hosts/add-comment'); + $this->view->stats = $hostStates; $this->view->objects = $this->hostList; - $this->view->unhandledObjects = $unhandledObjects; - $unhandledFilterQueryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString(); + $this->view->unhandledObjects = $this->hostList->getUnhandledObjects(); + $this->view->problemObjects = $this->hostList->getProblemObjects(); + $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem') - ->setQueryString($unhandledFilterQueryString); + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()); $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime') - ->setQueryString($unhandledFilterQueryString); - $this->view->acknowledgedObjects = $acknowledgedObjects; - $this->view->objectsInDowntime = $objectsInDowntime; - $this->view->inDowntimeLink = Url::fromPath('monitoring/list/downtimes') - ->setQueryString(Filter::matchAny($downtimeFilterExpressions)->toQueryString()); - $this->view->commentsLink = Url::fromRequest() - ->setPath('monitoring/list/comments'); - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') - ); - $this->view->sendCustomNotificationLink = - Url::fromRequest()->setPath( - 'monitoring/hosts/send-custom-notification' + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()); + $this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime') + ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()); + $this->view->acknowledgedObjects = $this->hostList->getAcknowledgedObjects(); + $this->view->objectsInDowntime = $this->hostList->getObjectsInDowntime(); + $this->view->inDowntimeLink = Url::fromPath('monitoring/list/hosts') + ->setQueryString( + $this->hostList + ->getObjectsInDowntime() + ->objectsFilter() + ->toQueryString() ); + $this->view->showDowntimesLink = Url::fromPath('monitoring/list/downtimes') + ->setQueryString( + $this->hostList + ->objectsFilter() + ->andFilter(FilterEqual::where('downtime_objecttype', 'host')) + ->toQueryString() + ); + $this->view->commentsLink = Url::fromRequest()->setPath('monitoring/list/comments'); + $this->view->baseFilter = $this->hostList->getFilter(); + $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath('monitoring/hosts/send-custom-notification'); } - protected function createPieChart(array $states, $title, array $colors) + /** + * Add a host comments + */ + public function addCommentAction() { - $chart = new InlinePie(array_values($states), $title, $colors); - return $chart - ->setSize(75) - ->setTitle('') - ->setSparklineClass('sparkline-multi'); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Host Comments')); + $this->handleCommandForm($form); + } + + /** + * Delete a comment + */ + public function deleteCommentAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $form = new DeleteCommentCommandForm(); + $form->setTitle($this->translate('Delete Host Comments')); + $this->handleCommandForm($form); } /** diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index b4167f83d..c08acf2ad 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -12,6 +12,7 @@ use Icinga\Web\Widget\Tabs; use Icinga\Data\Filter\Filter; use Icinga\Web\Widget; use Icinga\Module\Monitoring\Forms\StatehistoryForm; +use Icinga\Module\Monitoring\DataView\DataView; class Monitoring_ListController extends Controller { @@ -20,12 +21,8 @@ class Monitoring_ListController extends Controller */ public function init() { + parent::init(); $this->createTabs(); - $this->view->compact = $this->_request->getParam('view') === 'compact'; - if ($this->_request->getParam('view') === 'inline') { - $this->view->compact = true; - $this->view->inline = true; - } } /** @@ -46,25 +43,6 @@ class Monitoring_ListController extends Controller return $query; } - protected function hasBetterUrl() - { - $request = $this->getRequest(); - $url = Url::fromRequest(); - - if ($this->getRequest()->isPost()) { - if ($request->getPost('sort')) { - $url->setParam('sort', $request->getPost('sort')); - if ($request->getPost('dir')) { - $url->setParam('dir', $request->getPost('dir')); - } else { - $url->removeParam('dir'); - } - return $url; - } - } - return false; - } - /** * Overwrite the backend to use (used for testing) * @@ -80,10 +58,6 @@ class Monitoring_ListController extends Controller */ public function hostsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - // Handle soft and hard states if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { $stateColumn = 'host_hard_state'; @@ -121,20 +95,10 @@ class Monitoring_ListController extends Controller 'host_passive_checks_enabled', 'host_current_check_attempt', 'host_max_check_attempts' - ), $this->extraColumns())); - + ), $this->addColumns())); $this->filterQuery($query); - $this->applyRestriction('monitoring/hosts/filter', $query); - - $this->setupSortControl(array( - 'host_severity' => $this->translate('Severity'), - 'host_state' => $this->translate('Current State'), - 'host_display_name' => $this->translate('Hostname'), - 'host_address' => $this->translate('Address'), - 'host_last_check' => $this->translate('Last Check') - )); - $this->view->hosts = $query->paginate(); + $this->view->hosts = $query; $this->view->stats = $this->backend->select()->from('statusSummary', array( 'hosts_total', @@ -147,6 +111,16 @@ class Monitoring_ListController extends Controller 'hosts_unreachable_unhandled', 'hosts_pending', ))->getQuery()->fetchRow(); + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->hosts); + $this->setupSortControl(array( + 'host_severity' => $this->translate('Severity'), + 'host_state' => $this->translate('Current State'), + 'host_display_name' => $this->translate('Hostname'), + 'host_address' => $this->translate('Address'), + 'host_last_check' => $this->translate('Last Check') + ), $query); } /** @@ -154,10 +128,6 @@ class Monitoring_ListController extends Controller */ public function servicesAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } - // Handle soft and hard states if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { $stateColumn = 'service_hard_state'; @@ -208,13 +178,14 @@ class Monitoring_ListController extends Controller 'service_passive_checks_enabled', 'current_check_attempt' => 'service_current_check_attempt', 'max_check_attempts' => 'service_max_check_attempts' - ), $this->extraColumns()); + ), $this->addColumns()); $query = $this->backend->select()->from('serviceStatus', $columns); - $this->filterQuery($query); - $this->applyRestriction('monitoring/services/filter', $query); + $this->view->services = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->services); $this->setupSortControl(array( 'service_severity' => $this->translate('Service Severity'), 'service_state' => $this->translate('Current Service State'), @@ -225,15 +196,7 @@ class Monitoring_ListController extends Controller 'host_display_name' => $this->translate('Hostname'), 'host_address' => $this->translate('Host Address'), 'host_last_check' => $this->translate('Last Host Check') - )); - $limit = $this->params->get('limit'); - $this->view->limit = $limit; - if ($limit === 0) { - $this->view->services = $query->getQuery()->fetchAll(); - } else { - // TODO: Workaround, paginate should be able to fetch limit from new params - $this->view->services = $query->paginate($this->params->get('limit')); - } + ), $query); $this->view->stats = $this->backend->select()->from('statusSummary', array( 'services_total', @@ -252,7 +215,6 @@ class Monitoring_ListController extends Controller 'services_unknown_handled', 'services_pending', ))->getQuery()->fetchRow(); - } /** @@ -260,11 +222,9 @@ class Monitoring_ListController extends Controller */ public function downtimesAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('downtimes', $this->translate('Downtimes'), $this->translate('List downtimes')); $this->setAutorefreshInterval(12); + $query = $this->backend->select()->from('downtime', array( 'id' => 'downtime_internal_id', 'objecttype' => 'downtime_objecttype', @@ -286,9 +246,11 @@ class Monitoring_ListController extends Controller 'host_display_name', 'service_display_name' )); - $this->filterQuery($query); + $this->view->downtimes = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->downtimes); $this->setupSortControl(array( 'downtime_is_in_effect' => $this->translate('Is In Effect'), 'host_display_name' => $this->translate('Host'), @@ -300,12 +262,11 @@ class Monitoring_ListController extends Controller 'downtime_scheduled_start' => $this->translate('Scheduled Start'), 'downtime_scheduled_end' => $this->translate('Scheduled End'), 'downtime_duration' => $this->translate('Duration') - )); - - $this->view->downtimes = $query->paginate(); + ), $query); if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) { $this->view->delDowntimeForm = new DeleteDowntimeCommandForm(); + $this->view->delDowntimeForm->handleRequest(); } } @@ -314,15 +275,13 @@ class Monitoring_ListController extends Controller */ public function notificationsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab( 'notifications', $this->translate('Notifications'), $this->translate('List notifications') ); $this->setAutorefreshInterval(15); + $query = $this->backend->select()->from('notification', array( 'host_name', 'service_description', @@ -334,18 +293,19 @@ class Monitoring_ListController extends Controller 'service_display_name' )); $this->filterQuery($query); - $this->view->notifications = $query->paginate(); + $this->view->notifications = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); $this->setupSortControl(array( 'notification_start_time' => $this->translate('Notification Start') - )); + ), $query); } public function contactsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('contacts', $this->translate('Contacts'), $this->translate('List contacts')); + $query = $this->backend->select()->from('contact', array( 'contact_name', 'contact_id', @@ -367,8 +327,10 @@ class Monitoring_ListController extends Controller 'contact_notify_host_downtime', )); $this->filterQuery($query); - $this->view->contacts = $query->paginate(); + $this->view->contacts = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->contacts); $this->setupSortControl(array( 'contact_name' => $this->translate('Name'), 'contact_alias' => $this->translate('Alias'), @@ -376,14 +338,11 @@ class Monitoring_ListController extends Controller 'contact_pager' => $this->translate('Pager Address / Number'), 'contact_notify_service_timeperiod' => $this->translate('Service Notification Timeperiod'), 'contact_notify_host_timeperiod' => $this->translate('Host Notification Timeperiod') - )); + ), $query); } public function eventgridAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('eventgrid', $this->translate('Event Grid'), $this->translate('Show the Event Grid')); $form = new StatehistoryForm(); @@ -423,14 +382,12 @@ class Monitoring_ListController extends Controller public function contactgroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab( 'contactgroups', $this->translate('Contact Groups'), $this->translate('List contact groups') ); + $query = $this->backend->select()->from('contactgroup', array( 'contactgroup_name', 'contactgroup_alias', @@ -438,7 +395,7 @@ class Monitoring_ListController extends Controller 'contact_alias', 'contact_email', 'contact_pager', - ))->order('contactgroup_alias'); + )); $this->filterQuery($query); // Fetch and prepare all contact groups: @@ -455,15 +412,18 @@ class Monitoring_ListController extends Controller } // TODO: Find a better naming $this->view->groupData = $groupData; + + $this->setupSortControl(array( + 'contactgroup_name' => $this->translate('Contactgroup Name'), + 'contactgroup_alias' => $this->translate('Contactgroup Alias') + ), $query); } public function commentsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('comments', $this->translate('Comments'), $this->translate('List comments')); $this->setAutorefreshInterval(12); + $query = $this->backend->select()->from('comment', array( 'id' => 'comment_internal_id', 'objecttype' => 'comment_objecttype', @@ -479,8 +439,10 @@ class Monitoring_ListController extends Controller 'service_display_name' )); $this->filterQuery($query); - $this->view->comments = $query->paginate(); + $this->view->comments = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->comments); $this->setupSortControl( array( 'comment_timestamp' => $this->translate('Comment Timestamp'), @@ -488,123 +450,110 @@ class Monitoring_ListController extends Controller 'service_display_name' => $this->translate('Service'), 'comment_type' => $this->translate('Comment Type'), 'comment_expiration' => $this->translate('Expiration') - ) + ), + $query ); if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) { $this->view->delCommentForm = new DeleteCommentCommandForm(); + $this->view->delCommentForm->handleRequest(); } } public function servicegroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab( 'servicegroups', $this->translate('Service Groups'), $this->translate('List service groups') ); $this->setAutorefreshInterval(12); - $query = $this->backend->select()->from('groupsummary', array( - 'servicegroup_name', - 'servicegroup_alias', - 'hosts_up', - 'hosts_unreachable_handled', - 'hosts_unreachable_unhandled', + + $query = $this->backend->select()->from('servicegroupsummary', array( 'hosts_down_handled', 'hosts_down_unhandled', 'hosts_pending', - 'services_ok', - 'services_unknown_handled', - 'services_unknown_unhandled', + 'hosts_unreachable_handled', + 'hosts_unreachable_unhandled', + 'hosts_up', + 'servicegroup_alias', + 'servicegroup_name', 'services_critical_handled', - 'services_critical_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', - 'services_pending', - 'services_ok_last_state_change', - 'services_pending_last_state_change', - 'services_warning_last_state_change_handled', 'services_critical_last_state_change_handled', - 'services_unknown_last_state_change_handled', - 'services_warning_last_state_change_unhandled', 'services_critical_last_state_change_unhandled', + 'services_critical_unhandled', + 'services_ok', + 'services_ok_last_state_change', + 'services_pending', + 'services_pending_last_state_change', + 'services_total', + 'services_unknown_handled', + 'services_unknown_last_state_change_handled', 'services_unknown_last_state_change_unhandled', - 'services_total' - ))->order('services_severity')->order('servicegroup_alias'); - // TODO(el): Can't default to the sort rules of the data view because it's meant for both host groups and - // service groups. We should separate them. + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_last_state_change_handled', + 'services_warning_last_state_change_unhandled', + 'services_warning_unhandled' + )); $this->filterQuery($query); - $this->view->servicegroups = $query->paginate(); + $this->view->servicegroups = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->servicegroups); $this->setupSortControl(array( 'services_severity' => $this->translate('Severity'), 'servicegroup_alias' => $this->translate('Service Group Name'), - 'services_total' => $this->translate('Total Services'), - 'services_ok' => $this->translate('Services OK'), - 'services_unknown' => $this->translate('Services UNKNOWN'), - 'services_critical' => $this->translate('Services CRITICAL'), - 'services_warning' => $this->translate('Services WARNING'), - 'services_pending' => $this->translate('Services PENDING') - )); + 'services_total' => $this->translate('Total Services') + ), $query); } public function hostgroupsAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('hostgroups', $this->translate('Host Groups'), $this->translate('List host groups')); $this->setAutorefreshInterval(12); - $query = $this->backend->select()->from('groupsummary', array( - 'hostgroup_name', + + $query = $this->backend->select()->from('hostgroupsummary', array( 'hostgroup_alias', - 'hosts_up', - 'hosts_unreachable_handled', - 'hosts_unreachable_unhandled', + 'hostgroup_name', 'hosts_down_handled', + 'hosts_down_last_state_change_handled', + 'hosts_down_last_state_change_unhandled', 'hosts_down_unhandled', 'hosts_pending', - 'services_ok', - 'services_unknown_handled', - 'services_unknown_unhandled', + 'hosts_pending_last_state_change', + 'hosts_total', + 'hosts_unreachable_handled', + 'hosts_unreachable_last_state_change_handled', + 'hosts_unreachable_last_state_change_unhandled', + 'hosts_unreachable_unhandled', + 'hosts_up', + 'hosts_up_last_state_change', 'services_critical_handled', 'services_critical_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', + 'services_ok', 'services_pending', - 'services_ok_last_state_change', - 'services_pending_last_state_change', - 'services_warning_last_state_change_handled', - 'services_critical_last_state_change_handled', - 'services_unknown_last_state_change_handled', - 'services_warning_last_state_change_unhandled', - 'services_critical_last_state_change_unhandled', - 'services_unknown_last_state_change_unhandled', - 'services_total' - ))->order('services_severity')->order('hostgroup_alias'); - // TODO(el): Can't default to the sort rules of the data view because it's meant for both host groups and - // service groups. We should separate them. - $this->filterQuery($query); - $this->view->hostgroups = $query->paginate(); - $this->setupSortControl(array( - 'services_severity' => $this->translate('Severity'), - 'hostgroup_alias' => $this->translate('Host Group Name'), - 'services_total' => $this->translate('Total Services'), - 'services_ok' => $this->translate('Services OK'), - 'services_unknown' => $this->translate('Services UNKNOWN'), - 'services_critical' => $this->translate('Services CRITICAL'), - 'services_warning' => $this->translate('Services WARNING'), - 'services_pending' => $this->translate('Services PENDING') + 'services_total', + 'services_unknown_handled', + 'services_unknown_unhandled', + 'services_warning_handled', + 'services_warning_unhandled' )); + $this->filterQuery($query); + $this->view->hostgroups = $query; + + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->hostgroups); + $this->setupSortControl(array( + 'hosts_severity' => $this->translate('Severity'), + 'hostgroup_alias' => $this->translate('Host Group Name'), + 'hosts_total' => $this->translate('Total Hosts'), + 'services_total' => $this->translate('Total Services') + ), $query); } public function eventhistoryAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab( 'eventhistory', $this->translate('Event Overview'), @@ -626,18 +575,17 @@ class Monitoring_ListController extends Controller )); $this->filterQuery($query); + $this->view->history = $query; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->history); $this->setupSortControl(array( 'timestamp' => $this->translate('Occurence') - )); - $this->view->history = $query->paginate(); + ), $query); } public function servicegridAction() { - if ($url = $this->hasBetterUrl()) { - return $this->redirectNow($url); - } $this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid')); $this->setAutorefreshInterval(15); $query = $this->backend->select()->from('serviceStatus', array( @@ -651,33 +599,47 @@ class Monitoring_ListController extends Controller $this->setupSortControl(array( 'host_name' => $this->translate('Hostname'), 'service_description' => $this->translate('Service description') - )); + ), $query); $pivot = $query->pivot('service_description', 'host_name'); $this->view->pivot = $pivot; $this->view->horizontalPaginator = $pivot->paginateXAxis(); $this->view->verticalPaginator = $pivot->paginateYAxis(); } - protected function filterQuery($query) + /** + * Apply filters on a DataView + * + * @param DataView $dataView The DataView to apply filters on + * + * @return DataView $dataView + */ + protected function filterQuery(DataView $dataView) { $editor = Widget::create('filterEditor') - ->setQuery($query) - ->preserveParams('limit', 'sort', 'dir', 'format', 'view', 'backend', 'stateType', 'addColumns') + ->setQuery($dataView) + ->preserveParams( + 'limit', 'sort', 'dir', 'format', 'view', 'backend', + 'stateType', 'addColumns', '_dev' + ) ->ignoreParams('page') + ->setSearchColumns($dataView->getSearchColumns()) ->handleRequest($this->getRequest()); - $query->applyFilter($editor->getFilter()); + $dataView->applyFilter($editor->getFilter()); - $this->view->filterEditor = $editor; + $this->setupFilterControl($editor); $this->view->filter = $editor->getFilter(); - if ($sort = $this->params->get('sort')) { - $query->order($sort, $this->params->get('dir')); - } - $this->handleFormatRequest($query); - return $query; + $this->handleFormatRequest($dataView); + return $dataView; } - protected function extraColumns() + /** + * Get columns to be added from URL parameter 'addColumns' + * and assign to $this->view->addColumns (as array) + * + * @return array + */ + protected function addColumns() { $columns = preg_split( '~,~', @@ -685,7 +647,7 @@ class Monitoring_ListController extends Controller -1, PREG_SPLIT_NO_EMPTY ); - $this->view->extraColumns = $columns; + $this->view->addColumns = $columns; return $columns; } @@ -706,15 +668,6 @@ class Monitoring_ListController extends Controller */ private function createTabs() { - $tabs = $this->getTabs(); - if (in_array($this->_request->getActionName(), array( - 'hosts', - 'services', - 'eventhistory', - 'eventgrid', - 'notifications' - ))) { - $tabs->extend(new OutputFormat())->extend(new DashboardAction()); - } + $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction()); } } diff --git a/modules/monitoring/application/controllers/ProcessController.php b/modules/monitoring/application/controllers/ProcessController.php index b20bd7576..2e52733bd 100644 --- a/modules/monitoring/application/controllers/ProcessController.php +++ b/modules/monitoring/application/controllers/ProcessController.php @@ -1,6 +1,7 @@ $this->translate('Monitoring Health'), 'url' =>'monitoring/process/info' ) - ); + )->extend(new DashboardAction()); } /** @@ -48,8 +49,10 @@ class Monitoring_ProcessController extends Controller array( 'is_currently_running', 'process_id', + 'endpoint_name', 'program_start_time', 'status_update_time', + 'program_version', 'last_command_check', 'last_log_rotation', 'global_service_event_handler', diff --git a/modules/monitoring/application/controllers/ServiceController.php b/modules/monitoring/application/controllers/ServiceController.php index 86579343f..e61c4685d 100644 --- a/modules/monitoring/application/controllers/ServiceController.php +++ b/modules/monitoring/application/controllers/ServiceController.php @@ -1,6 +1,7 @@ backend, - $this->params->get('host_name'), - $this->params->get('service_description') - ); + if ($this->params->get('host') === null || $this->params->get('service') === null) { + throw new MissingParameterException( + $this->translate('One of the required parameters \'%s\' is missing'), + 'host or service' + ); + } + + $service = new Service($this->backend, $this->params->get('host'), $this->params->get('service')); $this->applyRestriction('monitoring/services/filter', $service); if ($service->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Service not found')); + throw new Zend_Controller_Action_Exception( + sprintf($this->translate('Service \'%s\' not found'), $this->params->get('service')), + 404 + ); } $this->object = $service; $this->createTabs(); $this->getTabs()->activate('service'); } + /** + * Get service actions from hook + * + * @return array + */ + protected function getServiceActions() + { + $urls = array(); + + foreach (Hook::all('Monitoring\\ServiceActions') as $hook) { + foreach ($hook->getActionsForService($this->object) as $id => $url) { + $urls[$id] = $url; + } + } + + return $urls; + } + + /** + * Show a service + */ + public function showAction() + { + $this->view->actions = $this->getServiceActions(); + parent::showAction(); + } + + /** * Acknowledge a service problem */ diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php index b82d6f00e..6877aca02 100644 --- a/modules/monitoring/application/controllers/ServicesController.php +++ b/modules/monitoring/application/controllers/ServicesController.php @@ -10,12 +10,12 @@ use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceCheckCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm; +use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm; -use Icinga\Module\Monitoring\Object\Host; -use Icinga\Module\Monitoring\Object\Service; use Icinga\Module\Monitoring\Object\ServiceList; use Icinga\Web\Url; -use Icinga\Web\Widget\Chart\InlinePie; +use Icinga\Web\Widget\Tabextension\DashboardAction; class Monitoring_ServicesController extends Controller { @@ -27,70 +27,12 @@ class Monitoring_ServicesController extends Controller public function init() { $serviceList = new ServiceList($this->backend); - $serviceList->setFilter(Filter::fromQueryString((string) $this->params->without('service_problem', 'service_handled'))); - $this->serviceList = $serviceList; - } - - protected function handleCommandForm(ObjectsCommandForm $form) - { - $this->serviceList->setColumns(array( - 'host_name', - 'host_state', - 'service_description', - 'service_state', - 'service_problem', - 'service_handled', - 'service_acknowledged', - 'service_in_downtime' + $serviceList->setFilter(Filter::fromQueryString( + (string) $this->params->without(array('service_problem', 'service_handled', 'view')) )); + $this->serviceList = $serviceList; + $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services'); - $form - ->setObjects($this->serviceList) - ->setRedirectUrl(Url::fromPath('monitoring/services/show')->setParams($this->params)) - ->handleRequest(); - - $serviceStates = array( - Service::getStateText(Service::STATE_OK) => 0, - Service::getStateText(Service::STATE_WARNING) => 0, - Service::getStateText(Service::STATE_CRITICAL) => 0, - Service::getStateText(Service::STATE_UNKNOWN) => 0, - Service::getStateText(Service::STATE_PENDING) => 0 - ); - $knownHostStates = array(); - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->serviceList as $service) { - ++$serviceStates[$service::getStateText($service->state)]; - if (! isset($knownHostStates[$service->getHost()->getName()])) { - $knownHostStates[$service->getHost()->getName()] = true; - ++$hostStates[$service->getHost()->getStateText($service->host_state)]; - } - } - - $this->view->form = $form; - $this->view->objects = $this->serviceList; - $this->view->serviceStates = $serviceStates; - $this->view->hostStates = $hostStates; - $this->view->serviceStatesPieChart = $this->createPieChart( - $serviceStates, - $this->translate('Service State'), - array('#44bb77', '#FFCC66', '#FF5566', '#E066FF', '#77AAFF') - ); - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') - ); - $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); - return $form; - } - - public function showAction() - { $this->getTabs()->add( 'show', array( @@ -98,10 +40,54 @@ class Monitoring_ServicesController extends Controller $this->translate('Show summarized information for %u services'), count($this->serviceList) ), - 'label' => $this->translate('Services'), - 'url' => Url::fromRequest() + 'label' => $this->translate('Services') . sprintf(' (%d)', count($this->serviceList)), + 'url' => Url::fromRequest(), + 'icon' => 'services' ) - )->activate('show'); + )->extend(new DashboardAction())->activate('show'); + } + + protected function handleCommandForm(ObjectsCommandForm $form) + { + $this->serviceList->setColumns(array( + 'host_icon_image', + 'host_name', + 'host_output', + 'host_state', + 'host_problem', + 'host_handled', + 'service_icon_image', + 'service_description', + 'service_state', + 'service_problem', + 'service_handled', + 'service_acknowledged', + 'service_in_downtime', + 'service_is_flapping', + 'service_output', + 'service_last_ack', + 'service_last_comment', + 'service_notifications_enabled', + 'service_active_checks_enabled', + 'service_passive_checks_enabled' + )); + + $form + ->setObjects($this->serviceList) + ->setRedirectUrl(Url::fromPath('monitoring/services/show')->setParams($this->params)) + ->handleRequest(); + + $this->view->form = $form; + $this->view->objects = $this->serviceList; + $this->view->stats = $this->serviceList->getServiceStateSummary(); + $this->view->serviceStates = true; + $this->view->hostStates = $this->serviceList->getHostStateSummary(); + $this->_helper->viewRenderer('partials/command/objects-command-form', null, true); + return $form; + } + + public function showAction() + { $this->setAutorefreshInterval(15); $checkNowForm = new CheckNowCommandForm(); $checkNowForm @@ -109,65 +95,33 @@ class Monitoring_ServicesController extends Controller ->handleRequest(); $this->view->checkNowForm = $checkNowForm; $this->serviceList->setColumns(array( + 'host_icon_image', 'host_name', + 'host_output', 'host_state', + 'host_problem', + 'host_handled', + 'service_icon_image', + 'service_output', 'service_description', 'service_state', 'service_problem', 'service_handled', 'service_acknowledged', - 'service_in_downtime'/*, - 'service_passive_checks_enabled', + 'service_in_downtime', + 'service_is_flapping', + 'service_last_comment', + 'service_last_ack', 'service_notifications_enabled', + 'service_active_checks_enabled', + 'service_passive_checks_enabled' + /* 'service_event_handler_enabled', 'service_flap_detection_enabled', - 'service_active_checks_enabled', 'service_obsessing'*/ )); - $unhandledObjects = array(); - $unhandledFilterExpressions = array(); - $acknowledgedObjects = array(); - $objectsInDowntime = array(); - $downtimeFilterExpressions = array(); - $serviceStates = array( - Service::getStateText(Service::STATE_OK) => 0, - Service::getStateText(Service::STATE_WARNING) => 0, - Service::getStateText(Service::STATE_CRITICAL) => 0, - Service::getStateText(Service::STATE_UNKNOWN) => 0, - Service::getStateText(Service::STATE_PENDING) => 0 - ); - $knownHostStates = array(); - $hostStates = array( - Host::getStateText(Host::STATE_UP) => 0, - Host::getStateText(Host::STATE_DOWN) => 0, - Host::getStateText(Host::STATE_UNREACHABLE) => 0, - Host::getStateText(Host::STATE_PENDING) => 0, - ); - foreach ($this->serviceList as $service) { - /** @var Service $service */ - if ((bool) $service->problem === true && (bool) $service->handled === false) { - $unhandledObjects[] = $service; - $unhandledFilterExpressions[] = Filter::matchAll( - Filter::where('host', $service->getHost()->getName()), - Filter::where('service', $service->getName()) - ); - } - if ((bool) $service->acknowledged === true) { - $acknowledgedObjects[] = $service; - } - if ((bool) $service->in_downtime === true) { - $objectsInDowntime[] = $service; - $downtimeFilterExpressions[] = Filter::matchAll( - Filter::where('host_name', $service->getHost()->getName()), - Filter::where('service_description', $service->getName()) - ); - } - ++$serviceStates[$service::getStateText($service->state)]; - if (! isset($knownHostStates[$service->getHost()->getName()])) { - $knownHostStates[$service->getHost()->getName()] = true; - ++$hostStates[$service->getHost()->getStateText($service->host_state)]; - } - } + + $acknowledgedObjects = $this->serviceList->getAcknowledgedObjects(); if (! empty($acknowledgedObjects)) { $removeAckForm = new RemoveAcknowledgementCommandForm(); $removeAckForm @@ -175,53 +129,70 @@ class Monitoring_ServicesController extends Controller ->handleRequest(); $this->view->removeAckForm = $removeAckForm; } + $this->setAutorefreshInterval(15); - $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services'); $this->view->rescheduleAllLink = Url::fromRequest()->setPath('monitoring/services/reschedule-check'); $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/services/schedule-downtime'); $this->view->processCheckResultAllLink = Url::fromRequest()->setPath( 'monitoring/services/process-check-result' ); - $this->view->hostStates = $hostStates; - $this->view->serviceStates = $serviceStates; + $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/services/add-comment'); + $this->view->deleteCommentLink = Url::fromRequest()->setPath('monitoring/services/delete-comment'); + $this->view->stats = $this->serviceList->getServiceStateSummary(); + $this->view->hostStats = $this->serviceList->getHostStateSummary(); $this->view->objects = $this->serviceList; - $this->view->unhandledObjects = $unhandledObjects; - $unhandledFilterQueryString = Filter::matchAny($unhandledFilterExpressions)->toQueryString(); + $this->view->unhandledObjects = $this->serviceList->getUnhandledObjects(); + $this->view->problemObjects = $this->serviceList->getProblemObjects(); $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/services/acknowledge-problem') - ->setQueryString($unhandledFilterQueryString); + ->setQueryString($this->serviceList->getUnhandledObjects()->objectsFilter()->toQueryString()); $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime') - ->setQueryString($unhandledFilterQueryString); + ->setQueryString($this->serviceList->getUnhandledObjects()->objectsFilter()->toQueryString()); + $this->view->downtimeLink = Url::fromPath('monitoring/services/schedule-downtime') + ->setQueryString($this->serviceList->getProblemObjects()->objectsFilter()->toQueryString()); $this->view->acknowledgedObjects = $acknowledgedObjects; - $this->view->objectsInDowntime = $objectsInDowntime; - $this->view->inDowntimeLink = Url::fromPath('monitoring/list/downtimes') - ->setQueryString(Filter::matchAny($downtimeFilterExpressions)->toQueryString()); + $this->view->objectsInDowntime = $this->serviceList->getObjectsInDowntime(); + $this->view->inDowntimeLink = Url::fromPath('monitoring/list/services') + ->setQueryString($this->serviceList->getObjectsInDowntime() + ->objectsFilter(array('host' => 'host_name', 'service' => 'service_description'))->toQueryString()); + $this->view->showDowntimesLink = Url::fromPath('monitoring/downtimes/show') + ->setQueryString( + $this->serviceList->getObjectsInDowntime() + ->objectsFilter()->toQueryString() + ); $this->view->commentsLink = Url::fromRequest() ->setPath('monitoring/list/comments'); - $this->view->serviceStatesPieChart = $this->createPieChart( - $serviceStates, - $this->translate('Service State'), - array('#44bb77', '#FFCC66', '#FF5566', '#E066FF', '#77AAFF') + $this->view->baseFilter = $this->serviceList->getFilter(); + $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath( + 'monitoring/services/send-custom-notification' ); - $this->view->hostStatesPieChart = $this->createPieChart( - $hostStates, - $this->translate('Host State'), - array('#44bb77', '#FF5566', '#E066FF', '#77AAFF') - ); - $this->view->sendCustomNotificationLink = - Url::fromRequest()->setPath( - 'monitoring/services/send-custom-notification' - ); } - protected function createPieChart(array $states, $title, array $colors) + /** + * Add a service comment + */ + public function addCommentAction() { - $chart = new InlinePie(array_values($states), $title, $colors); - return $chart - ->setSize(75) - ->setTitle('') - ->setSparklineClass('sparkline-multi'); + $this->assertPermission('monitoring/command/comment/add'); + + $form = new AddCommentCommandForm(); + $form->setTitle($this->translate('Add Service Comments')); + $this->handleCommandForm($form); } + + /** + * Delete a comment + */ + public function deleteCommentAction() + { + $this->assertPermission('monitoring/command/comment/delete'); + + $form = new DeleteCommentCommandForm(); + $form->setTitle($this->translate('Delete Service Comments')); + $this->handleCommandForm($form); + } + + /** * Acknowledge service problems */ diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index 0d7df501a..a8a4583d1 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -70,9 +70,12 @@ class Monitoring_ShowController extends Controller { $this->getTabs()->activate('history'); $this->view->object->fetchEventHistory(); - $this->view->history = $this->view->object->eventhistory->getQuery()->paginate($this->params->get('limit', 50)); + $this->view->history = $this->view->object->eventhistory; $this->handleFormatRequest($this->view->object->eventhistory); $this->fetchHostStats(); + + $this->setupLimitControl(50); + $this->setupPaginationControl($this->view->history, 50); } public function servicesAction() @@ -142,9 +145,7 @@ class Monitoring_ShowController extends Controller 'contact_notify_host_flapping', 'contact_notify_host_downtime', )); - $query->where('contact_name', $contactName); - $contact = $query->getQuery()->fetchRow(); if ($contact) { @@ -153,7 +154,7 @@ class Monitoring_ShowController extends Controller 'command_name' ))->where('contact_id', $contact->contact_id); - $this->view->commands = $commands->paginate(); + $this->view->commands = $commands; $notifications = $this->backend->select()->from('notification', array( 'host_name', @@ -167,9 +168,9 @@ class Monitoring_ShowController extends Controller )); $notifications->where('contact_object_id', $contact->contact_object_id); - - $this->view->compact = true; - $this->view->notifications = $notifications->paginate(); + $this->view->notifications = $notifications; + $this->setupLimitControl(); + $this->setupPaginationControl($this->view->notifications); } $this->view->contact = $contact; @@ -188,13 +189,13 @@ class Monitoring_ShowController extends Controller if ($object->getType() === $object::TYPE_HOST) { $isService = false; $params = array( - 'host_name' => $object->getName() + 'host' => $object->getName() ); } else { $isService = true; $params = array( - 'host_name' => $object->getHost()->getName(), - 'service_description' => $object->getName() + 'host' => $object->getHost()->getName(), + 'service' => $object->getName() ); } $tabs = $this->getTabs(); @@ -207,7 +208,7 @@ class Monitoring_ShowController extends Controller ), 'label' => $this->translate('Host'), 'icon' => 'host', - 'url' => 'monitoring/host/show', + 'url' => 'monitoring/show/host', 'urlParams' => $params, ) ); @@ -222,7 +223,7 @@ class Monitoring_ShowController extends Controller ), 'label' => $this->translate('Service'), 'icon' => 'service', - 'url' => 'monitoring/service/show', + 'url' => 'monitoring/show/service', 'urlParams' => $params, ) ); diff --git a/modules/monitoring/application/controllers/TacticalController.php b/modules/monitoring/application/controllers/TacticalController.php index 33681528c..d9da6d3d5 100644 --- a/modules/monitoring/application/controllers/TacticalController.php +++ b/modules/monitoring/application/controllers/TacticalController.php @@ -2,6 +2,7 @@ /* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ use Icinga\Module\Monitoring\Controller as MonitoringController; +use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Url; class Monitoring_TacticalController extends MonitoringController @@ -18,7 +19,7 @@ class Monitoring_TacticalController extends MonitoringController 'label' => $this->translate('Tactical Overview'), 'url' => Url::fromRequest() ) - )->activate('tactical_overview'); + )->extend(new DashboardAction())->activate('tactical_overview'); $this->view->statusSummary = $this->backend->select()->from( 'statusSummary', diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php index 78b2ad100..0427447be 100644 --- a/modules/monitoring/application/controllers/TimelineController.php +++ b/modules/monitoring/application/controllers/TimelineController.php @@ -7,6 +7,7 @@ use Icinga\Module\Monitoring\Controller; use Icinga\Module\Monitoring\Timeline\TimeLine; use Icinga\Module\Monitoring\Timeline\TimeRange; use Icinga\Module\Monitoring\Web\Widget\SelectBox; +use Icinga\Web\Widget\Tabextension\DashboardAction; class Monitoring_TimelineController extends Controller { @@ -19,7 +20,7 @@ class Monitoring_TimelineController extends Controller 'label' => $this->translate('Timeline'), 'url' => Url::fromRequest() ) - )->activate('timeline'); + )->extend(new DashboardAction())->activate('timeline'); $this->view->title = $this->translate('Timeline'); // TODO: filter for hard_states (precedence adjustments necessary!) diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php index ec58bb4b6..9ef90718f 100644 --- a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php @@ -85,7 +85,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS, array( - 'label' => $this->translate('Active Host Checks Being Executed'), + 'label' => $this->translate('Active Host Checks'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -94,7 +94,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS, array( - 'label' => $this->translate('Active Service Checks Being Executed'), + 'label' => $this->translate('Active Service Checks'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -103,7 +103,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS, array( - 'label' => $this->translate('Event Handlers Enabled'), + 'label' => $this->translate('Event Handlers'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -112,7 +112,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION, array( - 'label' => $this->translate('Flap Detection Enabled'), + 'label' => $this->translate('Flap Detection'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -121,7 +121,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS, array( - 'label' => $this->translate('Notifications Enabled'), + 'label' => $this->translate('Notifications'), 'autosubmit' => true, 'description' => $notificationDescription, 'decorators' => array( @@ -159,7 +159,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS, array( - 'label' => $this->translate('Passive Host Checks Being Accepted'), + 'label' => $this->translate('Passive Host Checks'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -168,7 +168,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS, array( - 'label' => $this->translate('Passive Service Checks Being Accepted'), + 'label' => $this->translate('Passive Service Checks'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -177,7 +177,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm 'checkbox', ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA, array( - 'label' => $this->translate('Performance Data Being Processed'), + 'label' => $this->translate('Performance Data'), 'autosubmit' => true, 'disabled' => $toggleDisabled ) @@ -209,14 +209,63 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm public function onSuccess() { $this->assertPermission('monitoring/command/feature/instance'); + + $notifications = array( + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => array( + $this->translate('Enabling active host checks..'), + $this->translate('Disabling active host checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => array( + $this->translate('Enabling active service checks..'), + $this->translate('Disabling active service checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => array( + $this->translate('Enabling event handlers..'), + $this->translate('Disabling event handlers..') + ), + ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => array( + $this->translate('Enabling flap detection..'), + $this->translate('Disabling flap detection..') + ), + ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => array( + $this->translate('Enabling notifications..'), + $this->translate('Disabling notifications..') + ), + ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING => array( + $this->translate('Enabling obsessing over hosts..'), + $this->translate('Disabling obsessing over hosts..') + ), + ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING => array( + $this->translate('Enabling obsessing over services..'), + $this->translate('Disabling obsessing over services..') + ), + ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS => array( + $this->translate('Enabling passive host checks..'), + $this->translate('Disabling passive host checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS => array( + $this->translate('Enabling passive service checks..'), + $this->translate('Disabling passive service checks..') + ), + ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => array( + $this->translate('Enabling performance data..'), + $this->translate('Disabling performance data..') + ) + ); + foreach ($this->getValues() as $feature => $enabled) { $toggleFeature = new ToggleInstanceFeatureCommand(); $toggleFeature ->setFeature($feature) ->setEnabled($enabled); $this->getTransport($this->request)->send($toggleFeature); + + if ((bool) $this->status->{$feature} !== (bool) $enabled) { + Notification::success( + $notifications[$feature][$enabled ? 0 : 1] + ); + } } - Notification::success($this->translate('Toggling feature..')); return true; } } diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php index 93b928b86..1c8144475 100644 --- a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php @@ -4,12 +4,13 @@ namespace Icinga\Module\Monitoring\Forms\Command\Object; use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand; +use Icinga\Module\Monitoring\Forms\Command\CommandForm; use Icinga\Web\Notification; /** * Form for deleting host or service comments */ -class DeleteCommentCommandForm extends ObjectsCommandForm +class DeleteCommentCommandForm extends CommandForm { /** * (non-PHPDoc) @@ -26,23 +27,34 @@ class DeleteCommentCommandForm extends ObjectsCommandForm */ public function createElements(array $formData = array()) { - $this->addElements(array( + $this->addElements( array( - 'hidden', - 'comment_id', array( - 'required' => true, - 'decorators' => array('ViewHelper') - ) - ), - array( - 'hidden', - 'redirect', + 'hidden', + 'comment_id', + array( + 'required' => true, + 'validators' => array('NotEmpty'), + 'decorators' => array('ViewHelper') + ) + ), array( - 'decorators' => array('ViewHelper') + 'hidden', + 'comment_is_service', + array( + 'filters' => array('Boolean'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'redirect', + array( + 'decorators' => array('ViewHelper') + ) ) ) - )); + ); return $this; } @@ -74,14 +86,10 @@ class DeleteCommentCommandForm extends ObjectsCommandForm */ public function onSuccess() { - foreach ($this->objects as $object) { - /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ - $delComment = new DeleteCommentCommand(); - $delComment - ->setObject($object) - ->setCommentId($this->getElement('comment_id')->getValue()); - $this->getTransport($this->request)->send($delComment); - } + $cmd = new DeleteCommentCommand(); + $cmd->setIsService($this->getElement('comment_is_service')->getValue()) + ->setCommentId($this->getElement('comment_id')->getValue()); + $this->getTransport($this->request)->send($cmd); $redirect = $this->getElement('redirect')->getValue(); if (! empty($redirect)) { $this->setRedirectUrl($redirect); diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php new file mode 100644 index 000000000..805002922 --- /dev/null +++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php @@ -0,0 +1,88 @@ +setAttrib('class', 'inline'); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::createElements() For the method documentation. + */ + public function createElements(array $formData = array()) + { + $this->addElements(array( + array( + 'hidden', + 'redirect', + array('decorators' => array('ViewHelper')) + ) + )); + return $this; + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. + */ + public function getSubmitLabel() + { + return $this->translatePlural('Remove', 'Remove All', count($this->downtimes)); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::onSuccess() For the method documentation. + */ + public function onSuccess() + { + foreach ($this->comments as $comment) { + $cmd = new DeleteCommentCommand(); + $cmd->setCommentId($comment->id) + ->setIsService(isset($comment->service_description)); + $this->getTransport($this->request)->send($cmd); + } + $redirect = $this->getElement('redirect')->getValue(); + if (! empty($redirect)) { + $this->setRedirectUrl($redirect); + } + Notification::success($this->translate('Deleting comment..')); + return true; + } + + /** + * Set the comments to be deleted upon success + * + * @param array $comments + * + * @return this fluent interface + */ + public function setComments(array $comments) + { + $this->comments = $comments; + return $this; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php index d240251cf..31ec778fa 100644 --- a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php @@ -4,12 +4,13 @@ namespace Icinga\Module\Monitoring\Forms\Command\Object; use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand; +use \Icinga\Module\Monitoring\Forms\Command\CommandForm; use Icinga\Web\Notification; /** * Form for deleting host or service downtimes */ -class DeleteDowntimeCommandForm extends ObjectsCommandForm +class DeleteDowntimeCommandForm extends CommandForm { /** * (non-PHPDoc) @@ -19,33 +20,44 @@ class DeleteDowntimeCommandForm extends ObjectsCommandForm { $this->setAttrib('class', 'inline'); } - + /** * (non-PHPDoc) * @see \Icinga\Web\Form::createElements() For the method documentation. */ public function createElements(array $formData = array()) { - $this->addElements(array( + $this->addElements( array( - 'hidden', - 'downtime_id', array( - 'required' => true, - 'decorators' => array('ViewHelper') - ) - ), - array( - 'hidden', - 'redirect', + 'hidden', + 'downtime_id', + array( + 'required' => true, + 'validators' => array('NotEmpty'), + 'decorators' => array('ViewHelper') + ) + ), array( - 'decorators' => array('ViewHelper') + 'hidden', + 'downtime_is_service', + array( + 'filters' => array('Boolean'), + 'decorators' => array('ViewHelper') + ) + ), + array( + 'hidden', + 'redirect', + array( + 'decorators' => array('ViewHelper') + ) ) ) - )); + ); return $this; } - + /** * (non-PHPDoc) * @see \Icinga\Web\Form::addSubmitButton() For the method documentation. @@ -67,26 +79,23 @@ class DeleteDowntimeCommandForm extends ObjectsCommandForm ); return $this; } - + /** * (non-PHPDoc) - * @see \Icinga\Web\Form::onSuccess() For the method documentation. + * @see \Icinga\Web\Form::onSuccess() For the method documentation. */ public function onSuccess() { - foreach ($this->objects as $object) { - /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ - $delDowntime = new DeleteDowntimeCommand(); - $delDowntime - ->setObject($object) - ->setDowntimeId($this->getElement('downtime_id')->getValue()); - $this->getTransport($this->request)->send($delDowntime); - } + $cmd = new DeleteDowntimeCommand(); + $cmd->setDowntimeId($this->getElement('downtime_id')->getValue()); + $cmd->setIsService($this->getElement('downtime_is_service')->getValue()); + $this->getTransport($this->request)->send($cmd); + $redirect = $this->getElement('redirect')->getValue(); if (! empty($redirect)) { $this->setRedirectUrl($redirect); } - Notification::success($this->translate('Deleting downtime..')); + Notification::success($this->translate('Deleting downtime.')); return true; } } diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php new file mode 100644 index 000000000..4a19cac96 --- /dev/null +++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php @@ -0,0 +1,88 @@ +setAttrib('class', 'inline'); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::createElements() For the method documentation. + */ + public function createElements(array $formData = array()) + { + $this->addElements(array( + array( + 'hidden', + 'redirect', + array('decorators' => array('ViewHelper')) + ) + )); + return $this; + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation. + */ + public function getSubmitLabel() + { + return $this->translatePlural('Remove', 'Remove All', count($this->downtimes)); + } + + /** + * (non-PHPDoc) + * @see \Icinga\Web\Form::onSuccess() For the method documentation. + */ + public function onSuccess() + { + foreach ($this->downtimes as $downtime) { + $delDowntime = new DeleteDowntimeCommand(); + $delDowntime->setDowntimeId($downtime->id); + $delDowntime->setIsService(isset($downtime->service_description)); + $this->getTransport($this->request)->send($delDowntime); + } + $redirect = $this->getElement('redirect')->getValue(); + if (! empty($redirect)) { + $this->setRedirectUrl($redirect); + } + Notification::success($this->translate('Deleting downtime.')); + return true; + } + + /** + * Set the downtimes to be deleted upon success + * + * @param type $downtimes + * + * @return $this + */ + public function setDowntimes(array $downtimes) + { + $this->downtimes = $downtimes; + return $this; + } +} diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php index a6c20f630..ad6196d44 100644 --- a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php @@ -30,7 +30,7 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm */ public function createElements(array $formData = array()) { - $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : ''; + $toggleDisabled = $this->hasPermission('monitoring/command/feature/object') ? null : ''; $this->addElements(array( array( 'checkbox', @@ -117,6 +117,34 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm public function onSuccess() { $this->assertPermission('monitoring/command/feature/object'); + + $notifications = array( + ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array( + $this->translate('Enabling active checks..'), + $this->translate('Disabling active checks..') + ), + ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array( + $this->translate('Enabling passive checks..'), + $this->translate('Disabling passive checks..') + ), + ToggleObjectFeatureCommand::FEATURE_OBSESSING => array( + $this->translate('Enabling obsessing..'), + $this->translate('Disabling obsessing..') + ), + ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array( + $this->translate('Enabling notifications..'), + $this->translate('Disabling notifications..') + ), + ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array( + $this->translate('Enabling event handler..'), + $this->translate('Disabling event handler..') + ), + ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array( + $this->translate('Enabling flap detection..'), + $this->translate('Disabling flap detection..') + ) + ); + foreach ($this->objects as $object) { /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ foreach ($this->getValues() as $feature => $enabled) { @@ -127,10 +155,13 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ->setObject($object) ->setEnabled($enabled); $this->getTransport($this->request)->send($toggleFeature); + + Notification::success( + $notifications[$feature][$enabled ? 0 : 1] + ); } } } - Notification::success($this->translate('Toggling feature..')); return true; } } diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php index d89c6623f..1380b416d 100644 --- a/modules/monitoring/application/forms/Config/BackendConfigForm.php +++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php @@ -3,6 +3,10 @@ namespace Icinga\Module\Monitoring\Forms\Config; +use Exception; +use Icinga\Data\ConfigObject; +use Icinga\Data\ResourceFactory; +use Icinga\Web\Form; use InvalidArgumentException; use Icinga\Application\Config; use Icinga\Exception\ConfigurationError; @@ -271,4 +275,63 @@ class BackendConfigForm extends ConfigForm ) ); } + + /** + * Validate the ido instance schema resource + * + * @param Form $form + * @param ConfigObject $resourceConfig + * + * @return bool Whether validation succeeded or not + */ + public static function isValidIdoSchema(Form $form, ConfigObject $resourceConfig) + { + try { + $resource = ResourceFactory::createResource($resourceConfig); + $result = $resource->select()->from('icinga_dbversion', array('version')); + $result->fetchOne(); + } catch (Exception $e) { + $form->addError( + $form->translate( + 'IDO schema validation failed, it looks like that the IDO schema is missing in the given database.' + ) + ); + return false; + } + return true; + } + + /** + * Validate the ido instance availability + * + * @param Form $form + * @param ConfigObject $resourceConfig + * + * @return bool Whether validation succeeded or not + */ + public static function isValidIdoInstance(Form $form, ConfigObject $resourceConfig) + { + $resource = ResourceFactory::createResource($resourceConfig); + $result = $resource->select()->from('icinga_instances', array('instance_name')); + $instances = $result->fetchAll(); + + if (count($instances) === 1) { + return true; + } elseif (count($instances) > 1) { + $form->warning( + $form->translate( + 'IDO instance validation failed, because there are multiple instances available.' + ) + ); + return false; + } + + $form->error( + $form->translate( + 'IDO instance validation failed, because there is no IDO instance available.' + ) + ); + + return false; + } } diff --git a/modules/monitoring/application/forms/Setup/IdoResourcePage.php b/modules/monitoring/application/forms/Setup/IdoResourcePage.php index f52601359..3f6bb1564 100644 --- a/modules/monitoring/application/forms/Setup/IdoResourcePage.php +++ b/modules/monitoring/application/forms/Setup/IdoResourcePage.php @@ -3,6 +3,8 @@ namespace Icinga\Module\Monitoring\Forms\Setup; +use Icinga\Data\ConfigObject; +use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm; use Icinga\Web\Form; use Icinga\Forms\Config\Resource\DbResourceForm; @@ -53,8 +55,21 @@ class IdoResourcePage extends Form } if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { - if (false === DbResourceForm::isValidResource($this)) { - $this->addSkipValidationCheckbox(); + $configObject = new ConfigObject($this->getValues()); + if (false === DbResourceForm::isValidResource($this, $configObject)) { + $this->addSkipValidationCheckbox($this->translate( + 'Check this to not to validate connectivity with the given database server' + )); + return false; + } elseif (false === BackendConfigForm::isValidIdoSchema($this, $configObject)) { + $this->addSkipValidationCheckbox($this->translate( + 'Check this to not to validate the ido schema' + )); + return false; + } elseif (false === BackendConfigForm::isValidIdoInstance($this, $configObject)) { + $this->addSkipValidationCheckbox($this->translate( + 'Check this to not to validate the ido instance' + )); return false; } } @@ -65,17 +80,21 @@ class IdoResourcePage extends Form /** * Add a checkbox to the form by which the user can skip the connection validation */ - protected function addSkipValidationCheckbox() + protected function addSkipValidationCheckbox($description = '') { + if (empty($description)) { + $description = $this->translate( + 'Proceed without any further (custom) validation' + ); + } + $this->addElement( 'checkbox', 'skip_validation', array( 'required' => true, 'label' => $this->translate('Skip Validation'), - 'description' => $this->translate( - 'Check this to not to validate connectivity with the given database server' - ) + 'description' => $description ) ); } diff --git a/modules/monitoring/application/views/helpers/Customvar.php b/modules/monitoring/application/views/helpers/Customvar.php index 32391b6ce..bb9f70a7e 100644 --- a/modules/monitoring/application/views/helpers/Customvar.php +++ b/modules/monitoring/application/views/helpers/Customvar.php @@ -15,8 +15,12 @@ class Zend_View_Helper_Customvar extends Zend_View_Helper_Abstract public function customvar($struct) { - if (is_string($struct) || is_int($struct) || is_float($struct)) { - return $this->view->escape((string) $struct); + if (is_scalar($struct)) { + return $this->view->escape( + is_string($struct) + ? $struct + : var_export($struct, true) + ); } elseif (is_array($struct)) { return $this->renderArray($struct); } elseif (is_object($struct)) { @@ -38,7 +42,7 @@ class Zend_View_Helper_Customvar extends Zend_View_Helper_Abstract protected function renderObject($object) { - if (empty($object)) { + if (0 === count((array) $object)) { return '{}'; } $out = "{
        \n"; diff --git a/modules/monitoring/application/views/helpers/HostFlags.php b/modules/monitoring/application/views/helpers/HostFlags.php new file mode 100644 index 000000000..97e6fbf3d --- /dev/null +++ b/modules/monitoring/application/views/helpers/HostFlags.php @@ -0,0 +1,39 @@ +host_handled && $host->host_state > 0) { + $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled')); + } + if ($host->host_acknowledged) { + $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged')); + } + if ($host->host_is_flapping) { + $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping')); + } + if (! $host->host_notifications_enabled) { + $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled')); + } + if ($host->host_in_downtime) { + $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime')); + } + if (! $host->host_active_checks_enabled) { + if (! $host->host_passive_checks_enabled) { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled')); + } else { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled')); + } + } + if (isset($host->host_last_comment) && $host->host_last_comment !== null) { + $icons[] = $this->view->icon('comment', $this->view->translate('Last Comment: ') . $host->host_last_comment); + } + return $icons; + } +} \ No newline at end of file diff --git a/modules/monitoring/application/views/helpers/IconImage.php b/modules/monitoring/application/views/helpers/IconImage.php new file mode 100644 index 000000000..2ed3b1d25 --- /dev/null +++ b/modules/monitoring/application/views/helpers/IconImage.php @@ -0,0 +1,46 @@ +host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) { + return $this->view->icon($this->view->resolveMacros($object->host_icon_image, $object)); + } + return ''; + } + + /** + * Display the image_icon of a MonitoredObject + * + * @param MonitoredObject|stdClass $object The host or service + * @return string + */ + public function service($object) + { + if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) { + return $this->view->icon($this->view->resolveMacros($object->service_icon_image, $object)); + } + return ''; + } +} diff --git a/modules/monitoring/application/views/helpers/Link.php b/modules/monitoring/application/views/helpers/Link.php index 7f1324573..8faf6e5fb 100644 --- a/modules/monitoring/application/views/helpers/Link.php +++ b/modules/monitoring/application/views/helpers/Link.php @@ -33,7 +33,7 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract return $this->view->qlink( $linkText, 'monitoring/host/show', - array('host_name' => $host), + array('host' => $host), array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $host)) ); } @@ -55,7 +55,7 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract $this->view->qlink( $serviceLinkText, 'monitoring/service/show', - array('host_name' => $host, 'service_description' => $service), + array('host' => $host, 'service' => $service), array('title' => sprintf( $this->view->translate('Show detailed information for service %s on host %s'), $service, diff --git a/modules/monitoring/application/views/helpers/PluginOutput.php b/modules/monitoring/application/views/helpers/PluginOutput.php index 8778d01b3..d494a7810 100644 --- a/modules/monitoring/application/views/helpers/PluginOutput.php +++ b/modules/monitoring/application/views/helpers/PluginOutput.php @@ -65,7 +65,7 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract parse_str($m[1], $params); if (isset($params['host'])) { $tag->setAttribute('href', $this->view->baseUrl( - '/monitoring/host/show?host_name=' . urlencode($params['host'] + '/monitoring/host/show?host=' . urlencode($params['host'] ))); } } else { diff --git a/modules/monitoring/application/views/helpers/SelectionToolbar.php b/modules/monitoring/application/views/helpers/SelectionToolbar.php deleted file mode 100644 index 7332b5c01..000000000 --- a/modules/monitoring/application/views/helpers/SelectionToolbar.php +++ /dev/null @@ -1,24 +0,0 @@ -' - . ' Show All
    '; - } else { - return ''; - } - } -} diff --git a/modules/monitoring/application/views/helpers/ServiceFlags.php b/modules/monitoring/application/views/helpers/ServiceFlags.php new file mode 100644 index 000000000..7e71bebda --- /dev/null +++ b/modules/monitoring/application/views/helpers/ServiceFlags.php @@ -0,0 +1,40 @@ +service_handled && $service->service_state > 0) { + $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled')); + } + if ($service->service_acknowledged && !$service->service_in_downtime) { + $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged') . ( + $service->service_last_ack ? ': ' . $service->service_last_ack : '' + )); + } + if ($service->service_is_flapping) { + $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping')) ; + } + if (!$service->service_notifications_enabled) { + $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled')); + } + if ($service->service_in_downtime) { + $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime')); + } + if (isset($service->service_last_comment) && $service->service_last_comment !== null) { + $icons[] = $this->view->icon( + 'comment', + $this->view->translate('Last Comment: ') . $service->service_last_comment + ); + } + if (!$service->service_active_checks_enabled) { + if (!$service->service_passive_checks_enabled) { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled')); + } else { + $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled')); + } + } + return $icons; + } +} \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/alertsummary/index.phtml b/modules/monitoring/application/views/scripts/alertsummary/index.phtml index dbfff6433..d96ca5bdc 100644 --- a/modules/monitoring/application/views/scripts/alertsummary/index.phtml +++ b/modules/monitoring/application/views/scripts/alertsummary/index.phtml @@ -1,12 +1,13 @@ +compact): ?>
    - tabs ?> -
    + tabs; ?> +
    - widget('limiter') ?> - paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> + limiter; ?> + paginator; ?>
    - +
    @@ -59,8 +60,7 @@
    partial('list/notifications.phtml', array( 'notifications' => $this->recentAlerts, - 'compact' => true, - 'inline' => true + 'compact' => true )); ?>
    @@ -71,8 +71,7 @@
    partial('list/notifications.phtml', array( 'notifications' => $this->notifications, - 'compact' => true, - 'inline' => true + 'compact' => true )); ?>
    diff --git a/modules/monitoring/application/views/scripts/comment/remove.phtml b/modules/monitoring/application/views/scripts/comment/remove.phtml new file mode 100644 index 000000000..73f8c68a8 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comment/remove.phtml @@ -0,0 +1,11 @@ +
    + + compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
    +
    + +
    diff --git a/modules/monitoring/application/views/scripts/comment/show.phtml b/modules/monitoring/application/views/scripts/comment/show.phtml new file mode 100644 index 000000000..8216a9c70 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comment/show.phtml @@ -0,0 +1,81 @@ +
    + compact): ?> + tabs; ?> + + +
    + render('partials/comment/comment-header.phtml'); ?> +
    +
    +
    + +

    translate('Comment detail information') ?>

    +
- escape($value->message) ?> + escape($value->message), false) ?>
+ + + comment->objecttype === 'service'): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
translate('Service') ?> + icon('service', $this->translate('Service')); ?> + link()->service( + $this->comment->service_description, + $this->comment->service_display_name, + $this->comment->host_name, + $this->comment->host_display_name + ); + ?> + translate('Host') ?> + icon('host', $this->translate('Host')); ?> + link()->host( + $this->comment->host_name, + $this->comment->host_display_name + ); + ?> +
translate('Author') ?>icon('user', $this->translate('User')) ?> escape($this->comment->author) ?>
translate('Persistent') ?>escape($this->comment->persistent) ? $this->translate('Yes') : $this->translate('No') ?>
translate('Created') ?>escape($this->comment->timestamp)) ?>
translate('Expires') ?> + comment->expiration ? sprintf( + $this->translate('This comment expires on %s at %s.'), + date('d.m.y', $this->comment->expiration), + date('H:i', $this->comment->expiration) + ) : $this->translate('This comment does not expire.'); + ?> +
translate('Commands') ?> + +
+ +
+ diff --git a/modules/monitoring/application/views/scripts/comments/delete-all.phtml b/modules/monitoring/application/views/scripts/comments/delete-all.phtml new file mode 100644 index 000000000..698c4eea7 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comments/delete-all.phtml @@ -0,0 +1,12 @@ +
+ + compact): ?> + tabs; ?> + + + render('partials/comment/comments-header.phtml'); ?> +
+ +
+ +
diff --git a/modules/monitoring/application/views/scripts/comments/show.phtml b/modules/monitoring/application/views/scripts/comments/show.phtml new file mode 100644 index 000000000..4f9c64d47 --- /dev/null +++ b/modules/monitoring/application/views/scripts/comments/show.phtml @@ -0,0 +1,25 @@ +
+ compact): ?> + tabs; ?> + + +
+ render('partials/comment/comments-header.phtml'); ?> +
+
+ +
+

icon('reschedule') ?> translate('Commands') ?>

+ qlink( + sprintf( + $this->translate('Remove %d comments'), + count($comments) + ), + $removeAllLink, + null, + array( + 'icon' => 'trash', + 'title' => $this->translate('Remove all selected comments.') + ) + ) ?> +
diff --git a/modules/monitoring/application/views/scripts/downtime/remove.phtml b/modules/monitoring/application/views/scripts/downtime/remove.phtml new file mode 100644 index 000000000..57fe36655 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtime/remove.phtml @@ -0,0 +1,11 @@ +
+ + compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
+
+ +
\ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/downtime/show.phtml b/modules/monitoring/application/views/scripts/downtime/show.phtml new file mode 100644 index 000000000..aed35b9ac --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtime/show.phtml @@ -0,0 +1,121 @@ +
+ compact): ?> + tabs; ?> + + + render('partials/downtime/downtime-header.phtml'); ?> +
+
+

translate('Downtime detail information') ?>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + downtime->is_flexible): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ isService ? $this->translate('Service') : $this->translate('Host') ?> + + isService): ?> + link()->service( + $downtime->service_description, + $downtime->service_display_name, + $downtime->host_name, + $downtime->host_display_name + ); + $icon = $this->icon('service', $this->translate('Service')); + ?> + + icon('host', $this->translate('Host')); + $link = $this->link()->host($downtime->host_name, $downtime->host_display_name) + ?> + + + +
translate('Author') ?>icon('user', $this->translate('User')) ?> escape($this->downtime->author_name) ?>
translate('Comment') ?>icon('comment', $this->translate('Comment')) ?> escape($this->downtime->comment) ?>
translate('Entry Time') ?> escape($this->downtime->entry_time)) ?>
escape( + $this->downtime->is_flexible ? + $this->translate('Flexible') : $this->translate('Fixed') + ); ?> + escape( + $this->downtime->is_flexible ? + $this->translate('Flexible downtimes have a hard start and end time,' + . ' but also an additional restriction on the duration in which ' + . ' the host or service may actually be down.') : + $this->translate('Fixed downtimes have a static start and end time.') + ); ?> +
translate('Scheduled start') ?>downtime->scheduled_start) ?>
translate('Scheduled end') ?>downtime->scheduled_end) ?>
translate('Duration') ?>format()->duration($this->escape($this->downtime->duration)); ?>
translate('Actual start time') ?>start); ?>
translate('Actual end time') ?>end); ?>
translate('In effect') ?> + escape( + $this->downtime->is_in_effect ? + $this->translate('Yes') : $this->translate('No') + ); + ?> +
translate('Commands') ?> + +
+ +
+ diff --git a/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml new file mode 100644 index 000000000..fed0e12f7 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml @@ -0,0 +1,12 @@ +
+ + compact): ?> + tabs; ?> + + + render('partials/downtime/downtimes-header.phtml'); ?> +
+ +
+ +
\ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/downtimes/show.phtml b/modules/monitoring/application/views/scripts/downtimes/show.phtml new file mode 100644 index 000000000..1c508bbc9 --- /dev/null +++ b/modules/monitoring/application/views/scripts/downtimes/show.phtml @@ -0,0 +1,24 @@ +
+ + compact): ?> + tabs; ?> + + + render('partials/downtime/downtimes-header.phtml'); ?> +

+
+
+

icon('reschedule') ?> translate('Commands') ?>

+ qlink( + sprintf( + $this->translate('Remove all %d scheduled downtimes'), + count($downtimes) + ), + $removeAllLink, + null, + array( + 'icon' => 'trash', + 'title' => $this->translate('Remove all selected downtimes.') + ) + ) ?> +
diff --git a/modules/monitoring/application/views/scripts/host/show.phtml b/modules/monitoring/application/views/scripts/host/show.phtml index 867692301..32389515b 100644 --- a/modules/monitoring/application/views/scripts/host/show.phtml +++ b/modules/monitoring/application/views/scripts/host/show.phtml @@ -1,4 +1,7 @@
+ compact): ?> + tabs; ?> + render('partials/host/object-header.phtml') ?> render('partials/host/servicesummary.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/hosts/show.phtml b/modules/monitoring/application/views/scripts/hosts/show.phtml index 5c00cf827..6bbb3c80d 100644 --- a/modules/monitoring/application/views/scripts/hosts/show.phtml +++ b/modules/monitoring/application/views/scripts/hosts/show.phtml @@ -1,124 +1,231 @@ -
+
+ compact): ?> + + + + render('list/components/hostssummary.phtml') ?> render('partials/host/objects-header.phtml'); ?> +
-
+ +
+

+ icon('reschedule') ?> + translate('Commands') ?> +

+ +

+ translatePlural( + 'Issue commands to %s selected host:', + 'Issue commands to all %s selected hosts:', + count($objects) + ), '' . count($objects) . '') ?> +

+ - translate('No hosts matching the filter'); ?> + translate('No hosts found matching the filter'); ?> -

translatePlural('%u Host', '%u Hosts', $hostCount), $hostCount); ?>

-
qlink( - sprintf($this->translate('List all %u hosts'), $hostCount), - $listAllLink - ); ?>
-
- -
-
qlink( - sprintf($this->translate('Reschedule the next check for all %u hosts'), $hostCount), + + +
+ qlink( + $this->translate('Reschedule next checks'), $rescheduleAllLink, null, array('icon' => 'reschedule') - ); ?>
-
qlink( - sprintf($this->translate('Schedule a downtime for all %u hosts'), $hostCount), + ); ?> + +
+ qlink( + $this->translate('Schedule downtimes'), $downtimeAllLink, null, array('icon' => 'plug') - ); ?>
-
qlink( - sprintf($this->translate('Submit a passive check result for all %u hosts'), $hostCount), + ); ?> + +
+ qlink( + $this->translate('Submit passive check results'), $processCheckResultAllLink, null, array('icon' => 'reply') - ); ?>
+ ); ?> + +
+ qlink( + $this->translate('Add comments'), + $addCommentLink, + null, + array('icon' => 'comment') + ); ?> + hasPermission('monitoring/command/send-custom-notification')): ?> -
qlink( - sprintf($this->translate('Send a custom notification for all %u hosts'), $hostCount), - $sendCustomNotificationLink, - null, - array('icon' => 'comment') - ); ?>
+
+ qlink( + sprintf($this->translate('Send a custom notification for all %u hosts'), $hostCount), + $sendCustomNotificationLink, + null, + array('icon' => 'comment') + ); ?> - 0): ?> -
-

translatePlural( - '%u Unhandled Host Problem', - '%u Unhandled Host Problems', - $unhandledCount - ), - $unhandledCount - ); ?>

-
qlink( - sprintf( + + + + +

+ icon('attention-alt') ?> + translatePlural( + 'Problem', + 'Problems', + $unhandledCount + $problemCount + ) ?> +

+ + +

+ translatePlural( - 'Schedule a downtime for %u unhandled host problem', - 'Schedule a downtime for %u unhandled host problems', + 'There is %s problem.', + 'There are %s problems.', + $problemCount + ), + '' . $problemCount . '' + ); ?> +

+ qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u problem host', + 'Schedule a downtime for %u problem hosts', + $problemCount + ), + $problemCount + ), + $downtimeLink, + null, + array('icon' => 'plug') + ); ?> + + + +

+ translatePlural( + 'There is %s unhandled problem host, issue commands to the problematic host:', + 'There are %s unhandled problem hosts, issue commands to the problematic hosts:', + $unhandledCount + ), + '' . $unhandledCount . '' + ); ?> +

+ + qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u unhandled problem host', + 'Schedule a downtime for %u unhandled problem hosts', + $unhandledCount + ), + $unhandledCount + ), + $downtimeUnhandledLink, + null, + array('icon' => 'plug') + ); ?> + +
+ qlink( + sprintf( + $this->translatePlural( + 'Acknowledge %u unhandled problem host', + 'Acknowledge %u unhandled problem hosts', + $unhandledCount + ), $unhandledCount ), - $unhandledCount - ), - $downtimeUnhandledLink, - null, - array('icon' => 'plug') - ); ?>
-
qlink( - sprintf( - $this->translatePlural( - 'Acknowledge %u unhandled host problem', - 'Acknowledge %u unhandled host problems', - $unhandledCount - ), - $unhandledCount - ), - $acknowledgeUnhandledLink, - null, - array('icon' => 'ok') - ); ?>
-
- + $acknowledgeUnhandledLink, + null, + array('icon' => 'ok') + ); ?> + + + + 0): ?>
-

icon('ok', $this->translate('Acknowledgements')) ?> translate('Acknowledgements') ?>

+ + translatePlural( - '%u Acknowledged Host Problem', - '%u Acknowledged Host Problems', + '%s Acknowledged Host Problem', + '%s Acknowledged Host Problems', $acknowledgedCount ), - $acknowledgedCount - ); ?> + '' . $acknowledgedCount . '' + ); ?> +
- 0): ?> -

qlink( - sprintf( - $this->translatePlural( - 'List %u host currently in downtime', - 'List %u hosts currently in downtime', - $inDowntimeCount + + getScheduledDowntimes()) ?> + + +

icon('plug', $this->translate('Downtimes'))?> translate('Downtimes')?>

+ qlink( + sprintf( + $this->translatePlural( + '%s scheduled downtime', + '%s scheduled downtimes', + $scheduledDowntimeCount + ), + $scheduledDowntimeCount ), - $inDowntimeCount - ), - $inDowntimeLink, - null, - array('icon' => 'plug') - ); ?> + $showDowntimesLink, + null, + array('data-base-target' => '_next') + );?> + translate('on all selected hosts.')) ?> + + 0): ?> +
+ qlink( + sprintf( + $this->translatePlural( + '%s host', + '%s hosts', + $inDowntimeCount + ), + $inDowntimeCount + ), + $inDowntimeLink, + null, + array('data-base-target' => '_next') + ); ?> + translate('are currently in downtime.') ?> + + getComments())) > 0): ?> -

qlink( +

icon('comment', $this->translate('Comments'))?> translate('Comments') ?>

+ qlink( sprintf( $this->translatePlural( - 'List %u host comment', - 'List %u host comments', + '%s comment', + '%s comments', $commentCount ), $commentCount ), $commentsLink, null, - array('icon' => 'comment') - ); ?> + array('data-base-target' => '_next') + ); ?> + translate('on all selected hosts.') ?> +
\ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml index 4a315a8a8..551d0cbac 100644 --- a/modules/monitoring/application/views/scripts/list/comments.phtml +++ b/modules/monitoring/application/views/scripts/list/comments.phtml @@ -1,71 +1,70 @@ -compact): ?> +compact): ?>
- tabs->render($this); ?> -
- translate('Sort by'); ?> sortControl->render($this); ?> + tabs; ?> +
+ render('list/components/selectioninfo.phtml'); ?>
- widget('limiter', array('url' => $this->url, 'max' => $comments->count())); ?> - paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?> +
+ translate('Comments') ?>: +
+ sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
- - translate('No comments matching the filter'); ?> -
- + +if (count($comments) === 0) { + echo $this->translate('No comments found matching the filter') . '
'; + return; +} +?> + - - type) { - case 'flapping': - $icon = 'flapping'; - $title = $this->translate('Flapping'); - $tooltip = $this->translate('Comment was caused by a flapping host or service.'); - break; - case 'comment': - $icon = 'user'; - $title = $this->translate('User Comment'); - $tooltip = $this->translate('Comment was created by an user.'); - break; - case 'downtime': - $icon = 'plug'; - $title = $this->translate('Downtime'); - $tooltip = $this->translate('Comment was caused by a downtime.'); - break; - case 'ack': - $icon = 'ok'; - $title = $this->translate('Acknowledgement'); - $tooltip = $this->translate('Comment was caused by an acknowledgement.'); - break; - } - ?> + comment = $comment; ?> + diff --git a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml index fc7d30867..d6166a52d 100644 --- a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml +++ b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml @@ -1,7 +1,17 @@ setQueryString($f->toQueryString()); +} +$this->baseFilter = isset($this->baseFilter) ? $this->baseFilter : null; $selfUrl = 'monitoring/list/hosts'; $currentUrl = Url::fromRequest()->getRelativeUrl(); ?>
compact ? ' data-base-target="col1"' : ''; ?>> @@ -10,17 +20,22 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); stats->hosts_up): ?> qlink( - $this->stats->hosts_up, - $selfUrl, - array('host_state' => 0), - array('title' => sprintf( - $this->translatePlural( - 'List %u host that is currently in state UP', - 'List %u hosts which are currently in state UP', - $this->stats->hosts_up + $this->stats->hosts_up, + urlAddFilterOptional( + $selfUrl, + Filter::where('host_state', 0), + $this->baseFilter ), - $this->stats->hosts_up - )) + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UP', + 'List %u hosts which are currently in state UP', + $this->stats->hosts_up + ), + $this->stats->hosts_up + ) + ) ); ?> @@ -29,11 +44,12 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); qlink( $this->stats->hosts_down_unhandled, - $selfUrl, - array( - 'host_state' => 1, - 'host_unhandled' => 1 + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 1), Filter::where('host_unhandled', 1)), + $this->baseFilter ), + null, array('title' => sprintf( $this->translatePlural( 'List %u host that is currently in state DOWN', @@ -49,11 +65,12 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); qlink( $this->stats->hosts_down_handled, - $selfUrl, - array( - 'host_state' => 1, - 'host_unhandled' => 0 + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 1), Filter::where('host_unhandled', 0)), + $this->baseFilter ), + null, array('title' => sprintf( $this->translatePlural( 'List %u host that is currently in state DOWN (Acknowledged)', @@ -74,11 +91,12 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); qlink( $this->stats->hosts_unreachable_unhandled, - $selfUrl, - array( - 'host_state' => 2, - 'host_unhandled' => 1 + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 2), Filter::where('host_unhandled', 1)), + $this->baseFilter ), + null, array('title' => sprintf( $this->translatePlural( 'List %u host that is currently in state UNREACHABLE', @@ -94,11 +112,12 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); qlink( $this->stats->hosts_unreachable_handled, - $selfUrl, - array( - 'host_state' => 2, - 'host_unhandled' => 0 + urlAddFilterOptional( + $selfUrl, + Filter::matchAll(Filter::where('host_state', 2), Filter::where('host_unhandled', 0)), + $this->baseFilter ), + null, array('title' => sprintf( $this->translatePlural( 'List %u host that is currently in state UNREACHABLE (Acknowledged)', @@ -119,8 +138,12 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); qlink( $this->stats->hosts_pending, - $selfUrl, - array('host_state' => 99), + urlAddFilterOptional( + $selfUrl, + Filter::where('host_state', 99), + $this->baseFilter + ), + null, array('title' => sprintf( $this->translatePlural( 'List %u host that is currently in state PENDING', diff --git a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml index 0554fcfd4..76a51de92 100644 --- a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml @@ -1,8 +1,19 @@ setQueryString($f->toQueryString()); +} +$this->baseFilter = isset($this->baseFilter) ? $this->baseFilter : null; + $selfUrl = 'monitoring/list/services'; $currentUrl = Url::fromRequest()->getRelativeUrl(); ?>
compact ? ' data-base-target="col1"' : ''; ?>> @@ -10,19 +21,21 @@ $currentUrl = Url::fromRequest()->getRelativeUrl(); stats->services_ok): ?> - qlink( - $this->stats->services_ok, - $selfUrl, - array('service_state' => 0), - array('title' => sprintf( - $this->translatePlural( - 'List %u service that is currently in state OK', - 'List %u services which are currently in state OK', + qlink( + $this->stats->services_ok, + urlAddFilterOptional($selfUrl, Filter::where('service_state', 0), $this->baseFilter), + null, + array('title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK', + 'List %u services which are currently in state OK', + $this->stats->services_ok + ), $this->stats->services_ok - ), - $this->stats->services_ok - )) - ); ?> + )) + ); + ?> 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ if ($this->stats->$pre) { $handled = $pre . '_handled'; $unhandled = $pre . '_unhandled'; - $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); - $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); + $paramsHandled = Filter::matchAll( + Filter::where('service_state', $stateId), + Filter::where('service_handled', 1) + ); + $paramsUnhandled = Filter::matchAll( + Filter::where('service_state', $stateId), + Filter::where('service_handled', 0) + ); if ($this->stats->$unhandled) { - $compareUrl = Url::fromPath($selfUrl, $paramsUnhandled)->getRelativeUrl(); + $compareUrl = Url::fromPath($selfUrl)->setQueryString($paramsUnhandled->toQueryString())->getRelativeUrl(); } else { - $compareUrl = Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl(); + $compareUrl = Url::fromPath($selfUrl)->setQueryString($paramsUnhandled->toQueryString())->getRelativeUrl(); } if ($compareUrl === $currentUrl) { @@ -50,8 +69,8 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ echo $this->qlink( $this->stats->$unhandled, - $selfUrl, - $paramsUnhandled, + urlAddFilterOptional($selfUrl, $paramsUnhandled, $this->baseFilter), + null, array('title' => sprintf( $this->translatePlural( 'List %u service that is currently in state %s', @@ -65,7 +84,7 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ } if ($this->stats->$handled) { - if (Url::fromPath($selfUrl, $paramsHandled)->getRelativeUrl() === $currentUrl) { + if (Url::fromPath($selfUrl)->setQueryString($paramsHandled->toQueryString())->getRelativeUrl() === $currentUrl) { $active = ' active'; } else { $active = ''; @@ -75,8 +94,8 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ } echo $this->qlink( $this->stats->$handled, - $selfUrl, - $paramsHandled, + urlAddFilterOptional($selfUrl, $paramsHandled, $this->baseFilter), + null, array('title' => sprintf( $this->translatePlural( 'List %u service that is currently in state %s (Acknowledged)', @@ -99,8 +118,8 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ qlink( $this->stats->services_pending, - $selfUrl, - array('service_state' => 99), + urlAddFilterOptional($selfUrl, Filter::where('service_state', 99), $this->baseFilter), + null, array('title' => sprintf( $this->translatePlural( 'List %u service that is currently in state PENDING', diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml index 09083a387..5e3b038a7 100644 --- a/modules/monitoring/application/views/scripts/list/contactgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml @@ -1,17 +1,22 @@ -compact): ?> +compact): ?>
- tabs ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
- $groupInfo): ?> +if (count($groupData) === 0) { + echo $this->translate('No contactgroups found matching the filter') . '
'; + return; +} +?> +
+ $groupInfo): ?>

diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml index 80405f7c6..78910cead 100644 --- a/modules/monitoring/application/views/scripts/list/contacts.phtml +++ b/modules/monitoring/application/views/scripts/list/contacts.phtml @@ -1,18 +1,21 @@ +compact): ?>
- tabs ?> -
- sortControl->render($this); ?> -
- paginationControl($contacts, null, null, array('preserve' => $this->preserve)); ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
- translate('No contacts matching the filter'); - return; - } - foreach ($contacts as $contact): ?> +translate('No contacts found matching the filter') . '
'; + return; +} +?> +
img('/static/gravatar', array('email' => $contact->contact_email)); ?> qlink( diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml index 25d68b459..9448eb402 100644 --- a/modules/monitoring/application/views/scripts/list/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml @@ -1,32 +1,35 @@ - -compact): ?> +if (! $this->compact): ?>
- tabs->render($this); ?> -
- translate('Sort by'); ?> sortControl->render($this); ?> - filterEditor): ?> - filterPreview ?> - -
- widget('limiter', array('url' => $this->url, 'max' => $downtimes->count())); ?> - paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?> + tabs; ?> +
+ render('list/components/selectioninfo.phtml'); ?> +
+
+ translate('Downtimes') ?> +
+ sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
-filterEditor ?> - - translate('No active downtimes'); ?> -
- + +if (count($downtimes) === 0) { + echo $this->translate('No downtimes found matching the filter,' + . ' maybe the downtime already expired.') . '
'; + return; +} +?> +
- icon($icon, $tooltip) ?> -
- escape($title); ?> -
- timeAgo($comment->timestamp, $this->compact); ?> + render('partials/comment/comment-description.phtml'); ?>
objecttype === 'service'): ?> icon('service', $this->translate('Service')); ?> - link()->service( - $comment->service_description, - $comment->service_display_name, - $comment->host_name, - $comment->host_display_name - ); ?> + + qlink( + sprintf( + $this->translate('%s on %s', 'Service running on host'), + $comment->service_display_name, + $comment->host_display_name + ), + 'monitoring/comment/show', + array('comment_id' => $comment->id), + array('title' => sprintf( + $this->translate('Show detailed information for comment on %s for %s'), + $comment->service_display_name, + $comment->host_display_name + ))) ?> icon('host', $this->translate('Host')); ?> - link()->host($comment->host_name, $comment->host_display_name); ?> + + qlink( + $comment->host_display_name, + 'monitoring/comment/show', + array('comment_id' => $comment->id), + array('title' => sprintf( + $this->translate('Show detailed information for comment on %s'), + $comment->host_display_name + ))) ?>
icon('comment', $this->translate('Comment')); ?> author) - ? '[' . $comment->author . '] ' + ? '[' . $this->escape($comment->author) . '] ' : ''; ?>escape($comment->comment); ?>
@@ -83,17 +82,12 @@
populate(array('comment_id' => $comment->id, 'redirect' => $this->url)); - if ($comment->objecttype === 'host') { - $delCommentForm->setAction( - $this->url('monitoring/host/delete-comment', array('host_name' => $comment->host_name)) - ); - } else { - $delCommentForm->setAction($this->url('monitoring/service/delete-comment', array( - 'host_name' => $comment->host_name, - 'service_description' => $comment->service_description - ))); - } + $delCommentForm->populate( + array( + 'comment_id' => $comment->id, + 'comment_is_service' => isset($comment->service_description) + ) + ); echo $delCommentForm; ?>
is_in_effect ? $this->timeSince($downtime->start, $this->compact) : $this->timeUntil($downtime->start, $this->compact) ?> diff --git a/modules/monitoring/application/views/scripts/list/eventgrid.phtml b/modules/monitoring/application/views/scripts/list/eventgrid.phtml index be327623a..20016f9f4 100644 --- a/modules/monitoring/application/views/scripts/list/eventgrid.phtml +++ b/modules/monitoring/application/views/scripts/list/eventgrid.phtml @@ -1,21 +1,25 @@ - - - -
- tabs->render($this); ?> -
- -
-
- +if (! $this->compact): ?> +
+ tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?> + +
+
translate('No state changes in the selected time period.') . '
'; + return; +} + $settings = array( 'cnt_up' => array( 'tooltip' => $this->translate('%d hosts ok on %s'), @@ -63,11 +67,8 @@ $to = intval($form->getValue('to', time())); if ($to - $from > 315360000) { $from = $to - 315360000; } -$data = array(); -if (count($summary) === 0) { - echo $this->translate('No state changes in the selected time period.'); -} +$data = array(); foreach ($summary as $entry) { $day = $entry->day; $value = $entry->$column; diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml index 1f09f80d7..491dee8ff 100644 --- a/modules/monitoring/application/views/scripts/list/eventhistory.phtml +++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml @@ -1,32 +1,23 @@ - -compact): ?> +if (! $this->compact): ?>
- tabs ?> -
-
- translate('Sort by') ?> sortControl ?> -
-
- - widget('limiter', array('url' => $this->url, 'max' => $this->history->count())); ?> - paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> - + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
-filterEditor ?> - - translate('No history events matching the filter') ?> -
- - +translate('No history events found matching the filter') . ''; + return; +} +?>
- - icon('service', $this->translate('Service')); ?> - link()->service( - $downtime->service_description, $downtime->service_display_name, $downtime->host_name, $downtime->host_display_name - ) ?> - - icon('host', $this->translate('Host')); ?> - link()->host($downtime->host_name, $downtime->host_display_name) ?> - + icon('service'); + } else { + echo $this->icon('host'); + } + ?> + qlink( + sprintf($this->translate('%s on %s', 'Service running on host'), $downtime->service_display_name, $downtime->host_display_name), + 'monitoring/downtime/show', + array('downtime_id' => $downtime->id), + array('title' => sprintf( + $this->translate('Show detailed information for downtime on %s for %s'), + $downtime->service_display_name, + $downtime->host_display_name + ))) ?>
icon('comment', $this->translate('Comment')); ?> [escape($downtime->author_name) ?>] escape($downtime->comment) ?>
@@ -109,15 +119,12 @@ use Icinga\Module\Monitoring\Object\Service;
populate(array('downtime_id' => $downtime->id, 'redirect' => $this->url)); - if (! $isService) { - $delDowntimeForm->setAction($this->url('monitoring/host/delete-downtime', array('host_name' => $downtime->host_name))); - } else { - $delDowntimeForm->setAction($this->url('monitoring/service/delete-downtime', array( - 'host_name' => $downtime->host_name, - 'service_description' => $downtime->service_description - ))); - } + $delDowntimeForm->populate( + array( + 'downtime_id' => $downtime->id, + 'downtime_is_service' => isset($downtime->service_description) + ) + ); echo $delDowntimeForm; ?>
diff --git a/modules/monitoring/application/views/scripts/list/hostgroups.phtml b/modules/monitoring/application/views/scripts/list/hostgroups.phtml index 5a4509317..f1a3e2004 100644 --- a/modules/monitoring/application/views/scripts/list/hostgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/hostgroups.phtml @@ -1,83 +1,67 @@ -compact): ?> -
- +compact): ?>
- tabs ?> -
- translate('Sort by'); ?> sortControl->render($this); ?> -
- widget('limiter')->setMaxLimit(count($hostgroups)); ?> - paginationControl($hostgroups, null, null, array('preserve' => $this->preserve)); ?> -
-
+ tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> filterEditor; ?> +
- translate('No host groups matching the filter'); - echo '
'; - return; - } - ?> -
+
+translate('No hostgroups found matching the filter') . '
'; + return; +} +?> +
+ + - - - services_critical_last_state_change_unhandled): ?> - + - services_unknown_last_state_change_unhandled): ?> - - services_warning_last_state_change_unhandled): ?> - - services_critical_last_state_change_handled): ?> - - services_unknown_last_state_change_handled): ?> - - services_warning_last_state_change_handled): ?> - - services_ok_last_state_change): ?> - - - - + + - host_state); - $hostLink = $this->href('monitoring/host/show', array('host_name' => $host->host_name)); + $hostLink = $this->href('monitoring/host/show', array('host' => $host->host_name)); - $icons = array(); - if (! $host->host_handled && $host->host_state > 0){ - $icons[] = $this->icon('attention-alt', $this->translate('Unhandled')); - } - if ($host->host_acknowledged) { - $icons[] = $this->icon('ok', $this->translate('Acknowledged')); - } - - if ($host->host_is_flapping) { - $icons[] = $this->icon('flapping', $this->translate('Flapping')); - } - - if (! $host->host_notifications_enabled) { - $icons[] = $this->icon('bell-off-empty', $this->translate('Notifications Disabled')); - } - - if ($host->host_in_downtime) { - $icons[] = $this->icon('plug', $this->translate('In Downtime')); - } - - if (! $host->host_active_checks_enabled) { - if (! $host->host_passive_checks_enabled) { - $icons[] = $this->icon('eye-off', $this->translate('Active And Passive Checks Disabled')); - } else { - $icons[] = $this->icon('eye-off', $this->translate('Active Checks Disabled')); - } - } - - if (isset($host->host_last_comment) && $host->host_last_comment !== null) { - $icons[] = $this->icon('comment', $this->translate('Last Comment: ') . $host->host_last_comment); - } ?> @@ -93,10 +53,8 @@ if ($hosts->count() === 0) { - extraColumns as $col): ?> + addColumns as $col): ?> diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml index c934f674b..3d1598a69 100644 --- a/modules/monitoring/application/views/scripts/list/notifications.phtml +++ b/modules/monitoring/application/views/scripts/list/notifications.phtml @@ -1,26 +1,24 @@ - -compact): ?> +if (! $this->compact): ?>
- tabs ?> -
- translate('Sort by') ?> sortControl->render($this) ?> -
- widget('limiter') ?> - paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> + tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
- - translate('No notifications matching the filter') ?> - +translate('No notifications found matching the filter') . '
'; + return; +} +?>
translate('Last Problem'); ?> translate('Host Group'); ?>translate('Total Hosts'); ?>translate('Host States'); ?> translate('Total Services'); ?> translate('Service States'); ?>
- translate('CRITICAL'); ?> + hosts_down_unhandled) { + $handled = false; + $state = Host::STATE_DOWN; + $lastStateChange = $h->hosts_down_last_state_change_unhandled; + } elseif ($h->hosts_unreachable_unhandled) { + $handled = false; + $state = Host::STATE_UNREACHABLE; + $lastStateChange = $h->hosts_unreachable_last_state_change_unhandled; + } else { + $handled = true; + if ($h->hosts_down_handled) { + $state = Host::STATE_DOWN; + $lastStateChange = $h->hosts_down_last_state_change_handled; + } elseif ($h->hosts_unreachable_handled) { + $state = Host::STATE_UNREACHABLE; + $lastStateChange = $h->hosts_unreachable_last_state_change_handled; + } elseif ($h->hosts_up) { + $state = Host::STATE_UP; + $lastStateChange = $h->hosts_up_last_state_change; + } else { + $state = Host::STATE_PENDING; + $lastStateChange = $h->hosts_pending_last_state_change; + } + } + ?> +
+
- timeSince($h->services_critical_last_state_change_unhandled); ?> + timeSince($lastStateChange, $this->compact); ?>
- translate('UNKNOWN'); ?> -
- timeSince($h->services_unknown_last_state_change_unhandled); ?> -
- translate('WARNING'); ?> -
- timeSince($h->services_warning_last_state_change_unhandled); ?> -
- translate('CRITICAL'); ?> -
- timeSince($h->services_critical_last_state_change_handled); ?> -
- translate('UNKNOWN'); ?> -
- timeSince($h->services_unknown_last_state_change_handled); ?> -
- translate('WARNING'); ?> -
- timeSince($h->services_warning_last_state_change_handled); ?> -
- translate('OK'); ?> -
- timeSince($h->services_ok_last_state_change); ?> -
- translate('PENDING'); ?> -
- timeSince($h->services_pending_last_state_change); ?> -
qlink( $h->hostgroup_alias, @@ -86,6 +70,173 @@ array('title' => sprintf($this->translate('List all hosts in the group "%s"'), $h->hostgroup_alias)) ); ?> + qlink( + $h->hosts_total, + 'monitoring/list/hosts', + array('hostgroup_name' => $h->hostgroup_name), + array('title' => sprintf( + $this->translate('List all hosts in host group "%s"'), + $h->hostgroup_alias + )) + ); ?> + + hosts_up): ?> + + qlink( + $h->hosts_up, + 'monitoring/list/hosts', + array( + 'host_state' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UP in the host group "%s"', + 'List %u hosts which are currently in state UP in the host group "%s"', + $h->hosts_up + ), + $h->hosts_up, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_down_unhandled): ?> + + qlink( + $h->hosts_down_unhandled, + 'monitoring/list/hosts', + array( + 'host_state' => 1, + 'host_acknowledged' => 0, + 'host_in_downtime' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN in the host group "%s"', + 'List %u hosts which are currently in state DOWN in the host group "%s"', + $h->hosts_down_unhandled + ), + $h->hosts_down_unhandled, + $h->hostgroup_alias + ) + ) + ); ?> + + hosts_down_handled): ?> + + qlink( + $h->hosts_down_handled, + 'monitoring/list/hosts', + array( + 'host_state' => 1, + 'host_handled' => 1, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"', + 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"', + $h->hosts_down_handled + ), + $h->hosts_down_handled, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_down_unhandled): ?> + + + hosts_unreachable_unhandled): ?> + + qlink( + $h->hosts_unreachable_unhandled, + 'monitoring/list/hosts', + array( + 'host_state' => 2, + 'host_acknowledged' => 0, + 'host_in_downtime' => 0, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE in the host group "%s"', + 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"', + $h->hosts_unreachable_unhandled + ), + $h->hosts_unreachable_unhandled, + $h->hostgroup_alias + ) + ) + ); ?> + + hosts_unreachable_handled): ?> + + qlink( + $h->hosts_unreachable_handled, + 'monitoring/list/hosts', + array( + 'host_state' => 2, + 'host_handled' => 1, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"', + 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"', + $h->hosts_unreachable_handled + ), + $h->hosts_unreachable_handled, + $h->hostgroup_alias + ) + ) + ); ?> + + + hosts_unreachable_unhandled): ?> + + + hosts_pending): ?> + + qlink( + $h->hosts_pending, + 'monitoring/list/hosts', + array( + 'host_state' => 99, + 'hostgroup_name' => $h->hostgroup_name, + 'sort' => 'host_severity' + ), + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u host that is currently in state PENDING in the host group "%s"', + 'List %u hosts which are currently in state PENDING in the host group "%s"', + $h->hosts_pending + ), + $h->hosts_pending, + $h->hostgroup_alias + ) + ) + ); ?> + + + qlink( $h->services_total, @@ -97,7 +248,7 @@ )) ); ?> + services_ok): ?> qlink( diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index 09b58cfa3..5535d14fa 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -1,33 +1,24 @@ compact): ?> -
- +if (! $this->compact): ?>
- tabs ?> -
- render('list/components/selectioninfo.phtml') ?> - render('list/components/hostssummary.phtml') ?> - translate('Sort by') ?> sortControl->render($this) ?> + tabs; ?> +
+ render('list/components/selectioninfo.phtml'); ?> + render('list/components/hostssummary.phtml'); ?>
- -widget('limiter')->setMaxLimit($this->hosts->count()) ?> -paginationControl($hosts, null, null, array('preserve' => $this->preserve)) ?> -selectionToolbar('multi', $this->href('monitoring/hosts/show?' . $this->filter->toQueryString())) ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
-filterEditor ?> count() === 0) { - echo $this->translate('No hosts matching the filter'); - if (! $this->compact) { - echo '
'; - } +if (count($hosts) === 0) { + echo $this->translate('No hosts found matching the filter') . '
'; return; } ?> @@ -36,46 +27,15 @@ if ($hosts->count() === 0) { data-base-target="_next" class="action multiselect" data-icinga-multiselect-url="href('monitoring/hosts/show') ?>" - data-icinga-multiselect-data="host_name" + data-icinga-multiselect-data="host" >
- host_icon_image && ! preg_match('/[\'"]/', $host->host_icon_image)): ?> - icon($this->resolveMacros($host->host_icon_image, $host)) ?> - - + iconImage()->host($host) ?> + hostFlags($host)) ?> qlink( $host->host_display_name, $hostLink, @@ -113,10 +71,10 @@ if ($hosts->count() === 0) { ), 'monitoring/show/services', array( - 'host_name' => $host->host_name, + 'host' => $host->host_name, 'service_problem' => 1, 'service_handled' => 0 - ), + ), array( 'style' => 'font-weight: normal', 'title' => sprintf( @@ -133,7 +91,7 @@ if ($hosts->count() === 0) {

escape($this->ellipsis($host->host_output, 10000)) ?>

escape($host->$col) ?>
-compact): ?> +if (! $this->compact): ?>
tabs; ?> -
- translate('Sort by'); ?> sortControl; ?> -
+ sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
pivot->toArray(); +if (count($pivotData) === 0) { + echo $this->translate('No services found matching the filter') . ''; + return; +} + $hostFilter = '(host_name=' . implode('|host_name=', array_keys($pivotData)) . ')'; ?> - - - translate('No Services matching the filter'); ?> - - +
$serviceStates): ?> @@ -68,7 +68,7 @@ foreach ($serviceDescriptions as $service_description): ?> qlink( $host_name, 'monitoring/show/services?' . $serviceFilter, - array('host_name' => $host_name), + array('host' => $host_name), array('title' => sprintf($this->translate('List all reported services on host %s'), $host_name)) ); ?> @@ -80,10 +80,10 @@ foreach ($serviceDescriptions as $service_description): ?> qlink( '', - 'monitoring/service/show', + 'monitoring/show/service', array( - 'host_name' => $service->host_name, - 'service_description' => $service->service_description + 'host' => $service->host_name, + 'service' => $service->service_description ), array( 'aria-describedby' => $service->host_name . '_' . $service->service_description . '_desc', diff --git a/modules/monitoring/application/views/scripts/list/servicegroups.phtml b/modules/monitoring/application/views/scripts/list/servicegroups.phtml index 9b0447928..ba160af42 100644 --- a/modules/monitoring/application/views/scripts/list/servicegroups.phtml +++ b/modules/monitoring/application/views/scripts/list/servicegroups.phtml @@ -1,24 +1,20 @@ -compact): ?> -
- +compact): ?>
- tabs ?> -
- translate('Sort by'); ?> sortControl->render($this); ?> -
- widget('limiter')->setMaxLimit(count($servicegroups)); ?> - paginationControl($servicegroups, null, null, array('preserve' => $this->preserve)); ?> -
-
+ tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> filterEditor; ?> +
- translate('No service groups matching the filter'); - echo '
'; - return; - } - ?> +
+translate('No servicegroups found matching the filter') . '
'; + return; +} +?>
diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index d6dd14815..9f87e7c60 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -4,52 +4,44 @@ use Icinga\Module\Monitoring\Object\Service; $selfUrl = 'monitoring/list/services'; -if (!$this->compact): ?> +if (! $this->compact): ?>
-tabs ?> -
-render('list/components/selectioninfo.phtml') ?> -render('list/components/servicesummary.phtml') ?> -
-translate('Sort by') ?> sortControl ?> + tabs; ?> +
+ render('list/components/selectioninfo.phtml'); ?> + render('list/components/servicesummary.phtml'); ?> +
+ sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
-
-limit === 0): ?> -widget('limiter') ?> - -widget('limiter')->setCurrentPageCount($this->services->count()) ?> -paginationControl($services, null, null, array('preserve' => $this->preserve)) ?> -
-
-filterEditor ?> - - -
- -
translate('Last Problem'); ?>
" - data-icinga-multiselect-data="service_description,host_name"> - translate('No services found matching the filter') . ''; + return; } -foreach ($services as $service): +?> +
" + data-icinga-multiselect-data="service,host"> + +href( 'monitoring/service/show', array( - 'host_name' => $service->host_name, - 'service_description' => $service->service_description + 'host' => $service->host_name, + 'service' => $service->service_description ) ); $hostLink = $this->href( - 'monitoring/host/show', + 'monitoring/show/host', array( - 'host_name' => $service->host_name, + 'host' => $service->host_name, ) ); $serviceStateName = Service::getStateText($service->service_state); @@ -69,44 +61,8 @@ foreach ($services as $service): -extraColumns as $col): ?> +addColumns as $col): ?> diff --git a/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml index d387202c2..42c6e73cd 100644 --- a/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml +++ b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml @@ -1,31 +1,19 @@
+ + compact): ?> + + + + render('list/components/servicesummary.phtml') ?> render('partials/service/objects-header.phtml'); ?> + render('list/components/hostssummary.phtml') ?> render('partials/host/objects-header.phtml'); ?>
+
-
perfdata($service->service_perfdata, true, 8) ?>
- - service_handled && $service->service_state > 0): ?> - icon('attention-alt', $this->translate('Unhandled')) ?> - - - service_acknowledged && !$service->service_in_downtime): ?> - icon('ok', $this->translate('Acknowledged') . ( - $service->service_last_ack ? ': ' . $service->service_last_ack : '' - )) ?> - - - service_is_flapping): ?> - icon('flapping', $this->translate('Flapping')) ?> - - - service_notifications_enabled): ?> - icon('bell-off-empty', $this->translate('Notifications Disabled')) ?> - - - service_in_downtime): ?> - icon('plug', $this->translate('In Downtime')) ?> - - - service_last_comment) && $service->service_last_comment !== null): ?> - icon('comment', $this->translate('Last Comment: ') . $service->service_last_comment) ?> - - - service_active_checks_enabled): ?> - service_passive_checks_enabled): ?> - icon('eye-off', $this->translate('Active And Passive Checks Disabled')) ?> - - icon('eye-off', $this->translate('Active Checks Disabled')) ?> - - - - service_icon_image && ! preg_match('/[\'"]/', $service->service_icon_image)): ?> - icon($this->resolveMacros($service->service_icon_image, $service)) ?> - + iconImage()->service($service) ?> + serviceFlags($service)); ?> qlink( $service->service_display_name, $serviceLink, @@ -124,7 +80,7 @@ foreach ($services as $service): ); ?>

escape($this->ellipsis($service->service_output, 10000)); ?>

escape($service->$col) ?>
- - - - - - - - getObjects() as $object): /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ ?> - - getType() === $object::TYPE_HOST): ?> - - - - - - - - -
icon('host'); ?> translate('Host'); ?>icon('conf'); ?> translate('Service'); ?>
escape($object->getName()); ?>escape($object->getHost()->getName()); ?>escape($object->getName()); ?>

diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml new file mode 100644 index 000000000..2c5f07133 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml @@ -0,0 +1,27 @@ +type) { + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + $tooltip = $this->translate('Comment was caused by a flapping host or service.'); + break; + case 'comment': + $icon = 'user'; + $title = $this->translate('User Comment'); + $tooltip = $this->translate('Comment was created by an user.'); + break; + case 'downtime': + $icon = 'plug'; + $title = $this->translate('Downtime'); + $tooltip = $this->translate('Comment was caused by a downtime.'); + break; + case 'ack': + $icon = 'ok'; + $title = $this->translate('Acknowledgement'); + $tooltip = $this->translate('Comment was caused by an acknowledgement.'); + break; +} +?> +escape($title); ?>
+icon($icon, $tooltip) ?> +prefixedTimeSince($comment->timestamp); ?> diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml new file mode 100644 index 000000000..98ee0534b --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml @@ -0,0 +1,18 @@ +objecttype === 'service'): ?> + icon('service', $this->translate('Service')); ?> + link()->service( + $comment->service_description, + $comment->service_display_name, + $comment->host_name, + $comment->host_display_name + ); ?> + + icon('host', $this->translate('Host')); ?> + link()->host($comment->host_name, $comment->host_display_name); ?> + + +
+icon('comment', $this->translate('Comment')); ?> author) + ? '[' . $this->escape($comment->author) . '] ' + : ''; +?>escape($comment->comment); ?> diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml new file mode 100644 index 000000000..c3e0fadda --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml @@ -0,0 +1,10 @@ + + + + + +
+ render('partials/comment/comment-description.phtml'); ?> + + render('partials/comment/comment-detail.phtml'); ?> +
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml new file mode 100644 index 000000000..7e2a5d87c --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml @@ -0,0 +1,32 @@ + + 5) { + continue; + } + $this->comment = $comment; + ?> + + + + + + +
+ render('partials/comment/comment-description.phtml'); ?> + + render('partials/comment/comment-detail.phtml'); ?> +
+ +

+ 5): ?> + qlink( + sprintf($this->translate('show all %d comments'), $i), + $listAllLink, + null, + array( + 'icon' => $i > 5 ? 'down-open' : '', + 'data-base-target' => "_next" + ) + ) ?> + +

diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml new file mode 100644 index 000000000..92759a12a --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml @@ -0,0 +1,67 @@ + + + + + +
+ is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?> +
+ dateTimeRenderer( + ($downtime->is_in_effect ? $downtime->end : $downtime->start), + true + )->render( + $this->translate('on %s', 'datetime'), + $this->translate('at %s', 'time'), + $this->translate('in %s', 'timespan') + ); + ?> +
+ + is_flexible): ?> + is_in_effect): ?> + isService + ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.') + : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'), + date('d.m.y', $downtime->start), + date('H:i', $downtime->start), + $this->format()->duration($downtime->duration), + date('d.m.y', $downtime->end), + date('H:i', $downtime->end) + ); ?> + + isService + ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.') + : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'), + date('d.m.y H:i', $downtime->scheduled_start), + date('d.m.y H:i', $downtime->scheduled_end), + $this->format()->duration($downtime->duration) + ); ?> + + + is_in_effect): ?> + isService + ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.') + : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'), + date('d.m.y', $downtime->start), + date('H:i', $downtime->start), + date('d.m.y', $downtime->end), + date('H:i', $downtime->end) + ); ?> + + isService + ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.') + : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'), + date('d.m.y', $downtime->scheduled_start), + date('H:i', $downtime->scheduled_start), + date('d.m.y', $downtime->scheduled_end), + date('H:i', $downtime->scheduled_end) + ); ?> + + + +
diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml new file mode 100644 index 000000000..5518e5022 --- /dev/null +++ b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml @@ -0,0 +1,94 @@ + + + 5) { + continue; + } ?> + + + + + + +
+ is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?> +
+ dateTimeRenderer( + ($downtime->is_in_effect ? $downtime->end : $downtime->start), + true + )->render( + $this->translate('on %s', 'datetime'), + $this->translate('at %s', 'time'), + $this->translate('in %s', 'timespan') + ); + ?> +
+ isService): ?> + icon('service', $this->translate('Service')) ?> + service ?> on host_name ?>. + + icon('host', $this->translate('Host')) ?> + host_name ?>. + + + is_flexible): ?> + is_in_effect): ?> + isService + ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.') + : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'), + date('d.m.y', $downtime->start), + date('H:i', $downtime->start), + $this->format()->duration($downtime->duration), + date('d.m.y', $downtime->end), + date('H:i', $downtime->end) + ); ?> + + isService + ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.') + : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'), + date('d.m.y H:i', $downtime->scheduled_start), + date('d.m.y H:i', $downtime->scheduled_end), + $this->format()->duration($downtime->duration) + ); ?> + + + is_in_effect): ?> + isService + ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.') + : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'), + date('d.m.y', $downtime->start), + date('H:i', $downtime->start), + date('d.m.y', $downtime->end), + date('H:i', $downtime->end) + ); ?> + + isService + ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.') + : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'), + date('d.m.y', $downtime->scheduled_start), + date('H:i', $downtime->scheduled_start), + date('d.m.y', $downtime->scheduled_end), + date('H:i', $downtime->scheduled_end) + ); ?> + + +
+ +

+ 5): ?> + qlink( + sprintf($this->translate('show all %d downtimes'), $i), + $listAllLink, + null, + array( + 'icon' => $i > 5 ? 'down-open' : '', + 'data-base-target' => "_next" + ) + ) ?> + +

\ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/partials/host/object-header.phtml b/modules/monitoring/application/views/scripts/partials/host/object-header.phtml index ed99fd1b7..1a2fc356d 100644 --- a/modules/monitoring/application/views/scripts/partials/host/object-header.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/object-header.phtml @@ -1,11 +1,7 @@ -compact): ?> - - - + check_latency): ?> diff --git a/modules/monitoring/application/views/scripts/show/components/command.phtml b/modules/monitoring/application/views/scripts/show/components/command.phtml index 0b028ba71..d7fca7cfa 100644 --- a/modules/monitoring/application/views/scripts/show/components/command.phtml +++ b/modules/monitoring/application/views/scripts/show/components/command.phtml @@ -15,7 +15,7 @@ $command = array_shift($parts); echo $this->qlink( $this->translate('Process check result'), 'monitoring/host/process-check-result', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'reply', 'data-base-target' => '_self', @@ -26,7 +26,7 @@ $command = array_shift($parts); echo $this->qlink( $this->translate('Process check result'), 'monitoring/service/process-check-result', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'reply', 'data-base-target' => '_self', diff --git a/modules/monitoring/application/views/scripts/show/components/comments.phtml b/modules/monitoring/application/views/scripts/show/components/comments.phtml index 99c735dd4..c4b947e3c 100644 --- a/modules/monitoring/application/views/scripts/show/components/comments.phtml +++ b/modules/monitoring/application/views/scripts/show/components/comments.phtml @@ -7,7 +7,7 @@ echo $this->qlink( $this->translate('Add comment'), 'monitoring/host/add-comment', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'comment', 'data-base-target' => '_self', @@ -18,7 +18,7 @@ echo $this->qlink( $this->translate('Add comment'), 'monitoring/service/add-comment', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'comment', 'data-base-target' => '_self', @@ -48,7 +48,12 @@ foreach ($object->comments as $comment) { + +customvars as $name => $value) { diff --git a/modules/monitoring/application/views/scripts/show/components/downtime.phtml b/modules/monitoring/application/views/scripts/show/components/downtime.phtml index 6e138d704..7ca246a43 100644 --- a/modules/monitoring/application/views/scripts/show/components/downtime.phtml +++ b/modules/monitoring/application/views/scripts/show/components/downtime.phtml @@ -7,7 +7,7 @@ echo $this->qlink( $this->translate('Schedule downtime'), 'monitoring/host/schedule-downtime', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'plug', 'data-base-target' => '_self', @@ -20,7 +20,7 @@ echo $this->qlink( $this->translate('Schedule downtime'), 'monitoring/service/schedule-downtime', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'plug', 'data-base-target' => '_self', @@ -71,7 +71,12 @@ foreach ($object->downtimes as $downtime) {
@@ -13,6 +9,7 @@ use Icinga\Module\Monitoring\Object\Host; timeSince($object->host_last_state_change); ?> + iconImage()->host($object) ?> escape($object->host_display_name); ?> host_display_name !== $object->host_name): ?> (escape($object->host_name); ?>) diff --git a/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml index f5edd0213..bce62db6b 100644 --- a/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml @@ -1,16 +1,45 @@ -compact): ?> - - + + 0): ?> -
- translatePlural('Host (%u)', 'Hosts (%u)', $hostCount), $hostCount); ?> -
-
-   -
-
- $count): ?> - translate(strtoupper($text)), $count); ?>
- -
- \ No newline at end of file + + + + 5) { + continue; + } + ?> + + + + + + + +
host_state, true); ?>
+ iconImage()->host($host) ?> + hostFlags($host)) ?> + escape($host->getName()); ?>
+ escape($host->host_output) ?> +
+
+ 5): ?> + qlink( + sprintf($this->translate('show all %d hosts'), $i), + $listAllLink, + null, + array( + 'icon' => 'down-open', + 'data-base-target' => '_next' + ) + ); + ?> + +
+ + diff --git a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml index 23cc21e97..04408808f 100644 --- a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml @@ -3,7 +3,16 @@ use Icinga\Web\Url; use Icinga\Module\Monitoring\Object\Service; -$selfUrl = Url::fromPath('monitoring/show/services', array('host_name' => $object->host_name)); +function urlAddFilterOptional($url, $filter, $optional) { + $url = Url::fromPath($url); + $f = $filter; + if (isset($optional)) { + $f = Filter::matchAll($filter, $optional); + } + return $url->setQueryString($f->toQueryString()); +} + +$selfUrl = Url::fromPath('monitoring/show/services', array('host' => $object->host_name)); $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); ?>
compact ? ' data-base-target="col1"' : ''; ?>> stats->services_total): ?> @@ -31,6 +40,7 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); translate('No services configured on this host'); ?> + stats->services_ok): ?> diff --git a/modules/monitoring/application/views/scripts/partials/service/object-header.phtml b/modules/monitoring/application/views/scripts/partials/service/object-header.phtml index 9ecd66ef9..c3df4044b 100644 --- a/modules/monitoring/application/views/scripts/partials/service/object-header.phtml +++ b/modules/monitoring/application/views/scripts/partials/service/object-header.phtml @@ -1,12 +1,8 @@ -compact): ?> - - diff --git a/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml index 1ac27b934..176c43d92 100644 --- a/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml +++ b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml @@ -1,32 +1,45 @@ -compact): ?> - - + 0): ?> -
-
- translatePlural('Service (%u)', 'Services (%u)', $serviceCount), $serviceCount); ?> +
@@ -14,7 +10,8 @@ use Icinga\Module\Monitoring\Object\Service; timeSince($object->host_last_state_change) ?> - escape($object->host_display_name); ?> + iconImage()->service($object) ?> + escape($object->host_display_name); ?> host_display_name !== $object->host_name): ?> (escape($object->host_name); ?>) @@ -31,10 +28,11 @@ use Icinga\Module\Monitoring\Object\Service; timeSince($object->service_last_state_change) ?> + iconImage()->host($object) ?> translate('Service'); ?>: escape($object->service_display_name); ?> - service_display_name !== $object->service_description): ?> - (escape($object->service_description); ?>) - + service_display_name !== $object->service_description): ?> + (escape($object->service_description); ?>) + render('partials/service/statusicons.phtml'); ?>
+ + + 5) { + continue; + } + ?> + + + + + + +
service_state, true); ?>
+ iconImage()->service($service) ?> + serviceFlags($service)) ?> + + escape($service->getName()); ?> + translate('on') ?> + escape($service->getHost()->getName()); ?>
+
+ escape($service->service_output) ?> +
+
+ 5): ?> + qlink( + sprintf($this->translate('show all %d services'), $i), + $listAllLink, + null, + array( + 'icon' => 'down-open', + 'data-base-target' => '_next' + ) + ); + ?> +
-
-   -
-
- $count): ?> - translate(strtoupper($text)), $count); ?>
- -
-
-
-
- - translatePlural('Host (%u)', 'Hosts (%u)', $hostCount), $hostCount); ?> -
-
-   -
-
- $count): ?> - translate(strtoupper($text)), $count); ?>
- -
-
- \ No newline at end of file + diff --git a/modules/monitoring/application/views/scripts/process/disable-notifications.phtml b/modules/monitoring/application/views/scripts/process/disable-notifications.phtml index 9b9a2b133..e8c75e53f 100644 --- a/modules/monitoring/application/views/scripts/process/disable-notifications.phtml +++ b/modules/monitoring/application/views/scripts/process/disable-notifications.phtml @@ -1,6 +1,8 @@ +compact): ?>
- tabs->showOnlyCloseButton() ?> + tabs->showOnlyCloseButton(); ?>
+

notifications_enabled === false): ?> diff --git a/modules/monitoring/application/views/scripts/process/info.phtml b/modules/monitoring/application/views/scripts/process/info.phtml index 04fe93c85..85e576116 100644 --- a/modules/monitoring/application/views/scripts/process/info.phtml +++ b/modules/monitoring/application/views/scripts/process/info.phtml @@ -1,12 +1,12 @@ runtimeVariables()->create($this->runtimevariables); $cp = $this->checkPerformance()->create($this->checkperformance); -?> +if (! $this->compact): ?>
- tabs ?> + tabs; ?>
+
@@ -19,6 +19,12 @@ $cp = $this->checkPerformance()->create($this->checkperformance);

translate('Process Info') ?>

+ + + + @@ -49,6 +55,12 @@ $cp = $this->checkPerformance()->create($this->checkperformance); ? $this->programStatus->global_host_event_handler : $this->translate('N/A'); ?> + + + +
translate('Program Version') ?>programStatus->program_version + ? $this->programStatus->program_version + : $this->translate('N/A') ?>
translate('Program Start Time') ?> formatDateTime($this->programStatus->program_start_time) ?>
translate('Active Endpoint'); ?>programStatus->endpoint_name + ? $this->programStatus->endpoint_name + : $this->translate('N/A') ?>
programStatus->is_currently_running === true): ?> @@ -64,7 +76,7 @@ $cp = $this->checkPerformance()->create($this->checkperformance);
- translate('%s is not running'), $this->backendName) ?> + translate('Backend %s is not running'), $this->backendName) ?>
diff --git a/modules/monitoring/application/views/scripts/process/not-running.phtml b/modules/monitoring/application/views/scripts/process/not-running.phtml index 58b6c2980..8439fc49e 100644 --- a/modules/monitoring/application/views/scripts/process/not-running.phtml +++ b/modules/monitoring/application/views/scripts/process/not-running.phtml @@ -1,6 +1,8 @@ +compact): ?>
- tabs ?> + tabs; ?>
+
translate('%s is currently not up and running'), $this->backendName) ?>
diff --git a/modules/monitoring/application/views/scripts/process/performance.phtml b/modules/monitoring/application/views/scripts/process/performance.phtml index 6276d76d1..0bff8891f 100644 --- a/modules/monitoring/application/views/scripts/process/performance.phtml +++ b/modules/monitoring/application/views/scripts/process/performance.phtml @@ -1,7 +1,8 @@ +compact): ?>
-tabs ?> + tabs; ?>
-runtimeVariables()->create($this->runtimevariables); $cp = $this->checkPerformance()->create($this->checkperformance); diff --git a/modules/monitoring/application/views/scripts/service/show.phtml b/modules/monitoring/application/views/scripts/service/show.phtml index 3a09e2bf4..4d33644ce 100644 --- a/modules/monitoring/application/views/scripts/service/show.phtml +++ b/modules/monitoring/application/views/scripts/service/show.phtml @@ -1,4 +1,7 @@
+ compact): ?> + tabs; ?> + render('partials/service/object-header.phtml') ?>

translate("Service detail information") ?>

diff --git a/modules/monitoring/application/views/scripts/services/show.phtml b/modules/monitoring/application/views/scripts/services/show.phtml index a71e32792..746e24871 100644 --- a/modules/monitoring/application/views/scripts/services/show.phtml +++ b/modules/monitoring/application/views/scripts/services/show.phtml @@ -1,124 +1,214 @@ -
+
+ + compact): ?> + + + + render('list/components/servicesummary.phtml') ?> render('partials/service/objects-header.phtml'); ?> +
-
+
+

+ icon('reschedule') ?> + translate('Commands') ?> +

+ - translate('No services matching the filter'); ?> + translate('No services found matching the filter'); ?> -

translatePlural('%u Service', '%u Services', $serviceCount), $serviceCount); ?>

-
qlink( - sprintf($this->translate('List all %u services'), $serviceCount), - $listAllLink - ); ?>
-
- -
-
qlink( - sprintf($this->translate('Reschedule the next check for all %u services'), $serviceCount), +

+ translatePlural( + 'Issue commands to %s selected service:', + 'Issue commands to all %s selected services:', + count($objects) + ), '' . count($objects) . '') ?> +

+ +
+ qlink( + $this->translate('Reschedule next checks'), $rescheduleAllLink, null, array('icon' => 'reschedule') - ); ?>
-
qlink( - sprintf($this->translate('Schedule a downtime for all %u services'), $serviceCount), + ); ?> + +
+ qlink( + $this->translate('Schedule downtimes'), $downtimeAllLink, null, array('icon' => 'plug') - ); ?>
-
qlink( - sprintf($this->translate('Submit a passive check result for all %u services'), $serviceCount), + ); ?> + +
+ qlink( + $this->translate('Submit passive check results'), $processCheckResultAllLink, null, array('icon' => 'reply') - ); ?>
- hasPermission('monitoring/command/send-custom-notification')): ?> -
qlink( - sprintf($this->translate('Send a custom notification for all %u services'), $serviceCount), - $sendCustomNotificationLink, - null, - array('icon' => 'comment') - ); ?>
- - 0): ?> -
-

translatePlural( - '%u Unhandled Service Problem', - '%u Unhandled Service Problems', - $unhandledCount - ), - $unhandledCount - ); ?>

-
qlink( - sprintf( - $this->translatePlural( - 'Schedule a downtime for %u unhandled service problem', - 'Schedule a downtime for %u unhandled service problems', - $unhandledCount - ), - $unhandledCount - ), - $downtimeUnhandledLink, - null, - array('icon' => 'plug') - ); ?>
-
qlink( - sprintf( - $this->translatePlural( - 'Acknowledge %u unhandled service problem', - 'Acknowledge %u unhandled service problems', - $unhandledCount - ), - $unhandledCount - ), - $acknowledgeUnhandledLink, - null, - array('icon' => 'ok') - ); ?>
-
- - 0): ?> -
-

translatePlural( - '%u Acknowledged Service Problem', - '%u Acknowledged Service Problems', - $acknowledgedCount - ), - $acknowledgedCount - ); ?>

- -
- - 0): ?> -

qlink( - sprintf( - $this->translatePlural( - 'List %u service currently in downtime', - 'List %u services currently in downtime', - $inDowntimeCount - ), - $inDowntimeCount - ), - $inDowntimeLink, - null, - array('icon' => 'plug') - ); ?>

- - getComments())) > 0): ?> -

qlink( - sprintf( - $this->translatePlural( - 'List %u service comment', - 'List %u service comments', - $commentCount - ), - $commentCount - ), - $commentsLink, + ); ?> + +
+ qlink( + $this->translate('Add comments'), + $addCommentLink, null, array('icon' => 'comment') - ); ?>

- + ); ?> + + hasPermission('monitoring/command/send-custom-notification')): ?> +
+ qlink( + sprintf($this->translate('Send a custom notification for all %u services'), $serviceCount), + $sendCustomNotificationLink, + null, + array('icon' => 'comment') + ); ?> + + + + + +
+

+ icon('attention-alt') ?> + translate('Problems') ?> +

+ + +

+ translatePlural( + 'There is %s problem, issue commands to the problem service', + 'There are %s problems, issue commands to the problem services', + $problemCount + ), + '' . $problemCount . '' + ); ?> +

+ qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %s problem service', + 'Schedule downtimes for %s problem services', + $problemCount + ), + $problemCount + ), + $downtimeLink, + null, + array('icon' => 'plug') + ); ?> + + + +

+ translate('There are %s unhandled problem services. ' . + 'Issue commands to the problematic services:'), + '' . $unhandledCount . '') ?> + +

+ qlink( + sprintf( + $this->translatePlural( + 'Schedule a downtime for %u unhandled problem service', + 'Schedule a downtime for %u unhandled problem services', + $unhandledCount + ), + $unhandledCount + ), + $downtimeUnhandledLink, + null, + array('icon' => 'plug') + ); ?> + +
+ qlink( + sprintf( + $this->translatePlural( + 'Acknowledge %u unhandled problem service', + 'Acknowledge %u unhandled problem services', + $unhandledCount + ), + $unhandledCount + ), + $acknowledgeUnhandledLink, + null, + array('icon' => 'ok') + ); ?> + + +
+ + + 0): ?> +

icon('ok', $this->translate('Acknowledgements')) ?> translate('Acknowledgements') ?>

+

+ translatePlural( + '%s Acknowledged Service Problem.', + '%s Acknowledged Service Problems.', + $acknowledgedCount + ), + '' . $acknowledgedCount . '' + ); ?> +

-
\ No newline at end of file + + getScheduledDowntimes()) ?> + + +

icon('plug', $this->translate('Downtimes')) ?> translate('Downtimes') ?>

+ qlink( + sprintf( + $this->translatePlural( + '%s scheduled downtime', + '%s scheduled downtimes', + $scheduledDowntimeCount + ), + $scheduledDowntimeCount + ), + $showDowntimesLink, + null, + array('data-base-target' => '_next') + );?> + translate('on all selected services.')) ?> + + 0): ?> +
+ qlink( + sprintf($this->translatePlural( + '%s service', + '%s services', + $inDowntimeCount + ), $inDowntimeCount), + $inDowntimeLink, + null, + array('data-base-target' => '_next') + );?> + translate('are currently in downtime.')) ?> + + + + getComments()) ?> + 0): ?> +

icon('comment') ?> translate('Comments') ?>

+ qlink( + sprintf( + $this->translatePlural( + '%s comment.', + '%s comments.', + $commentCount + ), $commentCount), + $commentsLink, + null, + array('data-base-target' => '_next') + ); ?> + translate('on all selected services.') ?> + + +
diff --git a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml index b91807d4f..e7b79354d 100644 --- a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml +++ b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml @@ -26,12 +26,12 @@ if ($object->acknowledged): ?> if ($object->getType() === $object::TYPE_HOST) { $ackLink = $this->href( 'monitoring/host/acknowledge-problem', - array('host_name' => $object->getName()) + array('host' => $object->getName()) ); } else { $ackLink = $this->href( 'monitoring/service/acknowledge-problem', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()) + array('host' => $object->getHost()->getName(), 'service' => $object->getName()) ); } ?> diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index 2defd616d..0681d5d91 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -33,8 +33,8 @@ if ($object->action_url) { } } -if (isset($this->hostActions)) { - foreach ($this->hostActions as $id => $action) { +if (isset($this->actions)) { + foreach ($this->actions as $id => $action) { $links[] = sprintf($localLinkText, $action, $id); } } diff --git a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml index ef886c337..415b66b76 100644 --- a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml +++ b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml @@ -31,7 +31,7 @@ if ($object->getType() === $object::TYPE_HOST) { echo $this->qlink( $this->translate('Reschedule'), 'monitoring/service/reschedule-check', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'reschedule', 'data-base-target' => '_self', @@ -44,7 +44,7 @@ if ($object->getType() === $object::TYPE_HOST) { echo $this->qlink( $this->translate('Reschedule'), 'monitoring/host/reschedule-check', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'reschedule', 'data-base-target' => '_self', @@ -69,7 +69,9 @@ if ($object->getType() === $object::TYPE_HOST) { check_execution_time): ?>
translate('Check execution time') ?>check_execution_time ?>scheck_execution_time === null + ? '-' : round((float) $object->check_execution_time, 3) + ?>s
populate(array('comment_id' => $comment->id)); + $delCommentForm->populate( + array( + 'comment_id' => $comment->id, + 'comment_is_service' => isset($comment->service_description) + ) + ); echo $delCommentForm; } ?> (translate('Comment'); ?>): ', $commentText); ?> diff --git a/modules/monitoring/application/views/scripts/show/components/customvars.phtml b/modules/monitoring/application/views/scripts/show/components/customvars.phtml index aa2e479e4..50b82dbef 100644 --- a/modules/monitoring/application/views/scripts/show/components/customvars.phtml +++ b/modules/monitoring/application/views/scripts/show/components/customvars.phtml @@ -1,3 +1,8 @@ +
+

translate('Custom variables') ?>

+
populate(array('downtime_id' => $downtime->id)); + $delDowntimeForm->populate( + array( + 'downtime_id' => $downtime->id, + 'downtime_is_service' => $object->getType() === $object::TYPE_SERVICE + ) + ); echo $delDowntimeForm; } ?> translate('Downtime'); ?> - ', $commentText); ?> diff --git a/modules/monitoring/application/views/scripts/show/contact.phtml b/modules/monitoring/application/views/scripts/show/contact.phtml index c4142707a..027301c88 100644 --- a/modules/monitoring/application/views/scripts/show/contact.phtml +++ b/modules/monitoring/application/views/scripts/show/contact.phtml @@ -1,6 +1,8 @@ getHelper('ContactFlags') ?>
- tabs ?> + compact): ?> + tabs; ?> +

translate('Contact details') ?>

translate('No notifications have been sent for this contact') ?>
diff --git a/modules/monitoring/application/views/scripts/show/history.phtml b/modules/monitoring/application/views/scripts/show/history.phtml index d82f454f6..3777d0384 100644 --- a/modules/monitoring/application/views/scripts/show/history.phtml +++ b/modules/monitoring/application/views/scripts/show/history.phtml @@ -1,29 +1,33 @@ getType() === 'host'; -?> - +if (! $this->compact): ?>
- + tabs; ?> + render('partials/host/object-header.phtml'); ?> - + render('partials/service/object-header.phtml'); ?> - +

translate('This Object\'s Event History'); ?>

- widget('limiter', array('url' => $url, 'max' => $history->count())); ?> - paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
- +
- - translate('No history available for this object'); ?> -
- +translate('No history available for this object') . '
'; + return; +} +?> tickets ? preg_replace_callback( $this->translate('%s on %s', 'Service running on host'), $hostContext ? $this->qlink( $event->service_display_name, - 'monitoring/service/show', + 'monitoring/show/service', array( - 'host_name' => $event->host_name, - 'service_description' => $event->service_description + 'host' => $event->host_name, + 'service' => $event->service_description ), array('title' => sprintf( $this->translate('Show detailed information for service %s on host %s'), diff --git a/modules/monitoring/application/views/scripts/show/services.phtml b/modules/monitoring/application/views/scripts/show/services.phtml index 57b4b204e..d3a0c3ef8 100644 --- a/modules/monitoring/application/views/scripts/show/services.phtml +++ b/modules/monitoring/application/views/scripts/show/services.phtml @@ -1,4 +1,7 @@
+ compact): ?> + tabs; ?> + render('partials/host/object-header.phtml') ?> render('partials/host/servicesummary.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml index 6557b4a28..d0cc8d34c 100644 --- a/modules/monitoring/application/views/scripts/timeline/index.phtml +++ b/modules/monitoring/application/views/scripts/timeline/index.phtml @@ -3,13 +3,12 @@ use Icinga\Web\Url; use Icinga\Util\Color; $groupInfo = $timeline->getGroupInfo(); -$firstRow = !$beingExtended; +$firstRow = ! $beingExtended; -?> - +if (! $beingExtended && !$this->compact): ?>
- tabs ?> -
+ tabs; ?> +
@@ -21,6 +20,8 @@ $firstRow = !$beingExtended;
+ +
diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 05fa86148..fd3ee334d 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -94,17 +94,17 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv * Problems Section */ $section = $this->menuSection($this->translate('Problems'), array( - 'renderer' => 'ProblemMenuItemRenderer', + 'renderer' => 'Icinga\Module\Monitoring\Web\Menu\ProblemMenuItemRenderer', 'icon' => 'block', 'priority' => 20 )); $section->add($this->translate('Unhandled Hosts'), array( - 'renderer' => 'UnhandledHostMenuItemRenderer', + 'renderer' => 'Icinga\Module\Monitoring\Web\Menu\UnhandledHostMenuItemRenderer', 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', 'priority' => 30 )); $section->add($this->translate('Unhandled Services'), array( - 'renderer' => 'UnhandledServiceMenuItemRenderer', + 'renderer' => 'Icinga\Module\Monitoring\Web\Menu\UnhandledServiceMenuItemRenderer', 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', 'priority' => 40 )); @@ -208,7 +208,8 @@ $section->add($this->translate('Alert Summary'), array( $section = $this->menuSection($this->translate('System')); $section->add($this->translate('Monitoring Health'), array( 'url' => 'monitoring/process/info', - 'priority' => 120 + 'priority' => 120, + 'renderer' => 'Icinga\Module\Monitoring\Web\Menu\BackendAvailabilityMenuItemRenderer' )); /* diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php index 8b72bbf79..da33c2bc6 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php @@ -5,52 +5,72 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query; use Zend_Db_Select; +/** + * Query for host and service group summaries + */ class GroupSummaryQuery extends IdoQuery { - protected $useSubqueryCount = true; - + /** + * {@inheritdoc} + */ protected $columnMap = array( 'hoststatussummary' => array( - 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', - 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', - 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', - 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', - 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', - 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', - 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', - 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', - 'hostgroup_name' => 'hostgroup_name', - 'hostgroup_alias' => 'hostgroup_alias', - 'hostgroup' => 'hostgroup' + 'hostgroup' => 'hostgroup COLLATE latin1_general_ci', + 'hostgroup_alias' => 'hostgroup_alias COLLATE latin1_general_ci', + 'hostgroup_name' => 'hostgroup_name', + 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)', + 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)', + 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)', + 'hosts_down_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_down_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)', + 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)', + 'hosts_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 99 THEN state_change ELSE 0 END)', + 'hosts_severity' => 'MAX(CASE WHEN object_type = \'host\' THEN severity ELSE 0 END)', + 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)', + 'hosts_unreachable_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)', + 'hosts_unreachable_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)', + 'hosts_up_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 0 THEN state_change ELSE 0 END)' ), 'servicestatussummary' => array( - 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', - 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', - 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', - 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', + 'servicegroup' => 'servicegroup COLLATE latin1_general_ci', + 'servicegroup_alias' => 'servicegroup_alias COLLATE latin1_general_ci', + 'servicegroup_name' => 'servicegroup_name', 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', + 'services_critical_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_critical_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', + 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', + 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', + 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', + 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', + 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', - 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)', - 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)', - 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)', - 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', - 'services_critical_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', 'services_unknown_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', - 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', - 'services_critical_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', 'services_unknown_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', - 'servicegroup_name' => 'servicegroup_name', - 'servicegroup_alias' => 'servicegroup_alias', - 'servicegroup' => 'servicegroup' + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)', + 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)', + 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)' ) ); + /** + * {@inheritdoc} + */ + protected $useSubqueryCount = true; + + /** + * {@inheritdoc} + */ protected function joinBaseTables() { $columns = array( @@ -72,11 +92,11 @@ class GroupSummaryQuery extends IdoQuery $hosts = $this->createSubQuery( 'Hoststatus', $columns + array( - 'state' => 'host_state', - 'acknowledged' => 'host_acknowledged', - 'in_downtime' => 'host_in_downtime', - 'state_change' => 'host_last_state_change', - 'severity' => 'host_severity' + 'state' => 'host_state', + 'acknowledged' => 'host_acknowledged', + 'in_downtime' => 'host_in_downtime', + 'state_change' => 'host_last_state_change', + 'severity' => 'host_severity' ) ); if (in_array('servicegroup_name', $this->desiredColumns)) { @@ -94,11 +114,11 @@ class GroupSummaryQuery extends IdoQuery $services = $this->createSubQuery( 'Status', $columns + array( - 'state' => 'service_state', - 'acknowledged' => 'service_acknowledged', - 'in_downtime' => 'service_in_downtime', - 'state_change' => 'service_last_state_change', - 'severity' => 'service_severity' + 'state' => 'service_state', + 'acknowledged' => 'service_acknowledged', + 'in_downtime' => 'service_in_downtime', + 'state_change' => 'service_last_state_change', + 'severity' => 'service_severity' ) ); $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php index 23a8b585a..ec9e6bd32 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php @@ -11,7 +11,7 @@ class HoststatusQuery extends IdoQuery 'hosts' => array( 'host' => 'ho.name1 COLLATE latin1_general_ci', 'host_name' => 'ho.name1 COLLATE latin1_general_ci', - 'host_display_name' => 'h.display_name', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', 'host_alias' => 'h.alias', 'host_address' => 'h.address', 'host_ipv4' => 'INET_ATON(h.address)', @@ -85,7 +85,7 @@ class HoststatusQuery extends IdoQuery 'hostgroups' => array( 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', 'hostgroup_name' => 'hgo.name1', - 'hostgroup_alias' => 'hg.alias' + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci' ), 'servicegroups' => array( 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 2e6bec024..fda2f188e 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -370,7 +370,11 @@ abstract class IdoQuery extends DbQuery if ($count > 0) { $this->columnsWithoutCollation[] = $this->getMappedField($key); } - $value = preg_replace('/inet_aton\(([[:word:].]+)\)/i', '$1::inet - \'0.0.0.0\'', $value); + $value = preg_replace( + '/inet_aton\(([[:word:].]+)\)/i', + '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', + $value + ); $value = preg_replace( '/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i', 'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END', diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php index d63db8173..d1e0b71b4 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php @@ -12,6 +12,7 @@ class ProgramstatusQuery extends IdoQuery 'programstatus' => array( 'id' => 'programstatus_id', 'status_update_time' => 'UNIX_TIMESTAMP(programstatus.status_update_time)', + 'program_version' => 'program_version', 'program_start_time' => 'UNIX_TIMESTAMP(programstatus.program_start_time)', 'program_end_time' => 'UNIX_TIMESTAMP(programstatus.program_end_time)', 'is_currently_running' => 'CASE WHEN (programstatus.is_currently_running = 0) @@ -26,6 +27,7 @@ class ProgramstatusQuery extends IdoQuery END END', 'process_id' => 'process_id', + 'endpoint_name' => 'endpoint_name', 'daemon_mode' => 'daemon_mode', 'last_command_check' => 'UNIX_TIMESTAMP(programstatus.last_command_check)', 'last_log_rotation' => 'UNIX_TIMESTAMP(programstatus.last_log_rotation)', @@ -47,4 +49,16 @@ class ProgramstatusQuery extends IdoQuery 'global_service_event_handler' => 'global_service_event_handler', ) ); + + protected function joinBaseTables() + { + parent::joinBaseTables(); + + if (version_compare($this->getIdoVersion(), '1.11.7', '<')) { + $this->columnMap['programstatus']['endpoint_name'] = '(0)'; + } + if (version_compare($this->getIdoVersion(), '1.11.8', '<')) { + $this->columnMap['programstatus']['program_version'] = '(NULL)'; + } + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index 17a12259e..84a7b64f1 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -36,7 +36,7 @@ class StatusQuery extends IdoQuery 'hosts' => array( 'host' => 'ho.name1 COLLATE latin1_general_ci', 'host_name' => 'ho.name1', - 'host_display_name' => 'h.display_name', + 'host_display_name' => 'h.display_name COLLATE latin1_general_ci', 'host_alias' => 'h.alias', 'host_address' => 'h.address', 'host_ipv4' => 'INET_ATON(h.address)', @@ -162,19 +162,19 @@ class StatusQuery extends IdoQuery 'hostgroups' => array( 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', 'hostgroup_name' => 'hgo.name1', - 'hostgroup_alias' => 'hg.alias' + 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci' ), 'servicegroups' => array( 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci', 'servicegroup_name' => 'sgo.name1', - 'servicegroup_alias' => 'sg.alias' + 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci' ), 'services' => array( 'service_host' => 'so.name1 COLLATE latin1_general_ci', 'service_host_name' => 'so.name1', 'service' => 'so.name2 COLLATE latin1_general_ci', 'service_description' => 'so.name2', - 'service_display_name' => 's.display_name', + 'service_display_name' => 's.display_name COLLATE latin1_general_ci', 'service_icon_image' => 's.icon_image', 'service_action_url' => 's.action_url', 'service_notes_url' => 's.notes_url', diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php index 00ba63a78..75f2ffb8e 100644 --- a/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php +++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php @@ -3,20 +3,13 @@ namespace Icinga\Module\Monitoring\Command\Object; +use Icinga\Module\Monitoring\Command\IcingaCommand; + /** * Delete a host or service comment */ -class DeleteCommentCommand extends ObjectCommand +class DeleteCommentCommand extends IcingaCommand { - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. - */ - protected $allowedObjects = array( - self::TYPE_HOST, - self::TYPE_SERVICE - ); - /** * ID of the comment that is to be deleted * @@ -24,6 +17,13 @@ class DeleteCommentCommand extends ObjectCommand */ protected $commentId; + /** + * The type of the comment, either 'host' or 'service' + * + * @var boolean + */ + protected $isService = false; + /** * Set the ID of the comment that is to be deleted * @@ -46,4 +46,27 @@ class DeleteCommentCommand extends ObjectCommand { return $this->commentId; } + + /** + * Whether the command affects a service comment + * + * @return boolean + */ + public function getIsService() + { + return $this->isService; + } + + /** + * Set whether the command affects a service + * + * @param boolean $value The value, defaults to true + * + * @return this fluent interface + */ + public function setIsService($value = true) + { + $this->isService = (bool) $value; + return $this; + } } diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php index 52ad99e1b..a2c593f26 100644 --- a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php +++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php @@ -3,20 +3,13 @@ namespace Icinga\Module\Monitoring\Command\Object; +use Icinga\Module\Monitoring\Command\IcingaCommand; + /** * Delete a host or service downtime */ -class DeleteDowntimeCommand extends ObjectCommand +class DeleteDowntimeCommand extends IcingaCommand { - /** - * (non-PHPDoc) - * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation. - */ - protected $allowedObjects = array( - self::TYPE_HOST, - self::TYPE_SERVICE - ); - /** * ID of the downtime that is to be deleted * @@ -24,6 +17,33 @@ class DeleteDowntimeCommand extends ObjectCommand */ protected $downtimeId; + /** + * If the command affects a service downtime + * + * @var boolean + */ + protected $isService = false; + + /** + * Set if this command affects a service + * + * @param type $value + */ + public function setIsService($value = true) + { + $this->isService = (bool) $value; + } + + /** + * Return whether the command affects a service + * + * @return type + */ + public function getIsService() + { + return $this->isService; + } + /** * Set the ID of the downtime that is to be deleted * diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php index cf88d7d2e..f6a35dcc2 100644 --- a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php +++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php @@ -323,28 +323,18 @@ class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface public function renderDeleteComment(DeleteCommentCommand $command) { - if ($command->getObject()->getType() === $command::TYPE_HOST) { - $commandString = 'DEL_HOST_COMMENT'; - } else { - $commandString = 'DEL_SVC_COMMENT'; - } return sprintf( '%s;%u', - $commandString, + $command->getIsService() ? 'DEL_SVC_COMMENT' : 'DEL_HOST_COMMENT', $command->getCommentId() ); } public function renderDeleteDowntime(DeleteDowntimeCommand $command) { - if ($command->getObject()->getType() === $command::TYPE_HOST) { - $commandString = 'DEL_HOST_DOWNTIME'; - } else { - $commandString = 'DEL_SVC_DOWNTIME'; - } return sprintf( '%s;%u', - $commandString, + $command->getIsService() ? 'DEL_SVC_DOWNTIME' : 'DEL_HOST_DOWNTIME', $command->getDowntimeId() ); } diff --git a/modules/monitoring/library/Monitoring/Controller.php b/modules/monitoring/library/Monitoring/Controller.php index bf69182d7..c3cbe3c2d 100644 --- a/modules/monitoring/library/Monitoring/Controller.php +++ b/modules/monitoring/library/Monitoring/Controller.php @@ -21,16 +21,6 @@ class Controller extends IcingaWebController */ protected $backend; - /** - * Compact layout name - * - * Set to a string containing the compact layout name to use when - * 'compact' is set as the layout parameter, otherwise null - * - * @var string - */ - protected $compactView; - protected function moduleInit() { $this->backend = Backend::createBackend($this->_getParam('backend')); @@ -39,10 +29,6 @@ class Controller extends IcingaWebController protected function handleFormatRequest($query) { - if ($this->compactView !== null && ($this->_getParam('view', false) === 'compact')) { - $this->_helper->viewRenderer($this->compactView); - } - if ($this->_getParam('format') === 'sql') { echo '
'
                 . htmlspecialchars(wordwrap($query->dump()))
diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
index c9537eff8..69d8631c8 100644
--- a/modules/monitoring/library/Monitoring/DataView/DataView.php
+++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
@@ -3,14 +3,13 @@
 
 namespace Icinga\Module\Monitoring\DataView;
 
-use Countable;
+use ArrayIterator;
+use IteratorAggregate;
+use Icinga\Data\QueryInterface;
 use Icinga\Data\Filter\Filter;
 use Icinga\Data\Filter\FilterMatch;
-use Icinga\Data\Browsable;
 use Icinga\Data\PivotTable;
-use Icinga\Data\Sortable;
 use Icinga\Data\ConnectionInterface;
-use Icinga\Data\Filterable;
 use Icinga\Exception\QueryException;
 use Icinga\Web\Request;
 use Icinga\Web\Url;
@@ -19,12 +18,12 @@ use Icinga\Module\Monitoring\Backend\MonitoringBackend;
 /**
  * A read-only view of an underlying query
  */
-abstract class DataView implements Browsable, Countable, Filterable, Sortable
+abstract class DataView implements QueryInterface, IteratorAggregate
 {
     /**
      * The query used to populate the view
      *
-     * @var \Icinga\Data\SimpleQuery
+     * @var QueryInterface
      */
     protected $query;
 
@@ -59,6 +58,16 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
     {
     }
 
+    /**
+     * Return a iterator for all rows of the result set
+     *
+     * @return  ArrayIterator
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->fetchAll());
+    }
+
     /**
      * Get the query name this data view relies on
      *
@@ -366,6 +375,16 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
         return $this;
     }
 
+    /**
+     * Get the view's search columns
+     *
+     * @return string[]
+     */
+    public function getSearchColumns()
+    {
+        return array();
+    }
+
     /**
      * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
      * column validation.
@@ -377,22 +396,6 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
         return $this;
     }
 
-    /**
-     * Paginate data
-     *
-     * @param   int $itemsPerPage   Number of items per page
-     * @param   int $pageNumber     Current page number
-     *
-     * @return  Zend_Paginator
-     */
-    public function paginate($itemsPerPage = null, $pageNumber = null)
-    {
-        if (! $this->isSorted) {
-            $this->order();
-        }
-        return $this->query->paginate($itemsPerPage, $pageNumber);
-    }
-
     /**
      * Count result set
      *
@@ -400,6 +403,114 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
      */
     public function count()
     {
-        return count($this->query);
+        return $this->query->count();
+    }
+
+    /**
+     * Set a limit count and offset
+     *
+     * @param   int $count  Number of rows to return
+     * @param   int $offset Start returning after this many rows
+     *
+     * @return  self
+     */
+    public function limit($count = null, $offset = null)
+    {
+        $this->query->limit($count, $offset);
+        return $this;
+    }
+
+    /**
+     * Whether a limit is set
+     *
+     * @return bool
+     */
+    public function hasLimit()
+    {
+        return $this->query->hasLimit();
+    }
+
+    /**
+     * Get the limit if any
+     *
+     * @return int|null
+     */
+    public function getLimit()
+    {
+        return $this->query->getLimit();
+    }
+
+    /**
+     * Whether an offset is set
+     *
+     * @return bool
+     */
+    public function hasOffset()
+    {
+        return $this->query->hasOffset();
+    }
+
+    /**
+     * Get the offset if any
+     *
+     * @return int|null
+     */
+    public function getOffset()
+    {
+        return $this->query->getOffset();
+    }
+
+    /**
+     * Retrieve an array containing all rows of the result set
+     *
+     * @return  array
+     */
+    public function fetchAll()
+    {
+        return $this->getQuery()->fetchAll();
+    }
+
+    /**
+     * Fetch the first row of the result set
+     *
+     * @return  mixed
+     */
+    public function fetchRow()
+    {
+        return $this->getQuery()->fetchRow();
+    }
+
+    /**
+     * Fetch a column of all rows of the result set as an array
+     *
+     * @param   int $columnIndex Index of the column to fetch
+     *
+     * @return  array
+     */
+    public function fetchColumn($columnIndex = 0)
+    {
+        return $this->getQuery()->fetchColumn($columnIndex);
+    }
+
+    /**
+     * Fetch the first column of the first row of the result set
+     *
+     * @return  string
+     */
+    public function fetchOne()
+    {
+        return $this->getQuery()->fetchOne();
+    }
+
+    /**
+     * Fetch all rows of the result set as an array of key-value pairs
+     *
+     * The first column is the key, the second column is the value.
+     *
+     * @return  array
+     */
+    public function fetchPairs()
+    {
+        return $this->getQuery()->fetchPairs();
     }
 }
diff --git a/modules/monitoring/library/Monitoring/DataView/HostStatus.php b/modules/monitoring/library/Monitoring/DataView/HostStatus.php
index 23df6d94a..a7c77e2e2 100644
--- a/modules/monitoring/library/Monitoring/DataView/HostStatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/HostStatus.php
@@ -94,10 +94,11 @@ class HostStatus extends DataView
             ),
             'host_severity' => array(
                 'columns' => array(
-                    'host_severity DESC',
+                    'host_severity',
                     'host_last_state_change DESC',
                     'host_display_name ASC'
-                )
+                ),
+                'order' => self::SORT_DESC
             ),
             'host_address' => array(
                 'columns' => array(
@@ -125,4 +126,12 @@ class HostStatus extends DataView
         }
         return parent::isValidFilterTarget($column);
     }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSearchColumns()
+    {
+        return array('host', 'host_display_name');
+    }
 }
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
new file mode 100644
index 000000000..63c04df79
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
@@ -0,0 +1,101 @@
+ array(
+                'order' => self::SORT_ASC
+            ),
+            'hosts_severity' => array(
+                'columns' => array(
+                    'hosts_severity',
+                    'hostgroup_alias ASC'
+                ),
+                'order' => self::SORT_DESC
+            ),
+            'hosts_total' => array(
+                'columns' => array(
+                    'hosts_total',
+                    'hostgroup_alias ASC'
+                ),
+                'order' => self::SORT_ASC
+            ),
+            'services_total' => array(
+                'columns' => array(
+                    'services_total',
+                    'hostgroup_alias ASC'
+                ),
+                'order' => self::SORT_ASC
+            )
+        );
+    }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php b/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php
index b4f1038da..5fa1bee74 100644
--- a/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/ServiceStatus.php
@@ -130,19 +130,21 @@ class ServiceStatus extends DataView
             ),
             'service_severity' => array(
                 'columns' => array(
-                    'service_severity DESC',
+                    'service_severity',
                     'service_last_state_change DESC',
                     'service_display_name ASC',
                     'host_display_name ASC'
-                )
+                ),
+                'order' => self::SORT_DESC
             ),
             'host_severity' => array(
                 'columns' => array(
-                    'host_severity DESC',
+                    'host_severity',
                     'host_last_state_change DESC',
                     'host_display_name ASC',
                     'service_display_name ASC'
-                )
+                ),
+                'order' => self::SORT_DESC
             ),
             'host_display_name' => array(
                 'columns' => array(
@@ -183,4 +185,12 @@ class ServiceStatus extends DataView
         }
         return parent::isValidFilterTarget($column);
     }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSearchColumns()
+    {
+        return array('service', 'service_display_name');
+    }
 }
diff --git a/modules/monitoring/library/Monitoring/DataView/Groupsummary.php b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
similarity index 56%
rename from modules/monitoring/library/Monitoring/DataView/Groupsummary.php
rename to modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
index 562bed1da..11d6ec07e 100644
--- a/modules/monitoring/library/Monitoring/DataView/Groupsummary.php
+++ b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
@@ -3,64 +3,90 @@
 
 namespace Icinga\Module\Monitoring\DataView;
 
-class Groupsummary extends DataView
+class Servicegroupsummary extends DataView
 {
     /**
-     * Retrieve columns provided by this view
-     *
-     * @return array
+     * {@inheritdoc}
      */
     public function getColumns()
     {
         return array(
-            'servicegroup_name',
-            'servicegroup_alias',
-            'hostgroup_name',
-            'hostgroup_alias',
-            'hosts_up',
-            'hosts_unreachable',
-            'hosts_unreachable_handled',
-            'hosts_unreachable_unhandled',
-            'hosts_down',
             'hosts_down_handled',
             'hosts_down_unhandled',
             'hosts_pending',
-            'services_total',
-            'services_ok',
-            'services_unknown',
-            'services_unknown_handled',
-            'services_unknown_unhandled',
-            'services_critical',
+            'hosts_unreachable_handled',
+            'hosts_unreachable_unhandled',
+            'hosts_up',
+            'servicegroup_alias',
+            'servicegroup_name',
             'services_critical_handled',
-            'services_critical_unhandled',
-            'services_warning',
-            'services_warning_handled',
-            'services_warning_unhandled',
-            'services_pending',
-            'services_severity',
-            'services_ok_last_state_change',
-            'services_pending_last_state_change',
-            'services_warning_last_state_change_handled',
             'services_critical_last_state_change_handled',
-            'services_unknown_last_state_change_handled',
-            'services_warning_last_state_change_unhandled',
             'services_critical_last_state_change_unhandled',
-            'services_unknown_last_state_change_unhandled'
+            'services_critical_unhandled',
+            'services_ok',
+            'services_ok_last_state_change',
+            'services_pending',
+            'services_pending_last_state_change',
+            'services_severity',
+            'services_total',
+            'services_unknown_handled',
+            'services_unknown_last_state_change_handled',
+            'services_unknown_last_state_change_unhandled',
+            'services_unknown_unhandled',
+            'services_warning_handled',
+            'services_warning_last_state_change_handled',
+            'services_warning_last_state_change_unhandled',
+            'services_warning_unhandled'
         );
     }
 
+    /**
+     * {@inheritdoc}
+     */
+    public function getFilterColumns()
+    {
+        return array('servicegroup');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function getQueryName()
+    {
+        return 'groupsummary';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSearchColumns()
+    {
+        return array('servicegroup', 'servicegroup_alias');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
     public function getSortRules()
     {
         return array(
+            'servicegroup_alias' => array(
+                'order' => self::SORT_ASC
+            ),
             'services_severity' => array(
-                'columns'   => array('services_severity'),
-                'order'     => self::SORT_DESC
+                'columns' => array(
+                    'services_severity',
+                    'servicegroup_alias ASC'
+                ),
+                'order' => self::SORT_DESC
+            ),
+            'services_total' => array(
+                'columns' => array(
+                    'services_total',
+                    'servicegroup_alias ASC'
+                ),
+                'order' => self::SORT_ASC
             )
         );
     }
-
-    public function getFilterColumns()
-    {
-        return array('hostgroup', 'servicegroup');
-    }
 }
diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php
index 342e78812..2c01fe0eb 100644
--- a/modules/monitoring/library/Monitoring/Object/Host.php
+++ b/modules/monitoring/library/Monitoring/Object/Host.php
@@ -89,6 +89,7 @@ class Host extends MonitoredObject
     protected function getDataView()
     {
         $columns = array(
+            'host_icon_image',
             'host_acknowledged',
             'host_action_url',
             'host_active_checks_enabled',
diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php
index f0adced24..2aa5bb04f 100644
--- a/modules/monitoring/library/Monitoring/Object/HostList.php
+++ b/modules/monitoring/library/Monitoring/Object/HostList.php
@@ -3,6 +3,10 @@
 
 namespace Icinga\Module\Monitoring\Object;
 
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Util\String;
+
 /**
  * A host list
  */
@@ -25,4 +29,74 @@ class HostList extends ObjectList
         }
         return $hosts;
     }
+
+    /**
+     * Create a state summary of all hosts that can be consumed by hostssummary.phtml
+     *
+     * @return object   The summary
+     */
+    public function getStateSummary()
+    {
+        $hostStates = array_fill_keys(self::getHostStatesSummaryEmpty(), 0);
+        foreach ($this as $host) {
+            $unhandled = (bool) $host->problem === true && (bool) $host->handled === false;
+
+            $stateName = 'hosts_' . $host::getStateText($host->state);
+            ++$hostStates[$stateName];
+            ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')];
+        }
+
+        $hostStates['hosts_total'] = count($this);
+
+        return (object)$hostStates;
+    }
+
+    /**
+     * Return an empty array with all possible host state names
+     *
+     * @return array    An array containing all possible host states as keys and 0 as values.
+     */
+    public static function getHostStatesSummaryEmpty()
+    {
+        return String::cartesianProduct(
+            array(
+                array('hosts'),
+                array(
+                    Host::getStateText(Host::STATE_UP),
+                    Host::getStateText(Host::STATE_DOWN),
+                    Host::getStateText(Host::STATE_UNREACHABLE),
+                    Host::getStateText(Host::STATE_PENDING)
+                ),
+                array(null, 'handled', 'unhandled')
+            ),
+            '_'
+        );
+    }
+
+    /**
+     * Returns a Filter that matches all hosts in this list
+     *
+     * @return Filter
+     */
+    public function objectsFilter($columns = array('host' => 'host'))
+    {
+        $filterExpression = array();
+        foreach ($this as $host) {
+            $filterExpression[] = Filter::where($columns['host'], $host->getName());
+        }
+        return FilterOr::matchAny($filterExpression);
+    }
+
+    /**
+     * Get the scheduled downtimes
+     *
+     * @return type
+     */
+    public function getScheduledDowntimes()
+    {
+        return $this->backend->select()
+                ->from('downtime')
+                ->applyFilter(clone $this->filter)
+                ->where('downtime_objecttype', 'host');
+    }
 }
diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
index a653ea6ed..05f201428 100644
--- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
+++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
@@ -556,14 +556,11 @@ abstract class MonitoredObject implements Filterable
      */
     public static function fromParams(UrlParams $params)
     {
-        if ($params->has('service_description') && $params->has('host_name')) {
-            return new Service(
-                MonitoringBackend::instance(),
-                $params->get('host_name'),
-                $params->get('service_description')
-            );
-        } elseif ($params->has('host_name')) {
-            return new Host(MonitoringBackend::instance(), $params->get('host_name'));
+        if ($params->has('service') && $params->has('host')) {
+            return new Service(MonitoringBackend::instance(), $params->get('host'), $params->get('service'));
+        } elseif ($params->has('host')) {
+            return new Host(MonitoringBackend::instance(), $params->get('host'));
         }
+        return null;
     }
 }
diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php
index 146499805..75fe1ba76 100644
--- a/modules/monitoring/library/Monitoring/Object/ObjectList.php
+++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php
@@ -5,21 +5,40 @@ namespace Icinga\Module\Monitoring\Object;
 
 use ArrayIterator;
 use Countable;
+use Icinga\Data\Filter\Filter;
 use IteratorAggregate;
 use Icinga\Module\Monitoring\Backend\MonitoringBackend;
 
 abstract class ObjectList implements Countable, IteratorAggregate
 {
+    /**
+     * @var string
+     */
     protected $dataViewName;
 
+    /**
+     * @var MonitoringBackend
+     */
     protected $backend;
 
+    /**
+     * @var array
+     */
     protected $columns;
 
+    /**
+     * @var Filter
+     */
     protected $filter;
 
+    /**
+     * @var array
+     */
     protected $objects;
 
+    /**
+     * @var int
+     */
     protected $count;
 
     public function __construct(MonitoringBackend $backend)
@@ -27,23 +46,39 @@ abstract class ObjectList implements Countable, IteratorAggregate
         $this->backend = $backend;
     }
 
+    /**
+     * @param array $columns
+     *
+     * @return $this
+     */
     public function setColumns(array $columns)
     {
         $this->columns = $columns;
         return $this;
     }
 
+    /**
+     * @return array
+     */
     public function getColumns()
     {
         return $this->columns;
     }
 
+    /**
+     * @param $filter
+     *
+     * @return $this
+     */
     public function setFilter($filter)
     {
         $this->filter = $filter;
         return $this;
     }
 
+    /**
+     * @return Filter
+     */
     public function getFilter()
     {
         return $this->filter;
@@ -51,6 +86,9 @@ abstract class ObjectList implements Countable, IteratorAggregate
 
     abstract protected function fetchObjects();
 
+    /**
+     * @return array
+     */
     public function fetch()
     {
         if ($this->objects === null) {
@@ -59,6 +97,9 @@ abstract class ObjectList implements Countable, IteratorAggregate
         return $this->objects;
     }
 
+    /**
+     * @return int
+     */
     public function count()
     {
         if ($this->count === null) {
@@ -85,4 +126,94 @@ abstract class ObjectList implements Countable, IteratorAggregate
     {
         return $this->backend->select()->from('comment')->applyFilter($this->filter);
     }
+
+    /**
+     * Get the scheduled downtimes
+     *
+     * @return type
+     */
+    public function getScheduledDowntimes()
+    {
+        return $this->backend->select()->from('downtime')->applyFilter($this->filter);
+    }
+
+    /**
+     * @return ObjectList
+     */
+    public function getAcknowledgedObjects()
+    {
+        $acknowledgedObjects = array();
+        foreach ($this as $object) {
+            if ((bool) $object->acknowledged === true) {
+                $acknowledgedObjects[] = $object;
+            }
+        }
+        return $this->newFromArray($acknowledgedObjects);
+    }
+
+    /**
+     * @return ObjectList
+     */
+    public function getObjectsInDowntime()
+    {
+        $objectsInDowntime = array();
+        foreach ($this as $object) {
+            if ((bool) $object->in_downtime === true) {
+                $objectsInDowntime[] = $object;
+            }
+        }
+        return $this->newFromArray($objectsInDowntime);
+    }
+
+    /**
+     * @return ObjectList
+     */
+    public function getUnhandledObjects()
+    {
+        $unhandledObjects = array();
+        foreach ($this as $object) {
+            if ((bool) $object->problem === true && (bool) $object->handled === false) {
+                $unhandledObjects[] = $object;
+            }
+        }
+        return $this->newFromArray($unhandledObjects);
+    }
+
+    /**
+     * @return ObjectList
+     */
+    public function getProblemObjects()
+    {
+        $handledObjects = array();
+        foreach ($this as $object) {
+            if ((bool) $object->problem === true) {
+                $handledObjects[] = $object;
+            }
+        }
+        return $this->newFromArray($handledObjects);
+    }
+
+    /**
+     * Create a ObjectList from an array of hosts without querying a backend
+     *
+     * @return ObjectList
+     */
+    protected function newFromArray(array $objects)
+    {
+        $class = get_called_class();
+        $list = new $class($this->backend);
+        $list->objects = $objects;
+        $list->count = count($objects);
+        $list->filter = $list->objectsFilter();
+        return $list;
+    }
+
+    /**
+     * Create a filter that matches exactly the elements of this object list
+     *
+     * @param   array   $columns    Override default column names.
+     *
+     * @return  Filter
+     */
+    abstract function objectsFilter($columns = array());
 }
diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php
index 7c22ae57d..fec5de238 100644
--- a/modules/monitoring/library/Monitoring/Object/Service.php
+++ b/modules/monitoring/library/Monitoring/Object/Service.php
@@ -106,6 +106,7 @@ class Service extends MonitoredObject
     protected function getDataView()
     {
         return $this->backend->select()->from('serviceStatus', array(
+            'host_icon_image',
             'host_acknowledged',
             'host_active_checks_enabled',
             'host_address',
@@ -118,6 +119,7 @@ class Service extends MonitoredObject
             'host_notifications_enabled',
             'host_passive_checks_enabled',
             'host_state',
+            'service_icon_image',
             'service_acknowledged',
             'service_action_url',
             'service_active_checks_enabled',
diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php
index d9ffa0d8e..5ae9b8193 100644
--- a/modules/monitoring/library/Monitoring/Object/ServiceList.php
+++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php
@@ -3,11 +3,19 @@
 
 namespace Icinga\Module\Monitoring\Object;
 
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Util\String;
+
 /**
  * A service list
  */
 class ServiceList extends ObjectList
 {
+    protected $hostStateSummary;
+
+    protected $serviceStateSummary;
+
     protected $dataViewName = 'serviceStatus';
 
     protected $columns = array('host_name', 'service_description');
@@ -25,4 +33,118 @@ class ServiceList extends ObjectList
         }
         return $services;
     }
+
+    /**
+     * Create a state summary of all services that can be consumed by servicesummary.phtml
+     *
+     * @return object   The summary
+     */
+    public function getServiceStateSummary()
+    {
+        if (! $this->serviceStateSummary) {
+            $this->initStateSummaries();
+        }
+        return (object)$this->serviceStateSummary;
+    }
+
+    /**
+     * Create a state summary of all hosts that can be consumed by hostsummary.phtml
+     *
+     * @return object   The summary
+     */
+    public function getHostStateSummary()
+    {
+        if (! $this->hostStateSummary) {
+            $this->initStateSummaries();
+        }
+        return (object)$this->hostStateSummary;
+    }
+
+    /**
+     * Calculate the current state summary and populate hostStateSummary and serviceStateSummary
+     * properties
+     */
+    protected function initStateSummaries()
+    {
+        $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0);
+        $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0);
+
+        foreach ($this as $service) {
+            $unhandled = false;
+            if ((bool) $service->problem === true && (bool) $service->handled === false) {
+                $unhandled = true;
+            }
+
+            $stateName = 'services_' . $service::getStateText($service->state);
+            ++$serviceStates[$stateName];
+            ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')];
+
+            if (! isset($knownHostStates[$service->getHost()->getName()])) {
+                $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false;
+                ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)];
+                ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)
+                        . ($unhandledHost ? '_unhandled' : '_handled')];
+                $knownHostStates[$service->getHost()->getName()] = true;
+            }
+        }
+
+        $serviceStates['services_total'] = count($this);
+        $this->hostStateSummary = $hostStates;
+        $this->serviceStateSummary = $serviceStates;
+    }
+
+    /**
+     * Return an empty array with all possible host state names
+     *
+     * @return array    An array containing all possible host states as keys and 0 as values.
+     */
+    public static function getServiceStatesSummaryEmpty()
+    {
+        return String::cartesianProduct(
+            array(
+                array('services'),
+                array(
+                    Service::getStateText(Service::STATE_OK),
+                    Service::getStateText(Service::STATE_WARNING),
+                    Service::getStateText(Service::STATE_CRITICAL),
+                    Service::getStateText(Service::STATE_UNKNOWN),
+                    Service::getStateText(Service::STATE_PENDING)
+                ),
+                array(null, 'handled', 'unhandled')
+            ),
+            '_'
+        );
+    }
+
+    /**
+     * Returns a Filter that matches all hosts in this HostList
+     *
+     * @param   array   $columns    Override filter column names
+     *
+     * @return  Filter
+     */
+    public function objectsFilter($columns = array('host' => 'host', 'service' => 'service'))
+    {
+        $filterExpression = array();
+        foreach ($this as $service) {
+            $filterExpression[] = Filter::matchAll(
+                Filter::where($columns['host'], $service->getHost()->getName()),
+                Filter::where($columns['service'], $service->getName())
+            );
+        }
+        return FilterOr::matchAny($filterExpression);
+    }
+
+    /**
+     * Get the scheduled downtimes
+     *
+     * @return type
+     */
+    public function getScheduledDowntimes()
+    {
+        return $this->backend->select()
+                ->from('downtime')
+                ->applyFilter(clone $this->filter)
+                ->where('downtime_objecttype', 'service');
+    }
 }
diff --git a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php
index 269107c82..d503bc19f 100644
--- a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php
+++ b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php
@@ -83,7 +83,7 @@ class PerfdataSet implements IteratorAggregate
             $label = trim($this->readLabel());
             $value = trim($this->readUntil(' '));
 
-            if ($label && $value) {
+            if ($label) {
                 $this->perfdata[] = new Perfdata($label, $value);
             }
         }
diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
index de0c3fbfe..9d27b1268 100644
--- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
+++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
@@ -62,7 +62,7 @@ abstract class MonitoredObjectController extends Controller
                 ->handleRequest();
             $this->view->checkNowForm = $checkNowForm;
         }
-        if ( ! in_array((int) $this->object->state, array(0, 99))) {
+        if (! in_array((int) $this->object->state, array(0, 99))) {
             if ((bool) $this->object->acknowledged) {
                 if ($auth->hasPermission('monitoring/command/remove-acknowledgement')) {
                     $removeAckForm = new RemoveAcknowledgementCommandForm();
@@ -82,16 +82,12 @@ abstract class MonitoredObjectController extends Controller
         $this->view->toggleFeaturesForm = $toggleFeaturesForm;
         if (! empty($this->object->comments) && $auth->hasPermission('monitoring/command/comment/delete')) {
             $delCommentForm = new DeleteCommentCommandForm();
-            $delCommentForm
-                ->setObjects($this->object)
-                ->handleRequest();
+            $delCommentForm->handleRequest();
             $this->view->delCommentForm = $delCommentForm;
         }
         if (! empty($this->object->downtimes) && $auth->hasPermission('monitoring/command/downtime/delete')) {
             $delDowntimeForm = new DeleteDowntimeCommandForm();
-            $delDowntimeForm
-                ->setObjects($this->object)
-                ->handleRequest();
+            $delDowntimeForm->handleRequest();
             $this->view->delDowntimeForm = $delDowntimeForm;
         }
         $this->view->object = $this->object;
@@ -137,26 +133,6 @@ abstract class MonitoredObjectController extends Controller
      */
     abstract public function scheduleDowntimeAction();
 
-    /**
-     * Delete a comment
-     */
-    public function deleteCommentAction()
-    {
-        $this->assertHttpMethod('POST');
-        $this->assertPermission('monitoring/command/comment/delete');
-        $this->handleCommandForm(new DeleteCommentCommandForm());
-    }
-
-    /**
-     * Delete a downtime
-     */
-    public function deleteDowntimeAction()
-    {
-        $this->assertHttpMethod('POST');
-        $this->assertPermission('monitoring/command/downtime/delete');
-        $this->handleCommandForm(new DeleteDowntimeCommandForm());
-    }
-
     /**
      * Create tabs
      */
@@ -167,13 +143,13 @@ abstract class MonitoredObjectController extends Controller
         if ($object->getType() === $object::TYPE_HOST) {
             $isService = false;
             $params = array(
-                'host_name' => $object->getName()
+                'host' => $object->getName()
             );
         } else {
             $isService = true;
             $params = array(
-                'host_name'             => $object->getHost()->getName(),
-                'service_description'   => $object->getName()
+                'host'      => $object->getHost()->getName(),
+                'service'   => $object->getName()
             );
         }
         $tabs->add(
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
new file mode 100644
index 000000000..2df3c4f55
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
@@ -0,0 +1,46 @@
+ url, where title will
+     * be used as link caption. Url should be an Icinga\Web\Url object when
+     * the link should point to an Icinga Web url - otherwise a string would
+     * be fine.
+     *
+     * Mixed example:
+     * 
+     * return array(
+     *     'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name),
+     *     'Logstash' => Url::fromPath(
+     *         'logstash/search/syslog',
+     *         array('service' => $service->host_name)
+     *     )
+     * );
+     * 
+     *
+     * One might also provide ssh:// or rdp:// urls if equipped with fitting
+     * (safe) URL handlers for his browser(s).
+     *
+     * TODO: I'd love to see some kind of a Link/LinkSet object implemented
+     *       for this and similar hooks.
+     *
+     * @param   Service $service Monitoring service object
+     *
+     * @return  array   An array containing a list of service action links
+     */
+    abstract public function getActionsForService(Service $service);
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php
new file mode 100644
index 000000000..186351a40
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php
@@ -0,0 +1,62 @@
+select()->from(
+            'programstatus',
+            array(
+                'is_currently_running'
+            )
+        )->getQuery()->fetchRow()->is_currently_running;
+    }
+
+    /**
+     * @see MenuItemRenderer::render()
+     */
+    public function render(Menu $menu)
+    {
+        return $this->getBadge() . $this->createLink($menu);
+    }
+
+    protected function getBadge()
+    {
+        if (! (bool)$this->isCurrentlyRunning()) {
+            return sprintf(
+                '
%s
', + mt('monitoring', 'monitoring backend is not running'), + 1 + ); + } + return ''; + } + + /** + * Get the problem data for the summary + * + * @return array|null + */ + public function getSummary() + { + if (! (bool)$this->isCurrentlyRunning()) { + return array( + 'problems' => 1, + 'title' => mt('monitoring', 'monitoring backend is not running') + ); + } + return null; + } +} diff --git a/library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php similarity index 68% rename from library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php rename to modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php index 33e1c6a60..80ffb3d9c 100644 --- a/library/Icinga/Web/Menu/MonitoringMenuItemRenderer.php +++ b/modules/monitoring/library/Monitoring/Web/Menu/MonitoringMenuItemRenderer.php @@ -1,14 +1,14 @@ countItems(); - $badge = ''; - if ($count) { - $badge = sprintf( + return $this->getBadge() . $this->createLink($menu); + } + + protected function getBadge() + { + if ($count = $this->countItems()) { + return sprintf( '
%s
', $this->getBadgeTitle(), $count ); } - if ($menu->getIcon() && strpos($menu->getIcon(), '.') === false) { - return sprintf( - '%s %s', - $badge, - $menu->getUrl() ?: '#', - $menu->getIcon(), - htmlspecialchars($menu->getTitle()) - ); - } - - return sprintf( - '%s%s%s', - $badge, - $menu->getUrl() ?: '#', - $menu->getIcon() ? ' ' : '', - htmlspecialchars($menu->getTitle()) - ); + return ''; } } diff --git a/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php new file mode 100644 index 000000000..d17431398 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Menu/ProblemMenuItemRenderer.php @@ -0,0 +1,12 @@ + a { color: white; font-size: 0.8em; @@ -128,14 +131,26 @@ span.state.ok { background: @colorOk; } +span.state.up { + background: @colorOk; +} + span.state.critical { background: @colorCritical; } +span.state.down { + background: @colorCritical; +} + span.state.handled.critical { background: @colorCriticalHandled; } +span.state.handled.down { + background: @colorCriticalHandled; +} + span.state.warning { background: @colorWarning; } @@ -196,7 +211,7 @@ div.selection-info { .optionbox label { max-width: 6.5em; text-align: left; - vertical-align: middle; + vertgical-align: middle; margin-right: 0em; } @@ -213,6 +228,13 @@ hr.command-separator { border-bottom: 2px solid @colorPetrol; } +div.backend-not-running { + background: @colorCritical; + color: white; + text-align: center; + padding: 0.1em; +} + td.state { .time-ago, .time-since, diff --git a/modules/monitoring/public/js/module.js b/modules/monitoring/public/js/module.js index 03cfc1c3d..3421be2cf 100644 --- a/modules/monitoring/public/js/module.js +++ b/modules/monitoring/public/js/module.js @@ -44,7 +44,8 @@ /** * Prepare the timer to handle the timeline's infinite loading */ - if ($('div.timeline').length) { + var $timeline = $('div.timeline'); + if ($timeline.length && !$timeline.closest('.dashboard').length) { if (this.scrollCheckTimer === null) { this.scrollCheckTimer = this.module.icinga.timer.register( this.checkTimelinePosition, diff --git a/public/css/icinga/controls.less b/public/css/icinga/controls.less new file mode 100644 index 000000000..b8e82b795 --- /dev/null +++ b/public/css/icinga/controls.less @@ -0,0 +1,21 @@ +/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ + +form.sort-control { + .dontprint; + float: right; + + label { + width: auto; + margin-right: 0.5em; + } + + select[name=sort] { + width: 12em; + margin-left: 0; + } + + select[name=dir] { + width: 5em; + margin-left: 0; + } +} diff --git a/public/css/icinga/defaults.less b/public/css/icinga/defaults.less index 3dc277300..e3e033eff 100644 --- a/public/css/icinga/defaults.less +++ b/public/css/icinga/defaults.less @@ -23,6 +23,9 @@ @colorUnreachableHandled: #cc77ff; @colorPending: #77aaff; @colorInvalid: #999; +@colorFormNotificationInfo: #77aaff; +@colorFormNotificationWarning: #ffaa44; +@colorFormNotificationError: #ff5566; /* Mixins */ diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index 41525a4c7..c8b5df161 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -87,6 +87,10 @@ button::-moz-focus-inner { outline: 0; } +button { + cursor: pointer; +} + select::-moz-focus-inner { border: 0; outline: 0; @@ -165,6 +169,32 @@ form ul.form-errors { } } +form ul.form-notifications { + .non-list-like-list; + margin-bottom: 1em; + padding: 0em; + + li.info { + background: @colorFormNotificationInfo; + } + + li.warning { + background: @colorFormNotificationWarning; + } + + li.error { + background: @colorFormNotificationError; + } + + li { + color: white; + font-weight: bold; + line-height: 1.5em; + padding: 0.5em; + margin-bottom: 0.5em; + } +} + form div.element { margin: 0.5em 0; } diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less index a1bf3682d..3a8713a38 100644 --- a/public/css/icinga/layout-structure.less +++ b/public/css/icinga/layout-structure.less @@ -119,12 +119,12 @@ html { .container .controls { top: 0; background-color: white; - padding: 0; + padding: 1em 1em 0; z-index: 100; } .container .fake-controls { - padding: 0; + padding: 1em 1em 0; } .container .controls .pagination { @@ -149,10 +149,6 @@ html { font-size: 0.9em; } -.container .controls > * { - margin-left: 1em; -} - .container .controls .pagination { margin-left: 1.2em; } @@ -167,7 +163,7 @@ html { } .dashboard .controls { - display: none; + padding: 0; } /* Not growing larger than 3840px at 1em=16px right now */ @@ -350,6 +346,7 @@ html { .hbox-item { display: inline-block; vertical-align: top; + margin-top: 0.5em; margin-bottom: 0.25em; margin-left: 1em; margin-right: 1em; @@ -375,3 +372,8 @@ html { position: absolute; width: 1px; } + +.multi-commands { + padding-top: 0em; + font-size: 0.9em; +} diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 32054e242..b442723cb 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -118,7 +118,6 @@ table.avp a:hover { /* Definitively monitoring-only: */ table.objectstate { - margin: 1em; border-collapse: separate; border-spacing: 1px; } @@ -194,11 +193,6 @@ table.benchmark { color: inherit; } -/* controls have no padding as of tabs */ -.controls > h1 { - margin-right: 1em; -} - [class^="icon-"]:before, [class*=" icon-"]:before { text-decoration: none; } diff --git a/public/css/icinga/monitoring-colors.less b/public/css/icinga/monitoring-colors.less index 5c19bfe35..020db57cd 100644 --- a/public/css/icinga/monitoring-colors.less +++ b/public/css/icinga/monitoring-colors.less @@ -23,7 +23,7 @@ table.action { border-collapse: separate; border-spacing: 1px; width: 100%; - table-layout: fixed; + table-layout: fixed !important; margin: 0; color: @colorTextDefault; } @@ -814,6 +814,10 @@ div.timeline { /* Monitoring groupsummary styles */ +.dashboard table.groupview { + margin-top: 0; +} + table.groupview { width: 100%; margin-top: 1em; @@ -922,3 +926,39 @@ table.groupview { } /* End of monitoring groupsummary styles */ + +/* compact table */ +table.statesummary { + text-align: left; + width: auto; + border-collapse: separate; + + tr.state td.state { + width: auto; + font-weight: bold; + } + + td { + font-size: 0.9em; + line-height: 1.2em; + padding-left: 0.2em; + margin: 0; + } + + td.state { + padding: 0.2em; + min-width: 75px; + font-size: 0.75em; + text-align: center; + } + + td.name { + font-weight: bold; + } + + td a { + color: inherit; + text-decoration: none; + } +} + diff --git a/public/css/icinga/tabs.less b/public/css/icinga/tabs.less index 8d391b038..2c02bfb94 100644 --- a/public/css/icinga/tabs.less +++ b/public/css/icinga/tabs.less @@ -1,12 +1,13 @@ /*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ ul.tabs { - padding: 1em 0 0 0; + padding: 0; list-style-type: inside; } .controls ul.tabs { - margin-left: 0em; + margin-left: -1em; + margin-right: -1em; margin-top: -3.54em; height: 2.6em; overflow: hidden; diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index 45701a697..7e6789dff 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -48,8 +48,22 @@ table.multiselect tr[href] td { -ms-user-select: none; } -#main form.filterEditor input[type=text], #main form.filterEditor select { - width: 12em; +#main div.filter { + margin-top: 1em; + + form.editor { + input[type=text], select { + width: 12em; + } + + ul.tree li.active { + background-color: #eee; + } + + div.buttons { + float: right; + } + } } ul.datafilter li { @@ -203,23 +217,13 @@ li li .badge-container { margin-right: 0.75em; } -/* -#layout.hoveredmenu .active > .badge-container { - display: none; -} - -#layout.hoveredmenu .hover > .badge-container { - //margin-right: 14.15em; - display: none; -} -*/ - .badge { position: relative; - top: 0.3em; + top: -0.15em; display: inline-block; min-width: 1em; padding: 3px 7px; + margin: 0 0.2em 0 0.2em; font-size: 0.8em; font-weight: 700; line-height: 1.1em; @@ -230,6 +234,11 @@ li li .badge-container { background-color: @colorInvalid; } +#menu nav ul .badge { + margin-right: 0em; + top: 0.3em; +} + #menu nav > ul > li.active > .badge-container { display: none; } @@ -265,6 +274,10 @@ li li .badge { background-color: @colorCritical; } +.badge-down { + background-color: @colorCritical; +} + .badge-warning { background-color: @colorWarning; } @@ -273,18 +286,18 @@ li li .badge { background-color: @colorOk; } +.badge-up { + background-color: @colorOk; +} + .badge-pending { background-color: @colorPending; } -.badge-pending { +.badge-unknown { background-color: @colorUnknown; } -.widgetFilter li.active { - background-color: #eee; -} - .sparkline-box { position: relative; top: -3px; @@ -303,3 +316,25 @@ li li .badge { margin: 0em 0em 0em 0.1em; } + +.tipsy .tipsy-inner { + // overwrite tooltip max width, we need them to grow bigger + max-width: 300px; + text-align: left; +} + +.color-box { + position: relative; + top: 2px; + margin: 0px 3px 0px 3px; + display: inline-block; + width: 12px; + height: 12px; +} + +.oneline { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + diff --git a/public/js/icinga/behavior/tooltip.js b/public/js/icinga/behavior/tooltip.js index c9a64257f..e0826b005 100644 --- a/public/js/icinga/behavior/tooltip.js +++ b/public/js/icinga/behavior/tooltip.js @@ -28,9 +28,29 @@ $el.attr('title', $el.data('title-rich') || $el.attr('title')); }); $('svg .chart-data', 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 }); + $('i[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 }); + $('[title]', el).each(function (i, el) { + var $el = $(el); + var delay, gravity; + if ($el.data('tooltip-delay') !== undefined) { + delay = $el.data('tooltip-delay'); + } + if ($el.data('tooltip-gravity')) { + gravity = $el.data('tooltip-gravity'); + } + if (delay === undefined && + gravity === undefined && + !$el.data('title-rich')) { + // use native tooltips for everything that doesn't + // require specific behavior or html markup + return; + } + delay = delay === undefined ? 500 : delay; + $el.tipsy({ + gravity: gravity || $.fn.tipsy.autoNS, + delayIn: delay + }); + }); // migrate or remove all orphaned tooltips $('.tipsy').each(function () { diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index 491ca82f2..cc927feb6 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -100,6 +100,7 @@ // Destroy Icinga, clean up and interrupt pending requests on unload $( window ).on('unload', { self: this }, this.onUnload); + $( window ).on('beforeunload', { self: this }, this.onUnload); // We catch scroll events in our containers $('.container').on('scroll', { self: this }, this.icinga.events.onContainerScroll); @@ -302,6 +303,10 @@ var data = self.icinga.ui.getSelectionKeys($table); var url = $table.data('icinga-multiselect-url'); + if ($(event.target).closest('form').length) { + // allow form actions in table rows to pass through + return; + } event.stopPropagation(); event.preventDefault(); @@ -555,6 +560,7 @@ $(window).off('resize', this.onWindowResize); $(window).off('load', this.onLoad); $(window).off('unload', this.onUnload); + $(window).off('beforeunload', this.onUnload); $(document).off('scroll', '.container', this.onContainerScroll); $(document).off('click', 'a', this.linkClicked); $(document).off('click', 'table.action tr[href]', this.rowSelected); diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 1c674b947..99667ac1c 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -640,7 +640,7 @@ this.icinga.logger.error( req.status, errorThrown + ':', - $(req.responseText).text().slice(0, 100) + $(req.responseText).text().replace(/\s+/g, ' ').slice(0, 100) ); this.renderContentToContainer( req.responseText, @@ -760,12 +760,8 @@ // $container.html(content); } else { - if ($container.closest('.dashboard').length - ) { - if (! $('h1', $content).length) { - var title = $('h1', $container).first().detach(); - $('h1', $content).first().detach(); - } + if ($container.closest('.dashboard').length) { + var title = $('h1', $container).first().detach(); $container.html(title).append(content); } else if (action === 'replace') { $container.html(content); diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 26c4abf96..b9e63aa11 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -706,8 +706,6 @@ }, initializeControls: function (parent) { - - var self = this; if ($(parent).closest('.dashboard').length) { return; } @@ -737,7 +735,6 @@ }, fixControls: function ($parent) { - var self = this; if ('undefined' === typeof $parent) { @@ -763,6 +760,11 @@ $('.controls', $parent).each(function (idx, el) { var $el = $(el); + + if ($el.closest('.dashboard').length) { + return; + } + var $fake = $el.next('.fake-controls'); var y = $parent.scrollTop(); diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php index b7d1ea3c1..fb93f6050 100644 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php @@ -31,7 +31,8 @@ class DbBackendFormTest extends BaseTestCase ->shouldReceive('count') ->andReturn(2); - $form = Mockery::mock('Icinga\Forms\Config\Authentication\DbBackendForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Authentication\DbBackendForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); @@ -56,7 +57,8 @@ class DbBackendFormTest extends BaseTestCase ->shouldReceive('count') ->andReturn(0); - $form = Mockery::mock('Icinga\Forms\Config\Authentication\DbBackendForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Authentication\DbBackendForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); diff --git a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php index d31033ba9..56ea08987 100644 --- a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php @@ -31,7 +31,8 @@ class LdapBackendFormTest extends BaseTestCase Mockery::mock('overload:Icinga\Authentication\Backend\LdapUserBackend') ->shouldReceive('assertAuthenticationPossible')->andReturnNull(); - $form = Mockery::mock('Icinga\Forms\Config\Authentication\LdapBackendForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Authentication\LdapBackendForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); @@ -55,7 +56,8 @@ class LdapBackendFormTest extends BaseTestCase Mockery::mock('overload:Icinga\Authentication\Backend\LdapUserBackend') ->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException); - $form = Mockery::mock('Icinga\Forms\Config\Authentication\LdapBackendForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Authentication\LdapBackendForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); diff --git a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php index d074f1b3b..f382f8bb8 100644 --- a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php +++ b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php @@ -29,7 +29,8 @@ class LdapResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('testCredentials')->once()->andReturn(true)->getMock() ); - $form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); @@ -51,7 +52,8 @@ class LdapResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('testCredentials')->once()->andThrow('\Exception')->getMock() ); - $form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]'); + // Passing array(null) is required to make Mockery call the constructor... + $form = Mockery::mock('Icinga\Forms\Config\Resource\LdapResourceForm[getView]', array(null)); $form->shouldReceive('getView->escape') ->with(Mockery::type('string')) ->andReturnUsing(function ($s) { return $s; }); diff --git a/test/php/library/Icinga/Data/Filter/FilterTest.php b/test/php/library/Icinga/Data/Filter/FilterTest.php index e14decb20..8080d47f7 100644 --- a/test/php/library/Icinga/Data/Filter/FilterTest.php +++ b/test/php/library/Icinga/Data/Filter/FilterTest.php @@ -198,16 +198,53 @@ class FilterTest extends BaseTestCase $this->assertNotEquals((string) $c, (string) $d); } - public function testLeadingAndTrailingWhitespacesSanitizing() + public function testLeadingAndTrailingWhitespaces() { - $columnHasWhitespaces = Filter::where(' host ', 'localhost'); - $expressionHasWhitespaces = Filter::where('host', ' localhost '); - $bothHaveWhitespaces = Filter::fromQueryString(' host = localhost '); - $withArray = Filter::where(' host ', array(' no match ', ' localhost ')); - $this->assertTrue($columnHasWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($expressionHasWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($bothHaveWhitespaces->matches($this->sampleData[0])); - $this->assertTrue($withArray->matches($this->sampleData[0])); + $columnWithWhitespaces = Filter::where(' host ', 'localhost'); + $this->assertTrue($columnWithWhitespaces->matches((object) array( + 'host' => 'localhost' + )), + 'Filter doesn\'t remove leading and trailing whitespaces from columns' + ); + $expressionWithLeadingWhitespaces = Filter::where('host', ' localhost'); + $this->assertTrue($expressionWithLeadingWhitespaces->matches((object) array( + 'host' => ' localhost' + )), + 'Filter doesn\'t take leading whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithLeadingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take trailing whitespaces of expressions into account' + ); + $expressionWithTrailingWhitespaces = Filter::where('host', 'localhost '); + $this->assertTrue($expressionWithTrailingWhitespaces->matches((object) array( + 'host' => 'localhost ' + )), + 'Filter doesn\'t take trailing whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading whitespaces of expressions into account' + ); + $expressionWithLeadingAndTrailingWhitespaces = Filter::where('host', ' localhost '); + $this->assertTrue($expressionWithLeadingAndTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions into account' + ); + $this->assertFalse($expressionWithLeadingAndTrailingWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions into account' + ); + $queryStringWithWhitespaces = Filter::fromQueryString(' host = localhost '); + $this->assertTrue($queryStringWithWhitespaces->matches((object) array( + 'host' => ' localhost ' + )), + 'Filter doesn\'t take leading and trailing whitespaces of expressions in query strings into account' + ); } private function row($idx) diff --git a/test/php/library/Icinga/UserTest.php b/test/php/library/Icinga/UserTest.php index 9c14a93d3..dc55dc62d 100644 --- a/test/php/library/Icinga/UserTest.php +++ b/test/php/library/Icinga/UserTest.php @@ -66,12 +66,16 @@ class UserTest extends BaseTestCase $user->setPermissions(array( 'test', 'test/some/specific', - 'test/more/*' + 'test/more/*', + 'test/wildcard-with-wildcard/*' )); $this->assertTrue($user->can('test')); $this->assertTrue($user->can('test/some/specific')); $this->assertTrue($user->can('test/more/everything')); + $this->assertTrue($user->can('test/wildcard-with-wildcard/*')); + $this->assertTrue($user->can('test/wildcard-with-wildcard/sub/sub')); $this->assertFalse($user->can('not/test')); $this->assertFalse($user->can('test/some/not/so/specific')); + $this->assertFalse($user->can('test/wildcard2/*')); } }