Merge branch 'master' into feature/restrict-custom-variables-10965

This commit is contained in:
Eric Lippmann 2016-04-13 15:44:12 +02:00
commit 4d488ab354
83 changed files with 2168 additions and 1407 deletions

View File

@ -16,7 +16,7 @@ Icinga Core and any other monitoring backend compatible with the IDO database.
## Installation ## Installation
For installing Icinga Web 2 please read [doc/installation.md](doc/installation.md). For installing Icinga Web 2 please read [doc/02-Installation.md](doc/02-Installation.md).
## Support ## Support

View File

@ -244,20 +244,35 @@ class DashboardController extends ActionController
if (! $this->dashboard->hasPanes()) { if (! $this->dashboard->hasPanes()) {
$this->view->title = 'Dashboard'; $this->view->title = 'Dashboard';
} else { } else {
if ($this->_getParam('pane')) { $panes = array_filter(
$pane = $this->_getParam('pane'); $this->dashboard->getPanes(),
$this->dashboard->activate($pane); function ($pane) {
} return ! $pane->getDisabled();
if ($this->dashboard === null) { }
$this->view->title = 'Dashboard'; );
} else { if (empty($panes)) {
$this->view->title = $this->dashboard->getActivePane()->getTitle() . ' :: Dashboard'; $this->view->title = 'Dashboard';
if ($this->hasParam('remove')) { $this->getTabs()->add('dashboard', array(
$this->dashboard->getActivePane()->removeDashlet($this->getParam('remove')); 'active' => true,
$this->dashboard->getConfig()->saveIni(); 'title' => $this->translate('Dashboard'),
$this->redirectNow(URL::fromRequest()->remove('remove')); 'url' => Url::fromRequest()
));
} else {
if ($this->_getParam('pane')) {
$pane = $this->_getParam('pane');
$this->dashboard->activate($pane);
}
if ($this->dashboard === null) {
$this->view->title = 'Dashboard';
} else {
$this->view->title = $this->dashboard->getActivePane()->getTitle() . ' :: Dashboard';
if ($this->hasParam('remove')) {
$this->dashboard->getActivePane()->removeDashlet($this->getParam('remove'));
$this->dashboard->getConfig()->saveIni();
$this->redirectNow(URL::fromRequest()->remove('remove'));
}
$this->view->dashboard = $this->dashboard;
} }
$this->view->dashboard = $this->dashboard;
} }
} }
} }

View File

@ -18,6 +18,7 @@
</thead> </thead>
<tbody> <tbody>
<?php foreach ($this->dashboard->getPanes() as $pane): ?> <?php foreach ($this->dashboard->getPanes() as $pane): ?>
<?php if ($pane->getDisabled()) continue; ?>
<tr style="background-color: #f1f1f1;"> <tr style="background-color: #f1f1f1;">
<th colspan="2" style="text-align: left; padding: 0.5em;"> <th colspan="2" style="text-align: left; padding: 0.5em;">
<?= $this->escape($pane->getName()) ?> <?= $this->escape($pane->getName()) ?>
@ -43,7 +44,7 @@
</tr> </tr>
<?php else: ?> <?php else: ?>
<?php foreach ($dashlets as $dashlet): ?> <?php foreach ($dashlets as $dashlet): ?>
<?php if ($dashlet->getDisabled() === true) continue; ?> <?php if ($dashlet->getDisabled()) continue; ?>
<tr> <tr>
<td> <td>
<?= $this->qlink( <?= $this->qlink(

View File

@ -5,16 +5,14 @@ use Icinga\Data\Reducible;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls separated"> <div class="controls separated">
<?= $tabs; ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div>
<div>
<?= $this->backendSelection; ?>
<?= $this->filterEditor; ?>
</div> </div>
<?= $this->backendSelection ?>
<?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
@ -45,8 +43,7 @@ if (! isset($backend)) {
<?php if (! $groups->hasResult()): ?> <?php if (! $groups->hasResult()): ?>
<p><?= $this->translate('No user groups found matching the filter'); ?></p> <p><?= $this->translate('No user groups found matching the filter'); ?></p>
</div> </div>
<?php endif ?> <?php return; endif ?>
<table data-base-target="_next" class="table-row-selectable common-table"> <table data-base-target="_next" class="table-row-selectable common-table">
<thead> <thead>
<tr> <tr>
@ -85,8 +82,9 @@ if (! isset($backend)) {
'group' => $group->group_name 'group' => $group->group_name
), ),
array( array(
'class' => 'action-link',
'title' => sprintf($this->translate('Remove user group %s'), $group->group_name), 'title' => sprintf($this->translate('Remove user group %s'), $group->group_name),
'icon' => 'trash' 'icon' => 'cancel'
) )
); ?> ); ?>
</td> </td>

View File

@ -5,16 +5,14 @@ use Icinga\Data\Reducible;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls separated"> <div class="controls separated">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div>
<div>
<?= $this->backendSelection ?>
<?= $this->filterEditor ?>
</div> </div>
<?= $this->backendSelection ?>
<?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">

View File

@ -4,7 +4,7 @@ The preferred way of installing Icinga Web 2 is to use the official package repo
system and distribution you are running. But it is also possible to install Icinga Web 2 directly from source. system and distribution you are running. But it is also possible to install Icinga Web 2 directly from source.
In case you are upgrading from an older version of Icinga Web 2 In case you are upgrading from an older version of Icinga Web 2
please make sure to read the [upgrading](installation.md#upgrading) section please make sure to read the [upgrading](02-Installation.md#upgrading) section
thoroughly. thoroughly.
## <a id="installing-requirements"></a> Installing Requirements ## <a id="installing-requirements"></a> Installing Requirements
@ -179,7 +179,7 @@ git clone git://git.icinga.org/icingaweb2.git
### <a id="installing-from-source-requirements"></a> Installing Requirements from Source ### <a id="installing-from-source-requirements"></a> Installing Requirements from Source
You will need to install certain dependencies depending on your setup listed [here](installation.md#installing-requirements). You will need to install certain dependencies depending on your setup listed [here](02-Installation.md#installing-requirements).
The following example installs Apache2 as web server, MySQL as RDBMS and uses the PHP adapter for MySQL. The following example installs Apache2 as web server, MySQL as RDBMS and uses the PHP adapter for MySQL.
Adopt the package requirements to your needs (e.g. adding ldap for authentication) and distribution. Adopt the package requirements to your needs (e.g. adding ldap for authentication) and distribution.
@ -318,7 +318,7 @@ Puppet, Ansible, Chef, etc. modules.
> Read the documentation on the respective linked configuration sections before > Read the documentation on the respective linked configuration sections before
> deploying the configuration manually. > deploying the configuration manually.
> >
> If you are unsure about certain settings, use the [setup wizard](installation.md#web-setup-wizard-from-source) once > If you are unsure about certain settings, use the [setup wizard](02-Installation.md#web-setup-wizard-from-source) once
> and then collect the generated configuration as well as sql dumps. > and then collect the generated configuration as well as sql dumps.
#### <a id="web-setup-manual-from-source-database"></a> Icinga Web 2 Manual Database Setup #### <a id="web-setup-manual-from-source-database"></a> Icinga Web 2 Manual Database Setup
@ -336,7 +336,7 @@ mysql -p icingaweb2 < /usr/share/icingaweb2/etc/schema/mysql.schema.sql
``` ```
Then generate a new password hash as described in the [authentication docs](authentication.md#authentication-configuration-db-setup) Then generate a new password hash as described in the [authentication docs](05-Authentication.md#authentication-configuration-db-setup)
and use it to insert a new user called `icingaadmin` into the database. and use it to insert a new user called `icingaadmin` into the database.
``` ```
@ -349,7 +349,7 @@ quit
#### <a id="web-setup-manual-from-source-config"></a> Icinga Web 2 Manual Configuration #### <a id="web-setup-manual-from-source-config"></a> Icinga Web 2 Manual Configuration
[resources.ini](resources.md#resources) providing the details for the Icinga Web 2 and [resources.ini](04-Resources.md#resources) providing the details for the Icinga Web 2 and
Icinga 2 IDO database configuration. Example for MySQL: Icinga 2 IDO database configuration. Example for MySQL:
``` ```
@ -375,7 +375,7 @@ username = "icinga"
password = "icinga" password = "icinga"
``` ```
[config.ini](configuration.md#configuration) defining general application settings. [config.ini](03-Configuration.md#configuration) defining general application settings.
``` ```
vim /etc/icingaweb2/config.ini vim /etc/icingaweb2/config.ini
@ -391,7 +391,7 @@ type = "db"
resource = "icingaweb2" resource = "icingaweb2"
``` ```
[authentication.ini](authentication.md#authentication) for e.g. using the previously created database. [authentication.ini](05-Authentication.md#authentication) for e.g. using the previously created database.
``` ```
vim /etc/icingaweb2/authentication.ini vim /etc/icingaweb2/authentication.ini
@ -402,7 +402,7 @@ resource = "icingaweb2"
``` ```
[roles.ini](security.md#security) granting the previously added `icingaadmin` user all permissions. [roles.ini](06-Security.md#security) granting the previously added `icingaadmin` user all permissions.
``` ```
vim /etc/icingaweb2/roles.ini vim /etc/icingaweb2/roles.ini
@ -415,7 +415,7 @@ permissions = "*"
#### <a id="web-setup-manual-from-source-config-monitoring-module"></a> Icinga Web 2 Manual Configuration Monitoring Module #### <a id="web-setup-manual-from-source-config-monitoring-module"></a> Icinga Web 2 Manual Configuration Monitoring Module
[config.ini](../modules/monitoring/doc/configuration.md#configuration) defining additional security settings. **config.ini** defining additional security settings.
``` ```
vim /etc/icingaweb2/modules/monitoring/config.ini vim /etc/icingaweb2/modules/monitoring/config.ini
@ -424,7 +424,7 @@ vim /etc/icingaweb2/modules/monitoring/config.ini
protected_customvars = "*pw*,*pass*,community" protected_customvars = "*pw*,*pass*,community"
``` ```
[backends.ini](../modules/monitoring/doc/configuration.md#configuration) referencing the Icinga 2 DB IDO resource. **backends.ini** referencing the Icinga 2 DB IDO resource.
``` ```
vim /etc/icingaweb2/modules/monitoring/backends.ini vim /etc/icingaweb2/modules/monitoring/backends.ini
@ -434,7 +434,7 @@ type = "ido"
resource = "icinga2" resource = "icinga2"
``` ```
[commandtransports.ini](../modules/monitoring/doc/commandtransports.md#commandtransports) defining the Icinga 2 command pipe. **commandtransports.ini** defining the Icinga command pipe.
``` ```
vim /etc/icingaweb2/modules/monitoring/commandtransports.ini vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
@ -451,46 +451,20 @@ Finally visit Icinga Web 2 in your browser to login as `icingaadmin` user: `/ici
# <a id="upgrading"></a> Upgrading Icinga Web 2 # <a id="upgrading"></a> Upgrading Icinga Web 2
## <a id="upgrading-to-beta2"></a> Upgrading to Icinga Web 2 Beta 2 ## <a id="upgrading-to-2.3.0"></a> Upgrading to Icinga Web 2 2.3.0
Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2, * Icinga Web 2 version 2.3.0 does not introduce any backward incompatible change.
you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following
content:
```
[administrators]
users = "your_user_name, another_user_name"
permissions = "*"
```
After please log out from Icinga Web 2 and log in again for having all permissions granted. ## <a id="upgrading-to-2.2.0"></a> Upgrading to Icinga Web 2 2.2.0
If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external` * The menu entry `Authorization` beneath `Config` has been renamed to `Authentication`. The role, user backend and user
authentication backend to be able to log in again. The new name better reflects whats going on. A similar change group backend configuration which was previously found beneath `Authentication` has been moved to `Application`.
affects environments that opted for not storing preferences, your new backend is `none`.
## <a id="upgrading-to-2.1.x"></a> Upgrading to Icinga Web 2 2.1.x
## <a id="upgrading-to-beta3"></a> Upgrading to Icinga Web 2 Beta 3 * Since Icinga Web 2 version 2.1.3 LDAP user group backends respect the configuration option `group_filter`.
Users who changed the configuration manually and used the option `filter` instead
Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your have to change it back to `group_filter`.
configuration files after upgrading to Icinga Web 2 Beta 3.
## <a id="upgrading-to-rc1"></a> Upgrading to Icinga Web 2 Release Candidate 1
The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes:
* The database schema has been adjusted and the tables `icingaweb_group` and
`icingaweb_group_membership` were altered to ensure referential integrity.
Please use the upgrade script located in **etc/schema/** to update your
database schema
* Users who are using PostgreSQL < v9.1 are required to upgrade their
environment to v9.1+ as this is the new minimum required version
for utilizing PostgreSQL as database backend
* The restrictions `monitoring/hosts/filter` and `monitoring/services/filter`
provided by the monitoring module were merged together. The new
restriction is called `monitoring/filter/objects` and supports only a
predefined subset of filter columns. Please see the module's security
related documentation for more details.
## <a id="upgrading-to-2.0.0"></a> Upgrading to Icinga Web 2 2.0.0 ## <a id="upgrading-to-2.0.0"></a> Upgrading to Icinga Web 2 2.0.0
@ -514,13 +488,43 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
**&lt;config-dir&gt;/preferences/&lt;username&gt;/config.ini**. **&lt;config-dir&gt;/preferences/&lt;username&gt;/config.ini**.
The content of the file remains unchanged. The content of the file remains unchanged.
## <a id="upgrading-to-2.1.x"></a> Upgrading to Icinga Web 2 2.1.x ## <a id="upgrading-to-rc1"></a> Upgrading to Icinga Web 2 Release Candidate 1
* Since Icinga Web 2 version 2.1.3 LDAP user group backends respect the configuration option `group_filter`. The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes:
Users who changed the configuration manually and used the option `filter` instead
have to change it back to `group_filter`.
## <a id="upgrading-to-2.2.0"></a> Upgrading to Icinga Web 2 2.2.0 * The database schema has been adjusted and the tables `icingaweb_group` and
`icingaweb_group_membership` were altered to ensure referential integrity.
Please use the upgrade script located in **etc/schema/** to update your
database schema
* The menu entry `Authorization` beneath `Config` has been renamed to `Authentication`. The role, user backend and user * Users who are using PostgreSQL < v9.1 are required to upgrade their
group backend configuration which was previously found beneath `Authentication` has been moved to `Application`. environment to v9.1+ as this is the new minimum required version
for utilizing PostgreSQL as database backend
* The restrictions `monitoring/hosts/filter` and `monitoring/services/filter`
provided by the monitoring module were merged together. The new
restriction is called `monitoring/filter/objects` and supports only a
predefined subset of filter columns. Please see the module's security
related documentation for more details.
## <a id="upgrading-to-beta3"></a> 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.
## <a id="upgrading-to-beta2"></a> 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
content:
```
[administrators]
users = "your_user_name, another_user_name"
permissions = "*"
```
After please log out from Icinga Web 2 and log in again for having all permissions granted.
If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external`
authentication backend to be able to log in again. The new name better reflects whats going on. A similar change
affects environments that opted for not storing preferences, your new backend is `none`.

15
doc/03-Configuration.md Normal file
View File

@ -0,0 +1,15 @@
# <a id="configuration"></a> Configuration
## Overview
Apart from its web configuration capabilities, the local configuration is
stored in `/etc/icingaweb2` by default (depending on your config setup).
File/Directory | Description
---------------------------------------------------------
config.ini | General configuration (logging, preferences)
[resources.ini](04-Ressources.md) | Global resources (Icinga Web 2 database for preferences and authentication, Icinga IDO database)
roles.ini | User specific roles (e.g. `administrators`) and permissions
[authentication.ini](05-Authentication.md) | Authentication backends (e.g. database)
enabledModules | Contains symlinks to enabled modules
modules | Directory for module specific configuration

View File

@ -18,29 +18,60 @@ The order of entries in the authentication configuration determines the order of
If the current authentication method errors or if the current authentication method does not know the account being If the current authentication method errors or if the current authentication method does not know the account being
authenticated, the next authentication method will be used. authenticated, the next authentication method will be used.
### <a id="authentication-configuration-external-authentication"></a> External Authentication ## <a id="authentication-configuration-external-authentication"></a> External Authentication
For delegating authentication to the web server simply add `autologin` to your authentication configuration: For delegating authentication to the web server simply add `autologin` to your authentication configuration:
```` ```
[autologin] [autologin]
backend = external backend = external
```` ```
If your web server is not configured for authentication though the `autologin` section has no effect. If your web server is not configured for authentication though, the `autologin` section has no effect.
### <a id="authentication-configuration-ad-or-ldap-authentication"></a> Active Directory or LDAP Authentication ### <a id="authentication-configuration-external-authentication-example"></a> Example Configuration for Apache and Basic Authentication
The following example will show you how to enable external authentication in Apache
using **Basic access authentication**.
**Creating Users**
To create users for **basic access authentication** you can use the tool `htpasswd`. In this example **.http-users** is
the name of the file containing the user credentials.
The following command creates a new file with the user **icingaadmin**. `htpasswd` will prompt you for a password.
If you want to add more users to the file you have to omit the `-c` switch to not overwrite the file.
```
sudo htpasswd -c /etc/icingaweb2/.http-users icingaadmin
```
**Configuring the Web Server**
Add the following configuration to the **&lt;Directory&gt; Directive** in the **icingaweb.conf** web server
configuration file.
```
AuthType Basic
AuthName "Icinga Web 2"
AuthUserFile /etc/icingaweb2/.http-users
Require valid-user
```
Restart your web server to apply the changes.
## <a id="authentication-configuration-ad-or-ldap-authentication"></a> Active Directory or LDAP Authentication
If you want to authenticate against Active Directory or LDAP, you have to define a If you want to authenticate against Active Directory or LDAP, you have to define a
[LDAP resource](resources.md#resources-configuration-ldap) which will be referenced as data source for the Active Directory [LDAP resource](04-Resources.md#resources-configuration-ldap) which will be referenced as data source for the
or LDAP configuration method. Active Directory or LDAP configuration method.
#### <a id="authentication-configuration-ldap-authentication"></a> LDAP ### <a id="authentication-configuration-ldap-authentication"></a> LDAP
Directive | Description Directive | Description
------------------------|------------ ------------------------|------------
**backend** | `ldap` **backend** | `ldap`
**resource** | The name of the LDAP resource defined in [resources.ini](resources.md#resources). **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
**user_class** | LDAP user class. **user_class** | LDAP user class.
**user_name_attribute** | LDAP attribute which contains the username. **user_name_attribute** | LDAP attribute which contains the username.
**filter** | LDAP search filter. **filter** | LDAP search filter.
@ -60,12 +91,12 @@ Note that in case the set *user_name_attribute* holds multiple values it is requ
values are unique. Additionally, a user will be logged in using the exact user id used to authenticate values are unique. Additionally, a user will be logged in using the exact user id used to authenticate
with Icinga Web 2 (e.g. an alias) no matter what the primary user id might actually be. with Icinga Web 2 (e.g. an alias) no matter what the primary user id might actually be.
#### <a id="authentication-configuration-ad-authentication"></a> Active Directory ### <a id="authentication-configuration-ad-authentication"></a> Active Directory
Directive | Description Directive | Description
------------------------|------------ ------------------------|------------
**backend** | `msldap` **backend** | `msldap`
**resource** | The name of the LDAP resource defined in [resources.ini](resources.md#resources). **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
**Example:** **Example:**
@ -75,16 +106,16 @@ backend = msldap
resource = my_ad resource = my_ad
``` ```
### <a id="authentication-configuration-db-authentication"></a> Database Authentication ## <a id="authentication-configuration-db-authentication"></a> Database Authentication
If you want to authenticate against a MySQL or a PostgreSQL database, you have to define a If you want to authenticate against a MySQL or a PostgreSQL database, you have to define a
[database resource](resources.md#resources-configuration-database) which will be referenced as data source for the database [database resource](04-Resources.md#resources-configuration-database) which will be referenced as data source for the database
authentication method. authentication method.
Directive | Description Directive | Description
------------------------|------------ ------------------------|------------
**backend** | `db` **backend** | `db`
**resource** | The name of the database resource defined in [resources.ini](resources.md#resources). **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources).
**Example:** **Example:**
@ -94,28 +125,28 @@ backend = db
resource = icingaweb-mysql resource = icingaweb-mysql
``` ```
#### <a id="authentication-configuration-db-setup"></a> Database Setup ### <a id="authentication-configuration-db-setup"></a> Database Setup
For authenticating against a database, you have to import one of the following database schemas: For authenticating against a database, you have to import one of the following database schemas:
* **etc/schema/preferences.mysql.sql** (for **MySQL** database) * **etc/schema/preferences.mysql.sql** (for **MySQL** database)
* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases) * **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases)
After that you have to define the [database resource](resources.md#resources-configuration-database). After that you have to define the [database resource](04-Resources.md#resources-configuration-database).
**Manually Creating Users** **Manually Creating Users**
Icinga Web 2 uses the MD5 based BSD password algorithm. For generating a password hash, please use the following Icinga Web 2 uses the MD5 based BSD password algorithm. For generating a password hash, please use the following
command: 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. > Note: The switch to `openssl passwd` is the **number one** (`-1`) for using the MD5 based BSD password algorithm.
Insert the user into the database using the generated password hash: Insert the user into the database using the generated password hash:
```` ```
INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, 'hash from openssl'); INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, 'hash from openssl');
```` ```

View File

@ -21,9 +21,8 @@ things to which access can be managed: actions and objects.
### Actions ### Actions
Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration,
changing permissions or sending a command to the Icinga instance through the changing permissions or sending a command to the Icinga instance through the Icinga command pipe.
<a href="http://docs.icinga.org/icinga2/latest/doc/module/icinga2/toc#!/icinga2/latest/doc/module/icinga2/chapter/getting-started#setting-up-external-command-pipe">Command Pipe</a> All actions must be be **allowed explicitly** using permissions.
in the monitoring module. All actions must be be **allowed explicitly** using permissions.
A permission is a simple list of identifiers of actions a user is A permission is a simple list of identifiers of actions a user is
allowed to do. Permissions are described in greater detail in the allowed to do. Permissions are described in greater detail in the
@ -47,7 +46,7 @@ using Active Directory, and a user **icingaadmin** that is authenticated using a
In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**. In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**.
Icinga Web 2 users and groups are not configured by a configuration file, but provided by Icinga Web 2 users and groups are not configured by a configuration file, but provided by
an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication). an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](05-Authentication.md#authentication).
<div class="info-box"> <div class="info-box">
@ -59,7 +58,7 @@ an **authentication backend**. For extended information on setting up authentica
#### Managing Users #### Managing Users
When using a [Database When using a [Database
as authentication backend](authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration as authentication backend](05-Authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration
can be found at **Configuration > Authentication > Users **. can be found at **Configuration > Authentication > Users **.
### Groups ### Groups
@ -70,12 +69,12 @@ A user can be member of multiple groups and will inherit all permissions and res
Like users, groups are identified solely by their **name** that is provided by Like users, groups are identified solely by their **name** that is provided by
a **group backend**. For extended information on setting up group backends, a **group backend**. For extended information on setting up group backends,
please read the chapter [Authentication](authentication.md#authentication). please read the chapter [Authentication](05-Authentication.md#authentication).
#### Managing Groups #### Managing Groups
When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication), When using a [Database as an authentication backend](05-Authentication.md#authentication-configuration-db-authentication),
it is possible to manage groups and group memberships directly in the frontend. This configuration it is possible to manage groups and group memberships directly in the frontend. This configuration
can be found at **Configuration > Authentication > Groups **. can be found at **Configuration > Authentication > Groups **.
@ -157,18 +156,18 @@ through a group) all permissions are added together to get the users actual perm
### Global Permissions ### Global Permissions
Name | Permits Name | Permits
--------------- ----|-------------------------------------------------------- --------------------------|--------------------------------------------------------
* | Allow everything, including module-specific permissions * | Allow everything, including module-specific permissions
config/* | Allow all configuration actions config/* | Allow all configuration actions
config/modules | Allow enabling or disabling modules config/modules | Allow enabling or disabling modules
module/<moduleName> | Allow access to module <moduleName> module/&lt;moduleName&gt; | Allow access to module &lt;moduleName&gt;
### Monitoring Module Permissions ### Monitoring Module Permissions
The built-in monitoring module defines an additional set of permissions, that The built-in monitoring module defines an additional set of permissions, that
is described in detail in the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). is described in detail in the monitoring module documentation.
## <a id="restrictions"></a> Restrictions ## <a id="restrictions"></a> Restrictions
@ -187,7 +186,7 @@ mentioned in the section [Syntax](#syntax).
### Filter Expressions ### Filter Expressions
Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in
the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). the monitoring module documentation.
Any filter expression that is allowed in the filtered view, is also an allowed filter expression. Any filter expression that is allowed in the filtered view, is also an allowed filter expression.
This means, that it is possible to define negations, wildcards, and even nested This means, that it is possible to define negations, wildcards, and even nested

View File

@ -27,13 +27,13 @@ type = ini
### <a id="preferences-configuration-db"></a> Store Preferences in a Database ### <a id="preferences-configuration-db"></a> Store Preferences in a Database
In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database. In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database.
For storing preferences in a database, you have to define a [database resource](resources.md#resources-configuration-database) For storing preferences in a database, you have to define a [database resource](04-Resources.md#resources-configuration-database)
which will be referenced as resource for the preferences storage. which will be referenced as resource for the preferences storage.
Directive | Description Directive | Description
------------------------|------------ ------------------------|------------
**type** | `db` **type** | `db`
**resource** | The name of the database resource defined in [resources.ini](resources.md#resources). **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources).
**Example:** **Example:**
@ -50,4 +50,4 @@ For storing preferences in a database, you have to import one of the following d
* **etc/schema/preferences.mysql.sql** (for **MySQL** database) * **etc/schema/preferences.mysql.sql** (for **MySQL** database)
* **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases) * **etc/schema/preferences.pgsql.sql** (for **PostgreSQL** databases)
After that you have to define the [database resource](resources.md#resources-configuration-database). After that you have to define the [database resource](04-Resources.md#resources-configuration-database).

View File

@ -1,15 +0,0 @@
# <a id="configuration"></a> Configuration
## Overview
Apart from its web configuration capabilities, the local configuration is
stored in `/etc/icingaweb2` by default (depending on your config setup).
Location | File | Description
------------------------------|-----------------------|---------------------------
. | config.ini | General configuration (logging, preferences)
. | resources.ini | Global resources (Icinga Web 2 database for preferences and authentication, icinga ido database)
. | roles.ini | User specific roles (e.g. `administrators`) and permissions
. | [authentication.ini](authentication.md) | Authentication backends (e.g. database)
enabledModules | Symlink | Contains symlinks to enabled modules from `/usr/share/icingaweb2/modules/*`. Defaults to [monitoring](modules/monitoring/doc/configuration.md) and `doc`.
modules | Directory | Module specific configuration

View File

@ -1,86 +0,0 @@
# External Authentication
It is possible to utilize the authentication mechanism of the webserver instead
of the internal authentication of Icinga Web 2 to authenticate users. This might
be useful if you only have very few users and user management over **.htaccess**
is not sufficient or if you are required to use some other authentication
mechanism that is only available by utilizing the webserver.
Icinga Web 2 will entrust the complete authentication process to the
authentication provider of the webserver, if external authentication is used.
So it is very important that the webserver's authentication is configured
correctly as wrong configuration might lead to unauthorized access or a
malfunction in the login-process.
## Using External Authentication
External authentication in Icinga Web 2 requires the following preparations:
1. The external authentication must be set up properly to correctly
authenticate users
2. Icinga Web 2 must be configured to use external authentication
### Preparing the External Authentication Provider
This step depends heavily on the used webserver and authentication mechanism you
want to use. It is not possible to cover all possibillities and you should
probably read the documentation for your webserver to get detailed instructions
on how to set up authentication properly.
In general you need to make sure that:
- All routes require authentication
- Only permitted users are allowed to authenticate
#### Example Configuration for Apache and HTTPDigestAuthentication
The following example will show how to enable external authentication in Apache
using *HTTP Digest Authentication*.
##### Creating users
To create users for digest authentication you can use the tool *htdigest*. In
this example **.icingawebdigest** is the name of the file containing the user
credentials.
This command creates a new file with the user *jdoe*. *htdigest* will prompt
you for a password. If you want to add more users to the file you need to omit
the *-c* parameter in all following commands to not to overwrite the file.
````
sudo htdigest -c /etc/icingaweb2/.icingawebdigest "Icinga Web 2" jdoe
````
##### Configuring the Webserver
The webserver should require authentication for all public Icinga Web 2 files.
````
<Directory "/usr/share/icingaweb2/public">
AuthType digest
AuthName "Icinga Web 2"
AuthDigestProvider file
AuthUserFile /etc/icingaweb2/.icingawebdigest
Require valid-user
</Directory>
````
To get these changes to work, make sure to enable the module for
HTTPDigestAuthentication and restart the webserver.
### Preparing Icinga Web 2
Once external authentication is set up correctly you need to configure Icinga
Web 2. In case you already completed the setup wizard it is likely that you are
now finished.
To get Icinga Web 2 to use external authentication the file
**config/authentication.ini** is required. Just add the following section
called "autologin", or any name of your choice, and save your changes:
````
[autologin]
backend = external
````
Congratulations! You are now logged in when visiting Icinga Web 2.

View File

@ -190,7 +190,7 @@ cp -pv etc/bash_completion.d/icingacli %{buildroot}/%{_sysconfdir}/bash_completi
cp -prv modules/{monitoring,setup,doc,translation} %{buildroot}/%{basedir}/modules cp -prv modules/{monitoring,setup,doc,translation} %{buildroot}/%{basedir}/modules
cp -prv library/Icinga %{buildroot}/%{phpdir} cp -prv library/Icinga %{buildroot}/%{phpdir}
cp -prv library/vendor/{dompdf,HTMLPurifier*,JShrink,lessphp,Parsedown,Zend} %{buildroot}/%{basedir}/library/vendor cp -prv library/vendor/{dompdf,HTMLPurifier*,JShrink,lessphp,Parsedown,Zend} %{buildroot}/%{basedir}/library/vendor
cp -prv public/{css,img,js,error_norewrite.html} %{buildroot}/%{basedir}/public cp -prv public/{css,font,img,js,error_norewrite.html} %{buildroot}/%{basedir}/public
cp -pv packages/files/apache/icingaweb2.conf %{buildroot}/%{wwwconfigdir}/icingaweb2.conf cp -pv packages/files/apache/icingaweb2.conf %{buildroot}/%{wwwconfigdir}/icingaweb2.conf
cp -pv packages/files/bin/icingacli %{buildroot}/%{bindir} cp -pv packages/files/bin/icingacli %{buildroot}/%{bindir}
cp -pv packages/files/public/index.php %{buildroot}/%{basedir}/public cp -pv packages/files/public/index.php %{buildroot}/%{basedir}/public

View File

@ -0,0 +1,152 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Application\Hook\Ticket;
use ArrayAccess;
/**
* A ticket pattern
*
* This class should be used by modules which provide implementations for the Web 2 ticket hook.
* Have a look at the GenericTTS module for a possible use case.
*/
class TicketPattern implements ArrayAccess
{
/**
* The result of a performed ticket match
*
* @var array
*/
protected $match = array();
/**
* The name of the TTS integration
*
* @var string
*/
protected $name;
/**
* The ticket pattern
*
* @var string
*/
protected $pattern;
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return isset($this->match[$offset]);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return array_key_exists($offset, $this->match) ? $this->match[$offset] : null;
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
if ($offset === null) {
$this->match[] = $value;
} else {
$this->match[$offset] = $value;
}
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
unset($this->match[$offset]);
}
/**
* Get the result of a performed ticket match
*
* @return array
*/
public function getMatch()
{
return $this->match;
}
/**
* Set the result of a performed ticket match
*
* @param array $match
*
* @return $this
*/
public function setMatch(array $match)
{
$this->match = $match;
return $this;
}
/**
* Get the name of the TTS integration
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the name of the TTS integration
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get the ticket pattern
*
* @return string
*/
public function getPattern()
{
return $this->pattern;
}
/**
* Set the ticket pattern
*
* @param string $pattern
*
* @return $this
*/
public function setPattern($pattern)
{
$this->pattern = $pattern;
return $this;
}
/**
* Whether the integration is properly configured, i.e. the pattern and the URL are not empty
*
* @return bool
*/
public function isValid()
{
return ! empty($this->pattern);
}
}

View File

@ -3,15 +3,17 @@
namespace Icinga\Application\Hook; namespace Icinga\Application\Hook;
use ArrayIterator;
use ErrorException; use ErrorException;
use Exception; use Exception;
use Icinga\Application\Hook\Ticket\TicketPattern;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Exception\IcingaException; use Icinga\Exception\IcingaException;
/** /**
* Base class for ticket hooks * Base class for ticket hooks
* *
* Extend this class if you want to integrate your ticketing solution Icinga Web 2 * Extend this class if you want to integrate your ticketing solution into Icinga Web 2.
*/ */
abstract class TicketHook abstract class TicketHook
{ {
@ -39,6 +41,100 @@ abstract class TicketHook
{ {
} }
/**
* Create a link for each matched element in the subject text
*
* @param array|TicketPattern $match Matched element according to {@link getPattern()}
*
* @return string Replacement string
*/
abstract public function createLink($match);
/**
* Get the pattern(s) to search for
*
* Return an array of TicketPattern instances here to support multiple TTS integrations.
*
* @return string|TicketPattern[]
*/
abstract public function getPattern();
/**
* Apply ticket patterns to the given text
*
* @param string $text
* @param TicketPattern[] $ticketPatterns
*
* @return string
*/
private function applyTicketPatterns($text, array $ticketPatterns)
{
$out = '';
$start = 0;
$iterator = new ArrayIterator($ticketPatterns);
$iterator->rewind();
while ($iterator->valid()) {
$ticketPattern = $iterator->current();
try {
preg_match($ticketPattern->getPattern(), $text, $match, PREG_OFFSET_CAPTURE, $start);
} catch (ErrorException $e) {
$this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e));
$iterator->next();
continue;
}
if (empty($match)) {
$iterator->next();
continue;
}
// Remove preg_offset from match for the ticket pattern
$carry = array();
array_walk($match, function ($value, $key) use (&$carry) {
$carry[$key] = $value[0];
}, $carry);
$ticketPattern->setMatch($carry);
$offsetLeft = $match[0][1];
$matchLength = strlen($match[0][0]);
$out .= substr($text, $start, $offsetLeft - $start);
try {
$out .= $this->createLink($ticketPattern);
} catch (Exception $e) {
$this->fail('Can\'t create ticket links: %s', IcingaException::describe($e));
return $text;
}
$start = $offsetLeft + $matchLength;
}
$out .= substr($text, $start);
return $out;
}
/**
* Helper function to create a TicketPattern instance
*
* @param string $name Name of the TTS integration
* @param string $pattern Ticket pattern
*
* @return TicketPattern
*/
protected function createTicketPattern($name, $pattern)
{
$ticketPattern = new TicketPattern();
$ticketPattern
->setName($name)
->setPattern($pattern);
return $ticketPattern;
}
/** /**
* Set the hook as failed w/ the given message * Set the hook as failed w/ the given message
* *
@ -63,22 +159,6 @@ abstract class TicketHook
return $this->lastError; return $this->lastError;
} }
/**
* Get the pattern
*
* @return string
*/
abstract public function getPattern();
/**
* Create a link for each matched element in the subject text
*
* @param array $match Array of matched elements according to {@link getPattern()}
*
* @return string Replacement string
*/
abstract public function createLink($match);
/** /**
* Create links w/ {@link createLink()} in the given text that matches to the subject from {@link getPattern()} * Create links w/ {@link createLink()} in the given text that matches to the subject from {@link getPattern()}
* *
@ -101,22 +181,28 @@ abstract class TicketHook
$this->fail('Can\'t create ticket links: Retrieving the pattern failed: %s', IcingaException::describe($e)); $this->fail('Can\'t create ticket links: Retrieving the pattern failed: %s', IcingaException::describe($e));
return $text; return $text;
} }
if (empty($pattern)) { if (empty($pattern)) {
$this->fail('Can\'t create ticket links: Pattern is empty'); $this->fail('Can\'t create ticket links: Pattern is empty');
return $text; return $text;
} }
try {
$text = preg_replace_callback( if (is_array($pattern)) {
$pattern, $text = $this->applyTicketPatterns($text, $pattern);
array($this, 'createLink'), } else {
$text try {
); $text = preg_replace_callback(
} catch (ErrorException $e) { $pattern,
$this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e)); array($this, 'createLink'),
return $text; $text
} catch (Exception $e) { );
$this->fail('Can\'t create ticket links: %s', IcingaException::describe($e)); } catch (ErrorException $e) {
return $text; $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e));
return $text;
} catch (Exception $e) {
$this->fail('Can\'t create ticket links: %s', IcingaException::describe($e));
return $text;
}
} }
return $text; return $text;

View File

@ -5,6 +5,7 @@ namespace Icinga\Authentication;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\Role;
use Icinga\Exception\NotReadableError; use Icinga\Exception\NotReadableError;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\User; use Icinga\User;
@ -43,16 +44,12 @@ class AdmissionLoader
} }
/** /**
* Get user permissions and restrictions * Apply permissions, restrictions and roles to the given user
* *
* @param User $user * @param User $user
*
* @return array
*/ */
public function getPermissionsAndRestrictions(User $user) public function applyRoles(User $user)
{ {
$permissions = array();
$restrictions = array();
$username = $user->getUsername(); $username = $user->getUsername();
try { try {
$roles = Config::app('roles'); $roles = Config::app('roles');
@ -62,14 +59,18 @@ class AdmissionLoader
$username, $username,
$e $e
); );
return array($permissions, $restrictions); return;
} }
$userGroups = $user->getGroups(); $userGroups = $user->getGroups();
foreach ($roles as $role) { $permissions = array();
$restrictions = array();
$roleObjs = array();
foreach ($roles as $roleName => $role) {
if ($this->match($username, $userGroups, $role)) { if ($this->match($username, $userGroups, $role)) {
$permissionsFromRole = StringHelper::trimSplit($role->permissions);
$permissions = array_merge( $permissions = array_merge(
$permissions, $permissions,
array_diff(StringHelper::trimSplit($role->permissions), $permissions) array_diff($permissionsFromRole, $permissions)
); );
$restrictionsFromRole = $role->toArray(); $restrictionsFromRole = $role->toArray();
unset($restrictionsFromRole['users']); unset($restrictionsFromRole['users']);
@ -81,8 +82,16 @@ class AdmissionLoader
} }
$restrictions[$name][] = $restriction; $restrictions[$name][] = $restriction;
} }
$roleObj = new Role();
$roleObjs[] = $roleObj
->setName($roleName)
->setPermissions($permissionsFromRole)
->setRestrictions($restrictionsFromRole);
} }
} }
return array($permissions, $restrictions); $user->setPermissions($permissions);
$user->setRestrictions($restrictions);
$user->setRoles($roleObjs);
} }
} }

View File

@ -160,9 +160,7 @@ class Auth
} }
$user->setGroups($groups); $user->setGroups($groups);
$admissionLoader = new AdmissionLoader(); $admissionLoader = new AdmissionLoader();
list($permissions, $restrictions) = $admissionLoader->getPermissionsAndRestrictions($user); $admissionLoader->applyRoles($user);
$user->setPermissions($permissions);
$user->setRestrictions($restrictions);
$this->user = $user; $this->user = $user;
if ($persist) { if ($persist) {
$this->persistCurrentUser(); $this->persistCurrentUser();
@ -242,10 +240,10 @@ class Auth
public function authenticateFromSession() public function authenticateFromSession()
{ {
$this->user = Session::getSession()->get('user'); $this->user = Session::getSession()->get('user');
if ($this->user !== null && $this->user->isExternalUser() === true) { if ($this->user !== null && $this->user->isExternalUser()) {
list($originUsername, $field) = $this->user->getExternalUserInformation(); list($originUsername, $field) = $this->user->getExternalUserInformation();
$username = getenv($field); // usually REMOTE_USER here $username = ExternalBackend::getRemoteUser($field);
if ( !$username || $username !== $originUsername) { if ($username === null || $username !== $originUsername) {
$this->removeAuthorization(); $this->removeAuthorization();
} }
} }

View File

@ -0,0 +1,109 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Authentication;
class Role
{
/**
* Name of the role
*
* @var string
*/
protected $name;
/**
* Permissions of the role
*
* @var string[]
*/
protected $permissions = array();
/**
* Restrictions of the role
*
* @var string[]
*/
protected $restrictions = array();
/**
* Get the name of the role
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the name of the role
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get the permissions of the role
*
* @return string[]
*/
public function getPermissions()
{
return $this->permissions;
}
/**
* Set the permissions of the role
*
* @param string[] $permissions
*
* @return $this
*/
public function setPermissions(array $permissions)
{
$this->permissions = $permissions;
return $this;
}
/**
* Get the restrictions of the role
*
* @param string $name Optional name of the restriction
*
* @return string[]|null
*/
public function getRestrictions($name = null)
{
$restrictions = $this->restrictions;
if ($name === null) {
return $restrictions;
}
if (isset($restrictions[$name])) {
return $restrictions[$name];
}
return null;
}
/**
* Set the restrictions of the role
*
* @param string[] $restrictions
*
* @return $this
*/
public function setRestrictions(array $restrictions)
{
$this->restrictions = $restrictions;
return $this;
}
}

View File

@ -35,6 +35,14 @@ class ExternalBackend implements UserBackendInterface
$this->stripUsernameRegexp = $config->get('strip_username_regexp'); $this->stripUsernameRegexp = $config->get('strip_username_regexp');
} }
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -45,11 +53,22 @@ class ExternalBackend implements UserBackendInterface
} }
/** /**
* {@inheritdoc} * Get the remote user from environment or $_SERVER, if any
*
* @param string $variable The name variable where to read the user from
*
* @return string|null
*/ */
public function getName() public static function getRemoteUser($variable = 'REMOTE_USER')
{ {
return $this->name; $username = getenv($variable);
if ($username !== false) {
return $username;
}
if (array_key_exists($variable, $_SERVER)) {
return $_SERVER[$variable];
}
return null;
} }
@ -58,8 +77,8 @@ class ExternalBackend implements UserBackendInterface
*/ */
public function authenticate(User $user, $password = null) public function authenticate(User $user, $password = null)
{ {
$username = getenv('REMOTE_USER'); $username = static::getRemoteUser();
if ($username !== false) { if ($username !== null) {
$user->setExternalUserInformation($username, 'REMOTE_USER'); $user->setExternalUserInformation($username, 'REMOTE_USER');
if ($this->stripUsernameRegexp) { if ($this->stripUsernameRegexp) {

View File

@ -152,11 +152,12 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
*/ */
$driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] = $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] =
'SET SESSION SQL_MODE=\'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,' 'SET SESSION SQL_MODE=\'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,'
. 'NO_AUTO_CREATE_USER,ANSI_QUOTES,PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION\';'; . 'NO_AUTO_CREATE_USER,ANSI_QUOTES,PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION\'';
if (array_key_exists('charset', $adapterParamaters) && $adapterParamaters['charset']) { if (isset($adapterParamaters['charset'])) {
$driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .= 'SET NAMES ' . $adapterParamaters['charset']. ';'; $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .= ', NAMES ' . $adapterParamaters['charset'];
unset($adapterParamaters['charset']); unset($adapterParamaters['charset']);
} }
$driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .=';';
$adapterParamaters['port'] = $this->config->get('port', 3306); $adapterParamaters['port'] = $this->config->get('port', 3306);
break; break;

View File

@ -92,6 +92,6 @@ class TreeNodeIterator implements RecursiveIterator
*/ */
public function isEmpty() public function isEmpty()
{ {
return empty($this->children); return ! $this->children->count();
} }
} }

View File

@ -3,20 +3,21 @@
namespace Icinga\Protocol\Ldap; namespace Icinga\Protocol\Ldap;
use Exception;
use ArrayIterator; use ArrayIterator;
use Exception;
use LogicException;
use stdClass;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Data\Inspectable; use Icinga\Data\Inspectable;
use Icinga\Data\Inspection; use Icinga\Data\Inspection;
use Icinga\Data\Selectable; use Icinga\Data\Selectable;
use Icinga\Data\Sortable; use Icinga\Data\Sortable;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Protocol\Ldap\LdapException;
/** /**
* Encapsulate LDAP connections and query creation * Encapsulate LDAP connections and query creation
@ -548,6 +549,23 @@ class LdapConnection implements Selectable, Inspectable
return $pairs; return $pairs;
} }
/**
* Fetch an LDAP entry by its DN
*
* @param string $dn
* @param array|null $fields
*
* @return StdClass|bool
*/
public function fetchByDn($dn, array $fields = null)
{
return $this->select()
->from('*', $fields)
->setBase($dn)
->setScope('base')
->fetchRow();
}
/** /**
* Test the given LDAP credentials by establishing a connection and attempting a LDAP bind * Test the given LDAP credentials by establishing a connection and attempting a LDAP bind
* *
@ -706,7 +724,7 @@ class LdapConnection implements Selectable, Inspectable
'value' => $this->encodeSortRules($query->getOrder()) 'value' => $this->encodeSortRules($query->getOrder())
) )
)); ));
} else { } elseif (! empty($fields)) {
foreach ($query->getOrder() as $rule) { foreach ($query->getOrder() as $rule) {
if (! in_array($rule[0], $fields, true)) { if (! in_array($rule[0], $fields, true)) {
$fields[] = $rule[0]; $fields[] = $rule[0];
@ -825,7 +843,7 @@ class LdapConnection implements Selectable, Inspectable
$ds = $this->getConnection(); $ds = $this->getConnection();
$serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID); $serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
if (! $serverSorting && $query->hasOrder()) { if (! $serverSorting && $query->hasOrder() && ! empty($fields)) {
foreach ($query->getOrder() as $rule) { foreach ($query->getOrder() as $rule) {
if (! in_array($rule[0], $fields, true)) { if (! in_array($rule[0], $fields, true)) {
$fields[] = $rule[0]; $fields[] = $rule[0];
@ -1179,6 +1197,8 @@ class LdapConnection implements Selectable, Inspectable
* @param int $deref * @param int $deref
* *
* @return resource|bool A search result identifier or false on error * @return resource|bool A search result identifier or false on error
*
* @throws LogicException If the LDAP query search scope is unsupported
*/ */
public function ldapSearch( public function ldapSearch(
LdapQuery $query, LdapQuery $query,
@ -1190,6 +1210,7 @@ class LdapConnection implements Selectable, Inspectable
) { ) {
$queryString = (string) $query; $queryString = (string) $query;
$baseDn = $query->getBase() ?: $this->getDn(); $baseDn = $query->getBase() ?: $this->getDn();
$scope = $query->getScope();
if (Logger::getInstance()->getLevel() === Logger::DEBUG) { if (Logger::getInstance()->getLevel() === Logger::DEBUG) {
// We're checking the level by ourself to avoid rendering the ldapsearch commandline for nothing // We're checking the level by ourself to avoid rendering the ldapsearch commandline for nothing
@ -1213,11 +1234,12 @@ class LdapConnection implements Selectable, Inspectable
} }
Logger::debug("Issueing LDAP search. Use '%s' to reproduce.", sprintf( Logger::debug("Issueing LDAP search. Use '%s' to reproduce.", sprintf(
'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "sub" -z %u -l %u -a "%s"%s%s%s', 'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "%s" -z %u -l %u -a "%s"%s%s%s',
$starttlsParam, $starttlsParam,
$ldapUrl, $ldapUrl,
$bindParams, $bindParams,
$baseDn, $baseDn,
$scope,
$sizelimit, $sizelimit,
$timelimit, $timelimit,
$derefName, $derefName,
@ -1227,7 +1249,21 @@ class LdapConnection implements Selectable, Inspectable
)); ));
} }
return @ldap_search( switch($scope) {
case LdapQuery::SCOPE_SUB:
$function = 'ldap_search';
break;
case LdapQuery::SCOPE_ONE:
$function = 'ldap_list';
break;
case LdapQuery::SCOPE_BASE:
$function = 'ldap_read';
break;
default:
throw new LogicException('LDAP scope %s not supported by ldapSearch', $scope);
}
return @$function(
$this->getConnection(), $this->getConnection(),
$baseDn, $baseDn,
$queryString, $queryString,

View File

@ -3,6 +3,7 @@
namespace Icinga\Protocol\Ldap; namespace Icinga\Protocol\Ldap;
use LogicException;
use Icinga\Data\SimpleQuery; use Icinga\Data\SimpleQuery;
/** /**
@ -38,6 +39,39 @@ class LdapQuery extends SimpleQuery
*/ */
protected $nativeFilter; protected $nativeFilter;
/**
* Only fetch the entry at the base of the search
*/
const SCOPE_BASE = 'base';
/**
* Fetch entries one below the base DN
*/
const SCOPE_ONE = 'one';
/**
* Fetch all entries below the base DN
*/
const SCOPE_SUB = 'sub';
/**
* All available scopes
*
* @var array
*/
public static $scopes = array(
LdapQuery::SCOPE_BASE,
LdapQuery::SCOPE_ONE,
LdapQuery::SCOPE_SUB
);
/**
* LDAP search scope (default: SCOPE_SUB)
*
* @var string
*/
protected $scope = LdapQuery::SCOPE_SUB;
/** /**
* Initialize this query * Initialize this query
*/ */
@ -223,4 +257,38 @@ class LdapQuery extends SimpleQuery
{ {
return $this->renderFilter(); return $this->renderFilter();
} }
/**
* Get LDAP search scope
*
* @return string
*/
public function getScope()
{
return $this->scope;
}
/**
* Set LDAP search scope
*
* Valid: sub one base (Default: sub)
*
* @param string $scope
*
* @return LdapQuery
*
* @throws LogicException If scope value is invalid
*/
public function setScope($scope)
{
if (! in_array($scope, static::$scopes)) {
throw new LogicException(
'Can\'t set scope %d, it is is invalid. Use one of %s or LdapQuery\'s constants.',
$scope, implode(', ', static::$scopes)
);
}
$this->scope = $scope;
return $this;
}
} }

View File

@ -28,9 +28,11 @@ class LdapUtils
$res = ldap_explode_dn($dn, $with_type ? 0 : 1); $res = ldap_explode_dn($dn, $with_type ? 0 : 1);
foreach ($res as $k => $v) { foreach ($res as $k => $v) {
$res[$k] = preg_replace( $res[$k] = preg_replace_callback(
'/\\\([0-9a-f]{2})/ei', '/\\\([0-9a-f]{2})/i',
"chr(hexdec('\\1'))", function ($m) {
return chr(hexdec($m[1]));
},
$v $v
); );
} }

View File

@ -6,6 +6,7 @@ namespace Icinga;
use DateTimeZone; use DateTimeZone;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Authentication\Role;
use Icinga\User\Preferences; use Icinga\User\Preferences;
use Icinga\Web\Navigation\Navigation; use Icinga\Web\Navigation\Navigation;
@ -91,6 +92,13 @@ class User
*/ */
protected $groups = array(); protected $groups = array();
/**
* Roles of this user
*
* @var Role[]
*/
protected $roles = array();
/** /**
* Preferences object * Preferences object
* *
@ -229,13 +237,39 @@ class User
} }
/** /**
* Settter for restrictions * Set the user's restrictions
* *
* @param array $restrictions * @param string[] $restrictions
*
* @return $this
*/ */
public function setRestrictions(array $restrictions) public function setRestrictions(array $restrictions)
{ {
$this->restrictions = $restrictions; $this->restrictions = $restrictions;
return $this;
}
/**
* Get the roles of the user
*
* @return Role[]
*/
public function getRoles()
{
return $this->roles;
}
/**
* Set the roles of the user
*
* @param Role[] $roles
*
* @return $this
*/
public function setRoles(array $roles)
{
$this->roles = $roles;
return $this;
} }
/** /**

View File

@ -3,14 +3,22 @@
namespace Icinga\Util; namespace Icinga\Util;
use ArrayIterator;
use InvalidArgumentException; use InvalidArgumentException;
use Iterator; use RecursiveIterator;
/** /**
* Iterator for traversing a directory * Iterator for traversing a directory
*/ */
class DirectoryIterator implements Iterator class DirectoryIterator implements RecursiveIterator
{ {
/**
* Iterate files first
*
* @var int
*/
const FILES_FIRST = 1;
/** /**
* Current directory item * Current directory item
* *
@ -26,11 +34,18 @@ class DirectoryIterator implements Iterator
protected $extension; protected $extension;
/** /**
* Directory handle * Scanned files
* *
* @var resource * @var ArrayIterator
*/ */
private $handle; private $files;
/**
* Iterator flags
*
* @var int
*/
protected $flags;
/** /**
* Current key * Current key
@ -46,6 +61,13 @@ class DirectoryIterator implements Iterator
*/ */
protected $path; protected $path;
/**
* Directory queue if FILES_FIRST flag is set
*
* @var array
*/
private $queue;
/** /**
* Whether to skip empty files * Whether to skip empty files
* *
@ -72,8 +94,9 @@ class DirectoryIterator implements Iterator
* *
* @param string $path The path of the directory to traverse * @param string $path The path of the directory to traverse
* @param string $extension The file extension to filter for. A leading dot is optional * @param string $extension The file extension to filter for. A leading dot is optional
* @param int $flags Iterator flags
*/ */
public function __construct($path, $extension = null) public function __construct($path, $extension = null, $flags = null)
{ {
if (empty($path)) { if (empty($path)) {
throw new InvalidArgumentException('The path can\'t be empty'); throw new InvalidArgumentException('The path can\'t be empty');
@ -82,6 +105,9 @@ class DirectoryIterator implements Iterator
if (! empty($extension)) { if (! empty($extension)) {
$this->extension = '.' . ltrim($extension, '.'); $this->extension = '.' . ltrim($extension, '.');
} }
if ($flags !== null) {
$this->flags = $flags;
}
} }
/** /**
@ -96,6 +122,23 @@ class DirectoryIterator implements Iterator
return is_dir($path) && is_readable($path); return is_dir($path) && is_readable($path);
} }
/**
* {@inheritdoc}
*/
public function hasChildren()
{
return static::isReadable($this->current);
}
/**
* {@inheritdoc}
*/
public function getChildren()
{
return new static($this->current, $this->extension, $this->flags);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -110,12 +153,14 @@ class DirectoryIterator implements Iterator
public function next() public function next()
{ {
do { do {
$file = readdir($this->handle); $this->files->next();
if ($file === false) { $skip = false;
$key = false; if (! $this->files->valid()) {
$file = false;
$path = false;
break; break;
} else { } else {
$skip = false; $file = $this->files->current();
do { do {
if ($this->skipHidden && $file[0] === '.') { if ($this->skipHidden && $file[0] === '.') {
$skip = true; $skip = true;
@ -125,7 +170,10 @@ class DirectoryIterator implements Iterator
$path = $this->path . '/' . $file; $path = $this->path . '/' . $file;
if (is_dir($path)) { if (is_dir($path)) {
$skip = true; if ($this->flags & static::FILES_FIRST === static::FILES_FIRST) {
$this->queue[] = array($path, $file);
$skip = true;
}
break; break;
} }
@ -138,16 +186,18 @@ class DirectoryIterator implements Iterator
$skip = true; $skip = true;
break; break;
} }
$key = $file;
$file = $path;
} while (0); } while (0);
} }
} while ($skip); } while ($skip);
$this->current = $file;
/** @noinspection PhpUndefinedVariableInspection */ /** @noinspection PhpUndefinedVariableInspection */
$this->key = $key;
if ($path === false && ! empty($this->queue)) {
list($path, $file) = array_shift($this->queue);
}
$this->current = $path;
$this->key = $file;
} }
/** /**
@ -171,21 +221,13 @@ class DirectoryIterator implements Iterator
*/ */
public function rewind() public function rewind()
{ {
if ($this->handle === null) { if ($this->files === null) {
$this->handle = opendir($this->path); $files = scandir($this->path);
} else { natcasesort($files);
rewinddir($this->handle); $this->files = new ArrayIterator($files);
} }
$this->files->rewind();
$this->queue = array();
$this->next(); $this->next();
} }
/**
* Close directory handle if created
*/
public function __destruct()
{
if ($this->handle !== null) {
closedir($this->handle);
}
}
} }

View File

@ -100,7 +100,7 @@ class Autosubmit extends Zend_Form_Decorator_Abstract
: t('Upon its value has changed, this field issues an automatic update of this page.'); : t('Upon its value has changed, this field issues an automatic update of this page.');
$content .= $this->getView()->icon('cw', $warning, array( $content .= $this->getView()->icon('cw', $warning, array(
'aria-hidden' => $isForm ? 'false' : 'true', 'aria-hidden' => $isForm ? 'false' : 'true',
'class' => 'spinner' 'class' => 'spinner autosubmit-info'
)); ));
if (! $isForm && $this->getAccessible()) { if (! $isForm && $this->getAccessible()) {
$content = '<span id="' . $this->getWarningId() . '" class="sr-only">' . $warning . '</span>' . $content; $content = '<span id="' . $this->getWarningId() . '" class="sr-only">' . $warning . '</span>' . $content;

View File

@ -70,7 +70,7 @@ class LessCompiler
*/ */
public function addLessFile($lessFile) public function addLessFile($lessFile)
{ {
$this->lessFiles[] = $lessFile; $this->lessFiles[] = realpath($lessFile);
return $this; return $this;
} }
@ -87,7 +87,7 @@ class LessCompiler
if (! isset($this->moduleLessFiles[$moduleName])) { if (! isset($this->moduleLessFiles[$moduleName])) {
$this->moduleLessFiles[$moduleName] = array(); $this->moduleLessFiles[$moduleName] = array();
} }
$this->moduleLessFiles[$moduleName][] = $lessFile; $this->moduleLessFiles[$moduleName][] = realpath($lessFile);
return $this; return $this;
} }
@ -98,9 +98,12 @@ class LessCompiler
*/ */
public function getLessFiles() public function getLessFiles()
{ {
$lessFiles = iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator( $lessFiles = $this->lessFiles;
$this->lessFiles + $this->moduleLessFiles
))); foreach ($this->moduleLessFiles as $moduleLessFiles) {
$lessFiles = array_merge($lessFiles, $moduleLessFiles);
}
if ($this->theme !== null) { if ($this->theme !== null) {
$lessFiles[] = $this->theme; $lessFiles[] = $this->theme;
} }

View File

@ -201,12 +201,6 @@ class Dashboard extends AbstractWidget
{ {
/** @var $pane Pane */ /** @var $pane Pane */
foreach ($panes as $pane) { foreach ($panes as $pane) {
if ($pane->getDisabled()) {
if ($this->hasPane($pane->getTitle()) === true) {
$this->removePane($pane->getTitle());
}
continue;
}
if ($this->hasPane($pane->getTitle()) === true) { if ($this->hasPane($pane->getTitle()) === true) {
/** @var $current Pane */ /** @var $current Pane */
$current = $this->panes[$pane->getName()]; $current = $this->panes[$pane->getName()];
@ -231,6 +225,9 @@ class Dashboard extends AbstractWidget
$this->tabs = new Tabs(); $this->tabs = new Tabs();
foreach ($this->panes as $key => $pane) { foreach ($this->panes as $key => $pane) {
if ($pane->getDisabled()) {
continue;
}
$this->tabs->add( $this->tabs->add(
$key, $key,
array( array(

View File

@ -40,7 +40,7 @@ class Pane extends UserWidget
* *
* @var bool * @var bool
*/ */
private $disabled; private $disabled = false;
/** /**
* Create a new pane * Create a new pane

View File

@ -377,8 +377,8 @@ EOT;
array( array(
$tabs, $tabs,
$drop, $drop,
$close, $refresh,
$refresh $close
), ),
$this->baseTpl $this->baseTpl
); );

View File

@ -17,7 +17,7 @@ class Parsedown
{ {
# ~ # ~
const version = '1.5.0'; const version = '1.6.0';
# ~ # ~
@ -107,12 +107,6 @@ class Parsedown
# ~ # ~
protected $DefinitionTypes = array(
'[' => array('Reference'),
);
# ~
protected $unmarkedBlockTypes = array( protected $unmarkedBlockTypes = array(
'Code', 'Code',
); );
@ -169,7 +163,7 @@ class Parsedown
# ~ # ~
if (isset($CurrentBlock['incomplete'])) if (isset($CurrentBlock['continuable']))
{ {
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
@ -185,8 +179,6 @@ class Parsedown
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
} }
unset($CurrentBlock['incomplete']);
} }
} }
@ -226,7 +218,7 @@ class Parsedown
if (method_exists($this, 'block'.$blockType.'Continue')) if (method_exists($this, 'block'.$blockType.'Continue'))
{ {
$Block['incomplete'] = true; $Block['continuable'] = true;
} }
$CurrentBlock = $Block; $CurrentBlock = $Block;
@ -253,7 +245,7 @@ class Parsedown
# ~ # ~
if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete')) if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
} }
@ -394,16 +386,16 @@ class Parsedown
protected function blockFencedCode($Line) protected function blockFencedCode($Line)
{ {
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
{ {
$Element = array( $Element = array(
'name' => 'code', 'name' => 'code',
'text' => '', 'text' => '',
); );
if (isset($matches[2])) if (isset($matches[1]))
{ {
$class = 'language-'.$matches[2]; $class = 'language-'.$matches[1];
$Element['attributes'] = array( $Element['attributes'] = array(
'class' => $class, 'class' => $class,
@ -673,7 +665,9 @@ class Parsedown
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{ {
if (in_array($matches[1], $this->textLevelElements)) $element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{ {
return; return;
} }
@ -736,8 +730,6 @@ class Parsedown
{ {
$Block['closed'] = true; $Block['closed'] = true;
} }
$Block['markup'] .= $matches[1];
} }
if (isset($Block['interrupted'])) if (isset($Block['interrupted']))
@ -989,15 +981,13 @@ class Parsedown
{ {
$markup = ''; $markup = '';
$unexaminedText = $text; # $excerpt is based on the first occurrence of a marker
$markerPosition = 0; while ($excerpt = strpbrk($text, $this->inlineMarkerList))
while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
{ {
$marker = $excerpt[0]; $marker = $excerpt[0];
$markerPosition += strpos($unexaminedText, $marker); $markerPosition = strpos($text, $marker);
$Excerpt = array('text' => $excerpt, 'context' => $text); $Excerpt = array('text' => $excerpt, 'context' => $text);
@ -1010,34 +1000,42 @@ class Parsedown
continue; continue;
} }
if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker # makes sure that the inline belongs to "our" marker
if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
{ {
continue; continue;
} }
# sets a default inline position
if ( ! isset($Inline['position'])) if ( ! isset($Inline['position']))
{ {
$Inline['position'] = $markerPosition; $Inline['position'] = $markerPosition;
} }
# the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']); $unmarkedText = substr($text, 0, $Inline['position']);
# compile the unmarked text
$markup .= $this->unmarkedText($unmarkedText); $markup .= $this->unmarkedText($unmarkedText);
# compile the inline
$markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
# remove the examined text
$text = substr($text, $Inline['position'] + $Inline['extent']); $text = substr($text, $Inline['position'] + $Inline['extent']);
$unexaminedText = $text;
$markerPosition = 0;
continue 2; continue 2;
} }
$unexaminedText = substr($excerpt, 1); # the marker does not belong to an inline
$markerPosition ++; $unmarkedText = substr($text, 0, $markerPosition + 1);
$markup .= $this->unmarkedText($unmarkedText);
$text = substr($text, $markerPosition + 1);
} }
$markup .= $this->unmarkedText($text); $markup .= $this->unmarkedText($text);
@ -1199,7 +1197,7 @@ class Parsedown
return; return;
} }
if (preg_match('/^[(]((?:[^ (]|[(][^ )]+[)])+)(?:[ ]+("[^"]+"|\'[^\']+\'))?[)]/', $remainder, $matches)) if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
{ {
$Element['attributes']['href'] = $matches[1]; $Element['attributes']['href'] = $matches[1];
@ -1214,7 +1212,7 @@ class Parsedown
{ {
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
{ {
$definition = $matches[1] ? $matches[1] : $Element['text']; $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
$definition = strtolower($definition); $definition = strtolower($definition);
$extent += strlen($matches[0]); $extent += strlen($matches[0]);
@ -1360,11 +1358,6 @@ class Parsedown
} }
} }
#
# ~
protected $unmarkedInlineTypes = array("\n" => 'Break', '://' => 'Url');
# ~ # ~
protected function unmarkedText($text) protected function unmarkedText($text)
@ -1409,7 +1402,7 @@ class Parsedown
if (isset($Element['handler'])) if (isset($Element['handler']))
{ {
$markup .= $this->$Element['handler']($Element['text']); $markup .= $this->{$Element['handler']}($Element['text']);
} }
else else
{ {
@ -1483,7 +1476,7 @@ class Parsedown
return self::$instances[$name]; return self::$instances[$name];
} }
$instance = new self(); $instance = new static();
self::$instances[$name] = $instance; self::$instances[$name] = $instance;

View File

@ -1,4 +1,4 @@
RELEASE=1.5.0 RELEASE=1.6.0
PARSEDOWN=parsedown-$RELEASE PARSEDOWN=parsedown-$RELEASE
curl https://codeload.github.com/erusev/parsedown/tar.gz/${RELEASE} -o ${PARSEDOWN}.tar.gz curl https://codeload.github.com/erusev/parsedown/tar.gz/${RELEASE} -o ${PARSEDOWN}.tar.gz
tar xfz ${PARSEDOWN}.tar.gz --strip-components 1 ${PARSEDOWN}/Parsedown.php ${PARSEDOWN}/LICENSE.txt tar xfz ${PARSEDOWN}.tar.gz --strip-components 1 ${PARSEDOWN}/Parsedown.php ${PARSEDOWN}/LICENSE.txt

View File

@ -3,6 +3,8 @@
namespace Icinga\Module\Doc\Controllers; namespace Icinga\Module\Doc\Controllers;
use finfo;
use SplFileInfo;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Module\Doc\DocController; use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\Exception\DocException; use Icinga\Module\Doc\Exception\DocException;
@ -88,11 +90,12 @@ class ModuleController extends DocController
{ {
$module = $this->params->getRequired('moduleName'); $module = $this->params->getRequired('moduleName');
$this->assertModuleInstalled($module); $this->assertModuleInstalled($module);
$this->view->moduleName = $module; $moduleManager = Icinga::app()->getModuleManager();
$name = $moduleManager->getModule($module)->getTitle();
try { try {
$this->renderToc( $this->renderToc(
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
$module, $name,
'doc/module/chapter', 'doc/module/chapter',
array('moduleName' => $module) array('moduleName' => $module)
); );
@ -120,6 +123,7 @@ class ModuleController extends DocController
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')), $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
$chapter, $chapter,
'doc/module/chapter', 'doc/module/chapter',
'doc/module/img',
array('moduleName' => $module) array('moduleName' => $module)
); );
} catch (DocException $e) { } catch (DocException $e) {
@ -127,6 +131,60 @@ class ModuleController extends DocController
} }
} }
/**
* Deliver images
*/
public function imageAction()
{
$module = $this->params->getRequired('moduleName');
$image = $this->params->getRequired('image');
$docPath = $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc'));
$imagePath = realpath($docPath . '/' . $image);
if ($imagePath === false) {
$this->httpNotFound('%s does not exist', $image);
}
$this->_helper->viewRenderer->setNoRender(true);
$this->_helper->layout()->disableLayout();
$imageInfo = new SplFileInfo($imagePath);
$ETag = md5($imageInfo->getMTime() . $imagePath);
$lastModified = gmdate('D, d M Y H:i:s T', $imageInfo->getMTime());
$match = false;
if (isset($_SERER['HTTP_IF_NONE_MATCH'])) {
$ifNoneMatch = explode(', ', stripslashes($_SERVER['HTTP_IF_NONE_MATCH']));
foreach ($ifNoneMatch as $tag) {
if ($tag === $ETag) {
$match = true;
break;
}
}
} elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$lastModifiedSince = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if ($lastModifiedSince === $lastModified) {
$match = true;
}
}
header('ETag: "' . $ETag . '"');
header('Cache-Control: no-transform,public,max-age=3600');
header('Last-Modified: ' . $lastModified);
// Set additional headers for compatibility reasons (Cache-Control should have precedence) in case
// session.cache_limiter is set to no cache
header('Pragma: cache');
header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + 3600));
if ($match) {
header('HTTP/1.1 304 Not Modified');
} else {
$finfo = new finfo();
header('Content-Type: ' . $finfo->file($imagePath, FILEINFO_MIME_TYPE));
readfile($imagePath);
}
}
/** /**
* View a module's documentation as PDF * View a module's documentation as PDF
* *

View File

@ -1,6 +1,6 @@
# <a id="module-documentation"></a> Writing Module Documentation # <a id="module-documentation"></a> Writing Module Documentation
![Markdown](/img/doc/doc/markdown.png) ![Markdown](img/markdown.png)
Icinga Web 2 is capable of viewing your module's documentation, if the documentation is written in Icinga Web 2 is capable of viewing your module's documentation, if the documentation is written in
[Markdown](http://en.wikipedia.org/wiki/Markdown). Please refer to [Markdown](http://en.wikipedia.org/wiki/Markdown). Please refer to
@ -50,13 +50,15 @@ This syntax is also supported in Icinga Web 2.
## <a id="images"></a> Including Images ## <a id="images"></a> Including Images
Images must placed in the `img` directory beneath your module's `public` directory, e.g.: Images must placed in the `doc` directory beneath your module's root directory, e.g.:
example-module/public/img/doc /path/to/icingaweb2/modules/example-module/doc/img/example.png
Note that the `img` sub directory is not mandatory but good for organizing your directory structure.
Module images can be accessed using the following URL: Module images can be accessed using the following URL:
{baseURL}/img/{moduleName}/{file} e.g. icingaweb/img/example-module/doc/example.png {baseURL}/doc/module/{moduleName}/image/{image} e.g. icingaweb2/doc/module/example-module/image/img/example.png
Markdown's image syntax is very similar to Markdown's link syntax, but prefixed with an exclamation mark, e.g.: Markdown's image syntax is very similar to Markdown's link syntax, but prefixed with an exclamation mark, e.g.:
@ -64,4 +66,4 @@ Markdown's image syntax is very similar to Markdown's link syntax, but prefixed
URLs to images inside your Markdown documentation files must be specified without the base URL, e.g.: URLs to images inside your Markdown documentation files must be specified without the base URL, e.g.:
![Example](/img/example-module/doc/example.png) ![Example](img/example.png)

View File

@ -20,6 +20,9 @@ class DocController extends Controller
if ($this->hasParam('chapter')) { if ($this->hasParam('chapter')) {
$this->params->set('chapter', $this->getParam('chapter')); $this->params->set('chapter', $this->getParam('chapter'));
} }
if ($this->hasParam('image')) {
$this->params->set('image', $this->getParam('image'));
}
if ($this->hasParam('moduleName')) { if ($this->hasParam('moduleName')) {
$this->params->set('moduleName', $this->getParam('moduleName')); $this->params->set('moduleName', $this->getParam('moduleName'));
} }
@ -31,20 +34,27 @@ class DocController extends Controller
* @param string $path Path to the documentation * @param string $path Path to the documentation
* @param string $chapter ID of the chapter * @param string $chapter ID of the chapter
* @param string $url URL to replace links with * @param string $url URL to replace links with
* @param string $imageUrl URL to images
* @param array $urlParams Additional URL parameters * @param array $urlParams Additional URL parameters
*/ */
protected function renderChapter($path, $chapter, $url, array $urlParams = array()) protected function renderChapter($path, $chapter, $url, $imageUrl = null, array $urlParams = array())
{ {
$parser = new DocParser($path); $parser = new DocParser($path);
$section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter)); $section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter));
$this->view->section = $section $this->view->section = $section
->setHighlightSearch($this->params->get('highlight-search'))
->setImageUrl($imageUrl)
->setUrl($url) ->setUrl($url)
->setUrlParams($urlParams) ->setUrlParams($urlParams);
->setHighlightSearch($this->params->get('highlight-search')); $first = null;
$this->view->title = $chapter; foreach ($section as $first) {
break;
}
$title = $first === null ? ucfirst($chapter) : $first->getTitle();
$this->view->title = $title;
$this->getTabs()->add('toc', array( $this->getTabs()->add('toc', array(
'active' => true, 'active' => true,
'title' => ucfirst($chapter), 'title' => $title,
'url' => Url::fromRequest() 'url' => Url::fromRequest()
)); ));
$this->render('chapter', null, true); $this->render('chapter', null, true);
@ -66,10 +76,10 @@ class DocController extends Controller
->setUrl($url) ->setUrl($url)
->setUrlParams($urlParams); ->setUrlParams($urlParams);
$name = ucfirst($name); $name = ucfirst($name);
$this->view->title = sprintf($this->translate('%s Documentation'), $name); $title = sprintf($this->translate('%s Documentation'), $name);
$this->getTabs()->add('toc', array( $this->getTabs()->add('toc', array(
'active' => true, 'active' => true,
'title' => $name, 'title' => $title,
'url' => Url::fromRequest() 'url' => Url::fromRequest()
)); ));
$this->render('toc', null, true); $this->render('toc', null, true);

View File

@ -4,6 +4,7 @@
namespace Icinga\Module\Doc; namespace Icinga\Module\Doc;
use CachingIterator; use CachingIterator;
use RecursiveIteratorIterator;
use SplFileObject; use SplFileObject;
use SplStack; use SplStack;
use Icinga\Data\Tree\SimpleTree; use Icinga\Data\Tree\SimpleTree;
@ -61,7 +62,7 @@ class DocParser
); );
} }
$this->path = $path; $this->path = $path;
$this->docIterator = new DirectoryIterator($path, 'md'); $this->docIterator = new DirectoryIterator($path, 'md', DirectoryIterator::FILES_FIRST);
} }
/** /**
@ -117,9 +118,37 @@ class DocParser
} else { } else {
$id = null; $id = null;
} }
/** @noinspection PhpUndefinedVariableInspection */
return array($header, $id, $level, $headerStyle); return array($header, $id, $level, $headerStyle);
} }
/**
* Generate unique section ID
*
* @param string $id
* @param string $filename
* @param SimpleTree $tree
*
* @return string
*/
protected function uuid($id, $filename, SimpleTree $tree)
{
$id = str_replace(' ', '-', $id);
if ($tree->getNode($id) === null) {
return $id;
}
$id = $id . '-' . md5($filename);
$offset = 0;
while ($tree->getNode($id)) {
if ($offset++ === 0) {
$id .= '-' . $offset;
} else {
$id = substr($id, 0, -1) . $offset;
}
}
return $id;
}
/** /**
* Get the documentation tree * Get the documentation tree
* *
@ -128,7 +157,7 @@ class DocParser
public function getDocTree() public function getDocTree()
{ {
$tree = new SimpleTree(); $tree = new SimpleTree();
foreach ($this->docIterator as $filename) { foreach (new RecursiveIteratorIterator($this->docIterator) as $filename) {
$file = new SplFileObject($filename); $file = new SplFileObject($filename);
$lastLine = null; $lastLine = null;
$stack = new SplStack(); $stack = new SplStack();
@ -154,9 +183,9 @@ class DocParser
} else { } else {
$noFollow = false; $noFollow = false;
} }
if ($tree->getNode($id) !== null) {
$id = uniqid($id); $id = $this->uuid($id, $filename, $tree);
}
$section = new DocSection(); $section = new DocSection();
$section $section
->setId($id) ->setId($id)
@ -178,10 +207,7 @@ class DocParser
} else { } else {
if ($stack->isEmpty()) { if ($stack->isEmpty()) {
$title = ucfirst($file->getBasename('.' . pathinfo($file->getFilename(), PATHINFO_EXTENSION))); $title = ucfirst($file->getBasename('.' . pathinfo($file->getFilename(), PATHINFO_EXTENSION)));
$id = $title; $id = $this->uuid($title, $filename, $tree);
if ($tree->getNode($id) !== null) {
$id = uniqid($id);
}
$section = new DocSection(); $section = new DocSection();
$section $section
->setId($id) ->setId($id)

View File

@ -88,14 +88,6 @@ class DocSection extends TreeNode
return $this->content; return $this->content;
} }
/**
* {@inheritdoc}
*/
public function setId($id)
{
return parent::setId(str_replace(' ', '-', (string) $id));
}
/** /**
* Set the header level * Set the header level
* *

View File

@ -13,6 +13,13 @@ use Icinga\Web\View;
*/ */
abstract class DocRenderer extends RecursiveIteratorIterator abstract class DocRenderer extends RecursiveIteratorIterator
{ {
/**
* URL to images
*
* @var string
*/
protected $imageUrl;
/** /**
* URL to replace links with * URL to replace links with
* *
@ -34,6 +41,38 @@ abstract class DocRenderer extends RecursiveIteratorIterator
*/ */
protected $view; protected $view;
/**
* Get the URL to images
*
* @return string
*/
public function getImageUrl()
{
return $this->imageUrl;
}
/**
* Set the URL to images
*
* @param string $imageUrl
*
* @return $this
*/
public function setImageUrl($imageUrl)
{
$this->imageUrl = (string) $imageUrl;
return $this;
}
/**
* Get the URL to replace links with
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/** /**
* Set the URL to replace links with * Set the URL to replace links with
* *
@ -48,13 +87,13 @@ abstract class DocRenderer extends RecursiveIteratorIterator
} }
/** /**
* Get the URL to replace links with * Get additional URL parameters
* *
* @return string * @return array
*/ */
public function getUrl() public function getUrlParams()
{ {
return $this->url; return $this->urlParams;
} }
/** /**
@ -71,13 +110,16 @@ abstract class DocRenderer extends RecursiveIteratorIterator
} }
/** /**
* Get additional URL parameters * Get the view
* *
* @return array * @return View
*/ */
public function getUrlParams() public function getView()
{ {
return $this->urlParams; if ($this->view === null) {
$this->view = Icinga::app()->getViewRenderer()->view;
}
return $this->view;
} }
/** /**
@ -93,19 +135,6 @@ abstract class DocRenderer extends RecursiveIteratorIterator
return $this; return $this;
} }
/**
* Get the view
*
* @return View
*/
public function getView()
{
if ($this->view === null) {
$this->view = Icinga::app()->getViewRenderer()->view;
}
return $this->view;
}
/** /**
* Encode an anchor identifier * Encode an anchor identifier
* *

View File

@ -190,7 +190,20 @@ class DocSectionRenderer extends DocRenderer
$xpath = new DOMXPath($doc); $xpath = new DOMXPath($doc);
$img = $xpath->query('//img[1]')->item(0); $img = $xpath->query('//img[1]')->item(0);
/** @var \DOMElement $img */ /** @var \DOMElement $img */
$img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl()); $path = $this->getView()->getHelper('Url')->url(
array_merge(
array(
'image' => $img->getAttribute('src')
),
$this->urlParams
),
$this->imageUrl,
false,
false
);
$url = $this->getView()->url($path);
/** @var \Icinga\Web\Url $url */
$img->setAttribute('src', $url->getAbsoluteUrl());
return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>' return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>'
} }
@ -274,7 +287,7 @@ class DocSectionRenderer extends DocRenderer
$html $html
); );
$html = preg_replace_callback( $html = preg_replace_callback(
'#<blockquote>.+</blockquote>#ms', '#<blockquote>.+?</blockquote>#ms',
array($this, 'markupNotes'), array($this, 'markupNotes'),
$html $html
); );

View File

@ -25,6 +25,7 @@ pre > code {
.chapter a { .chapter a {
border-bottom: 1px @gray-light dotted; border-bottom: 1px @gray-light dotted;
font-weight: @font-weight-bold;
&:hover { &:hover {
border-bottom: 1px @text-color solid; border-bottom: 1px @text-color solid;
@ -60,14 +61,11 @@ pre > code {
a { a {
&:before { &:before {
.rounded-corners(); color: @icinga-blue;
content: counters(li,".") " ";
background-color: @icinga-blue;
color: @text-color-on-icinga-blue;
content: counter(li) ".";
display: inline-block; display: inline-block;
font-size: small; font-size: small;
margin-right: 0.25em; font-weight: @font-weight-bold;
min-width: 1.5em; min-width: 1.5em;
padding: 0.25em; padding: 0.25em;
text-align: center; text-align: center;

View File

@ -43,7 +43,22 @@ $docModulePdf = new Zend_Controller_Router_Route(
) )
); );
$docModuleImg = new Zend_Controller_Router_Route_Regex(
'doc/module/([^/]+)/image/(.+)',
array(
'controller' => 'module',
'action' => 'image',
'module' => 'doc'
),
array(
'moduleName' => 1,
'image' => 2
),
'doc/module/%s/image/%s'
);
$this->addRoute('doc/module/chapter', $docModuleChapter); $this->addRoute('doc/module/chapter', $docModuleChapter);
$this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter); $this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter);
$this->addRoute('doc/module/toc', $docModuleToc); $this->addRoute('doc/module/toc', $docModuleToc);
$this->addRoute('doc/module/pdf', $docModulePdf); $this->addRoute('doc/module/pdf', $docModulePdf);
$this->addRoute('doc/module/img', $docModuleImg);

View File

@ -55,6 +55,9 @@ class ListCommand extends Command
$query->limit($limit, $this->params->shift('offset')); $query->limit($limit, $this->params->shift('offset'));
} }
foreach ($this->params->getParams() as $col => $filter) { foreach ($this->params->getParams() as $col => $filter) {
if (strtolower($col) === 'problems') {
$col = 'service_problem';
}
$query->where($col, $filter); $query->where($col, $filter);
} }
// $query->applyFilters($this->params->getParams()); // $query->applyFilters($this->params->getParams());

View File

@ -49,7 +49,8 @@ class HostsController extends Controller
'host_obsessing', 'host_obsessing',
'host_passive_checks_enabled', 'host_passive_checks_enabled',
'host_problem', 'host_problem',
'host_state' 'host_state',
'instance_name'
)); ));
$this->view->baseFilter = $this->hostList->getFilter(); $this->view->baseFilter = $this->hostList->getFilter();
$this->getTabs()->add( $this->getTabs()->add(

View File

@ -39,10 +39,18 @@ class ListController extends Controller
} }
/** /**
* Display host list * List hosts
*/ */
public function hostsAction() public function hostsAction()
{ {
$this->addTitleTab(
'hosts',
$this->translate('Hosts'),
$this->translate('List hosts')
);
$this->setAutorefreshInterval(10);
// Handle soft and hard states // Handle soft and hard states
if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
$stateColumn = 'host_hard_state'; $stateColumn = 'host_hard_state';
@ -51,9 +59,8 @@ class ListController extends Controller
$stateColumn = 'host_state'; $stateColumn = 'host_state';
$stateChangeColumn = 'host_last_state_change'; $stateChangeColumn = 'host_last_state_change';
} }
$this->addTitleTab('hosts', $this->translate('Hosts'), $this->translate('List hosts'));
$this->setAutorefreshInterval(10); $hosts = $this->backend->select()->from('hoststatus', array_merge(array(
$query = $this->backend->select()->from('hoststatus', array_merge(array(
'host_icon_image', 'host_icon_image',
'host_icon_image_alt', 'host_icon_image_alt',
'host_name', 'host_name',
@ -71,9 +78,20 @@ class ListController extends Controller
'host_active_checks_enabled', 'host_active_checks_enabled',
'host_passive_checks_enabled' 'host_passive_checks_enabled'
), $this->addColumns())); ), $this->addColumns()));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $hosts);
$this->filterQuery($query); $this->filterQuery($hosts);
$this->view->hosts = $query;
$this->setupPaginationControl($hosts);
$this->setupLimitControl();
$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'),
'host_last_state_change' => $this->translate('Last State Change')
), $hosts);
$stats = $this->backend->select()->from('hoststatussummary', array( $stats = $this->backend->select()->from('hoststatussummary', array(
'hosts_total', 'hosts_total',
'hosts_up', 'hosts_up',
@ -86,28 +104,26 @@ class ListController extends Controller
'hosts_pending', 'hosts_pending',
)); ));
$this->applyRestriction('monitoring/filter/objects', $stats); $this->applyRestriction('monitoring/filter/objects', $stats);
$this->view->stats = $stats;
$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'),
'host_last_state_change' => $this->translate('Last State Change')
), $query);
$summary = $query->getQuery()->queryServiceProblemSummary(); $summary = $hosts->getQuery()->queryServiceProblemSummary();
$this->applyRestriction('monitoring/filter/objects', $summary); $this->applyRestriction('monitoring/filter/objects', $summary);
$this->view->hosts = $hosts;
$this->view->stats = $stats;
$this->view->summary = $summary->fetchPairs(); $this->view->summary = $summary->fetchPairs();
} }
/** /**
* Display service list * List services
*/ */
public function servicesAction() public function servicesAction()
{ {
$this->addTitleTab(
'services',
$this->translate('Services'),
$this->translate('List services')
);
// Handle soft and hard states // Handle soft and hard states
if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') { if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
$stateColumn = 'service_hard_state'; $stateColumn = 'service_hard_state';
@ -117,14 +133,9 @@ class ListController extends Controller
$stateChangeColumn = 'service_last_state_change'; $stateChangeColumn = 'service_last_state_change';
} }
$this->addTitleTab('services', $this->translate('Services'), $this->translate('List services'));
$this->view->showHost = true;
if (strpos($this->params->get('host_name', '*'), '*') === false) {
$this->view->showHost = false;
}
$this->setAutorefreshInterval(10); $this->setAutorefreshInterval(10);
$columns = array_merge(array( $services = $this->backend->select()->from('servicestatus', array_merge(array(
'host_name', 'host_name',
'host_display_name', 'host_display_name',
'host_state', 'host_state',
@ -147,14 +158,12 @@ class ListController extends Controller
'service_notifications_enabled', 'service_notifications_enabled',
'service_active_checks_enabled', 'service_active_checks_enabled',
'service_passive_checks_enabled' 'service_passive_checks_enabled'
), $this->addColumns()); ), $this->addColumns()));
$query = $this->backend->select()->from('servicestatus', $columns); $this->applyRestriction('monitoring/filter/objects', $services);
$this->applyRestriction('monitoring/filter/objects', $query); $this->filterQuery($services);
$this->filterQuery($query);
$this->view->services = $query;
$this->setupPaginationControl($services);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->services);
$this->setupSortControl(array( $this->setupSortControl(array(
'service_severity' => $this->translate('Service Severity'), 'service_severity' => $this->translate('Service Severity'),
'service_state' => $this->translate('Current Service State'), 'service_state' => $this->translate('Current Service State'),
@ -166,7 +175,7 @@ class ListController extends Controller
'host_display_name' => $this->translate('Hostname'), 'host_display_name' => $this->translate('Hostname'),
'host_address' => $this->translate('Host Address'), 'host_address' => $this->translate('Host Address'),
'host_last_check' => $this->translate('Last Host Check') 'host_last_check' => $this->translate('Last Host Check')
), $query); ), $services);
$stats = $this->backend->select()->from('servicestatussummary', array( $stats = $this->backend->select()->from('servicestatussummary', array(
'services_critical', 'services_critical',
@ -183,18 +192,30 @@ class ListController extends Controller
'services_warning_unhandled' 'services_warning_unhandled'
)); ));
$this->applyRestriction('monitoring/filter/objects', $stats); $this->applyRestriction('monitoring/filter/objects', $stats);
$this->view->services = $services;
$this->view->stats = $stats; $this->view->stats = $stats;
if (strpos($this->params->get('host_name', '*'), '*') === false) {
$this->view->showHost = false;
} else {
$this->view->showHost = true;
}
} }
/** /**
* Fetch the current downtimes and put them into the view property `downtimes` * List downtimes
*/ */
public function downtimesAction() public function downtimesAction()
{ {
$this->addTitleTab('downtimes', $this->translate('Downtimes'), $this->translate('List downtimes')); $this->addTitleTab(
'downtimes',
$this->translate('Downtimes'),
$this->translate('List downtimes')
);
$this->setAutorefreshInterval(12); $this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('downtime', array( $downtimes = $this->backend->select()->from('downtime', array(
'id' => 'downtime_internal_id', 'id' => 'downtime_internal_id',
'objecttype' => 'object_type', 'objecttype' => 'object_type',
'comment' => 'downtime_comment', 'comment' => 'downtime_comment',
@ -215,13 +236,11 @@ class ListController extends Controller
'host_display_name', 'host_display_name',
'service_display_name' 'service_display_name'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $downtimes);
$this->filterQuery($query); $this->filterQuery($downtimes);
$this->view->downtimes = $query;
$this->setupPaginationControl($downtimes);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->downtimes);
$this->setupSortControl(array( $this->setupSortControl(array(
'downtime_is_in_effect' => $this->translate('Is In Effect'), 'downtime_is_in_effect' => $this->translate('Is In Effect'),
'host_display_name' => $this->translate('Host'), 'host_display_name' => $this->translate('Host'),
@ -233,7 +252,9 @@ class ListController extends Controller
'downtime_scheduled_start' => $this->translate('Scheduled Start'), 'downtime_scheduled_start' => $this->translate('Scheduled Start'),
'downtime_scheduled_end' => $this->translate('Scheduled End'), 'downtime_scheduled_end' => $this->translate('Scheduled End'),
'downtime_duration' => $this->translate('Duration') 'downtime_duration' => $this->translate('Duration')
), $query); ), $downtimes);
$this->view->downtimes = $downtimes;
if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) { if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) {
$this->view->delDowntimeForm = new DeleteDowntimeCommandForm(); $this->view->delDowntimeForm = new DeleteDowntimeCommandForm();
@ -242,7 +263,7 @@ class ListController extends Controller
} }
/** /**
* Display notification overview * List notifications
*/ */
public function notificationsAction() public function notificationsAction()
{ {
@ -251,9 +272,10 @@ class ListController extends Controller
$this->translate('Notifications'), $this->translate('Notifications'),
$this->translate('List notifications') $this->translate('List notifications')
); );
$this->setAutorefreshInterval(15); $this->setAutorefreshInterval(15);
$query = $this->backend->select()->from('notification', array( $notifications = $this->backend->select()->from('notification', array(
'host_name', 'host_name',
'service_description', 'service_description',
'notification_output', 'notification_output',
@ -263,22 +285,30 @@ class ListController extends Controller
'host_display_name', 'host_display_name',
'service_display_name' 'service_display_name'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $notifications);
$this->filterQuery($query); $this->filterQuery($notifications);
$this->view->notifications = $query;
$this->setupPaginationControl($notifications);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->notifications);
$this->setupSortControl(array( $this->setupSortControl(array(
'notification_start_time' => $this->translate('Notification Start') 'notification_start_time' => $this->translate('Notification Start')
), $query); ), $notifications);
$this->view->notifications = $notifications;
} }
/**
* List contacts
*/
public function contactsAction() public function contactsAction()
{ {
$this->addTitleTab('contacts', $this->translate('Contacts'), $this->translate('List contacts')); $this->addTitleTab(
'contacts',
$this->translate('Contacts'),
$this->translate('List contacts')
);
$query = $this->backend->select()->from('contact', array( $contacts = $this->backend->select()->from('contact', array(
'contact_name', 'contact_name',
'contact_alias', 'contact_alias',
'contact_email', 'contact_email',
@ -286,20 +316,19 @@ class ListController extends Controller
'contact_notify_service_timeperiod', 'contact_notify_service_timeperiod',
'contact_notify_host_timeperiod' 'contact_notify_host_timeperiod'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $contacts);
$this->filterQuery($query); $this->filterQuery($contacts);
$this->view->contacts = $query;
$this->setupPaginationControl($contacts);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->contacts);
$this->setupSortControl(array( $this->setupSortControl(array(
'contact_name' => $this->translate('Name'), 'contact_name' => $this->translate('Name'),
'contact_alias' => $this->translate('Alias'), 'contact_alias' => $this->translate('Alias'),
'contact_email' => $this->translate('Email'), 'contact_email' => $this->translate('Email'),
'contact_pager' => $this->translate('Pager Address / Number'), 'contact_pager' => $this->translate('Pager Address / Number')
'contact_notify_service_timeperiod' => $this->translate('Service Notification Timeperiod'), ), $contacts);
'contact_notify_host_timeperiod' => $this->translate('Host Notification Timeperiod')
), $query); $this->view->contacts = $contacts;
} }
public function eventgridAction() public function eventgridAction()
@ -342,6 +371,9 @@ class ListController extends Controller
$this->view->orientation = $orientation; $this->view->orientation = $orientation;
} }
/**
* List contact groups
*/
public function contactgroupsAction() public function contactgroupsAction()
{ {
$this->addTitleTab( $this->addTitleTab(
@ -350,47 +382,38 @@ class ListController extends Controller
$this->translate('List contact groups') $this->translate('List contact groups')
); );
$query = $this->backend->select()->from('contactgroup', array( $contactGroups = $this->backend->select()->from('contactgroup', array(
'contactgroup_name', 'contactgroup_name',
'contactgroup_alias', 'contactgroup_alias',
'contact_name', 'contact_count'
'contact_alias',
'contact_email',
'contact_pager'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $contactGroups);
$this->filterQuery($query); $this->filterQuery($contactGroups);
$this->setupPaginationControl($contactGroups);
$this->setupLimitControl();
$this->setupSortControl(array( $this->setupSortControl(array(
'contactgroup_name' => $this->translate('Contactgroup Name'), 'contactgroup_name' => $this->translate('Contactgroup Name'),
'contactgroup_alias' => $this->translate('Contactgroup Alias') 'contactgroup_alias' => $this->translate('Contactgroup Alias')
), $query); ), $contactGroups);
// Fetch and prepare all contact groups: $this->view->contactGroups = $contactGroups;
$contactgroups = $query->getQuery()->fetchAll();
$groupData = array();
foreach ($contactgroups as $c) {
if (!array_key_exists($c->contactgroup_name, $groupData)) {
$groupData[$c->contactgroup_name] = array(
'alias' => $c->contactgroup_alias,
'contacts' => array()
);
}
if (isset ($c->contact_name)) {
$groupData[$c->contactgroup_name]['contacts'][] = $c;
}
}
// TODO: Find a better naming
$this->view->groupData = $groupData;
} }
/**
* List all comments
*/
public function commentsAction() public function commentsAction()
{ {
$this->addTitleTab('comments', $this->translate('Comments'), $this->translate('List comments')); $this->addTitleTab(
'comments',
$this->translate('Comments'),
$this->translate('List comments')
);
$this->setAutorefreshInterval(12); $this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('comment', array( $comments = $this->backend->select()->from('comment', array(
'id' => 'comment_internal_id', 'id' => 'comment_internal_id',
'objecttype' => 'object_type', 'objecttype' => 'object_type',
'comment' => 'comment_data', 'comment' => 'comment_data',
@ -404,12 +427,11 @@ class ListController extends Controller
'host_display_name', 'host_display_name',
'service_display_name' 'service_display_name'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $comments);
$this->filterQuery($query); $this->filterQuery($comments);
$this->view->comments = $query;
$this->setupPaginationControl($comments);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->comments);
$this->setupSortControl( $this->setupSortControl(
array( array(
'comment_timestamp' => $this->translate('Comment Timestamp'), 'comment_timestamp' => $this->translate('Comment Timestamp'),
@ -418,15 +440,20 @@ class ListController extends Controller
'comment_type' => $this->translate('Comment Type'), 'comment_type' => $this->translate('Comment Type'),
'comment_expiration' => $this->translate('Expiration') 'comment_expiration' => $this->translate('Expiration')
), ),
$query $comments
); );
$this->view->comments = $comments;
if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) { if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) {
$this->view->delCommentForm = new DeleteCommentCommandForm(); $this->view->delCommentForm = new DeleteCommentCommandForm();
$this->view->delCommentForm->handleRequest(); $this->view->delCommentForm->handleRequest();
} }
} }
/**
* List service groups
*/
public function servicegroupsAction() public function servicegroupsAction()
{ {
$this->addTitleTab( $this->addTitleTab(
@ -434,9 +461,10 @@ class ListController extends Controller
$this->translate('Service Groups'), $this->translate('Service Groups'),
$this->translate('List service groups') $this->translate('List service groups')
); );
$this->setAutorefreshInterval(12); $this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('servicegroupsummary', array( $serviceGroups = $this->backend->select()->from('servicegroupsummary', array(
'servicegroup_alias', 'servicegroup_alias',
'servicegroup_name', 'servicegroup_name',
'services_critical_handled', 'services_critical_handled',
@ -449,25 +477,34 @@ class ListController extends Controller
'services_warning_handled', 'services_warning_handled',
'services_warning_unhandled' 'services_warning_unhandled'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $serviceGroups);
$this->filterQuery($query); $this->filterQuery($serviceGroups);
$this->view->servicegroups = $query;
$this->setupPaginationControl($serviceGroups);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->servicegroups);
$this->setupSortControl(array( $this->setupSortControl(array(
'services_severity' => $this->translate('Severity'), 'services_severity' => $this->translate('Severity'),
'servicegroup_alias' => $this->translate('Service Group Name'), 'servicegroup_alias' => $this->translate('Service Group Name'),
'services_total' => $this->translate('Total Services') 'services_total' => $this->translate('Total Services')
), $query); ), $serviceGroups);
$this->view->serviceGroups = $serviceGroups;
} }
/**
* List host groups
*/
public function hostgroupsAction() public function hostgroupsAction()
{ {
$this->addTitleTab('hostgroups', $this->translate('Host Groups'), $this->translate('List host groups')); $this->addTitleTab(
'hostgroups',
$this->translate('Host Groups'),
$this->translate('List host groups')
);
$this->setAutorefreshInterval(12); $this->setAutorefreshInterval(12);
$query = $this->backend->select()->from('hostgroupsummary', array( $hostGroups = $this->backend->select()->from('hostgroupsummary', array(
'hostgroup_alias', 'hostgroup_alias',
'hostgroup_name', 'hostgroup_name',
'hosts_down_handled', 'hosts_down_handled',
@ -487,18 +524,19 @@ class ListController extends Controller
'services_warning_handled', 'services_warning_handled',
'services_warning_unhandled' 'services_warning_unhandled'
)); ));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $hostGroups);
$this->filterQuery($query); $this->filterQuery($hostGroups);
$this->view->hostgroups = $query;
$this->setupPaginationControl($hostGroups);
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->hostgroups);
$this->setupSortControl(array( $this->setupSortControl(array(
'hosts_severity' => $this->translate('Severity'), 'hosts_severity' => $this->translate('Severity'),
'hostgroup_alias' => $this->translate('Host Group Name'), 'hostgroup_alias' => $this->translate('Host Group Name'),
'hosts_total' => $this->translate('Total Hosts'), 'hosts_total' => $this->translate('Total Hosts'),
'services_total' => $this->translate('Total Services') 'services_total' => $this->translate('Total Services')
), $query); ), $hostGroups);
$this->view->hostGroups = $hostGroups;
} }
public function eventhistoryAction() public function eventhistoryAction()

View File

@ -41,6 +41,7 @@ class ServicesController extends Controller
'host_name', 'host_name',
'host_problem', 'host_problem',
'host_state', 'host_state',
'instance_name',
'service_acknowledged', 'service_acknowledged',
'service_active_checks_enabled', 'service_active_checks_enabled',
'service_description', 'service_description',

View File

@ -58,24 +58,27 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
*/ */
public function createElements(array $formData = array()) public function createElements(array $formData = array())
{ {
if ((bool) $this->status->notifications_enabled) { $notificationDescription = null;
if ($this->hasPermission('monitoring/command/feature/instance')) { $isIcinga2 = $this->getBackend()->isIcinga2($this->status->program_version);
if (! $isIcinga2) {
if ((bool) $this->status->notifications_enabled) {
if ($this->hasPermission('monitoring/command/feature/instance')) {
$notificationDescription = sprintf(
'<a aria-label="%1$s" class="action-link" title="%1$s" href="%2$s" data-base-target="_next">%3$s</a>',
$this->translate('Disable notifications for a specific time on a program-wide basis'),
$this->getView()->href('monitoring/health/disable-notifications'),
$this->translate('Disable temporarily')
);
} else {
$notificationDescription = null;
}
} elseif ($this->status->disable_notif_expire_time) {
$notificationDescription = sprintf( $notificationDescription = sprintf(
'<a aria-label="%1$s" class="action-link" title="%1$s" href="%2$s" data-base-target="_next">%3$s</a>', $this->translate('Notifications will be re-enabled in <strong>%s</strong>'),
$this->translate('Disable notifications for a specific time on a program-wide basis'), $this->getView()->timeUntil($this->status->disable_notif_expire_time)
$this->getView()->href('monitoring/health/disable-notifications'),
$this->translate('Disable temporarily')
); );
} else {
$notificationDescription = null;
} }
} elseif ($this->status->disable_notif_expire_time) {
$notificationDescription = sprintf(
$this->translate('Notifications will be re-enabled in <strong>%s</strong>'),
$this->getView()->timeUntil($this->status->disable_notif_expire_time)
);
} else {
$notificationDescription = null;
} }
$toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : ''; $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : '';
@ -138,7 +141,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm
) )
); );
if (! $this->getBackend()->isIcinga2($this->status->program_version)) { if (! $isIcinga2) {
$this->addElement( $this->addElement(
'checkbox', 'checkbox',
ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING, ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING,

View File

@ -27,7 +27,7 @@ class Zend_View_Helper_IconImage extends Zend_View_Helper_Abstract
public function host($object) public function host($object)
{ {
if ($object->host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) { if ($object->host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) {
return $this->view->img( return $this->view->icon(
Macro::resolveMacros($object->host_icon_image, $object), Macro::resolveMacros($object->host_icon_image, $object),
null, null,
array( array(
@ -50,7 +50,7 @@ class Zend_View_Helper_IconImage extends Zend_View_Helper_Abstract
public function service($object) public function service($object)
{ {
if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) { if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) {
return $this->view->img( return $this->view->icon(
Macro::resolveMacros($object->service_icon_image, $object), Macro::resolveMacros($object->service_icon_image, $object),
null, null,
array( array(

View File

@ -51,6 +51,14 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
$isHtml = false; $isHtml = false;
} }
$output = $this->fixLinks($output); $output = $this->fixLinks($output);
// Help browsers to break words in plugin output
$output = trim($output);
// Add space after comma where missing
$output = preg_replace('/,[^\s]/', ', ', $output);
// Add zero width space after ')', ']', ':', '.', '_' and '-' if not surrounded by whitespaces
$output = preg_replace('/([^\s])([\\)\\]:._-])([^\s])/', '$1$2&#8203;$3', $output);
// Add zero width space before '(' and '[' if not surrounded by whitespaces
$output = preg_replace('/([^\s])([([])([^\s])/', '$1&#8203;$2$3', $output);
if (! $raw) { if (! $raw) {
if ($isHtml) { if ($isHtml) {

View File

@ -1,11 +1,11 @@
<?php if (! $this->compact): ?> <?php if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<?= $this->render('list/components/selectioninfo.phtml') ?> <?= $this->render('list/components/selectioninfo.phtml') ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>

View File

@ -2,52 +2,49 @@
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<?php <?php if (! $contactGroups->hasResult()): ?>
if (count($groupData) === 0) { <p><?= $this->translate('No contact groups found matching the filter') ?></p>
echo $this->translate('No contactgroups found matching the filter') . '</div>'; </div>
return; <?php return; endif ?>
} <table class="common-table table-row-selectable" data-base-target="_next">
?>
<table class="action table-row-selectable common-table" data-base-target="_next">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th><?= $this->translate('Contact Group ') ?></th> <th><?= $this->translate('Contact Group ') ?></th>
<th><?= $this->translate('Alias') ?></th> <th><?= $this->translate('Alias') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($contactGroups as $contactGroup): ?>
<?php foreach ($groupData as $groupName => $groupInfo): ?> <tr>
<tr> <td class="count-col">
<td class="count-col"> <span class="badge"><?= $contactGroup->contact_count ?></span>
<span class="badge"><?= count($groupInfo['contacts']) ?></span> </td>
</td> <td>
<?= $this->qlink(
$contactGroup->contactgroup_name,
'monitoring/list/contacts',
array('contactgroup_name' => $contactGroup->contactgroup_name),
array('title' => sprintf(
$this->translate('Show detailed information about %s'),
$contactGroup->contactgroup_name
))
) ?>
</td>
<td> <td>
<?= $this->qlink( <?php if ($contactGroup->contactgroup_name !== $contactGroup->contactgroup_alias): ?>
$groupName, <?= $contactGroup->contactgroup_alias ?>
'monitoring/list/contacts', <?php endif ?>
array('contactgroup_name' => $groupName),
array('title' => sprintf(
$this->translate('Show detailed information about %s'),
$groupName
))
) ?>
</td>
<td>
<?php if ($groupInfo['alias'] !== $groupName): ?>
<?= $groupInfo['alias'] ?>
<?php endif ?>
</td> </td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>

View File

@ -1,79 +1,83 @@
<?php if (! $this->compact): ?> <?php if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<?php if ($contacts->hasResult()): ?> <?php if (! $contacts->hasResult()): ?>
<table class="action table-row-selectable common-table" data-base-target="_next"> <p><?= $this->translate('No contacts found matching the filter') ?></p>
<thead> </div>
<?php return; endif ?>
<table class="common-table table-row-selectable" data-base-target="_next">
<thead>
<tr> <tr>
<th><?= $this->translate('Name') ?></th> <th><?= $this->translate('Name') ?></th>
<th><?= $this->translate('Email') ?></th> <th><?= $this->translate('Email') ?></th>
<th><?= $this->translate('Pager') ?></th> <th><?= $this->translate('Pager') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($contacts->peekAhead($this->compact) as $contact): ?> <?php foreach ($contacts->peekAhead($this->compact) as $contact): ?>
<tr> <tr>
<td> <td>
<?= $this->qlink( <?= $this->qlink(
$contact->contact_name, $contact->contact_name,
'monitoring/show/contact', 'monitoring/show/contact',
array('contact_name' => $contact->contact_name), array('contact_name' => $contact->contact_name),
array('title' => sprintf( array(
'title' => sprintf(
$this->translate('Show detailed information about %s'), $this->translate('Show detailed information about %s'),
$contact->contact_alias $contact->contact_alias
), 'class' => 'rowaction') )
); ?> )
</td> ) ?>
</td>
<td>
<?= $this->translate('Email') ?>:
<a href="mailto:<?= $contact->contact_email ?>"
title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>"
aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>">
<?= $this->escape($contact->contact_email) ?>
</a>
</td>
<td> <td>
<?= $this->translate('Email') ?>: <?php if ($contact->contact_pager): ?>
<a href="mailto:<?= $contact->contact_email; ?>" <?= $this->escape($contact->contact_pager) ?>
title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>" <?php endif ?>
aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>">
<?= $this->escape($contact->contact_email); ?>
</a>
</td> </td>
<td>
<?php if ($contact->contact_pager): ?>
<?= $this->escape($contact->contact_pager) ?>
<?php endif; ?>
</td>
<?php if ($contact->contact_notify_service_timeperiod): ?> <?php if ($contact->contact_notify_service_timeperiod): ?>
<td> <td>
<?= $this->escape($contact->contact_notify_service_timeperiod) ?> <?= $this->escape($contact->contact_notify_service_timeperiod) ?>
</td> </td>
<?php endif; ?> <?php endif ?>
<?php if ($contact->contact_notify_host_timeperiod): ?> <?php if ($contact->contact_notify_host_timeperiod): ?>
<td> <td>
<?= $this->escape($contact->contact_notify_host_timeperiod) ?> <?= $this->escape($contact->contact_notify_host_timeperiod) ?>
</td> </td>
<?php endif; ?> <?php endif ?>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<?php if ($contacts->hasMore()): ?> <?php if ($contacts->hasMore()): ?>
<div class="action-links">
<?= $this->qlink( <?= $this->qlink(
$this->translate('Show More'), $this->translate('Show More'),
$this->url()->without(array('view', 'limit')), $this->url()->without(array('view', 'limit')),
null, null,
array( array(
'data-base-target' => '_next', 'class' => 'action-link',
'class' => 'pull-right action-link' 'data-base-target' => '_next'
) )
); ?> ) ?>
<?php endif ?> </div>
<?php else: ?>
<?= $this->translate('No contacts found matching the filter'); ?>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -3,51 +3,56 @@ use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service; use Icinga\Module\Monitoring\Object\Service;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls separated"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<?= $this->render('list/components/selectioninfo.phtml') ?> <?= $this->render('list/components/selectioninfo.phtml') ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<table data-base-target="_next" <?php if (! $downtimes->hasResult()): ?>
class="table-row-selectable state-table multiselect common-table" <p><?= $this->translate('No downtimes found matching the filter.') ?></p>
data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show'); ?>" </div>
data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>" <?php return; endif ?>
data-icinga-multiselect-data="downtime_id"> <table class="common-table state-table table-row-selectable multiselect"
data-base-target="_next"
data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show') ?>"
data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>"
data-icinga-multiselect-data="downtime_id">
<tbody> <tbody>
<?php foreach ($downtimes->peekAhead($this->compact) as $downtime): <?php foreach ($downtimes->peekAhead($this->compact) as $downtime):
if (isset($downtime->service_description)) { if (isset($downtime->service_description)) {
$this->isService = true; $this->isService = true;
$this->stateName = Service::getStateText($downtime->service_state); $this->stateName = Service::getStateText($downtime->service_state);
} else { } else {
$this->isService = false; $this->isService = false;
$this->stateName = Host::getStateText($downtime->host_state); $this->stateName = Host::getStateText($downtime->host_state);
} }
$this->downtime = $downtime; // Set downtime for partials
?> $this->downtime = $downtime;
<tr href="<?= $this->href('monitoring/downtime/show', array('downtime_id' => $downtime->id)) ?>"> ?>
<?= $this->render('partials/downtime/downtime-header.phtml'); ?> <tr href="<?= $this->href('monitoring/downtime/show', array('downtime_id' => $downtime->id)) ?>">
</tr> <?= $this->render('partials/downtime/downtime-header.phtml') ?>
</tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<?php if (! $downtimes->hasResult()): ?> <?php if ($downtimes->hasMore()): ?>
<?= $this->translate('No downtimes found matching the filter, maybe the downtime already expired.'); ?> <div class="action-links">
<?php elseif ($downtimes->hasMore()): ?> <?= $this->qlink(
<?= $this->qlink( $this->translate('Show More'),
$this->translate('Show More'), $this->url()->without(array('view', 'limit')),
$this->url()->without(array('view', 'limit')), null,
null, array(
array( 'class' => 'action-link',
'data-base-target' => '_next', 'data-base-target' => '_next'
'class' => 'pull-right action-link' )
) ) ?>
); ?> </div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -4,12 +4,8 @@ use Icinga\Web\Widget\Chart\HistoryColorGrid;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs; ?> <?= $this->tabs ?>
<?= $this->sortBox; ?> <?= $this->form ?>
<?= $this->limiter; ?>
<?= $this->paginator; ?>
<?= $this->filterEditor; ?>
<?= $form; ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content" data-base-target="_next"> <div class="content" data-base-target="_next">

View File

@ -3,11 +3,11 @@
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs ?> <?= $this->tabs ?>
<div class="grid"> <div class="sort-controls-container">
<?= $this->sortBox ?>
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->filterEditor ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<?= $this->partial( <?= $this->partial(

View File

@ -1,278 +1,280 @@
<?php <?php
use Icinga\Module\Monitoring\Web\Widget\StateBadges; use Icinga\Module\Monitoring\Web\Widget\StateBadges;
/** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostgroups */
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<?php if (! $hostgroups->hasResult()): ?> <?php /** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostGroups */ if (! $hostGroups->hasResult()): ?>
<p><?= $this->translate('No host groups found matching the filter.') ?></p> <p><?= $this->translate('No host groups found matching the filter.') ?></p>
</div> </div>
<?php return; endif ?> <?php return; endif ?>
<table class="table-row-selectable common-table" data-base-target="_next"> <table class="common-table table-row-selectable" data-base-target="_next">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th><?= $this->translate('Host Group') ?></th> <th><?= $this->translate('Host Group') ?></th>
<th><?= $this->translate('Host States') ?></th> <th><?= $this->translate('Host States') ?></th>
<th></th> <th></th>
<th><?= $this->translate('Service States') ?></th> <th><?= $this->translate('Service States') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($hostgroups->peekAhead($this->compact) as $hostgroup): ?> <?php foreach ($hostGroups->peekAhead($this->compact) as $hostGroup): ?>
<tr> <tr>
<td class="count-col"> <td class="count-col">
<span class="badge"><?= $hostgroup->hosts_total ?></span> <span class="badge"><?= $hostGroup->hosts_total ?></span>
</td> </td>
<th> <th>
<?= $this->qlink( <?= $this->qlink(
$hostgroup->hostgroup_alias, $hostGroup->hostgroup_alias,
'monitoring/list/hosts', 'monitoring/list/hosts',
array('hostgroup_name' => $hostgroup->hostgroup_name), array('hostgroup_name' => $hostGroup->hostgroup_name),
array('title' => sprintf( array('title' => sprintf(
$this->translate('List all hosts in the group "%s"'), $this->translate('List all hosts in the group "%s"'),
$hostgroup->hostgroup_alias $hostGroup->hostgroup_alias
)) ))
) ?> ) ?>
</th> </th>
<td> <td>
<?php <?php
$stateBadges = new StateBadges(); $stateBadges = new StateBadges();
$stateBadges $stateBadges
->setUrl('monitoring/list/hosts') ->setUrl('monitoring/list/hosts')
->setBaseFilter($this->filterEditor->getFilter()) ->setBaseFilter($this->filterEditor->getFilter())
->add( ->add(
StateBadges::STATE_UP, StateBadges::STATE_UP,
$hostgroup->hosts_up, $hostGroup->hosts_up,
array( array(
'host_state' => 0, 'host_state' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state UP in the host group "%s"', '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"', 'List %u hosts which are currently in state UP in the host group "%s"',
array($hostgroup->hosts_up, $hostgroup->hostgroup_alias) array($hostGroup->hosts_up, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_DOWN, StateBadges::STATE_DOWN,
$hostgroup->hosts_down_unhandled, $hostGroup->hosts_down_unhandled,
array( array(
'host_state' => 1, 'host_state' => 1,
'host_acknowledged' => 0, 'host_acknowledged' => 0,
'host_in_downtime' => 0, 'host_in_downtime' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state DOWN in the host group "%s"', '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"', 'List %u hosts which are currently in state DOWN in the host group "%s"',
array($hostgroup->hosts_down_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->hosts_down_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_DOWN_HANDLED, StateBadges::STATE_DOWN_HANDLED,
$hostgroup->hosts_down_handled, $hostGroup->hosts_down_handled,
array( array(
'host_state' => 1, 'host_state' => 1,
'host_handled' => 1, 'host_handled' => 1,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"', '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"', 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"',
array($hostgroup->hosts_down_handled, $hostgroup->hostgroup_alias) array($hostGroup->hosts_down_handled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNREACHABLE, StateBadges::STATE_UNREACHABLE,
$hostgroup->hosts_unreachable_unhandled, $hostGroup->hosts_unreachable_unhandled,
array( array(
'host_state' => 2, 'host_state' => 2,
'host_acknowledged' => 0, 'host_acknowledged' => 0,
'host_in_downtime' => 0, 'host_in_downtime' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state UNREACHABLE in the host group "%s"', '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"', 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"',
array($hostgroup->hosts_unreachable_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->hosts_unreachable_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNREACHABLE_HANDLED, StateBadges::STATE_UNREACHABLE_HANDLED,
$hostgroup->hosts_unreachable_handled, $hostGroup->hosts_unreachable_handled,
array( array(
'host_state' => 2, 'host_state' => 2,
'host_handled' => 1, 'host_handled' => 1,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"', '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"', 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
array($hostgroup->hosts_unreachable_handled, $hostgroup->hostgroup_alias) array($hostGroup->hosts_unreachable_handled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_PENDING, StateBadges::STATE_PENDING,
$hostgroup->hosts_pending, $hostGroup->hosts_pending,
array( array(
'host_state' => 99, 'host_state' => 99,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'host_severity' 'sort' => 'host_severity'
), ),
'List %u host that is currently in state PENDING in the host group "%s"', '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"', 'List %u hosts which are currently in state PENDING in the host group "%s"',
array($hostgroup->hosts_pending, $hostgroup->hostgroup_alias) array($hostGroup->hosts_pending, $hostGroup->hostgroup_alias)
); );
echo $stateBadges->render(); echo $stateBadges->render();
?> ?>
</td> </td>
<td class="count-col"> <td class="count-col">
<?= $this->qlink( <?= $this->qlink(
$hostgroup->services_total, $hostGroup->services_total,
'monitoring/list/services', 'monitoring/list/services',
array('hostgroup_name' => $hostgroup->hostgroup_name), array('hostgroup_name' => $hostGroup->hostgroup_name),
array('title' => sprintf( array('title' => sprintf(
$this->translate('List all services of all hosts in host group "%s"'), $this->translate('List all services of all hosts in host group "%s"'),
$hostgroup->hostgroup_alias $hostGroup->hostgroup_alias
), 'class' => 'badge') ), 'class' => 'badge')
) ?> ) ?>
</td> </td>
<td> <td>
<?php <?php
$stateBadges = new StateBadges(); $stateBadges = new StateBadges();
$stateBadges $stateBadges
->setUrl('monitoring/list/services') ->setUrl('monitoring/list/services')
->add( ->add(
StateBadges::STATE_OK, StateBadges::STATE_OK,
$hostgroup->services_ok, $hostGroup->services_ok,
array( array(
'service_state' => 0, 'service_state' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state OK on hosts in the host group "%s"', 'List %u service that is currently in state OK on hosts in the host group "%s"',
'List %u services which are currently in state OK on hosts in the host group "%s"', 'List %u services which are currently in state OK on hosts in the host group "%s"',
array($hostgroup->services_ok, $hostgroup->hostgroup_alias) array($hostGroup->services_ok, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_CRITICAL, StateBadges::STATE_CRITICAL,
$hostgroup->services_critical_unhandled, $hostGroup->services_critical_unhandled,
array( array(
'service_state' => 2, 'service_state' => 2,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state CRITICAL on hosts in the host group "%s"', 'List %u service that is currently in state CRITICAL on hosts in the host group "%s"',
'List %u services which are currently in state CRITICAL on hosts in the host group "%s"', 'List %u services which are currently in state CRITICAL on hosts in the host group "%s"',
array($hostgroup->services_critical_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->services_critical_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_CRITICAL_HANDLED, StateBadges::STATE_CRITICAL_HANDLED,
$hostgroup->services_critical_handled, $hostGroup->services_critical_handled,
array( array(
'service_state' => 2, 'service_state' => 2,
'service_handled' => 1, 'service_handled' => 1,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"', 'List %u service that is currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
'List %u services which are currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"', 'List %u services which are currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
array($hostgroup->services_critical_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->services_critical_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNKNOWN, StateBadges::STATE_UNKNOWN,
$hostgroup->services_unknown_unhandled, $hostGroup->services_unknown_unhandled,
array( array(
'service_state' => 3, 'service_state' => 3,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state UNKNOWN on hosts in the host group "%s"', 'List %u service that is currently in state UNKNOWN on hosts in the host group "%s"',
'List %u services which are currently in state UNKNOWN on hosts in the host group "%s"', 'List %u services which are currently in state UNKNOWN on hosts in the host group "%s"',
array($hostgroup->services_unknown_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->services_unknown_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNKNOWN_HANDLED, StateBadges::STATE_UNKNOWN_HANDLED,
$hostgroup->services_unknown_handled, $hostGroup->services_unknown_handled,
array( array(
'service_state' => 3, 'service_state' => 3,
'service_handled' => 1, 'service_handled' => 1,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"', 'List %u service that is currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
'List %u services which are currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"', 'List %u services which are currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
array($hostgroup->services_unknown_handled, $hostgroup->hostgroup_alias) array($hostGroup->services_unknown_handled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_WARNING, StateBadges::STATE_WARNING,
$hostgroup->services_warning_unhandled, $hostGroup->services_warning_unhandled,
array( array(
'service_state' => 1, 'service_state' => 1,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state WARNING on hosts in the host group "%s"', 'List %u service that is currently in state WARNING on hosts in the host group "%s"',
'List %u services which are currently in state WARNING on hosts in the host group "%s"', 'List %u services which are currently in state WARNING on hosts in the host group "%s"',
array($hostgroup->services_warning_unhandled, $hostgroup->hostgroup_alias) array($hostGroup->services_warning_unhandled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_WARNING_HANDLED, StateBadges::STATE_WARNING_HANDLED,
$hostgroup->services_warning_handled, $hostGroup->services_warning_handled,
array( array(
'service_state' => 1, 'service_state' => 1,
'service_handled' => 1, 'service_handled' => 1,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state WARNING (Acknowledged) on hosts in the host group "%s"', 'List %u service that is currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
'List %u services which are currently in state WARNING (Acknowledged) on hosts in the host group "%s"', 'List %u services which are currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
array($hostgroup->services_warning_handled, $hostgroup->hostgroup_alias) array($hostGroup->services_warning_handled, $hostGroup->hostgroup_alias)
) )
->add( ->add(
StateBadges::STATE_PENDING, StateBadges::STATE_PENDING,
$hostgroup->services_pending, $hostGroup->services_pending,
array( array(
'service_state' => 99, 'service_state' => 99,
'hostgroup_name' => $hostgroup->hostgroup_name, 'hostgroup_name' => $hostGroup->hostgroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %u service that is currently in state PENDING on hosts in the host group "%s"', 'List %u service that is currently in state PENDING on hosts in the host group "%s"',
'List %u services which are currently in state PENDING on hosts in the host group "%s"', 'List %u services which are currently in state PENDING on hosts in the host group "%s"',
array($hostgroup->services_pending, $hostgroup->hostgroup_alias) array($hostGroup->services_pending, $hostGroup->hostgroup_alias)
); );
echo $stateBadges->render(); echo $stateBadges->render();
?> ?>
</td> </td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<?php if ($hostgroups->hasMore()): ?> <?php if ($hostGroups->hasMore()): ?>
<?= $this->qlink( <div class="action-links">
$this->translate('Show More'), <?= $this->qlink(
$this->url()->without(array('view', 'limit')), $this->translate('Show More'),
null, $this->url()->without(array('view', 'limit')),
array( null,
'data-base-target' => '_next', array(
'class' => 'pull-right action-link' 'class' => 'action-link',
) 'data-base-target' => '_next'
) ?> )
) ?>
</div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -4,14 +4,12 @@ use Icinga\Module\Monitoring\Object\Host;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->render('list/components/hostssummary.phtml') ?>
<?= $this->render('list/components/hostssummary.phtml') ?> <?= $this->render('list/components/selectioninfo.phtml') ?>
<?= $this->render('list/components/selectioninfo.phtml') ?> <?= $this->paginator ?>
</div> <div class="sort-controls-container">
<div class="grid">
<?= $this->sortBox ?>
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
@ -19,7 +17,7 @@ if (! $this->compact): ?>
<div class="content"> <div class="content">
<?php if (! $hosts->hasResult()): ?> <?php if (! $hosts->hasResult()): ?>
<p><?= $this->translate('No hosts found matching the filter.') ?></p> <p><?= $this->translate('No hosts found matching the filter.') ?></p>
</div> </div>
<?php return; endif ?> <?php return; endif ?>
<table data-base-target="_next" <table data-base-target="_next"
class="table-row-selectable state-table multiselect" class="table-row-selectable state-table multiselect"
@ -96,14 +94,14 @@ if (! $this->compact): ?>
</tbody> </tbody>
</table> </table>
<?php if ($hosts->hasMore()): ?> <?php if ($hosts->hasMore()): ?>
<div class="text-right"> <div class="action-links">
<?= $this->qlink( <?= $this->qlink(
$this->translate('Show More'), $this->translate('Show More'),
$this->url()->without(array('view', 'limit')), $this->url()->without(array('view', 'limit')),
null, null,
array( array(
'data-base-target' => '_next', 'class' => 'action-link',
'class' => 'action-link' 'data-base-target' => '_next'
) )
) ?> ) ?>
</div> </div>

View File

@ -4,11 +4,11 @@ use Icinga\Module\Monitoring\Object\Service;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
@ -77,7 +77,7 @@ if (! $this->compact): ?>
</tbody> </tbody>
</table> </table>
<?php if ($notifications->hasMore()): ?> <?php if ($notifications->hasMore()): ?>
<div class="text-right"> <div class="action-links">
<?= $this->qlink( <?= $this->qlink(
$this->translate('Show More'), $this->translate('Show More'),
$this->url(isset($notificationsUrl) ? $notificationsUrl : null)->without(array('view', 'limit')), $this->url(isset($notificationsUrl) ? $notificationsUrl : null)->without(array('view', 'limit')),

View File

@ -2,173 +2,175 @@
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->paginator ?>
<?= $this->sortBox ?> <div class="sort-controls-container">
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<?php if (! $servicegroups->hasResult()): ?> <?php if (! $serviceGroups->hasResult()): ?>
<p><?= $this->translate('No service groups found matching the filter.') ?></p> <p><?= $this->translate('No service groups found matching the filter.') ?></p>
</div> </div>
<?php return; endif ?> <?php return; endif ?>
<table class="table-row-selectable common-table" data-base-target="_next"> <table class="table-row-selectable common-table" data-base-target="_next">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th><?= $this->translate('Service Group') ?></th> <th><?= $this->translate('Service Group') ?></th>
<th><?= $this->translate('Service States') ?></th> <th><?= $this->translate('Service States') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($servicegroups->peekAhead($this->compact) as $serviceGroup): ?> <?php foreach ($serviceGroups->peekAhead($this->compact) as $serviceGroup): ?>
<tr> <tr>
<td class="count-col"> <td class="count-col">
<span class="badge"><?= $serviceGroup->services_total ?></span> <span class="badge"><?= $serviceGroup->services_total ?></span>
</td> </td>
<th> <th>
<?= $this->qlink( <?= $this->qlink(
$serviceGroup->servicegroup_alias, $serviceGroup->servicegroup_alias,
'monitoring/list/services', 'monitoring/list/services',
array('servicegroup_name' => $serviceGroup->servicegroup_name), array('servicegroup_name' => $serviceGroup->servicegroup_name),
array('title' => sprintf($this->translate('List all services in the group "%s"'), $serviceGroup->servicegroup_alias)) array('title' => sprintf($this->translate('List all services in the group "%s"'), $serviceGroup->servicegroup_alias))
) ?> ) ?>
</th> </th>
<td> <td>
<?php <?php
$stateBadges = new StateBadges(); $stateBadges = new StateBadges();
$stateBadges $stateBadges
->setUrl('monitoring/list/services') ->setUrl('monitoring/list/services')
->setBaseFilter($this->filterEditor->getFilter()) ->setBaseFilter($this->filterEditor->getFilter())
->add( ->add(
StateBadges::STATE_OK, StateBadges::STATE_OK,
$serviceGroup->services_ok, $serviceGroup->services_ok,
array( array(
'service_state' => 0, 'service_state' => 0,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state OK in service group "%s"', 'List %s service that is currently in state OK in service group "%s"',
'List %s services which are currently in state OK in service group "%s"', 'List %s services which are currently in state OK in service group "%s"',
array($serviceGroup->services_ok, $serviceGroup->servicegroup_alias) array($serviceGroup->services_ok, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_CRITICAL, StateBadges::STATE_CRITICAL,
$serviceGroup->services_critical_unhandled, $serviceGroup->services_critical_unhandled,
array( array(
'service_state' => 2, 'service_state' => 2,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state CRITICAL in service group "%s"', 'List %s service that is currently in state CRITICAL in service group "%s"',
'List %s services which are currently in state CRITICAL in service group "%s"', 'List %s services which are currently in state CRITICAL in service group "%s"',
array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_CRITICAL_HANDLED, StateBadges::STATE_CRITICAL_HANDLED,
$serviceGroup->services_critical_handled, $serviceGroup->services_critical_handled,
array( array(
'service_state' => 2, 'service_state' => 2,
'service_handled' => 1, 'service_handled' => 1,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"', 'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"',
'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"', 'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"',
array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNKNOWN, StateBadges::STATE_UNKNOWN,
$serviceGroup->services_unknown_unhandled, $serviceGroup->services_unknown_unhandled,
array( array(
'service_state' => 3, 'service_state' => 3,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state UNKNOWN in service group "%s"', 'List %s service that is currently in state UNKNOWN in service group "%s"',
'List %s services which are currently in state UNKNOWN in service group "%s"', 'List %s services which are currently in state UNKNOWN in service group "%s"',
array($serviceGroup->services_unknown_unhandled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_unknown_unhandled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_UNKNOWN_HANDLED, StateBadges::STATE_UNKNOWN_HANDLED,
$serviceGroup->services_unknown_handled, $serviceGroup->services_unknown_handled,
array( array(
'service_state' => 3, 'service_state' => 3,
'service_handled' => 1, 'service_handled' => 1,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"', 'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"',
'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"', 'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"',
array($serviceGroup->services_unknown_handled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_unknown_handled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_WARNING, StateBadges::STATE_WARNING,
$serviceGroup->services_warning_unhandled, $serviceGroup->services_warning_unhandled,
array( array(
'service_state' => 1, 'service_state' => 1,
'service_acknowledged' => 0, 'service_acknowledged' => 0,
'service_in_downtime' => 0, 'service_in_downtime' => 0,
'host_problem' => 0, 'host_problem' => 0,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state WARNING in service group "%s"', 'List %s service that is currently in state WARNING in service group "%s"',
'List %s services which are currently in state WARNING in service group "%s"', 'List %s services which are currently in state WARNING in service group "%s"',
array($serviceGroup->services_warning_unhandled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_warning_unhandled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_WARNING_HANDLED, StateBadges::STATE_WARNING_HANDLED,
$serviceGroup->services_warning_handled, $serviceGroup->services_warning_handled,
array( array(
'service_state' => 1, 'service_state' => 1,
'service_handled' => 1, 'service_handled' => 1,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"', 'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"',
'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"', 'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"',
array($serviceGroup->services_warning_handled, $serviceGroup->servicegroup_alias) array($serviceGroup->services_warning_handled, $serviceGroup->servicegroup_alias)
) )
->add( ->add(
StateBadges::STATE_PENDING, StateBadges::STATE_PENDING,
$serviceGroup->services_pending, $serviceGroup->services_pending,
array( array(
'service_state' => 99, 'service_state' => 99,
'servicegroup_name' => $serviceGroup->servicegroup_name, 'servicegroup_name' => $serviceGroup->servicegroup_name,
'sort' => 'service_severity' 'sort' => 'service_severity'
), ),
'List %s service that is currenlty in state PENDING in service group "%s"', 'List %s service that is currenlty in state PENDING in service group "%s"',
'List %s services which are currently in state PENDING in service group "%s"', 'List %s services which are currently in state PENDING in service group "%s"',
array($serviceGroup->services_pending, $serviceGroup->servicegroup_alias) array($serviceGroup->services_pending, $serviceGroup->servicegroup_alias)
); );
echo $stateBadges->render(); echo $stateBadges->render();
?> ?>
</td> </td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
<?php if ($servicegroups->hasMore()): ?> <?php if ($serviceGroups->hasMore()): ?>
<?= $this->qlink( <div class="action-links">
$this->translate('Show More'), <?= $this->qlink(
$this->url()->without(array('view', 'limit')), $this->translate('Show More'),
null, $this->url()->without(array('view', 'limit')),
array( null,
'data-base-target' => '_next', array(
'class' => 'pull-right action-link' 'class' => 'action-link',
) 'data-base-target' => '_next'
) ?> )
) ?>
</div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -5,14 +5,12 @@ use Icinga\Module\Monitoring\Object\Service;
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs ?> <?= $this->tabs ?>
<div class="grid"> <?= $this->render('list/components/servicesummary.phtml') ?>
<?= $this->render('list/components/servicesummary.phtml') ?> <?= $this->render('list/components/selectioninfo.phtml') ?>
<?= $this->render('list/components/selectioninfo.phtml') ?> <?= $this->paginator ?>
</div> <div class="sort-controls-container">
<div class="grid">
<?= $this->sortBox ?>
<?= $this->limiter ?> <?= $this->limiter ?>
<?= $this->paginator ?> <?= $this->sortBox ?>
</div> </div>
<?= $this->filterEditor ?> <?= $this->filterEditor ?>
</div> </div>
@ -101,16 +99,16 @@ if (! $this->compact): ?>
</tbody> </tbody>
</table> </table>
<?php if ($services->hasMore()): ?> <?php if ($services->hasMore()): ?>
<div class="text-right"> <div class="action-links">
<?= $this->qlink( <?= $this->qlink(
$this->translate('Show More'), $this->translate('Show More'),
$this->url()->without(array('view', 'limit')), $this->url()->without(array('view', 'limit')),
null, null,
array( array(
'data-base-target' => '_next', 'class' => 'action-link',
'class' => 'action-link' 'data-base-target' => '_next'
) )
) ?> ) ?>
</div> </div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -16,12 +16,12 @@ class ContactgroupQuery extends IdoQuery
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $groupBase = array('contactgroups' => array('cg.contactgroup_id', 'cgo.object_id')); protected $groupBase = array('contactgroups' => array('cg.contactgroup_id'));
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $groupOrigin = array('contacts', 'hosts', 'services'); protected $groupOrigin = array('hosts', 'members', 'services');
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -32,28 +32,8 @@ class ContactgroupQuery extends IdoQuery
'contactgroup_name' => 'cgo.name1', 'contactgroup_name' => 'cgo.name1',
'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci' 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci'
), ),
'contacts' => array( 'members' => array(
'contact_id' => 'c.contact_id', 'contact_count' => 'COUNT(cgm.contactgroup_member_id)'
'contact' => 'co.name1 COLLATE latin1_general_ci',
'contact_name' => 'co.name1',
'contact_alias' => 'c.alias COLLATE latin1_general_ci',
'contact_email' => 'c.email_address COLLATE latin1_general_ci',
'contact_pager' => 'c.pager_address',
'contact_object_id' => 'c.contact_object_id',
'contact_has_host_notfications' => 'c.host_notifications_enabled',
'contact_has_service_notfications' => 'c.service_notifications_enabled',
'contact_can_submit_commands' => 'c.can_submit_commands',
'contact_notify_service_recovery' => 'c.notify_service_recovery',
'contact_notify_service_warning' => 'c.notify_service_warning',
'contact_notify_service_critical' => 'c.notify_service_critical',
'contact_notify_service_unknown' => 'c.notify_service_unknown',
'contact_notify_service_flapping' => 'c.notify_service_flapping',
'contact_notify_service_downtime' => 'c.notify_service_recovery',
'contact_notify_host_recovery' => 'c.notify_host_recovery',
'contact_notify_host_down' => 'c.notify_host_down',
'contact_notify_host_unreachable' => 'c.notify_host_unreachable',
'contact_notify_host_flapping' => 'c.notify_host_flapping',
'contact_notify_host_downtime' => 'c.notify_host_downtime'
), ),
'hostgroups' => array( 'hostgroups' => array(
'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci', 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
@ -99,22 +79,18 @@ class ContactgroupQuery extends IdoQuery
} }
/** /**
* Join contacts * Join contact group members
*/ */
protected function joinContacts() protected function joinMembers()
{ {
$this->select->joinLeft( $this->select->joinLeft(
array('cgm' => $this->prefix . 'contactgroup_members'), array('cgm' => $this->prefix . 'contactgroup_members'),
'cgm.contactgroup_id = cg.contactgroup_id', 'cgm.contactgroup_id = cg.contactgroup_id',
array() array()
)->joinLeft( )->join(
array('co' => $this->prefix . 'objects'), array('co' => $this->prefix . 'objects'),
'co.object_id = cgm.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10', 'co.object_id = cgm.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10',
array() array()
)->joinLeft(
array('c' => $this->prefix . 'contacts'),
'c.contact_object_id = co.object_id',
array()
); );
} }

View File

@ -11,31 +11,9 @@ class Contactgroup extends DataView
public function getColumns() public function getColumns()
{ {
return array( return array(
'instance_name',
'contactgroup_name', 'contactgroup_name',
'contactgroup_alias', 'contactgroup_alias',
'contact_object_id', 'contact_count'
'contact_id',
'contact_name',
'contact_alias',
'contact_email',
'contact_pager',
'contact_has_host_notfications',
'contact_has_service_notfications',
'contact_can_submit_commands',
'contact_notify_service_recovery',
'contact_notify_service_warning',
'contact_notify_service_critical',
'contact_notify_service_unknown',
'contact_notify_service_flapping',
'contact_notify_service_downtime',
'contact_notify_host_recovery',
'contact_notify_host_down',
'contact_notify_host_unreachable',
'contact_notify_host_flapping',
'contact_notify_host_downtime',
'contact_notify_host_timeperiod',
'contact_notify_service_timeperiod'
); );
} }
@ -60,9 +38,10 @@ class Contactgroup extends DataView
public function getStaticFilterColumns() public function getStaticFilterColumns()
{ {
return array( return array(
'contactgroup', 'contact', 'contactgroup',
'host', 'host_name', 'host_display_name', 'host_alias', 'host', 'host_name', 'host_display_name', 'host_alias',
'hostgroup', 'hostgroup_alias', 'hostgroup_name', 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
'instance_name',
'service', 'service_description', 'service_display_name', 'service', 'service_description', 'service_display_name',
'servicegroup', 'servicegroup_alias', 'servicegroup_name' 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
); );

View File

@ -11,58 +11,56 @@ class HostStatus extends DataView
public function getColumns() public function getColumns()
{ {
return array_merge($this->getHookedColumns(), array( return array_merge($this->getHookedColumns(), array(
'instance_name', 'host_acknowledged',
'host_name', 'host_acknowledgement_type',
'host_display_name', 'host_action_url',
'host_alias', 'host_active_checks_enabled',
'host_active_checks_enabled_changed',
'host_address', 'host_address',
'host_address6', 'host_address6',
'host_state', 'host_alias',
'host_hard_state', 'host_check_command',
'host_state_type',
'host_handled',
'host_unhandled',
'host_in_downtime',
'host_acknowledged',
'host_last_state_change',
'host_last_state_change',
'host_last_notification',
'host_last_check',
'host_next_check',
'host_check_execution_time', 'host_check_execution_time',
'host_check_latency', 'host_check_latency',
'host_output',
'host_long_output',
'host_check_command',
'host_check_timeperiod',
'host_perfdata',
'host_check_source', 'host_check_source',
'host_passive_checks_enabled', 'host_check_timeperiod',
'host_passive_checks_enabled_changed', 'host_current_check_attempt',
'host_obsessing', 'host_current_notification_number',
'host_obsessing_changed', 'host_display_name',
'host_notifications_enabled',
'host_notifications_enabled_changed',
'host_event_handler_enabled', 'host_event_handler_enabled',
'host_event_handler_enabled_changed', 'host_event_handler_enabled_changed',
'host_flap_detection_enabled', 'host_flap_detection_enabled',
'host_flap_detection_enabled_changed', 'host_flap_detection_enabled_changed',
'host_active_checks_enabled', 'host_handled',
'host_active_checks_enabled_changed', 'host_hard_state',
'host_current_check_attempt', 'host_in_downtime',
'host_max_check_attempts',
'host_last_notification',
'host_current_notification_number',
'host_percent_state_change',
'host_is_flapping',
'host_action_url',
'host_notes_url',
'host_percent_state_change',
'host_modified_host_attributes',
'host_severity',
'host_problem',
'host_ipv4', 'host_ipv4',
'host_acknowledgement_type' 'host_is_flapping',
'host_is_reachable',
'host_last_check',
'host_last_notification',
'host_last_state_change',
'host_long_output',
'host_max_check_attempts',
'host_modified_host_attributes',
'host_name',
'host_next_check',
'host_notes_url',
'host_notifications_enabled',
'host_notifications_enabled_changed',
'host_obsessing',
'host_obsessing_changed',
'host_output',
'host_passive_checks_enabled',
'host_passive_checks_enabled_changed',
'host_percent_state_change',
'host_perfdata',
'host_problem',
'host_severity',
'host_state',
'host_state_type',
'host_unhandled',
'instance_name'
)); ));
} }

View File

@ -11,93 +11,91 @@ class ServiceStatus extends DataView
public function getColumns() public function getColumns()
{ {
return array_merge($this->getHookedColumns(), array( return array_merge($this->getHookedColumns(), array(
'instance_name', 'host_acknowledged',
'host_name', 'host_action_url',
'host_display_name', 'host_active_checks_enabled',
'host_state',
'host_hard_state',
'host_state_type',
'host_last_state_change',
'host_address', 'host_address',
'host_address6', 'host_address6',
'host_problem', 'host_alias',
'host_check_source',
'host_display_name',
'host_handled', 'host_handled',
'service_description', 'host_hard_state',
'service_display_name',
'service_state',
'service_hard_state',
'service_in_downtime',
'service_acknowledged',
'service_handled',
'service_unhandled',
'service_output',
'service_last_state_change',
'service_long_output',
'service_is_flapping',
'service_state_type',
'service_severity',
'service_last_check',
'service_notifications_enabled',
'service_notifications_enabled_changed',
'service_action_url',
'service_notes',
'service_notes_url',
'service_last_check',
'service_next_check',
'service_attempt',
'service_last_notification',
'service_check_command',
'service_current_notification_number',
'host_acknowledged',
'host_output',
'host_long_output',
'host_in_downtime', 'host_in_downtime',
'host_ipv4',
'host_is_flapping', 'host_is_flapping',
'host_last_check', 'host_last_check',
'host_notifications_enabled',
'host_unhandled_service_count',
'host_action_url',
'host_notes_url',
'host_display_name',
'host_alias',
'host_ipv4',
'host_severity',
'host_perfdata',
'host_check_source',
'host_active_checks_enabled',
'host_passive_checks_enabled',
'host_last_hard_state', 'host_last_hard_state',
'host_last_hard_state_change', 'host_last_hard_state_change',
'host_last_time_up', 'host_last_state_change',
'host_last_time_down', 'host_last_time_down',
'host_last_time_unreachable', 'host_last_time_unreachable',
'host_last_time_up',
'host_long_output',
'host_modified_host_attributes', 'host_modified_host_attributes',
'service_hard_state', 'host_name',
'service_problem', 'host_notes_url',
'service_perfdata', 'host_notifications_enabled',
'service_check_source', 'host_output',
'service_check_timeperiod', 'host_passive_checks_enabled',
'host_perfdata',
'host_problem',
'host_severity',
'host_state',
'host_state_type',
'host_unhandled_service_count',
'instance_name',
'service_acknowledged',
'service_acknowledgement_type',
'service_action_url',
'service_active_checks_enabled', 'service_active_checks_enabled',
'service_active_checks_enabled_changed', 'service_active_checks_enabled_changed',
'service_passive_checks_enabled', 'service_attempt',
'service_passive_checks_enabled_changed', 'service_check_command',
'service_last_hard_state', 'service_check_source',
'service_last_hard_state_change', 'service_check_timeperiod',
'service_last_time_ok',
'service_last_time_warning',
'service_last_time_critical',
'service_last_time_unknown',
'service_current_check_attempt', 'service_current_check_attempt',
'service_max_check_attempts', 'service_current_notification_number',
'service_obsessing', 'service_description',
'service_obsessing_changed', 'service_display_name',
'service_event_handler_enabled', 'service_event_handler_enabled',
'service_event_handler_enabled_changed', 'service_event_handler_enabled_changed',
'service_flap_detection_enabled', 'service_flap_detection_enabled',
'service_flap_detection_enabled_changed', 'service_flap_detection_enabled_changed',
'service_modified_service_attributes', 'service_handled',
'service_hard_state',
'service_host_name', 'service_host_name',
'service_acknowledgement_type', 'service_in_downtime',
'service_is_flapping',
'service_is_reachable',
'service_last_check',
'service_last_hard_state',
'service_last_hard_state_change',
'service_last_notification',
'service_last_state_change',
'service_last_time_critical',
'service_last_time_ok',
'service_last_time_unknown',
'service_last_time_warning',
'service_long_output',
'service_max_check_attempts',
'service_modified_service_attributes',
'service_next_check',
'service_notes',
'service_notes_url',
'service_notifications_enabled',
'service_notifications_enabled_changed',
'service_obsessing',
'service_obsessing_changed',
'service_output',
'service_passive_checks_enabled',
'service_passive_checks_enabled_changed',
'service_perfdata',
'service_problem',
'service_severity',
'service_state',
'service_state_type',
'service_unhandled'
)); ));
} }

View File

@ -257,6 +257,7 @@ class StateBadges extends AbstractWidget
$groupItem = new NavigationItem( $groupItem = new NavigationItem(
uniqid(), uniqid(),
array( array(
'cssClass' => 'state-badge-group',
'label' => '', 'label' => '',
'priority' => $this->priority++ 'priority' => $this->priority++
) )

View File

@ -45,16 +45,27 @@
padding-right: 0; padding-right: 0;
} }
} }
}
// Multi-selection info .state-badge-group li {
.selection-info { margin-right: 1px;
float: right; }
font-size: @font-size-small;
padding: @vertical-padding / 2 0;
&:hover { .state-badge-group li:last-child {
cursor: help; margin-right: 0;
}
.state-badge-group .badge {
border-radius: 0;
}
.state-badge-group li:first-child > .badge {
border-top-left-radius: 0.4em;
border-bottom-left-radius: 0.4em;
}
.state-badge-group li:last-child > .badge {
border-top-right-radius: 0.4em;
border-bottom-right-radius: 0.4em;
} }
} }
@ -79,6 +90,11 @@
} }
} }
.controls > .hosts-summary,
.controls > .services-summary {
float: left;
}
// State table in the host and service multi-selection and detail views // State table in the host and service multi-selection and detail views
.host-detail-state, .host-detail-state,
.service-detail-state { .service-detail-state {
@ -94,10 +110,6 @@
/* Generic box element */ /* Generic box element */
.boxview a {
text-decoration: none;
}
.boxview > div.box { .boxview > div.box {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
@ -126,7 +138,6 @@
/* Any line of a box entry */ /* Any line of a box entry */
.boxview div.box.entry a { .boxview div.box.entry a {
display: block; display: block;
color: inherit;
} }
.boxview div.box.badge { .boxview div.box.badge {
@ -370,13 +381,7 @@ div.timeline {
a { a {
font-weight: bold; font-weight: bold;
text-decoration: none;
white-space: nowrap; white-space: nowrap;
&:hover {
text-decoration: underline;
}
} }
} }

View File

@ -78,13 +78,32 @@
font-size: @font-size-small; font-size: @font-size-small;
} }
// Plugin output in overviews // Plugin output in detail views
.plugin-output,
// Plugin output in overvies
.overview-plugin-output {
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
overflow-wrap: break-word;
word-wrap: break-word;
}
// Plugin output in overvies
.overview-plugin-output { .overview-plugin-output {
color: @text-color-light; color: @text-color-light;
font-family: @font-family-fixed; font-family: @font-family-fixed;
font-size: @font-size-small; font-size: @font-size-small;
margin: 0; margin: 0;
white-space: pre-wrap; white-space: pre-wrap;
// Long text in table cells overflows the table's width if the table's layout is not fixed.
// Thus overflow-wrap will not have any effect. But w/ the following we set a width of any value
// plus a min-width of 100% to consume the full width nonetheless which seems to always
// instruct browsers to not overflow the table. Ridiculous.
min-width: 100%;
width: 1em;
} }
// Table for performance data in detail views // Table for performance data in detail views

View File

@ -284,6 +284,7 @@ class GettextTranslationHelper
'--keyword=translatePlural:1,2', '--keyword=translatePlural:1,2',
'--keyword=translatePlural:1,2,4c', '--keyword=translatePlural:1,2,4c',
'--keyword=mt:2', '--keyword=mt:2',
'--keyword=mt:2,3c',
'--keyword=mtp:2,3', '--keyword=mtp:2,3',
'--keyword=mtp:2,3,5c', '--keyword=mtp:2,3,5c',
'--keyword=t', '--keyword=t',

View File

@ -44,7 +44,7 @@
@tr-hover-color: #F5FDFF; @tr-hover-color: #F5FDFF;
// Font families // Font families
@font-family: Calibri, Helvetica, sans-serif; @font-family: Calibri, "Lucida Grande", sans-serif;
@font-family-fixed: "Liberation Mono", "Lucida Console", Courier, monospace; @font-family-fixed: "Liberation Mono", "Lucida Console", Courier, monospace;
@font-family-wide: Tahoma, Verdana, sans-serif; @font-family-wide: Tahoma, Verdana, sans-serif;
@ -80,15 +80,19 @@ a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
&:focus {
outline-color: @icinga-blue;
}
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
} }
a:focus,
button:focus,
input[type="checkbox"]:focus {
outline-color: @icinga-blue;
outline-style: solid; // IE
outline-style: auto;
}
// Default margin for block text // Default margin for block text
blockquote, p, pre { blockquote, p, pre {
margin: 0 0 1em 0; margin: 0 0 1em 0;

View File

@ -1,7 +1,49 @@
/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ /*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
// TODO(el): Rename .filter to .filter-control
// Hide auto sumbit info in controls but keep information for screen readers
.controls .autosubmit-info {
.sr-only();
}
// Backend selection control in user and group list views
.backend-selection {
float: left;
> .control-group {
padding: 0;
> .control-label-group {
text-align: left;
width: auto;
}
}
}
.backend-selection,
.pagination-control,
.selection-info,
.sort-controls-container {
margin-bottom: 0.5em;
}
.backend-selection,
.pagination-control,
.sort-controls-container {
// Select controls may not respect font-size thus leading to improperly vertical alignment w/o big enough line-height
line-height: 2em;
}
.filter {
// Display filter control on a new line
clear: both;
}
.limiter-control > .control-group { .limiter-control > .control-group {
padding: 0; padding: 0;
// Note that the sort-control form does not have padding as it's utilizing different decorators
> .control-label-group { > .control-label-group {
text-align: left; text-align: left;
@ -18,7 +60,17 @@
} }
} }
.limiter-control,
.sort-control {
// Display limiter and sort control both on the same line; floating could be used here too to achieve the same
display: inline-block;
}
.pagination-control { .pagination-control {
// Display the pagination-control on a new line
clear: both;
float: left;
li { li {
&.active { &.active {
> a, > a,
@ -57,7 +109,19 @@
} }
} }
// Multi-selection info
.selection-info {
float: right;
font-size: @font-size-small;
&:hover {
cursor: help;
}
}
.sort-control { .sort-control {
margin-left: 0.25em;
label { label {
width: auto; width: auto;
margin-right: 0.5em; margin-right: 0.5em;
@ -72,10 +136,16 @@
width: 8em; width: 8em;
margin-left: 0; margin-left: 0;
} }
}
> form > i { .sort-controls-container {
.sr-only(); clear: right;
} float: right;
}
.sort-direction-control {
margin-left: 0.25em;
width: 1em;
} }
html.no-js .sort-control form { html.no-js .sort-control form {

View File

@ -83,6 +83,10 @@ form.inline {
margin-left: 10em; margin-left: 10em;
} }
.autosubmit-info {
margin-left: 0.5em;
}
button:hover .icon-cancel { button:hover .icon-cancel {
color: @color-critical; color: @color-critical;
} }

View File

@ -76,7 +76,6 @@
@gutter: 1em; @gutter: 1em;
// x-column-layout // x-column-layout
#main { #main {
.clearfix(); .clearfix();
@ -109,6 +108,22 @@
} }
} }
// Mobile menu
#mobile-menu-toggle {
text-align: right;
> button {
background: none;
border: none;
height: 2.5em;
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
}
}
#sidebar { #sidebar {
background-color: @gray-lighter; background-color: @gray-lighter;
} }

View File

@ -3,8 +3,6 @@
// Login page styles // Login page styles
#login { #login {
font-size: 1em;
#icinga-logo { #icinga-logo {
.fadein(); .fadein();
} }

View File

@ -21,6 +21,7 @@
} }
.icon-col { .icon-col {
text-align: center;
width: 1em; width: 1em;
} }
@ -68,6 +69,16 @@ a:hover > .icon-cancel {
.button-link { .button-link {
.action-link(); .action-link();
.rounded-corners(3px);
background: @gray-lighter;
display: inline-block;
padding: 0.25em 0.5em;
&:hover {
background: @gray-lightest;
text-decoration: none;
}
} }
// List styles // List styles
@ -177,41 +188,6 @@ a:hover > .icon-cancel {
width: @name-value-table-name-width; width: @name-value-table-name-width;
} }
// TODO(el): Fix
.controls {
.limiter-control {
float: right;
padding: @vertical-padding / 2 0;
margin-bottom: 0.25em;
}
.pagination-control {
padding: @vertical-padding / 2 0;
margin-bottom: 0.25em;
}
.sort-control {
float: right;
padding: @vertical-padding / 2 0;
margin-bottom: 0.25em;
margin-left: 0.5em;
}
.sort-direction-control {
margin-bottom: 0.5em;
margin-left: 0.5em;
width: 1em;
}
.filter {
margin-bottom: 0.5em;
}
.selection-info {
margin-left: 0.5em;
}
}
/* Styles for centering content of unknown width and height both horizontally and vertically /* Styles for centering content of unknown width and height both horizontally and vertically
* *
* Example markup: * Example markup:

View File

@ -56,9 +56,7 @@
} }
.rounded-corners(@border-radius: 0.4em) { .rounded-corners(@border-radius: 0.4em) {
-webkit-border-radius: @border-radius; border-radius: @border-radius;
-moz-border-radius: @border-radius;
border-radius: @border-radius;
-webkit-background-clip: padding-box; -webkit-background-clip: padding-box;
-moz-background-clip: padding; -moz-background-clip: padding;

View File

@ -29,10 +29,6 @@
> a { > a {
color: @body-bg-color; color: @body-bg-color;
&:hover {
text-decoration: underline;
}
} }
&.active > a, &.active > a,

View File

@ -59,3 +59,8 @@
#menu .active > a { #menu .active > a {
text-decoration: underline; text-decoration: underline;
} }
.boxview a:focus {
color: @text-color;
text-decoration: underline;
}

View File

@ -55,6 +55,20 @@
* @returns {string|NULL} The content to be rendered, or NULL, when nothing should be changed * @returns {string|NULL} The content to be rendered, or NULL, when nothing should be changed
*/ */
Form.prototype.renderHook = function(content, $container, action, autorefresh) { Form.prototype.renderHook = function(content, $container, action, autorefresh) {
if ($container.attr('id') === 'menu') {
var $search = $container.find('#search');
if ($search[0] === document.activeElement) {
return null;
}
var search = $container.find('#search').val();
if (search.length) {
var $content = $('<div></div>').append(content);
$content.find('#search').attr('value', search).addClass('active');
return $content.html();
}
return content;
}
var origFocus = document.activeElement; var origFocus = document.activeElement;
var containerId = $container.attr('id'); var containerId = $container.attr('id');
var icinga = this.icinga; var icinga = this.icinga;

View File

@ -421,7 +421,7 @@
var $target; var $target;
var formerUrl; var formerUrl;
var remote = /^(?:[a-z]+:)\/\//; var remote = /^(?:[a-z]+:)\/\//;
if (href.match(/^(mailto|javascript):/)) { if (href.match(/^(mailto|javascript|data):/)) {
return true; return true;
} }

View File

@ -100,9 +100,6 @@
var $oldLink = $(this); var $oldLink = $(this);
if ($oldLink.hasAttr('type') && $oldLink.attr('type').indexOf('css') > -1) { if ($oldLink.hasAttr('type') && $oldLink.attr('type').indexOf('css') > -1) {
var base = location.protocol + '//' + location.host; var base = location.protocol + '//' + location.host;
if (location.port) {
base += location.port;
}
var url = icinga.utils.addUrlParams( var url = icinga.utils.addUrlParams(
$(this).attr('href'), $(this).attr('href'),
{ id: new Date().getTime() } { id: new Date().getTime() }
@ -587,7 +584,7 @@
}); });
if ($layout.hasClass('minimal-layout')) { if ($layout.hasClass('minimal-layout')) {
if (! this.mobileMenu) { if (! this.mobileMenu && $sidebar.length) {
$header.css({ $header.css({
top: $sidebar.outerHeight() + 'px' top: $sidebar.outerHeight() + 'px'
}); });
@ -599,19 +596,18 @@
zIndex: 2 zIndex: 2
}); });
$sidebar $sidebar
.on(
'click',
this.toggleMobileMenu
)
.prepend(
$('<div id="mobile-menu-toggle"><button><i class="icon-menu"></i></button></div>')
)
.css({ .css({
paddingBottom: 32, paddingBottom: $('#mobile-menu-toggle').height(),
top: 0, top: 0,
zIndex: 3 zIndex: 3
}) });
.on('click', this.toggleMobileMenu)
.prepend(
$('<i id="mobile-menu-toggle" class="icon-menu"></i>').css({
cursor: 'pointer',
display: 'block',
height: '32px'
})
);
$search.on('keypress', this.closeMobileMenu); $search.on('keypress', this.closeMobileMenu);
this.mobileMenu = true; this.mobileMenu = true;