Merge branch 'master' into bugfix/rebuild-form-builder-5525

Conflicts:
	application/controllers/AuthenticationController.php
	application/controllers/ConfigController.php
	application/forms/Authentication/LoginForm.php
	application/forms/Preference/GeneralForm.php
	modules/monitoring/application/controllers/ChartController.php
This commit is contained in:
Johannes Meyer 2014-08-20 13:13:50 +02:00
commit bb7972aa39
124 changed files with 3291 additions and 1426 deletions

3
.gitignore vendored
View File

@ -36,6 +36,9 @@ config/preferences/*.ini
# Application logfiles # Application logfiles
var/log/*.log var/log/*.log
# Packaging
/debian /debian
*.tar.gz
*.komodoproject

View File

@ -1,6 +1,6 @@
object CheckCommand "dummy-host" { object CheckCommand "dummy-host" {
import "plugin-check-command" import "plugin-check-command"
command = [ PluginDir + "/libexec/test_hostcheck.pl" ] command = [ PluginDir + "/test_hostcheck.pl" ]
arguments = { arguments = {
"--type" = "$check_type$" "--type" = "$check_type$"
"--failchance" = "$check_failchance$" "--failchance" = "$check_failchance$"
@ -18,7 +18,7 @@ object CheckCommand "dummy-host" {
object CheckCommand "dummy-service" { object CheckCommand "dummy-service" {
import "plugin-check-command" import "plugin-check-command"
command = [ PluginDir + "/libexec/test_servicecheck.pl" ] command = [ PluginDir + "/test_servicecheck.pl" ]
arguments = { arguments = {
"--total-critical-on-host" = "$check_critical_on_host$" "--total-critical-on-host" = "$check_critical_on_host$"
"--total-warning-on-host" = "$check_warning_on_host$" "--total-warning-on-host" = "$check_warning_on_host$"

View File

@ -18,10 +18,15 @@ title = "Configuration"
url = "config" url = "config"
priority = 300 priority = 300
[System.Modules]
title = "Modules"
url = "config/modules"
priority = 400
[System.ApplicationLog] [System.ApplicationLog]
title = "Application log" title = "Application log"
url = "list/applicationlog" url = "list/applicationlog"
priority = 400 priority = 500
[Logout] [Logout]
url = "authentication/logout" url = "authentication/logout"

View File

@ -0,0 +1,5 @@
[Documentation]
title = "Documentation"
icon = "img/icons/comment.png"
url = "doc"
priority = 80

View File

@ -68,7 +68,7 @@ priority = 70
[Overview.Comments] [Overview.Comments]
title = "Comments" title = "Comments"
url = "monitoring/list/comments" url = "monitoring/list/comments?comment_type=(comment|ack)"
priority = 70 priority = 70
[Overview.Contacts] [Overview.Contacts]

View File

@ -3,10 +3,14 @@ include mysql
include pgsql include pgsql
include openldap include openldap
Exec { path => '/bin:/usr/bin:/sbin' } Exec { path => '/bin:/usr/bin:/sbin:/usr/sbin' }
$icingaVersion = '1.11.2' $icingaVersion = '1.11.5'
$icinga2Version = '2.0.0' $icinga2Version = '2.0.1'
$pluginVersion = '2.0'
$livestatusVersion = '1.2.4p5'
$phantomjsVersion = '1.9.1'
$casperjsVersion = '1.0.2'
exec { 'create-mysql-icinga-db': exec { 'create-mysql-icinga-db':
unless => 'mysql -uicinga -picinga icinga', unless => 'mysql -uicinga -picinga icinga',
@ -72,10 +76,10 @@ cmmi { 'icinga-mysql':
--with-htmurl=/icinga-mysql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-mysql.conf \ --with-htmurl=/icinga-mysql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-mysql.conf \
--with-cgiurl=/icinga-mysql/cgi-bin \ --with-cgiurl=/icinga-mysql/cgi-bin \
--with-http-auth-file=/usr/share/icinga/htpasswd.users \ --with-http-auth-file=/usr/share/icinga/htpasswd.users \
--with-plugin-dir=/usr/lib64/nagios/plugins/libexec', --with-plugin-dir=/usr/lib64/nagios/plugins',
creates => '/usr/local/icinga-mysql', creates => '/usr/local/icinga-mysql',
make => 'make all && make fullinstall install-config', make => 'make all && make fullinstall install-config',
require => [ User['icinga'], Cmmi['icinga-plugins'], Package['apache'] ], require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ],
notify => Service['apache'] notify => Service['apache']
} }
@ -98,10 +102,10 @@ cmmi { 'icinga-pgsql':
--with-htmurl=/icinga-pgsql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-pgsql.conf \ --with-htmurl=/icinga-pgsql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-pgsql.conf \
--with-cgiurl=/icinga-pgsql/cgi-bin \ --with-cgiurl=/icinga-pgsql/cgi-bin \
--with-http-auth-file=/usr/share/icinga/htpasswd.users \ --with-http-auth-file=/usr/share/icinga/htpasswd.users \
--with-plugin-dir=/usr/lib64/nagios/plugins/libexec', --with-plugin-dir=/usr/lib64/nagios/plugins',
creates => '/usr/local/icinga-pgsql', creates => '/usr/local/icinga-pgsql',
make => 'make all && make fullinstall install-config', make => 'make all && make fullinstall install-config',
require => [ User['icinga'], Cmmi['icinga-plugins'], Package['apache'] ], require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ],
notify => Service['apache'] notify => Service['apache']
} }
@ -206,20 +210,11 @@ exec { 'icinga-htpasswd':
require => Class['apache'] require => Class['apache']
} }
cmmi { 'icinga-plugins': include monitoring-plugins
url => 'https://www.monitoring-plugins.org/download/nagios-plugins-1.5.tar.gz',
output => 'nagios-plugins-1.5.tar.gz',
flags => '--prefix=/usr/lib64/nagios/plugins \
--with-nagios-user=icinga --with-nagios-group=icinga \
--with-cgiurl=/icinga-mysql/cgi-bin',
creates => '/usr/lib64/nagios/plugins/libexec',
make => 'make && make install',
require => User['icinga']
}
cmmi { 'mk-livestatus': cmmi { 'mk-livestatus':
url => 'http://mathias-kettner.de/download/mk-livestatus-1.2.2p1.tar.gz', url => "http://mathias-kettner.de/download/mk-livestatus-${livestatusVersion}.tar.gz",
output => 'mk-livestatus-1.2.2p1.tar.gz', output => "mk-livestatus-${livestatusVersion}.tar.gz",
flags => '--prefix=/usr/local/icinga-mysql --exec-prefix=/usr/local/icinga-mysql', flags => '--prefix=/usr/local/icinga-mysql --exec-prefix=/usr/local/icinga-mysql',
creates => '/usr/local/icinga-mysql/lib/mk-livestatus', creates => '/usr/local/icinga-mysql/lib/mk-livestatus',
make => 'make && make install', make => 'make && make install',
@ -262,14 +257,14 @@ exec { 'populate-openldap':
} }
class { 'phantomjs': class { 'phantomjs':
url => 'https://phantomjs.googlecode.com/files/phantomjs-1.9.1-linux-x86_64.tar.bz2', url => "https://phantomjs.googlecode.com/files/phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2",
output => 'phantomjs-1.9.1-linux-x86_64.tar.bz2', output => "phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2",
creates => '/usr/local/phantomjs' creates => '/usr/local/phantomjs'
} }
class { 'casperjs': class { 'casperjs':
url => 'https://github.com/n1k0/casperjs/tarball/1.0.2', url => "https://github.com/n1k0/casperjs/tarball/${casperjsVersion}",
output => 'casperjs-1.0.2.tar.gz', output => "casperjs-${casperjsVersion}.tar.gz",
creates => '/usr/local/casperjs' creates => '/usr/local/casperjs'
} }
@ -421,11 +416,10 @@ package { 'icinga2-ido-mysql':
exec { 'populate-icinga2-mysql-db': exec { 'populate-icinga2-mysql-db':
unless => 'mysql -uicinga2 -picinga2 icinga2 -e "SELECT * FROM icinga_dbversion;" &> /dev/null', unless => 'mysql -uicinga2 -picinga2 icinga2 -e "SELECT * FROM icinga_dbversion;" &> /dev/null',
command => "mysql -uroot icinga2 < /usr/share/doc/icinga2-ido-mysql-$icinga2Version/schema/mysql.sql", command => 'mysql -uroot icinga2 < /usr/share/icinga2-ido-mysql/schema/mysql.sql',
require => [ Exec['create-mysql-icinga2-db'], Package['icinga2-ido-mysql'] ] require => [ Exec['create-mysql-icinga2-db'], Package['icinga2-ido-mysql'] ]
} }
file { '/etc/icinga2/features-available/ido-mysql.conf': file { '/etc/icinga2/features-available/ido-mysql.conf':
source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf', source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf',
owner => 'icinga', owner => 'icinga',
@ -574,7 +568,7 @@ populate_monitoring_test_config { ['commands', 'contacts', 'dependencies',
} }
define populate_monitoring_test_config_plugins { define populate_monitoring_test_config_plugins {
file { "/usr/lib64/nagios/plugins/libexec/${name}": file { "/usr/lib64/nagios/plugins/${name}":
owner => 'icinga', owner => 'icinga',
group => 'icinga', group => 'icinga',
source => "/usr/local/share/misc/monitoring_test_config/plugins/${name}", source => "/usr/local/share/misc/monitoring_test_config/plugins/${name}",
@ -792,3 +786,15 @@ file { '/etc/bash_completion.d/icingacli':
require => Exec['install bash-completion'] require => Exec['install bash-completion']
} }
file { '/etc/icingaweb/modules/doc/':
ensure => 'directory',
owner => 'apache',
group => 'apache'
}
file { '/etc/icingaweb/modules/doc/menu.ini':
source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/modules/doc/menu.ini',
owner => 'apache',
group => 'apache',
}

View File

@ -0,0 +1,9 @@
class monitoring-plugins {
include epel
# nagios plugins from epel
package { 'nagios-plugins-all':
ensure => installed,
require => Class['epel']
}
}

View File

@ -2,13 +2,59 @@
## Table of Contents ## Table of Contents
1. [Vagrant - Virtual development environment](#vagrant) 0. [General Information](#general information)
1. [Installation](#installation)
2. [Support](#support)
3. [Vagrant - Virtual development environment](#vagrant)
## General Information
`Icinga Web 2` is the next generation monitoring web interface, framework
and CLI tool developed by the [Icinga Project](https://www.icinga.org/community/team/).
Responsive and fast, rewritten from scratch supporting multiple backends and
providing a CLI tool. Compatible with Icinga Core 2.x and 1.x.
Check the Icinga website for some [insights](https://www.icinga.org/icinga/screenshots/icinga-web-2/).
> **Note**
>
> `Icinga Web 2` is still in development and not meant for production deployment.
> Watch the [development roadmap](https://dev.icinga.org/projects/icingaweb2/roadmap)
> and [Icinga website](https://www.icinga.org/) for release schedule updates!
## Installation
Please navigate to [doc/installation.md](doc/installation.md) for updated details.
## Support
Please head over to the [community support channels](https://www.icinga.org/icinga/faq/get-help/)
in case of questions, bugs, etc.
Please make sure to provide the following details:
* OS, distribution, version
* PHP and/or MySQL/PostgreSQL version
* Which browser and its version
* Screenshot and problem description
## Vagrant ## Vagrant
> **Note** that the deployment of the virtual machine is tested against Vagrant starting with version 1.1. ### Requirements
* Vagrant 1.2+
* Virtualbox 4.2.16+
* a fairly powerful hardware (quad core, 4gb ram, fast hdd)
> **Note**
>
> The deployment of the virtual machine is tested against Vagrant starting with version 1.2.
> Unfortunately older versions will not work. > Unfortunately older versions will not work.
### General
The Icinga Web 2 project ships with a Vagrant virtual machine that integrates The Icinga Web 2 project ships with a Vagrant virtual machine that integrates
the source code with various services and example data in a controlled the source code with various services and example data in a controlled
environment. This enables developers and users to test Livestatus, status.dat, environment. This enables developers and users to test Livestatus, status.dat,
@ -17,7 +63,9 @@ have to do is install Vagrant and run:
vagrant up vagrant up
> **Note** that the first boot of the vm takes a fairly long time because > **Note**
>
> The first boot of the vm takes a fairly long time because
> you'll download a plain CentOS base box and Vagrant will automatically > you'll download a plain CentOS base box and Vagrant will automatically
> provision the environment on the first go. > provision the environment on the first go.
@ -68,7 +116,7 @@ After you should be able to browse [localhost:8080/icingaweb](http://localhost:8
**Installed files**: **Installed files**:
* `/usr/share/icinga/htpasswd.users` account information for logging into the Icinga classic web interface for both icinga instances * `/usr/share/icinga/htpasswd.users` account information for logging into the Icinga classic web interface for both icinga instances
* `/usr/lib64/nagios/plugins` Nagios Plugins for both icinga instances * `/usr/lib64/nagios/plugins` Monitoring Plugins for all Icinga instances
#### Icinga with IDOUtils using a MySQL database #### Icinga with IDOUtils using a MySQL database
@ -196,12 +244,13 @@ code style issues.
#### Icinga 2 #### Icinga 2
**Installation path**: `/usr/local/icinga2` Installed from the Icinga [snapshot package repository](http://packages.icinga.org/epel/).
The configuration is located in `/etc/icinga2`.
**Example usage**: **Example usage**:
cd /usr/local/icinga2 /etc/init.d/icinga2 (start|stop|restart|reload)
./sbin/icinga2 -c etc/icinga2/icinga2.conf.dist
## Log into Icinga Web 2 ## Log into Icinga Web 2
@ -211,3 +260,9 @@ If you've configure LDAP as authentication backend (which is the default) use th
> **Password**: password > **Password**: password
Have a look at [LDAP example data](#ldap example data) for more accounts. Have a look at [LDAP example data](#ldap example data) for more accounts.
Using MySQL as backend:
> **Username**: icingaadmin
> **Password**: icinga

View File

@ -34,11 +34,16 @@ class AuthenticationController extends ActionController
public function loginAction() public function loginAction()
{ {
$auth = $this->Auth(); $auth = $this->Auth();
$this->view->form = new LoginForm(); $this->view->form = $form = new LoginForm();
$this->view->title = $this->translate('Icingaweb Login'); $this->view->title = $this->translate('Icingaweb Login');
try { try {
$redirectUrl = Url::fromPath($this->params->get('redirect', 'dashboard')); $redirectUrl = $this->view->form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
}
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {
$this->rerenderLayout()->redirectNow($redirectUrl); $this->rerenderLayout()->redirectNow($redirectUrl);
@ -47,14 +52,10 @@ class AuthenticationController extends ActionController
try { try {
$config = Config::app('authentication'); $config = Config::app('authentication');
} catch (NotReadableError $e) { } catch (NotReadableError $e) {
Logger::error(
new Exception('Cannot load authentication configuration. An exception was thrown:', 0, $e)
);
throw new ConfigurationError( throw new ConfigurationError(
t( $this->translate('Could not read your authentiction.ini, no authentication methods are available.'),
'No authentication methods available. Authentication configuration could not be loaded.' 0,
. ' Please check the system log or Icinga Web 2 log for more information' $e
)
); );
} }
@ -66,6 +67,14 @@ class AuthenticationController extends ActionController
$backendsTried = 0; $backendsTried = 0;
$backendsWithError = 0; $backendsWithError = 0;
$redirectUrl = $form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
}
foreach ($chain as $backend) { foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) { if ($backend instanceof AutoLoginBackend) {
continue; continue;
@ -85,25 +94,25 @@ class AuthenticationController extends ActionController
} }
if ($backendsTried === 0) { if ($backendsTried === 0) {
throw new ConfigurationError( throw new ConfigurationError(
t( $this->translate(
'No authentication methods available. It seems that no authentication method has been set' 'No authentication methods available. Did you create'
. ' up. Please check the system log or Icinga Web 2 log for more information' . ' authentication.ini when installing Icinga Web 2?'
) )
); );
} }
if ($backendsTried === $backendsWithError) { if ($backendsTried === $backendsWithError) {
throw new ConfigurationError( throw new ConfigurationError(
$this->translate( $this->translate(
'No authentication methods available. It seems that all set up authentication methods have' 'All configured authentication methods failed.'
. ' errors. Please check the system log or Icinga Web 2 log for more information' . ' Please check the system log or Icinga Web 2 log for more information.'
) )
); );
} }
if ($backendsWithError) { if ($backendsWithError) {
$this->view->form->getElement('username')->addError( $this->view->form->getElement('username')->addError(
$this->translate( $this->translate(
'Note that not all authentication backends are available for authentication because they' 'Please note that not all authentication methods where available.'
. ' have errors. Please check the system log or Icinga Web 2 log for more information' . ' Check the system log or Icinga Web 2 log for more information.'
) )
); );
} }
@ -131,9 +140,10 @@ class AuthenticationController extends ActionController
public function logoutAction() public function logoutAction()
{ {
$auth = $this->Auth(); $auth = $this->Auth();
$isRemoteUser = $auth->getUser()->isRemoteUser();
$auth->removeAuthorization(); $auth->removeAuthorization();
if ($auth->isAuthenticatedFromRemoteUser()) { if ($isRemoteUser === true) {
$this->_helper->layout->setLayout('login'); $this->_helper->layout->setLayout('login');
$this->_response->setHttpResponseCode(401); $this->_response->setHttpResponseCode(401);
} else { } else {

View File

@ -6,7 +6,6 @@ use Icinga\Web\Controller\BaseConfigController;
use Icinga\Web\Widget\AlertMessageBox; use Icinga\Web\Widget\AlertMessageBox;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Icinga\Application\Modules\Module; use Icinga\Application\Modules\Module;
use Icinga\Web\Url;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Widget; use Icinga\Web\Widget;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
@ -41,9 +40,6 @@ class ConfigController extends BaseConfigController
))->add('logging', array( ))->add('logging', array(
'title' => 'Logging', 'title' => 'Logging',
'url' => 'config/logging' 'url' => 'config/logging'
))->add('modules', array(
'title' => 'Modules',
'url' => 'config/modules'
)); ));
} }
@ -116,6 +112,11 @@ class ConfigController extends BaseConfigController
*/ */
public function modulesAction() public function modulesAction()
{ {
$this->view->tabs = Widget::create('tabs')->add('modules', array(
'title' => 'Modules',
'url' => 'config/modules'
));
$this->view->tabs->activate('modules'); $this->view->tabs->activate('modules');
$this->view->modules = Icinga::app()->getModuleManager()->select() $this->view->modules = Icinga::app()->getModuleManager()->select()
->from('modules') ->from('modules')

View File

@ -18,7 +18,9 @@ class LayoutController extends ActionController
*/ */
public function menuAction() public function menuAction()
{ {
$this->view->menuRenderer = new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl()); $this->view->menuRenderer = new MenuRenderer(
Menu::fromConfig()->order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()
);
} }
/** /**

View File

@ -5,6 +5,7 @@
namespace Icinga\Form\Authentication; namespace Icinga\Form\Authentication;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Url;
/** /**
* Class LoginForm * Class LoginForm
@ -44,6 +45,13 @@ class LoginForm extends Form
'placeholder' => t('...and your password'), 'placeholder' => t('...and your password'),
'class' => isset($formData['username']) ? 'autofocus' : '' 'class' => isset($formData['username']) ? 'autofocus' : ''
) )
),
$this->createElement(
'hidden',
'redirect',
array(
'value' => Url::fromRequest()->getParam('redirect')
)
) )
); );
} }

View File

@ -110,7 +110,6 @@ class GeneralForm extends Form
foreach (Translator::getAvailableLocaleCodes() as $language) { foreach (Translator::getAvailableLocaleCodes() as $language) {
$languages[$language] = $language; $languages[$language] = $language;
} }
$languages[Translator::DEFAULT_LOCALE] = Translator::DEFAULT_LOCALE;
return $this->createElement( return $this->createElement(
'select', 'select',

View File

@ -36,7 +36,6 @@ class GeneralForm extends Form
foreach (Translator::getAvailableLocaleCodes() as $language) { foreach (Translator::getAvailableLocaleCodes() as $language) {
$languages[$language] = $language; $languages[$language] = $language;
} }
$languages[Translator::DEFAULT_LOCALE] = Translator::DEFAULT_LOCALE;
$useBrowserLanguage = isset($formData['browser_language']) ? $formData['browser_language'] == 1 : true; $useBrowserLanguage = isset($formData['browser_language']) ? $formData['browser_language'] == 1 : true;
$selectOptions = array( $selectOptions = array(

View File

@ -38,7 +38,7 @@ if ($notifications->hasMessages()) {
</div> </div>
<?php endif ?> <?php endif ?>
<div id="main" role="main"> <div id="main" role="main">
<div id="col1" class="container<?= $moduleClass ?>"<?php if ($moduleName): ?> data-icinga-module="<?= $moduleName ?>" <?php endif ?> data-icinga-url="<?= Url::fromRequest() ?>"<?= $refresh ?> style="display: block"> <div id="col1" class="container<?= $moduleClass ?>"<?php if ($moduleName): ?> data-icinga-module="<?= $moduleName ?>" <?php endif ?> data-icinga-url="<?= Url::fromRequest()->without('renderLayout') ?>"<?= $refresh ?> style="display: block">
<?= $this->render('inline.phtml') ?> <?= $this->render('inline.phtml') ?>
</div> </div>
<div id="col2" class="container"> <div id="col2" class="container">

View File

@ -44,6 +44,7 @@ $iframeClass = $isIframe ? ' iframe' : '';
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script> <script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
<![endif]--> <![endif]-->
<link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
</head> </head>
<body id="body"> <body id="body">

View File

@ -14,5 +14,5 @@ if (! $this->auth()->isAuthenticated()) {
<form action="<?= $this->href('search') ?>" method="get" role="search"> <form action="<?= $this->href('search') ?>" method="get" role="search">
<input type="text" name="q" class="search autofocus" placeholder="<?= $this->translate('Search...') ?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> <input type="text" name="q" class="search autofocus" placeholder="<?= $this->translate('Search...') ?>" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</form> </form>
<?= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl()); ?> <?= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()); ?>
</div> </div>

View File

@ -7,10 +7,7 @@
in every further request until the browser was closed. To allow logout and to allow the user to change the in every further request until the browser was closed. To allow logout and to allow the user to change the
logged-in user this JavaScript provides a workaround to force a new authentication prompt in most browsers. logged-in user this JavaScript provides a workaround to force a new authentication prompt in most browsers.
--> -->
<div class="content">
<div class="row">
<br/>
<div class="md-offset-3 col-md-6 col-sm-6 col-sm-offset-3">
<div class="alert alert-warning" id="logout-status"> <div class="alert alert-warning" id="logout-status">
<b> <?= t('Logging out...'); ?> </b> <br /> <b> <?= t('Logging out...'); ?> </b> <br />
<?= t( <?= t(
@ -19,37 +16,19 @@
'browser session.' 'browser session.'
); ?> ); ?>
</div> </div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3">
<div class="container" > <div class="container" >
<a class="button btn btn-cta form-control input-sm" href="<?= $this->href('dashboard/index'); ?>"> <?= t('Login'); ?></a> <a href="<?= $this->href('dashboard/index'); ?>"> <?= t('Login'); ?></a>
</div>
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
/** /**
* When JavaScript is available, trigger an XmlHTTPRequest with the non-existing user 'logout' and abort it * When JavaScript is available, trigger an XmlHTTPRequest with the non-existing user 'logout' and abort it
* before it is able to finish. This will cause the browser to show a new authentication prompt in the next * before it is able to finish. This will cause the browser to show a new authentication prompt in the next
* request. * request.
*/ */
window.onload = function () { $(document).ready(function() {
function getXMLHttpRequest() { msg = $('#logout-status');
var xmlhttp = null;
try {
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
} catch (e) {}
return xmlhttp;
}
var msg = document.getElementById('logout-status');
try { try {
if (navigator.userAgent.toLowerCase().indexOf('msie') !== -1) { if (navigator.userAgent.toLowerCase().indexOf('msie') !== -1) {
document.execCommand('ClearAuthenticationCache'); document.execCommand('ClearAuthenticationCache');
@ -60,13 +39,9 @@
xhttp.abort(); xhttp.abort();
} }
} catch (e) { } catch (e) {
msg.innerHTML = '<?= t(
'Logout not possible, it may be necessary to quit the session manually ' .
'by clearing the cache, or closing the current browser session. Error: '
);?>' + ': ' + e.getMessage() ;
msg.setAttribute('class', 'alert alert-danger');
} }
msg.innerHTML = '<?= t('Logout successful!'); ?>'; msg.html('<?= t('Logout successful!'); ?>');
msg.setAttribute('class', 'alert alert-success'); msg.removeClass();
}; msg.addClass('alert alert-success');
});
</script> </script>

View File

@ -1,7 +1,7 @@
; authentication.ini ; authentication.ini
; ;
; Each section listed in this configuration represents a backend used to authenticate users. The backend configurations ; Each section listed in this configuration represents a backend used to authenticate users. The backend configurations
: must define a resource referring to a configured database or LDAP connection in the INI file resources.ini. ; must define a resource referring to a configured database or LDAP connection in the INI file resources.ini.
; ;
; The backends will be processed from top to bottom using the first backend for authentication which reports that ; The backends will be processed from top to bottom using the first backend for authentication which reports that
; the user trying to log in is available. ; the user trying to log in is available.

View File

@ -23,13 +23,17 @@ sort = host_severity
title = "Landing page" title = "Landing page"
[Landing.Hostgroups] [Landing.Hostgroups]
url = "monitoring/chart/hostgroup?height=400&width=500" url = "monitoring/chart/hostgroup"
[Landing.Servicegroups] [Landing.Servicegroups]
url = "monitoring/chart/servicegroup?height=360&width=450" url = "monitoring/chart/servicegroup"
[Landing.Unhandled Problem Services] [Landing.Unhandled Problem Services]
url = "monitoring/list/services?service_handled=0&service_problem=1" url = "monitoring/list/services"
service_handled = 0
service_problem = 1
[Landing.Unhandled Problem Hosts] [Landing.Unhandled Problem Hosts]
url = "monitoring/list/hosts?host_handled=0&host_problem=1" url = "monitoring/list/hosts"
host_handled = 0
host_problem = 1

View File

@ -18,10 +18,15 @@ title = "Configuration"
url = "config" url = "config"
priority = 300 priority = 300
[System.Modules]
title = "Modules"
url = "config/modules"
priority = 400
[System.ApplicationLog] [System.ApplicationLog]
title = "Application log" title = "Application log"
url = "list/applicationlog" url = "list/applicationlog"
priority = 400 priority = 500
[Logout] [Logout]
url = "authentication/logout" url = "authentication/logout"

View File

@ -0,0 +1,5 @@
[Documentation]
title = "Documentation"
icon = "img/icons/comment.png"
url = "doc"
priority = 80

View File

@ -68,7 +68,7 @@ priority = 70
[Overview.Comments] [Overview.Comments]
title = "Comments" title = "Comments"
url = "monitoring/list/comments" url = "monitoring/list/comments?comment_type=(comment|ack)"
priority = 70 priority = 70
[Overview.Contacts] [Overview.Contacts]

View File

@ -3,8 +3,19 @@ Alias @web_path@ "@prefix@/public"
<Directory "@prefix@/public"> <Directory "@prefix@/public">
Options SymLinksIfOwnerMatch Options SymLinksIfOwnerMatch
AllowOverride None AllowOverride None
<IfModule mod_authz_core.c>
# Apache 2.4
<RequireAll>
Require all granted
</RequireAll>
</IfModule>
<IfModule !mod_authz_core.c>
# Apache 2.2
Order allow,deny Order allow,deny
Allow from all Allow from all
</IfModule>
SetEnv ICINGAWEB_CONFIGDIR @icingaweb_config_path@ SetEnv ICINGAWEB_CONFIGDIR @icingaweb_config_path@

View File

@ -1,16 +1,37 @@
# $Id$ #/**
# Authority: The icinga devel team <icinga-devel at lists.icinga.org> # * This file is part of Icinga Web 2.
# Upstream: The icinga devel team <icinga-devel at lists.icinga.org> # *
# ExcludeDist: el4 el3 # * Icinga Web 2 - Head for multiple monitoring backends.
# * Copyright (C) 2014 Icinga Development Team
# *
# * This program is free software; you can redistribute it and/or
# * modify it under the terms of the GNU General Public License
# * as published by the Free Software Foundation; either version 2
# * of the License, or (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# *
# * @copyright 2014 Icinga Development Team <info@icinga.org>
# * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
# * @author Icinga Development Team <info@icinga.org>
# *
# */
%define revision 0 %define revision 1
%define configdir %{_sysconfdir}/icingaweb %define configdir %{_sysconfdir}/icingaweb
%define sharedir %{_datadir}/icingaweb %define sharedir %{_datadir}/icingaweb
%define prefixdir %{_datadir}/icingaweb %define prefixdir %{_datadir}/icingaweb
%define logdir %{sharedir}/log %define logdir %{sharedir}/log
%define usermodparam -a -G %define usermodparam -a -G
#%define logdir %{_localstatedir}/log/icingaweb %define logdir %{_localstatedir}/log/icingaweb
%if "%{_vendor}" == "suse" %if "%{_vendor}" == "suse"
%define phpname php5 %define phpname php5
@ -19,11 +40,12 @@
%endif %endif
# SLE 11 = 1110 # SLE 11 = 1110
%if 0%{?suse_version} == 1110 %if 0%{?suse_version} == 1110
%define phpname php53
%define apache2modphpname apache2-mod_php53 %define apache2modphpname apache2-mod_php53
%define usermodparam -A %define usermodparam -A
%endif %endif
%if "%{_vendor}" == "redhat" || 0%{?suse_version} == 1110 %if "%{_vendor}" == "redhat"
%define phpname php %define phpname php
%define phpzendname php-ZendFramework %define phpzendname php-ZendFramework
%endif %endif
@ -37,15 +59,15 @@
%define apacheconfdir %{_sysconfdir}/apache2/conf.d %define apacheconfdir %{_sysconfdir}/apache2/conf.d
%define apacheuser wwwrun %define apacheuser wwwrun
%define apachegroup www %define apachegroup www
%define extcmdfile1x %{_localstatedir}/icinga/rw/icinga.cmd %define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd
%define livestatussocket1x %{_localstatedir}/icinga/rw/live %define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus
%endif %endif
%if "%{_vendor}" == "redhat" %if "%{_vendor}" == "redhat"
%define apacheconfdir %{_sysconfdir}/httpd/conf.d %define apacheconfdir %{_sysconfdir}/httpd/conf.d
%define apacheuser apache %define apacheuser apache
%define apachegroup apache %define apachegroup apache
%define extcmdfile-1x %{_localstatedir}/spool/icinga/cmd/icinga.cmd %define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd
%define livestatussocket1x %{_localstatedir}/spool/icinga/cmd/live %define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus
%endif %endif
Summary: Open Source host, service and network monitoring Web UI Summary: Open Source host, service and network monitoring Web UI
@ -109,8 +131,8 @@ Requires: php-Icinga
%description %description
IcingaWeb for Icinga 2 or Icinga 1.x using status data, Icinga Web 2 for Icinga 2 or Icinga 1.x using multiple backends
IDOUtils or Livestatus as backend provider. for example DB IDO.
%package -n icingacli %package -n icingacli
Summary: Icinga CLI Summary: Icinga CLI
@ -130,58 +152,54 @@ Requires: %{phpzendname}
%description -n php-Icinga %description -n php-Icinga
Icinga Web 2 PHP Libraries shared with icingacli. Icinga Web 2 PHP Libraries required by the web frontend and cli tool.
%prep %prep
#%setup -q -n %{name}-%{version} #VERSION=0.0.1; git archive --format=tar --prefix=icingaweb2-$VERSION/ HEAD | gzip >icingaweb2-$VERSION.tar.gz
%setup -q -n %{name} %setup -q -n %{name}-%{version}
%build %build
cat > README.RHEL.SUSE <<"EOF"
IcingaWeb for RHEL and SUSE
===========================
Please check ./doc/installation.md
for requirements and database setup.
EOF
%install %install
[ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot} [ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot}
# prepare configuration for sub packages # prepare configuration for sub packages
# install rhel apache config # install rhel apache config
install -D -m0644 packages/rhel/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{apacheconfdir}/icingaweb.conf install -D -m0644 packages/rpm/etc/httpd/conf.d/icingaweb.conf %{buildroot}/%{apacheconfdir}/icingaweb.conf
# install public, library, modules # install public, library, modules
%{__mkdir} -p %{buildroot}/%{sharedir} %{__mkdir} -p %{buildroot}/%{sharedir}
%{__mkdir} -p %{buildroot}/%{logdir} %{__mkdir} -p %{buildroot}/%{logdir}
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/dashboard
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring
%{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules %{__mkdir} -p %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules
%{__cp} -r application library modules public %{buildroot}/%{sharedir}/ %{__cp} -r application library modules public %{buildroot}/%{sharedir}/
# install index.php ## config
install -m0644 packages/rhel/usr/share/icingaweb/public/index.php %{buildroot}/%{sharedir}/public/index.php # use the default menu.ini for application and monitoring mobule
install -D -m0644 config/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/menu.ini
# use the vagrant config for configuration for now - TODO install -D -m0644 config/modules/monitoring/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/menu.ini
%{__cp} -r .vagrant-puppet/files/etc/icingaweb %{buildroot}/%{_sysconfdir}/ # authentication is db only
install -D -m0644 packages/rpm/etc/icingaweb/authentication.ini %{buildroot}/%{_sysconfdir}/icingaweb/authentication.ini
# we use the default 'icinga' database # custom resource paths
sed -i 's/icinga2/icinga/g' %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini install -D -m0644 packages/rpm/etc/icingaweb/resources.ini %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini
# dashboard
install -D -m0644 config/dashboard/dashboard.ini %{buildroot}/%{_sysconfdir}/icingaweb/dashboard/dashboard.ini
# monitoring module (icinga2)
install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/backends.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/backends.ini
install -D -m0644 packages/rpm/etc/icingaweb/modules/monitoring/instances.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/instances.ini
# enable the monitoring module by default # enable the monitoring module by default
ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring
## config
# install icingacli # install icingacli
install -D -m0755 bin/icingacli %{buildroot}/usr/bin/icingacli install -D -m0755 packages/rpm/usr/bin/icingacli %{buildroot}/usr/bin/icingacli
# install sql schema files as example
# delete all *.in files
%pre %pre
# Add apacheuser in the icingacmd group # Add apacheuser in the icingacmd group
@ -196,9 +214,6 @@ if [ $? -eq 0 ]; then
%{_sbindir}/usermod %{usermodparam} icingacmd %{apacheuser} %{_sbindir}/usermod %{usermodparam} icingacmd %{apacheuser}
fi fi
# uncomment if building from git
# %{__rm} -rf %{buildroot}%{_datadir}/icinga2-web/.git
%preun %preun
%post %post
@ -209,14 +224,13 @@ fi
%files %files
# main dirs # main dirs
%defattr(-,root,root) %defattr(-,root,root)
%doc etc/schema doc packages/rhel/README %doc etc/schema doc packages/rpm/README.md
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/public %attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/public
%attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/modules %attr(755,%{apacheuser},%{apachegroup}) %{sharedir}/modules
# configs # configs
%defattr(-,root,root) %defattr(-,root,root)
%config(noreplace) %attr(-,root,root) %{apacheconfdir}/icingaweb.conf %config(noreplace) %attr(-,root,root) %{apacheconfdir}/icingaweb.conf
%dir %{configdir} %config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir}
%config(noreplace) %attr(775,%{apacheuser},%{apachegroup}) %{configdir}
# logs # logs
%attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir} %attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir}
@ -228,6 +242,3 @@ fi
%attr(0755,root,root) /usr/bin/icingacli %attr(0755,root,root) /usr/bin/icingacli
%changelog %changelog
* Tue May 11 2014 Michael Friedrich <michael.friedrich@netways.de> - 0.0.1-1
- initial creation

View File

@ -5,6 +5,7 @@
namespace Icinga\Application\Modules; namespace Icinga\Application\Modules;
use Exception; use Exception;
use Zend_Controller_Router_Route_Abstract;
use Zend_Controller_Router_Route as Route; use Zend_Controller_Router_Route as Route;
use Icinga\Application\ApplicationBootstrap; use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config; use Icinga\Application\Config;
@ -135,6 +136,16 @@ class Module
*/ */
private $app; private $app;
/**
* Routes to add to the route chain
*
* @var array Array of name-route pairs
*
* @see addRoute()
*/
protected $routes = array();
/** /**
* Create a new module object * Create a new module object
* *
@ -166,8 +177,7 @@ class Module
*/ */
public function register() public function register()
{ {
$this->registerAutoloader() $this->registerAutoloader();
->registerWebIntegration();
try { try {
$this->launchRunScript(); $this->launchRunScript();
} catch (Exception $e) { } catch (Exception $e) {
@ -179,6 +189,7 @@ class Module
); );
return false; return false;
} }
$this->registerWebIntegration();
return true; return true;
} }
@ -658,13 +669,18 @@ class Module
} }
/** /**
* Register routes for web access * Add routes for static content and any route added via addRoute() to the route chain
* *
* @return self * @return self
* @see addRoute()
*/ */
protected function registerRoutes() protected function registerRoutes()
{ {
$this->app->getFrontController()->getRouter()->addRoute( $router = $this->app->getFrontController()->getRouter();
foreach ($this->routes as $name => $route) {
$router->addRoute($name, $route);
}
$router->addRoute(
$this->name . '_jsprovider', $this->name . '_jsprovider',
new Route( new Route(
'js/' . $this->name . '/:file', 'js/' . $this->name . '/:file',
@ -675,7 +691,7 @@ class Module
) )
) )
); );
$this->app->getFrontController()->getRouter()->addRoute( $router->addRoute(
$this->name . '_img', $this->name . '_img',
new Route( new Route(
'img/' . $this->name . '/:file', 'img/' . $this->name . '/:file',
@ -750,4 +766,19 @@ class Module
return $this; return $this;
} }
/**
* Add a route which will be added to the route chain
*
* @param string $name Name of the route
* @param Zend_Controller_Router_Route_Abstract $route Instance of the route
*
* @return self
* @see registerRoutes()
*/
protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route)
{
$this->routes[$name] = $route;
return $this;
}
} }

View File

@ -53,6 +53,7 @@ class AutoLoginBackend extends UserBackend
{ {
if (isset($_SERVER['REMOTE_USER'])) { if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER']; $username = $_SERVER['REMOTE_USER'];
$user->setRemoteUserInformation($username, 'REMOTE_USER');
if ($this->stripUsernameRegexp !== null) { if ($this->stripUsernameRegexp !== null) {
$stripped = preg_replace($this->stripUsernameRegexp, '', $username); $stripped = preg_replace($this->stripUsernameRegexp, '', $username);
if ($stripped !== false) { if ($stripped !== false) {

View File

@ -30,12 +30,6 @@ class Manager
*/ */
private $user; private $user;
/**
* If the user was authenticated from the REMOTE_USER server variable
*
* @var Boolean
*/
private $fromRemoteUser = false;
private function __construct() private function __construct()
{ {
@ -117,6 +111,13 @@ class Manager
public function authenticateFromSession() public function authenticateFromSession()
{ {
$this->user = Session::getSession()->get('user'); $this->user = Session::getSession()->get('user');
if ($this->user !== null && $this->user->isRemoteUser() === true) {
list($originUsername, $field) = $this->user->getRemoteUserInformation();
if (array_key_exists($field, $_SERVER) && $_SERVER[$field] !== $originUsername) {
$this->removeAuthorization();
}
}
} }
/** /**
@ -204,35 +205,4 @@ class Manager
{ {
return $this->user->getGroups(); return $this->user->getGroups();
} }
/**
* Tries to authenticate the user from the session, and then from the REMOTE_USER superglobal, that can be set by
* an external authentication provider.
*/
public function authenticateFromRemoteUser()
{
if (array_key_exists('REMOTE_USER', $_SERVER)) {
$this->fromRemoteUser = true;
}
$this->authenticateFromSession();
if ($this->user !== null) {
if (array_key_exists('REMOTE_USER', $_SERVER) && $this->user->getUsername() !== $_SERVER["REMOTE_USER"]) {
// Remote user has changed, clear all sessions
$this->removeAuthorization();
}
return;
}
if (array_key_exists('REMOTE_USER', $_SERVER) && $_SERVER["REMOTE_USER"]) {
$this->user = new User($_SERVER["REMOTE_USER"]);
$this->persistCurrentUser();
}
}
/**
* If the session was established from the REMOTE_USER server variable.
*/
public function isAuthenticatedFromRemoteUser()
{
return $this->fromRemoteUser;
}
} }

View File

@ -9,6 +9,7 @@ use Icinga\Chart\Primitive\Drawable;
use Icinga\Chart\Primitive\Line; use Icinga\Chart\Primitive\Line;
use Icinga\Chart\Primitive\Text; use Icinga\Chart\Primitive\Text;
use Icinga\Chart\Render\RenderContext; use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Render\Rotator;
use Icinga\Chart\Unit\AxisUnit; use Icinga\Chart\Unit\AxisUnit;
use Icinga\Chart\Unit\CalendarUnit; use Icinga\Chart\Unit\CalendarUnit;
use Icinga\Chart\Unit\LinearUnit; use Icinga\Chart\Unit\LinearUnit;
@ -188,11 +189,11 @@ class Axis implements Drawable
$labelField->setFontSize('2.5em'); $labelField->setFontSize('2.5em');
} }
if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
$labelField = new Rotator($labelField, 45);
}
$labelField = $labelField->toSvg($ctx); $labelField = $labelField->toSvg($ctx);
if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
$labelField = $this->rotate($ctx, $labelField, 45);
}
$group->appendChild($labelField); $group->appendChild($labelField);
if ($this->drawYGrid) { if ($this->drawYGrid) {
@ -214,34 +215,6 @@ class Axis implements Drawable
} }
} }
/**
* Rotate the given element.
*
* @param RenderContext $ctx The rendering context
* @param DOMElement $el The element to rotate
* @param $degrees The rotation degrees
*
* @return DOMElement
*/
private function rotate(RenderContext $ctx, DOMElement $el, $degrees)
{
// Create a box containing the rotated element relative to the original text position
$container = $ctx->getDocument()->createElement('g');
$x = $el->getAttribute('x');
$y = $el->getAttribute('y');
$container->setAttribute('transform', 'translate(' . $x . ',' . $y . ')');
$el->removeAttribute('x');
$el->removeAttribute('y');
// Create a rotated box containing the text
$rotate = $ctx->getDocument()->createElement('g');
$rotate->setAttribute('transform', 'rotate(' . $degrees . ')');
$rotate->appendChild($el);
$container->appendChild($rotate);
return $container;
}
/** /**
* Render the vertical axis * Render the vertical axis
* *
@ -275,9 +248,9 @@ class Axis implements Drawable
if ($this->yLabel) { if ($this->yLabel) {
$axisLabel = new Text(-8, 50, $this->yLabel); $axisLabel = new Text(-8, 50, $this->yLabel);
$axisLabel->setFontSize('2em') $axisLabel->setFontSize('2em')
->setAdditionalStyle(Text::ORIENTATION_VERTICAL)
->setFontWeight('bold') ->setFontWeight('bold')
->setAlignment(Text::ALIGN_MIDDLE); ->setAlignment(Text::ALIGN_MIDDLE);
$axisLabel = new Rotator($axisLabel, 90);
$group->appendChild($axisLabel->toSvg($ctx)); $group->appendChild($axisLabel->toSvg($ctx));
} }

View File

@ -69,7 +69,7 @@ class BarGraph extends Styleable implements Drawable
$group = $doc->createElement('g'); $group = $doc->createElement('g');
$idx = 0; $idx = 0;
foreach ($this->dataSet as $point) { foreach ($this->dataSet as $point) {
$rect = new Rect($point[0] - 1, $point[1], 2, 100 - $point[1]); $rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]);
$rect->setFill($this->fill); $rect->setFill($this->fill);
$rect->setStrokeWidth($this->strokeWidth); $rect->setStrokeWidth($this->strokeWidth);
$rect->setStrokeColor('black'); $rect->setStrokeColor('black');

View File

@ -30,16 +30,6 @@ class Text extends Styleable implements Drawable
*/ */
const ALIGN_MIDDLE = 'middle'; const ALIGN_MIDDLE = 'middle';
/**
* Normal left to right orientation
*/
const ORIENTATION_HORIZONTAL = "";
/**
* Top down orientation (rotated by 90°)
*/
const ORIENTATION_VERTICAL = "writing-mode: tb;";
/** /**
* The x position of the Text * The x position of the Text
* *

View File

@ -0,0 +1,86 @@
<?php
/**
* Created by PhpStorm.
* User: mjentsch
* Date: 22.07.14
* Time: 10:17
*/
namespace Icinga\Chart\Render;
use Icinga\Chart\Render\RenderContext;
use Icinga\Chart\Primitive\Drawable;
use DOMElement;
/**
* Class Rotator
* @package Icinga\Chart\Render
*/
class Rotator implements Drawable
{
/**
* The drawable element to rotate
*
* @var Drawable
*/
private $element;
/**
* @var int
*/
private $degrees;
/**
* Wrap an element into a new instance of Rotator
*
* @param Drawable $element The element to rotate
* @param int $degrees The amount of degrees
*/
public function __construct(Drawable $element, $degrees)
{
$this->element = $element;
$this->degrees = $degrees;
}
/**
* Rotate the given element.
*
* @param RenderContext $ctx The rendering context
* @param DOMElement $el The element to rotate
* @param $degrees The amount of degrees
*
* @return DOMElement The rotated DOMElement
*/
private function rotate(RenderContext $ctx, DOMElement $el, $degrees)
{
// Create a box containing the rotated element relative to the original element position
$container = $ctx->getDocument()->createElement('g');
$x = $el->getAttribute('x');
$y = $el->getAttribute('y');
$container->setAttribute('transform', 'translate(' . $x . ',' . $y . ')');
$el->removeAttribute('x');
$el->removeAttribute('y');
// Put the element into a rotated group
//$rotate = $ctx->getDocument()->createElement('g');
$el->setAttribute('transform', 'rotate(' . $degrees . ')');
//$rotate->appendChild($el);
$container->appendChild($el);
return $container;
}
/**
* Create the SVG representation from this Drawable
*
* @param RenderContext $ctx The context to use for rendering
*
* @return DOMElement The SVG Element
*/
public function toSvg(RenderContext $ctx)
{
$el = $this->element->toSvg($ctx);
return $this->rotate($ctx, $el, $this->degrees);
}
}

View File

@ -90,17 +90,52 @@ class LinearUnit implements AxisUnit
} }
sort($datapoints); sort($datapoints);
if (!$this->staticMax) { if (!$this->staticMax) {
$this->max = max($this->max, $datapoints[count($datapoints)-1]); $this->max = max($this->max, $datapoints[count($datapoints) - 1]);
} }
if (!$this->staticMin) { if (!$this->staticMin) {
$this->min = min($this->min, $datapoints[0]); $this->min = min($this->min, $datapoints[0]);
} }
if (!$this->staticMin || !$this->staticMax) {
$this->updateMaxValue();
}
$this->currentTick = 0; $this->currentTick = 0;
$this->currentValue = $this->min; $this->currentValue = $this->min;
return $this; return $this;
} }
/**
* Refresh the range depending on the current values of min, max and nrOfTicks
*/
private function updateMaxValue()
{
$this->max = $this->calculateTickRange($this->max - $this->min, $this->nrOfTicks) *
$this->nrOfTicks + $this->min;
}
/**
* Determine the minimum tick range that is necessary to display the given value range
* correctly
*
* @param int range The range to display
* @param int ticks The amount of ticks to use
*
* @return int The value for each tick
*/
private function calculateTickRange($range, $ticks)
{
$factor = 1;
$steps = array(1, 2, 5);
$step = 0;
while ($range / ($factor * $steps[$step]) > $ticks) {
$step++;
if ($step === count($steps)) {
$step = 0;
$factor *= 10;
}
}
return $steps[$step] * $factor;
}
/** /**
* Transform the absolute value to an axis relative value * Transform the absolute value to an axis relative value
* *
@ -114,7 +149,7 @@ class LinearUnit implements AxisUnit
} elseif ($value > $this->max) { } elseif ($value > $this->max) {
return 100; return 100;
} else { } else {
return 100 * ($value - $this->min) / ($this->max - $this->min); return 100 * ($value - $this->min) / $this->max - $this->min;
} }
} }
@ -176,6 +211,7 @@ class LinearUnit implements AxisUnit
if ($max !== null) { if ($max !== null) {
$this->max = $max; $this->max = $max;
$this->staticMax = true; $this->staticMax = true;
$this->updateMaxValue();
} }
} }
@ -189,6 +225,7 @@ class LinearUnit implements AxisUnit
if ($min !== null) { if ($min !== null) {
$this->min = $min; $this->min = $min;
$this->staticMin = true; $this->staticMin = true;
$this->updateMaxValue();
} }
} }

View File

@ -7,6 +7,7 @@ namespace Icinga\Cli;
use Icinga\Cli\Screen; use Icinga\Cli\Screen;
use Icinga\Util\Translator; use Icinga\Util\Translator;
use Icinga\Cli\Params; use Icinga\Cli\Params;
use Icinga\Application\Config;
use Icinga\Application\ApplicationBootstrap as App; use Icinga\Application\ApplicationBootstrap as App;
use Exception; use Exception;
@ -23,6 +24,10 @@ abstract class Command
protected $commandName; protected $commandName;
protected $actionName; protected $actionName;
private $config;
private $configs;
protected $defaultActionName = 'default'; protected $defaultActionName = 'default';
public function __construct(App $app, $moduleName, $commandName, $actionName, $initialize = true) public function __construct(App $app, $moduleName, $commandName, $actionName, $initialize = true)
@ -41,6 +46,51 @@ abstract class Command
} }
} }
public function Config($file = null)
{
if ($this->isModule()) {
return $this->getModuleConfig($file);
} else {
return $this->getMainConfig($file);
}
}
private function getModuleConfig($file = null)
{
if ($file === null) {
if ($this->config === null) {
$this->config = Config::module($this->moduleName);
}
return $this->config;
} else {
if (! array_key_exists($file, $this->configs)) {
$this->configs[$file] = Config::module($this->moduleName, $file);
}
return $this->configs[$file];
}
}
private function getMainConfig($file = null)
{
if ($file === null) {
if ($this->config === null) {
$this->config = Config::app();
}
return $this->config;
} else {
if (! array_key_exists($file, $this->configs)) {
$this->configs[$file] = Config::module($module, $file);
}
return $this->configs[$file];
}
return $this->config;
}
public function isModule()
{
return substr(get_class($this), 0, 14) === 'Icinga\\Module\\';
}
public function setParams(Params $params) public function setParams(Params $params)
{ {
$this->params = $params; $this->params = $params;

View File

@ -192,6 +192,24 @@ class DbQuery extends SimpleQuery
return $this->escapeForSql(date('Y-m-d H:i:s', $value)); return $this->escapeForSql(date('Y-m-d H:i:s', $value));
} }
/**
* Check for timestamp fields
*
* TODO: This is not here to do automagic timestamp stuff. One may
* override this function for custom voodoo, IdoQuery right now
* does. IMO we need to split whereToSql functionality, however
* I'd prefer to wait with this unless we understood how other
* backends will work. We probably should also rename this
* function to isTimestampColumn().
*
* @param string $field Field Field name to checked
* @return bool Whether this field expects timestamps
*/
public function isTimestamp($field)
{
return $this;
}
public function whereToSql($col, $sign, $expression) public function whereToSql($col, $sign, $expression)
{ {
if ($this->isTimestamp($col)) { if ($this->isTimestamp($col)) {

View File

@ -0,0 +1,18 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data;
/**
* Interface for objects that are identifiable by an ID of any type
*/
interface Identifiable
{
/**
* Get the ID associated with this Identifiable object
*
* @return mixed
*/
public function getId();
}

View File

@ -0,0 +1,79 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Tree;
use SplDoublyLinkedList;
class Node extends SplDoublyLinkedList implements NodeInterface
{
/**
* The node's value
*
* @var mixed
*/
protected $value;
/**
* Create a new node
*
* @param mixed $value The node's value
*/
public function __construct($value = null)
{
$this->value = $value;
}
/**
* Get the node's value
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Create a new node from the given value and insert the node as the last child of this node
*
* @param mixed $value The node's value
*
* @return NodeInterface The appended node
*/
public function appendChild($value)
{
$child = new static($value);
$this->push($child);
return $child;
}
/**
* Whether this node has child nodes
*
* @return bool
*/
public function hasChildren()
{
$current = $this->current();
if ($current === null) {
$current = $this;
}
return ! $current->isEmpty();
}
/**
* Get the node's child nodes
*
* @return NodeInterface
*/
public function getChildren()
{
$current = $this->current();
if ($current === null) {
$current = $this;
}
return $current;
}
}

View File

@ -0,0 +1,26 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Data\Tree;
use RecursiveIterator;
interface NodeInterface extends RecursiveIterator
{
/**
* Create a new node from the given value and insert the node as the last child of this node
*
* @param mixed $value The node's value
*
* @return NodeInterface The appended node
*/
public function appendChild($value);
/**
* Get the node's value
*
* @return mixed
*/
public function getValue();
}

View File

@ -109,7 +109,19 @@ class CommandPipe
*/ */
public function send($command) public function send($command)
{ {
$this->transport->send($command); $this->transport->send($this->escape($command));
}
/**
* Return the given command string with escaped newlines
*
* @param string $command The command string to escape
*
* @return string The escaped command string
*/
public function escape($command)
{
return str_replace(array("\r", "\n"), array('\r', '\n'), $command);
} }
/** /**
@ -121,16 +133,14 @@ class CommandPipe
public function sendCommand(Command $command, array $objects = array()) public function sendCommand(Command $command, array $objects = array())
{ {
if ($command->provideGlobalCommand() === true) { if ($command->provideGlobalCommand() === true) {
$this->transport->send($command->getGlobalCommand()); $this->send($command->getGlobalCommand());
} else { } else {
foreach ($objects as $object) { foreach ($objects as $object) {
$objectType = $this->getObjectType($object); $objectType = $this->getObjectType($object);
if ($objectType === self::TYPE_SERVICE) { if ($objectType === self::TYPE_SERVICE) {
$this->transport->send( $this->send($command->getServiceCommand($object->host_name, $object->service_description));
$command->getServiceCommand($object->host_name, $object->service_description)
);
} else { } else {
$this->transport->send($command->getHostCommand($object->host_name)); $this->send($command->getHostCommand($object->host_name));
} }
} }
} }

View File

@ -0,0 +1,10 @@
<?php
namespace Icinga\Protocol\File;
use RuntimeException;
/**
* Exception thrown if a file reader specific error occurs
*/
class FileReaderException extends RuntimeException {}

View File

@ -5,6 +5,7 @@
namespace Icinga\Protocol\File; namespace Icinga\Protocol\File;
use Icinga\Data\SimpleQuery; use Icinga\Data\SimpleQuery;
use Icinga\Data\Filter\Filter;
/** /**
* Class Query * Class Query
@ -32,7 +33,7 @@ class Query extends SimpleQuery
/** /**
* Nothing to do here * Nothing to do here
*/ */
public function applyFilter() public function applyFilter(Filter $filter)
{} {}
/** /**
@ -42,9 +43,11 @@ class Query extends SimpleQuery
* *
* @return Query * @return Query
*/ */
public function order($dir) public function order($field, $direction = null)
{ {
$this->sortDir = ($dir === null || strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC; $this->sortDir = (
$direction === null || strtoupper(trim($direction)) === 'DESC'
) ? self::SORT_DESC : self::SORT_ASC;
return $this; return $this;
} }

View File

@ -4,38 +4,92 @@
namespace Icinga\Protocol\File; namespace Icinga\Protocol\File;
use Icinga\Data\DatasourceInterface; use FilterIterator;
use Iterator;
use Zend_Config;
use Icinga\Protocol\File\FileReaderException;
use Icinga\Util\File;
/** /**
* Class Reader
*
* Read file line by line * Read file line by line
*
* @package Icinga\Protocol\File
*/ */
class Reader implements DatasourceInterface class Reader extends FilterIterator
{ {
/** /**
* Name of the file to read * A PCRE string with the fields to extract from the file's lines as named subpatterns
* *
* @var string * @var string
*/ */
private $filename; protected $fields;
/** /**
* Configuration for this Datasource * An associative array of the current line's fields ($field => $value)
* *
* @var \Zend_Config * @var array
*/ */
private $config; protected $currentData;
/** /**
* @param \Zend_Config $config * Create a new reader
*
* @param Zend_Config $config
*
* @throws FileReaderException If a required $config directive (filename or fields) is missing
*/ */
public function __construct($config) public function __construct(Zend_Config $config)
{ {
$this->config = $config; foreach (array('filename', 'fields') as $key) {
$this->filename = $config->filename; if (! isset($config->{$key})) {
throw new FileReaderException('The directive `' . $key . '\' is required');
}
}
$this->fields = $config->fields;
$f = new File($config->filename);
$f->setFlags(
File::DROP_NEW_LINE |
File::READ_AHEAD |
File::SKIP_EMPTY
);
parent::__construct($f);
}
/**
* Return the current data
*
* @return array
*/
public function current()
{
return $this->currentData;
}
/**
* Accept lines matching the given PCRE pattern
*
* @return bool
*
* @throws FileReaderException If PHP failed parsing the PCRE pattern
*/
public function accept()
{
$data = array();
$matched = @preg_match(
$this->fields,
$this->getInnerIterator()->current(),
$data
);
if ($matched === false) {
throw new FileReaderException('Failed parsing regular expression!');
} else if ($matched === 1) {
foreach ($data as $key) {
if (is_int($key)) {
unset($data[$key]);
}
}
$this->currentData = $data;
return true;
}
return false;
} }
/** /**
@ -48,26 +102,77 @@ class Reader implements DatasourceInterface
return new Query($this); return new Query($this);
} }
/**
* Return the number of available valid lines.
*
* @return int
*/
public function count()
{
return iterator_count($this);
}
/** /**
* Fetch result as an array of objects * Fetch result as an array of objects
* *
* @param Query $query
*
* @return array * @return array
*/ */
public function fetchAll(Query $query) public function fetchAll(Query $query)
{ {
$all = array(); $all = array();
foreach ($this->fetchPairs($query) as $index => $value) { foreach ($this->fetchPairs($query) as $index => $value) {
$all[$index] = new \stdClass(); $all[$index] = (object) $value;
foreach ($value as $key => $value_2) {
$all[$index]->{$key} = $value_2;
}
} }
return $all; return $all;
} }
/**
* Fetch result as a key/value pair array
*
* @param Query $query
*
* @return array
*/
public function fetchPairs(Query $query)
{
$skipLines = $query->getOffset();
$readLines = $query->getLimit();
if ($skipLines === null) {
$skipLines = 0;
}
$lines = array();
if ($query->sortDesc()) {
$count = $this->count($query);
if ($count <= $skipLines) {
return $lines;
} else if ($count < ($skipLines + $readLines)) {
$readLines = $count - $skipLines;
$skipLines = 0;
} else {
$skipLines = $count - ($skipLines + $readLines);
}
}
foreach ($this as $index => $line) {
if ($index >= $skipLines) {
if ($index >= $skipLines + $readLines) {
break;
}
$lines[] = $line;
}
}
if ($query->sortDesc()) {
$lines = array_reverse($lines);
}
return $lines;
}
/** /**
* Fetch first result row * Fetch first result row
* *
* @param Query $query
*
* @return object * @return object
*/ */
public function fetchRow(Query $query) public function fetchRow(Query $query)
@ -82,14 +187,16 @@ class Reader implements DatasourceInterface
/** /**
* Fetch first result column * Fetch first result column
* *
* @param Query $query
*
* @return array * @return array
*/ */
public function fetchColumn(Query $query) public function fetchColumn(Query $query)
{ {
$column = array(); $column = array();
foreach ($this->fetchPairs($query) as $value) { foreach ($this->fetchPairs($query) as $pair) {
foreach ($value as $value_2) { foreach ($pair as $value) {
$column[] = $value_2; $column[] = $value;
break; break;
} }
} }
@ -99,6 +206,8 @@ class Reader implements DatasourceInterface
/** /**
* Fetch first column value from first result row * Fetch first column value from first result row
* *
* @param Query $query
*
* @return mixed * @return mixed
*/ */
public function fetchOne(Query $query) public function fetchOne(Query $query)
@ -111,215 +220,4 @@ class Reader implements DatasourceInterface
} }
return null; return null;
} }
/**
* Fetch result as a key/value pair array
*
* @return array
*/
public function fetchPairs(Query $query)
{
return $this->read($query);
}
/**
* If given $line matches the $query's PCRE pattern and contains all the strings in the $query's filters array,
* return an associative array of the matches of the PCRE pattern.
* Otherwise, return false.
* If preg_match returns false, it failed parsing the PCRE pattern.
* In that case, throw an exception.
*
* @param string $line
* @param Query $query
*
* @return array|bool
*
* @throws \Exception
*/
public function validateLine($line, Query $query)
{
$data = array();
$PCRE_result = @preg_match($this->config->fields, $line, $data);
if ($PCRE_result === false) {
throw new \Exception('Failed parsing regular expression!');
} else if ($PCRE_result === 1) {
foreach ($query->getFilters() as $filter) {
if (strpos($line, $filter) === false) {
return false;
}
}
foreach ($data as $key => $value) {
if (is_int($key)) {
unset($data[$key]);
}
}
return $data;
}
return false;
}
/**
* Skip and read as many lines as needed according to given $query.
*
* @param Query $query
*
* @return array result
*/
public function read(Query $query)
{
$skip_lines = $query->getOffset();
$read_lines = $query->getLimit();
if ($skip_lines === null) {
$skip_lines = 0;
}
return $this->{$query->sortDesc() ? 'readFromEnd' : 'readFromStart'}($skip_lines, $read_lines, $query);
}
/**
* Backend for $this->read
* Direction: LIFO
*/
public function readFromEnd($skip_lines, $read_lines, Query $query)
{
$PHP_EOL_len = strlen(PHP_EOL);
$lines = array();
$s = '';
$f = @fopen($this->filename, 'rb');
if ($f !== false) {
$buffer = '';
fseek($f, 0, SEEK_END);
if (ftell($f) === 0) {
return array();
}
while ($read_lines === null || count($lines) < $read_lines) {
$c = $this->fgetc($f, $buffer);
if ($c === false) {
$l = $this->validateLine($s, $query);
if (!($l === false || $skip_lines)) {
$lines[] = $l;
}
break;
}
$s = $c . $s;
if (strpos($s, PHP_EOL) === 0) {
$l = $this->validateLine((string)substr($s, $PHP_EOL_len), $query);
if ($l !== false) {
if ($skip_lines) {
$skip_lines--;
} else {
$lines[] = $l;
}
}
$s = '';
}
}
}
return $lines;
}
/**
* Backend for $this->readFromEnd
*/
public function fgetc($file, &$buffer)
{
$strlen = strlen($buffer);
if ($strlen === 0) {
$pos = ftell($file);
if ($pos === 0) {
return false;
}
if ($pos < 4096) {
fseek($file, 0);
$buffer = fread($file, $pos);
fseek($file, 0);
} else {
fseek($file, -4096, SEEK_CUR);
$buffer = fread($file, 4096);
fseek($file, -4096, SEEK_CUR);
}
return $this->fgetc($file, $buffer);
} else {
$char = substr($buffer, -1);
$buffer = substr($buffer, 0, $strlen - 1);
return $char;
}
}
/**
* Backend for $this->read
* Direction: FIFO
*/
public function readFromStart($skip_lines, $read_lines, Query $query)
{
$PHP_EOL_len = strlen(PHP_EOL);
$lines = array();
$s = '';
$f = @fopen($this->filename, 'rb');
if ($f !== false) {
$buffer = '';
while ($read_lines === null || count($lines) < $read_lines) {
if (strlen($buffer) === 0) {
$buffer = fread($f, 4096);
if (strlen($buffer) === 0) {
$l = $this->validateLine($s, $query);
if (!($l === false || $skip_lines)) {
$lines[] = $l;
}
break;
}
}
$s .= substr($buffer, 0, 1);
$buffer = substr($buffer, 1);
if (strpos($s, PHP_EOL) !== false) {
$l = $this->validateLine((string)substr($s, 0, strlen($s) - $PHP_EOL_len), $query);
if ($l !== false) {
if ($skip_lines) {
$skip_lines--;
} else {
$lines[] = $l;
}
}
$s = '';
}
}
}
return $lines;
}
/**
* Return the number of available valid lines.
*
* @param Query $query
*
* @return int
*/
public function count(Query $query) {
$PHP_EOL_len = strlen(PHP_EOL);
$lines = 0;
$s = '';
$f = @fopen($this->filename, 'rb');
if ($f !== false) {
$buffer = '';
while (true) {
if (strlen($buffer) === 0) {
$buffer = fread($f, 4096);
if (strlen($buffer) === 0) {
if ($this->validateLine($s, $query) !== false) {
$lines++;
}
break;
}
}
$s .= substr($buffer, 0, 1);
$buffer = substr($buffer, 1);
if (strpos($s, PHP_EOL) !== false) {
if ($this->validateLine((string)substr($s, 0, strlen($s) - $PHP_EOL_len), $query) !== false) {
$lines++;
}
$s = '';
}
}
}
return $lines;
}
} }

View File

@ -58,6 +58,18 @@ class User
*/ */
protected $additionalInformation = array(); protected $additionalInformation = array();
/**
* Information if the user is external authenticated
*
* Keys:
*
* 0: origin username
* 1: origin field name
*
* @var array
*/
protected $remoteUserInformation = array();
/** /**
* Set of permissions * Set of permissions
* *
@ -401,4 +413,35 @@ class User
{ {
$this->messages = null; $this->messages = null;
} }
/**
* Set additional remote user information
*
* @param stirng $username
* @param string $field
*/
public function setRemoteUserInformation($username, $field)
{
$this->remoteUserInformation = array($username, $field);
}
/**
* Get additional remote user information
*
* @return array
*/
public function getRemoteUserInformation()
{
return $this->remoteUserInformation;
}
/**
* Return true if user has remote user information set
*
* @return bool
*/
public function isRemoteUser()
{
return (count($this->remoteUserInformation)) ? true : false;
}
} }

View File

@ -115,7 +115,7 @@ class Translator
*/ */
public static function getAvailableLocaleCodes() public static function getAvailableLocaleCodes()
{ {
$codes = array(); $codes = array(static::DEFAULT_LOCALE);
foreach (array_values(self::$knownDomains) as $directory) { foreach (array_values(self::$knownDomains) as $directory) {
$dh = opendir($directory); $dh = opendir($directory);
while (false !== ($name = readdir($dh))) { while (false !== ($name = readdir($dh))) {

View File

@ -254,10 +254,27 @@ class ActionController extends Zend_Controller_Action
* *
* @throws \Exception * @throws \Exception
*/ */
protected function redirectToLogin($afterLogin = '/dashboard') protected function redirectToLogin($afterLogin = null)
{ {
$redir = null;
if ($afterLogin !== null) {
if (! $afterLogin instanceof Url) {
$afterLogin = Url::fromPath($afterLogin);
}
if ($this->isXhr()) {
$redir = '__SELF__';
} else {
// TODO: Ignore /?
$redir = $afterLogin->getRelativeUrl();
}
}
$url = Url::fromPath('authentication/login'); $url = Url::fromPath('authentication/login');
$url->setParam('redirect', $afterLogin);
if ($redir) {
$url->setParam('redirect', $redir);
}
$this->rerenderLayout()->redirectNow($url); $this->rerenderLayout()->redirectNow($url);
} }
@ -273,18 +290,12 @@ class ActionController extends Zend_Controller_Action
return $this->getRequest()->isXmlHttpRequest(); return $this->getRequest()->isXmlHttpRequest();
} }
/** protected function redirectXhr($url)
* Redirect to a specific url, updating the browsers URL field
*
* @param Url|string $url The target to redirect to
**/
public function redirectNow($url)
{ {
if (! $url instanceof Url) { if (! $url instanceof Url) {
$url = Url::fromPath($url); $url = Url::fromPath($url);
} }
$url = preg_replace('~&amp;~', '&', $url);
if ($this->isXhr()) {
if ($this->rerenderLayout) { if ($this->rerenderLayout) {
$this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes'); $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
} }
@ -293,13 +304,27 @@ class ActionController extends Zend_Controller_Action
} }
$this->getResponse() $this->getResponse()
->setHeader('X-Icinga-Redirect', rawurlencode($url)) ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
->sendHeaders(); ->sendHeaders();
// TODO: Session shutdown? // TODO: Session shutdown?
exit; exit;
}
/**
* Redirect to a specific url, updating the browsers URL field
*
* @param Url|string $url The target to redirect to
**/
public function redirectNow($url)
{
if ($this->isXhr()) {
$this->redirectXhr($url);
} else { } else {
$this->_helper->Redirector->gotoUrlAndExit(Url::fromPath($url)->getRelativeUrl()); if (! $url instanceof Url) {
$url = Url::fromPath($url);
}
$this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
} }
} }
@ -362,6 +387,8 @@ class ActionController extends Zend_Controller_Action
'X-Icinga-Title', 'X-Icinga-Title',
rawurlencode($this->view->title . ' :: Icinga Web') rawurlencode($this->view->title . ' :: Icinga Web')
); );
} else {
$resp->setHeader('X-Icinga-Title', rawurlencode('Icinga Web'));
} }
if ($this->rerenderLayout) { if ($this->rerenderLayout) {

View File

@ -127,10 +127,6 @@ class Url
$baseUrl = $request->getBaseUrl(); $baseUrl = $request->getBaseUrl();
$urlObject->setBaseUrl($baseUrl); $urlObject->setBaseUrl($baseUrl);
// Fetch fragment manually and remove it from the url, to 'help' the parse_url() function
// parsing the url properly. Otherwise calling the function with a fragment, but without a
// query will cause unpredictable behaviour.
$url = self::stripUrlFragment($url);
$urlParts = parse_url($url); $urlParts = parse_url($url);
if (isset($urlParts['path'])) { if (isset($urlParts['path'])) {
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) { if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
@ -144,44 +140,14 @@ class Url
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params); $params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
} }
$fragment = self::getUrlFragment($url); if (isset($urlParts['fragment'])) {
if ($fragment !== '') { $urlObject->setAnchor($urlParts['fragment']);
$urlObject->setAnchor($fragment);
} }
$urlObject->setParams($params); $urlObject->setParams($params);
return $urlObject; return $urlObject;
} }
/**
* Get the fragment of a given url
*
* @param string $url The url containing the fragment.
*
* @return string The fragment without the '#'
*/
protected static function getUrlFragment($url)
{
$url = parse_url($url);
if (isset($url['fragment'])) {
return $url['fragment'];
} else {
return '';
}
}
/**
* Remove the fragment-part of a given url
*
* @param string $url The url to strip from its fragment
*
* @return string The url without the fragment
*/
protected static function stripUrlFragment($url)
{
return preg_replace('/#.*$/', '', $url);
}
/** /**
* Overwrite the baseUrl * Overwrite the baseUrl
* *
@ -241,12 +207,12 @@ class Url
* *
* @return string * @return string
*/ */
public function getRelativeUrl() public function getRelativeUrl($separator = '&')
{ {
if ($this->params->isEmpty()) { if ($this->params->isEmpty()) {
return $this->path . $this->anchor; return $this->path . $this->anchor;
} else { } else {
return $this->path . '?' . $this->params->setSeparator('&amp;') . $this->anchor; return $this->path . '?' . $this->params->toString($separator) . $this->anchor;
} }
} }
@ -266,9 +232,9 @@ class Url
* *
* @return string * @return string
*/ */
public function getAbsoluteUrl() public function getAbsoluteUrl($separator = '&')
{ {
return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl(); return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator);
} }
/** /**
@ -331,11 +297,11 @@ class Url
/** /**
* Return all parameters that will be used in the query part * Return all parameters that will be used in the query part
* *
* @return array An associative key => value array containing all parameters * @return UrlParams An instance of UrlParam containing all parameters
*/ */
public function getParams() public function getParams()
{ {
return $this->params->asArray(); return $this->params;
} }
/** /**
@ -450,6 +416,6 @@ class Url
*/ */
public function __toString() public function __toString()
{ {
return $this->getAbsoluteUrl(); return $this->getAbsoluteUrl('&amp;');
} }
} }

View File

@ -114,6 +114,18 @@ class UrlParams
return $ret; return $ret;
} }
public function addEncoded($param, $value = true)
{
$this->params[] = array($param, $this->cleanupValue($value));
$this->indexLastOne();
return $this;
}
protected function urlEncode($value)
{
return rawurlencode((string) $value);
}
/** /**
* Add the given parameter with the given value * Add the given parameter with the given value
* *
@ -127,9 +139,7 @@ class UrlParams
*/ */
public function add($param, $value = true) public function add($param, $value = true)
{ {
$this->params[] = array($param, $this->cleanupValue($value)); return $this->addEncoded($this->urlEncode($param), $this->urlEncode($value));
$this->indexLastOne();
return $this;
} }
/** /**
@ -198,7 +208,7 @@ class UrlParams
*/ */
public function unshift($param, $value) public function unshift($param, $value)
{ {
array_unshift($this->params, array($param, $this->cleanupValue($value))); array_unshift($this->params, array($this->urlEncode($param), $this->urlEncode($value)));
$this->reIndexAll(); $this->reIndexAll();
return $this; return $this;
} }
@ -224,7 +234,10 @@ class UrlParams
unset($this->params[$remove]); unset($this->params[$remove]);
} }
$this->params[$this->index[$param][0]] = array($param, $this->cleanupValue($value)); $this->params[$this->index[$param][0]] = array(
$this->urlEncode($param),
$this->urlEncode($this->cleanupValue($value))
);
$this->reIndexAll(); $this->reIndexAll();
return $this; return $this;
@ -243,7 +256,7 @@ class UrlParams
foreach ($this->index[$p] as $key) { foreach ($this->index[$p] as $key) {
unset($this->params[$key]); unset($this->params[$key]);
} }
$this->changed = true; $changed = true;
} }
} }
@ -303,20 +316,23 @@ class UrlParams
protected function parseQueryStringPart($part) protected function parseQueryStringPart($part)
{ {
if (strpos($part, '=') === false) { if (strpos($part, '=') === false) {
$this->add($part, true); $this->addEncoded($part, true);
} else { } else {
list($key, $val) = preg_split('/=/', $part, 2); list($key, $val) = preg_split('/=/', $part, 2);
$this->add($key, $val); $this->addEncoded($key, $val);
} }
} }
public function asArray() public function toArray()
{ {
return $this->params; return $this->params;
} }
public function __toString() public function toString($separator = null)
{ {
if ($separator === null) {
$separator = $this->separator;
}
$parts = array(); $parts = array();
foreach ($this->params as $p) { foreach ($this->params as $p) {
if ($p[1] === true) { if ($p[1] === true) {
@ -325,13 +341,18 @@ class UrlParams
$parts[] = $p[0] . '=' . $p[1]; $parts[] = $p[0] . '=' . $p[1];
} }
} }
return implode($this->separator, $parts); return implode($separator, $parts);
}
public function __toString()
{
return $this->toString();
} }
public static function fromQueryString($queryString = null) public static function fromQueryString($queryString = null)
{ {
if ($queryString === null) { if ($queryString === null) {
$queryString = $_SERVER['QUERY_STRING']; $queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
} }
$params = new static(); $params = new static();
$params->parseQueryString($queryString); $params->parseQueryString($queryString);

View File

@ -126,7 +126,7 @@ EOD;
public function toArray() public function toArray()
{ {
$array = array('url' => $this->url->getPath()); $array = array('url' => $this->url->getPath());
foreach ($this->url->getParams() as $param) { foreach ($this->url->getParams()->toArray() as $param) {
$array[$param[0]] = $param[1]; $array[$param[0]] = $param[1];
} }
return $array; return $array;

View File

@ -81,7 +81,7 @@ class Limiter extends AbstractWidget
$this->url->setParam('limit', $limit), $this->url->setParam('limit', $limit),
null, null,
array( array(
'title' => t(sprintf('Show %s rows on one page', $caption)) 'title' => sprintf(t('Show %s rows on one page'), $caption)
) )
); );
} }

View File

@ -28,7 +28,7 @@ class DashboardAction implements Tabextension
'title' => 'Add To Dashboard', 'title' => 'Add To Dashboard',
'url' => Url::fromPath('dashboard/addurl'), 'url' => Url::fromPath('dashboard/addurl'),
'urlParams' => array( 'urlParams' => array(
'url' => Url::fromRequest()->getRelativeUrl() 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
) )
) )
); );

View File

@ -0,0 +1,48 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use \Zend_Controller_Action_Exception;
use Icinga\Application\Icinga;
use Icinga\Module\Doc\DocController;
class Doc_IcingawebController extends DocController
{
/**
* View the toc of Icinga Web 2's documentation
*/
public function tocAction()
{
$this->renderToc(Icinga::app()->getApplicationDir('/../doc'), 'Icinga Web 2', 'doc/icingaweb/chapter');
}
/**
* View a chapter of Icinga Web 2's documentation
*
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing
*/
public function chapterAction()
{
$chapterId = $this->getParam('chapterId');
if ($chapterId === null) {
throw new Zend_Controller_Action_Exception(
$this->translate('Missing parameter \'chapterId\''),
404
);
}
$this->renderChapter(
Icinga::app()->getApplicationDir('/../doc'),
$chapterId,
'doc/icingaweb/toc',
'doc/icingaweb/chapter'
);
}
/**
* View Icinga Web 2's documentation as PDF
*/
public function pdfAction()
{
$this->renderPdf(Icinga::app()->getApplicationDir('/../doc'), 'Icinga Web 2', 'doc/icingaweb/chapter');
}
}

View File

@ -2,34 +2,9 @@
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Module\Doc\Controller as DocController; use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\DocParser;
class Doc_IndexController extends DocController class Doc_IndexController extends DocController
{ {
protected $parser; public function indexAction() {}
public function init()
{
$module = null;
$this->parser = new DocParser($module);
}
public function tocAction()
{
// Temporary workaround
list($html, $toc) = $this->parser->getDocumentation();
$this->view->toc = $toc;
}
/**
* Display the application's documentation
*/
public function indexAction()
{
$this->populateView();
}
} }

View File

@ -2,44 +2,131 @@
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
use \Zend_Controller_Action_Exception;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Module\Doc\Controller as DocController; use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\Exception\DocException;
class Doc_ModuleController extends DocController class Doc_ModuleController extends DocController
{ {
/** /**
* Display module documentations index * List modules which are enabled and having the 'doc' directory
*/ */
public function indexAction() public function indexAction()
{ {
$this->view->enabledModules = Icinga::app()->getModuleManager()->listEnabledModules();
}
/**
* Display a module's documentation
*/
public function viewAction()
{
$this->populateView($this->getParam('name'));
}
/**
* Provide run-time dispatching of module documentation
*
* @param string $methodName
* @param array $args
*
* @return mixed
*/
public function __call($methodName, $args)
{
// TODO(el): Setup routing to retrieve module name as param and point route to moduleAction
$moduleManager = Icinga::app()->getModuleManager(); $moduleManager = Icinga::app()->getModuleManager();
$moduleName = substr($methodName, 0, -6); // Strip 'Action' suffix $modules = array();
if (!$moduleManager->hasEnabled($moduleName)) { foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) {
// TODO(el): Throw a not found exception once the code has been moved to the moduleAction (see TODO above) $docDir = $moduleManager->getModuleDir($enabledModule, '/doc');
return parent::__call($methodName, $args); if (is_dir($docDir)) {
$modules[] = $enabledModule;
} }
$this->_helper->redirector->gotoSimpleAndExit('view', null, null, array('name' => $moduleName)); }
$this->view->modules = $modules;
}
/**
* Assert that the given module is enabled
*
* @param $moduleName
*
* @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the
* given module is neither installed nor enabled
*/
protected function assertModuleEnabled($moduleName)
{
if (empty($moduleName)) {
throw new Zend_Controller_Action_Exception(
$this->translate('Missing parameter \'moduleName\''),
404
);
}
$moduleManager = Icinga::app()->getModuleManager();
if (! $moduleManager->hasInstalled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not installed'), $moduleName),
404
);
}
if (! $moduleManager->hasEnabled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not enabled'), $moduleName),
404
);
}
}
/**
* View the toc of a module's documentation
*
* @see assertModuleEnabled()
*/
public function tocAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$moduleManager = Icinga::app()->getModuleManager();
try {
$this->renderToc(
$moduleManager->getModuleDir($moduleName, '/doc'),
$moduleName,
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
}
$this->view->moduleName = $moduleName;
}
/**
* View a chapter of a module's documentation
*
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing or if an error in
* the documentation module's library occurs
* @see assertModuleEnabled()
*/
public function chapterAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$chapterId = $this->getParam('chapterId');
if ($chapterId === null) {
throw new Zend_Controller_Action_Exception(
$this->translate('Missing parameter \'chapterId\''),
404
);
}
$moduleManager = Icinga::app()->getModuleManager();
try {
$this->renderChapter(
$moduleManager->getModuleDir($moduleName, '/doc'),
$chapterId,
$this->_helper->url->url(array('moduleName' => $moduleName), 'doc/module/toc'),
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
}
$this->view->moduleName = $moduleName;
}
/**
* View a module's documentation as PDF
*
* @see assertModuleEnabled()
*/
public function pdfAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$moduleManager = Icinga::app()->getModuleManager();
$this->renderPdf(
$moduleManager->getModuleDir($moduleName, '/doc'),
$moduleName,
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} }
} }

View File

@ -0,0 +1,3 @@
<div class="chapter">
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
</div>

View File

@ -1,5 +1,6 @@
<h1>Icinga 2 Documentation</h1> <div class="controls"></div>
<?= $this->partial('module/view.phtml', 'doc', array( <h1><?= $this->translate('Available documentations'); ?></h1>
'toc' => $toc, <ul>
'html' => $html <li><a href="<?= $this->href('doc/icingaweb/toc'); ?>">Icinga Web 2</a></li>
)); ?> <li><a href="<?= $this->href('doc/module/'); ?>"><?= $this->translate('Module documentations'); ?></a></li>
</ul>

View File

@ -1,14 +0,0 @@
<div class="controls">
<h1>Module documentations</h1>
</div>
<div class="content" data-base-target="_next">
<?= $this->partial(
'layout/menu.phtml',
'default',
array(
'items' => $toc->getChildren(),
'sub' => false,
'url' => ''
)
) ?>
</div>

View File

@ -1,6 +1,10 @@
<h1>Module documentations</h1> <h1><?= $this->translate('Module documentations'); ?></h1>
<ul> <ul>
<?php foreach ($enabledModules as $module): ?> <?php foreach ($modules as $module): ?>
<li><a href="<?= $this->href('doc/module/view', array('name' => $module)); ?>"><?= $module ?></a></li> <li>
<?php endforeach ?> <a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false); ?>">
<?= $module ?>
</a>
</li>
<?php endforeach ?>
</ul> </ul>

View File

@ -1,7 +0,0 @@
<?php if ($html === null): ?>
<p>No documentation available.</p>
<?php else: ?>
<div class="content">
<?= $html ?>
</div>
<?php endif ?>

View File

@ -0,0 +1,7 @@
<h1><?= $docName ?> <?= $this->translate('Documentation'); ?></h1>
<div class="toc">
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
</div>
<div class="chapter">
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
</div>

View File

@ -0,0 +1,6 @@
<div class="controls">
<h1><?= $title ?></h1>
</div>
<div class="content toc">
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
</div>

View File

@ -1,23 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use Icinga\Web\Controller\ModuleActionController;
class Controller extends ModuleActionController
{
/**
* Set HTML and toc
*
* @param string $module
*/
protected function populateView($module = null)
{
$parser = new DocParser($module);
list($html, $toc) = $parser->getDocumentation();
$this->view->html = $html;
$this->view->toc = $toc;
}
}

View File

@ -0,0 +1,76 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use Icinga\Web\Controller\ModuleActionController;
class DocController extends ModuleActionController
{
/**
* Render a chapter
*
* @param string $path Path to the documentation
* @param string $chapterId ID of the chapter
* @param string $tocUrl
* @param string $url
* @param array $urlParams
*/
protected function renderChapter($path, $chapterId, $tocUrl, $url, array $urlParams = array())
{
$parser = new DocParser($path);
$this->view->sectionRenderer = new SectionRenderer(
$parser->getDocTree(),
SectionRenderer::decodeUrlParam($chapterId),
$tocUrl,
$url,
$urlParams
);
$this->view->title = $chapterId;
$this->_helper->viewRenderer('chapter', null, true);
}
/**
* Render a toc
*
* @param string $path Path to the documentation
* @param string $name Name of the documentation
* @param string $url
* @param array $urlParams
*/
protected function renderToc($path, $name, $url, array $urlParams = array())
{
$parser = new DocParser($path);
$this->view->tocRenderer = new TocRenderer($parser->getDocTree(), $url, $urlParams);
$name = ucfirst($name);
$this->view->docName = $name;
$this->view->title = sprintf($this->translate('%s Documentation'), $name);
$this->_helper->viewRenderer('toc', null, true);
}
/**
* Render a pdf
*
* @param string $path Path to the documentation
* @param string $name Name of the documentation
* @param string $url
* @param array $urlParams
*/
protected function renderPdf($path, $name, $url, array $urlParams = array())
{
$parser = new DocParser($path);
$docTree = $parser->getDocTree();
$this->view->tocRenderer = new TocRenderer($docTree, $url, $urlParams);
$this->view->sectionRenderer = new SectionRenderer(
$docTree,
null,
null,
$url,
$urlParams
);
$this->view->docName = $name;
$this->_helper->viewRenderer('pdf', null, true);
$this->_request->setParam('format', 'pdf');
}
}

View File

@ -1,11 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use \Exception;
class DocException extends Exception
{
}

View File

@ -0,0 +1,62 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
/**
* Iterator over non-empty Markdown files ordered by the case insensitive "natural order" of file names
*/
class DocIterator implements Countable, IteratorAggregate
{
/**
* Ordered files
*
* @var array
*/
protected $fileInfo;
/**
* Create a new DocIterator
*
* @param string $path Path to the documentation
*/
public function __construct($path)
{
$it = new RecursiveIteratorIterator(
new NonEmptyFileIterator(
new MarkdownFileIterator(
new RecursiveDirectoryIterator($path)
)
)
);
// Unfortunately we have no chance to sort the iterator
$fileInfo = iterator_to_array($it);
natcasesort($fileInfo);
$this->fileInfo = $fileInfo;
}
/**
* (non-PHPDoc)
* @see Countable::count()
*/
public function count()
{
return count($this->fileInfo);
}
/**
* (non-PHPDoc)
* @see IteratorAggregate::getIterator()
*/
public function getIterator()
{
return new ArrayIterator($this->fileInfo);
}
}

View File

@ -4,146 +4,65 @@
namespace Icinga\Module\Doc; namespace Icinga\Module\Doc;
use RecursiveIteratorIterator; use SplDoublyLinkedList;
use RecursiveDirectoryIterator; use Icinga\Exception\NotReadableError;
use Parsedown; use Icinga\Module\Doc\Exception\DocEmptyException;
use Icinga\Application\Icinga; use Icinga\Module\Doc\Exception\DocException;
use Icinga\Web\Menu;
use Icinga\Web\Url;
require_once 'IcingaVendor/Parsedown/Parsedown.php';
/** /**
* Parser for documentation written in Markdown * Parser for documentation written in Markdown
*/ */
class DocParser class DocParser
{ {
protected $dir; /**
* Path to the documentation
protected $module; *
* @var string
*/
protected $path;
/** /**
* Create a new documentation parser for the given module or the application * Iterator over documentation files
* *
* @param string $module * @var DocIterator
*
* @throws DocException
*/ */
public function __construct($module = null) protected $docIterator;
{
if ($module === null) {
$dir = Icinga::app()->getApplicationDir('/../doc');
} else {
$mm = Icinga::app()->getModuleManager();
if (!$mm->hasInstalled($module)) {
throw new DocException('Module is not installed');
}
if (!$mm->hasEnabled($module)) {
throw new DocException('Module is not enabled');
}
$dir = $mm->getModuleDir($module, '/doc');
}
if (!is_dir($dir)) {
throw new DocException('Doc directory does not exist');
}
$this->dir = $dir;
$this->module = $module;
}
/** /**
* Retrieve table of contents and HTML converted from markdown files sorted by filename * Create a new documentation parser for the given path
* *
* @return array * @param string $path Path to the documentation
* @throws DocException *
* @throws DocException If the documentation directory does not exist
* @throws NotReadableError If the documentation directory is not readable
* @throws DocEmptyException If the documentation directory is empty
*/ */
public function getDocumentation() public function __construct($path)
{ {
$iter = new RecursiveIteratorIterator( if (! is_dir($path)) {
new MarkdownFileIterator( throw new DocException(
new RecursiveDirectoryIterator($this->dir) sprintf(mt('doc', 'Documentation directory \'%s\' does not exist'), $path)
);
}
if (! is_readable($path)) {
throw new DocException(
sprintf(mt('doc', 'Documentation directory \'%s\' is not readable'), $path)
);
}
$docIterator = new DocIterator($path);
if ($docIterator->count() === 0) {
throw new DocEmptyException(
sprintf(
mt(
'doc',
'Documentation directory \'%s\' does not contain any non-empty Markdown file (\'.md\' suffix)'
),
$path
) )
); );
$fileInfos = iterator_to_array($iter);
natcasesort($fileInfos);
$cat = array();
$toc = array((object) array(
'level' => 0,
'item' => new Menu('doc')
));
$itemPriority = 1;
foreach ($fileInfos as $fileInfo) {
try {
$fileObject = $fileInfo->openFile();
} catch (RuntimeException $e) {
throw new DocException($e->getMessage());
} }
if ($fileObject->flock(LOCK_SH) === false) { $this->path = $path;
throw new DocException('Couldn\'t get the lock'); $this->docIterator = $docIterator;
}
$line = null;
while (!$fileObject->eof()) {
// Save last line for setext-style headers
$lastLine = $line;
$line = $fileObject->fgets();
$header = $this->extractHeader($line, $lastLine);
if ($header !== null) {
list($header, $level) = $header;
$id = $this->extractHeaderId($header);
$attribs = array();
$this->reduceToc($toc, $level);
if ($id === null) {
$path = array();
foreach (array_slice($toc, 1) as $entry) {
$path[] = $entry->item->getTitle();
}
$path[] = $header;
$id = implode('-', $path);
$attribs['rel'] = 'nofollow';
}
$id = urlencode(str_replace('.', '&#46;', strip_tags($id)));
$item = end($toc)->item->addChild(
$id,
array(
'url' => Url::fromPath(
'doc/module/view',
array(
'name' => $this->module
)
)->setAnchor($id)->getRelativeUrl(),
'title' => htmlspecialchars($header),
'priority' => $itemPriority++,
'attribs' => $attribs
)
);
$toc[] = ((object) array(
'level' => $level,
'item' => $item
));
$line = '<a name="' . $id . '"></a>' . PHP_EOL . $line;
}
$cat[] = $line;
}
$fileObject->flock(LOCK_UN);
}
$html = Parsedown::instance()->parse(implode('', $cat));
$html = preg_replace_callback(
'#<pre><code class="language-php">(.*?)\</code></pre>#s',
array($this, 'highlight'),
$html
);
return array($html, $toc[0]->item);
}
/**
* Syntax highlighting for PHP code
*
* @param $match
*
* @return string
*/
protected function highlight($match)
{
return highlight_string(htmlspecialchars_decode($match[1]), true);
} }
/** /**
@ -156,28 +75,28 @@ class DocParser
*/ */
protected function extractHeader($line, $lastLine) protected function extractHeader($line, $lastLine)
{ {
if (!$line) { if (! $line) {
return null; return null;
} }
$header = null; $header = null;
if ($line && if ($line
$line[0] === '#' && && $line[0] === '#'
preg_match('/^#+/', $line, $match) === 1 && preg_match('/^#+/', $line, $match) === 1
) { ) {
// Atx-style // Atx
$level = strlen($match[0]); $level = strlen($match[0]);
$header = trim(substr($line, $level)); $header = trim(substr($line, $level));
if (!$header) { if (! $header) {
return null; return null;
} }
} elseif ( } elseif (
$line && $line
($line[0] === '=' || $line[0] === '-') && && ($line[0] === '=' || $line[0] === '-')
preg_match('/^[=-]+\s*$/', $line, $match) === 1 && preg_match('/^[=-]+\s*$/', $line, $match) === 1
) { ) {
// Setext // Setext
$header = trim($lastLine); $header = trim($lastLine);
if (!$header) { if (! $header) {
return null; return null;
} }
if ($match[0][0] === '=') { if ($match[0][0] === '=') {
@ -189,36 +108,67 @@ class DocParser
if ($header === null) { if ($header === null) {
return null; return null;
} }
return array($header, $level); if ($header[0] === '<'
} && preg_match('#(?:<(?P<tag>a|span) (?:id|name)="(?P<id>.+)"></(?P=tag)>)\s*#u', $header, $match)
/**
* Extract header id in an a or a span tag
*
* @param string &$header
*
* @return id|null
*/
protected function extractHeaderId(&$header)
{
if ($header[0] === '<' &&
preg_match('#(?:<(?P<tag>a|span) id="(?P<id>.+)"></(?P=tag)>)#u', $header, $match)
) { ) {
$header = str_replace($match[0], '', $header); $header = str_replace($match[0], '', $header);
return $match['id']; $id = $match['id'];
} else {
$id = null;
} }
return null; return array($header, $id, $level);
} }
/** /**
* Reduce the toc to the given level * Get the documentation tree
* *
* @param array &$toc * @return DocTree
* @param int $level
*/ */
protected function reduceToc(array &$toc, $level) { public function getDocTree()
while (end($toc)->level >= $level) { {
array_pop($toc); $tree = new DocTree();
$stack = new SplDoublyLinkedList();
foreach ($this->docIterator as $fileInfo) {
/* @var $file \SplFileInfo */
$file = $fileInfo->openFile();
/* @var $file \SplFileObject */
$lastLine = null;
foreach ($file as $line) {
$header = $this->extractHeader($line, $lastLine);
if ($header !== null) {
list($title, $id, $level) = $header;
while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) {
$stack->pop();
} }
if ($id === null) {
$path = array();
foreach ($stack as $section) {
/* @var $section Section */
$path[] = $section->getTitle();
}
$path[] = $title;
$id = implode('-', $path);
$noFollow = true;
} else {
$noFollow = false;
}
if ($stack->isEmpty()) {
$chapterId = $id;
$section = new Section($id, $title, $level, $noFollow, $chapterId);
$tree->addRoot($section);
} else {
$chapterId = $stack->bottom()->getId();
$section = new Section($id, $title, $level, $noFollow, $chapterId);
$tree->addChild($section, $stack->top());
}
$stack->push($section);
} else {
$stack->top()->appendContent($line);
}
// Save last line for setext-style headers
$lastLine = $line;
}
}
return $tree;
} }
} }

View File

@ -0,0 +1,80 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use LogicException;
use Icinga\Data\Identifiable;
use Icinga\Data\Tree\Node;
/**
* Documentation tree
*/
class DocTree extends Node
{
/**
* All nodes of the tree
*
* @var array
*/
protected $nodes = array();
/**
* Append a root node to the tree
*
* @param Identifiable $root
*/
public function addRoot(Identifiable $root)
{
$rootId = $root->getId();
if (isset($this->nodes[$rootId])) {
$rootId = uniqid($rootId);
// throw new LogicException(
// sprintf('Can\'t add root node: a root node with the id \'%s\' already exists', $rootId)
// );
}
$this->nodes[$rootId] = $this->appendChild($root);
}
/**
* Append a child node to a parent node
*
* @param Identifiable $child
* @param Identifiable $parent
*
* @throws LogicException If the the tree does not contain the parent node
*/
public function addChild(Identifiable $child, Identifiable $parent)
{
$childId = $child->getId();
$parentId = $parent->getId();
if (isset($this->nodes[$childId])) {
$childId = uniqid($childId);
// throw new LogicException(
// sprintf('Can\'t add child node: a child node with the id \'%s\' already exists', $childId)
// );
}
if (! isset($this->nodes[$parentId])) {
throw new LogicException(
sprintf(mt('doc', 'Can\'t add child node: there\'s no parent node having the id \'%s\''), $parentId)
);
}
$this->nodes[$childId] = $this->nodes[$parentId]->appendChild($child);
}
/**
* Get a node
*
* @param mixed $id
*
* @return Node|null
*/
public function getNode($id)
{
if (! isset($this->nodes[$id])) {
return null;
}
return $this->nodes[$id];
}
}

View File

@ -0,0 +1,10 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc\Exception;
/**
* Exception thrown if a chapter was not found
*/
class ChapterNotFoundException extends DocException {}

View File

@ -0,0 +1,10 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc\Exception;
/**
* Exception thrown if a documentation directory is empty
*/
class DocEmptyException extends DocException {}

View File

@ -0,0 +1,12 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc\Exception;
use RuntimeException;
/**
* Exception thrown if an error in the documentation module's library occurs
*/
class DocException extends RuntimeException {}

View File

@ -4,15 +4,15 @@
namespace Icinga\Module\Doc; namespace Icinga\Module\Doc;
use \RecursiveFilterIterator; use RecursiveFilterIterator;
/** /**
* Iterator over Markdown files recursively * Recursive iterator over Markdown files
*/ */
class MarkdownFileIterator extends RecursiveFilterIterator class MarkdownFileIterator extends RecursiveFilterIterator
{ {
/** /**
* Accept files with .md suffix * Accept files with '.md' suffix
* *
* @return bool Whether the current element of the iterator is acceptable * @return bool Whether the current element of the iterator is acceptable
* through this filter * through this filter
@ -20,7 +20,8 @@ class MarkdownFileIterator extends RecursiveFilterIterator
public function accept() public function accept()
{ {
$current = $this->getInnerIterator()->current(); $current = $this->getInnerIterator()->current();
if (!$current->isFile()) { /* @var $current \SplFileInfo */
if (! $current->isFile()) {
return false; return false;
} }
$filename = $current->getFilename(); $filename = $current->getFilename();

View File

@ -0,0 +1,31 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
use RecursiveFilterIterator;
/**
* Recursive iterator over non-empty files
*/
class NonEmptyFileIterator extends RecursiveFilterIterator
{
/**
* Accept non-empty files
*
* @return bool Whether the current element of the iterator is acceptable
* through this filter
*/
public function accept()
{
$current = $this->getInnerIterator()->current();
/* @var $current \SplFileInfo */
if (! $current->isFile()
|| $current->getSize() === 0
) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,75 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use RecursiveIteratorIterator;
use Zend_View_Helper_Url;
use Icinga\Web\View;
/**
* Base class for toc and section renderer
*/
abstract class Renderer extends RecursiveIteratorIterator
{
/**
* Encode an anchor identifier
*
* @param string $anchor
*
* @return string
*/
public static function encodeAnchor($anchor)
{
return rawurlencode($anchor);
}
/**
* Decode an anchor identifier
*
* @param string $anchor
*
* @return string
*/
public static function decodeAnchor($anchor)
{
return rawurldecode($anchor);
}
/**
* Encode a URL parameter
*
* @param string $param
*
* @return string
*/
public static function encodeUrlParam($param)
{
return str_replace(array('%2F','%5C'), array('%252F','%255C'), rawurlencode($param));
}
/**
* Decode a URL parameter
*
* @param string $param
*
* @return string
*/
public static function decodeUrlParam($param)
{
return str_replace(array('%2F', '%5C'), array('/', '\\'), $param);
}
/**
* Render to HTML
*
* Meant to be overwritten by concrete classes.
*
* @param View $view
* @param Zend_View_Helper_Url $zendUrlHelper
*
* @return string
*/
abstract public function render(View $view, Zend_View_Helper_Url $zendUrlHelper);
}

View File

@ -0,0 +1,143 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use Icinga\Data\Identifiable;
/**
* A section of a documentation
*/
class Section implements Identifiable
{
/**
* The ID of the section
*
* @var string
*/
protected $id;
/**
* The title of the section
*
* @var string
*/
protected $title;
/**
* The header level
*
* @var int
*/
protected $level;
/**
* Whether to instruct search engines to not index the link to the section
*
* @var bool
*/
protected $noFollow;
/**
* The ID of the chapter the section is part of
*
* @var string
*/
protected $chapterId;
/**
* The content of the section
*
* @var array
*/
protected $content = array();
/**
* Create a new section
*
* @param string $id The ID of the section
* @param string $title The title of the section
* @param int $level The header level
* @param bool $noFollow Whether to instruct search engines to not index the link to the section
* @param string $chapterId The ID of the chapter the section is part of
*/
public function __construct($id, $title, $level, $noFollow, $chapterId)
{
$this->id = $id;
$this->title = $title;
$this->level = $level;
$this->noFollow = $noFollow;
$this->chapterId= $chapterId;
}
/**
* Get the ID of the section
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Get the title of the section
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Get the header level
*
* @return int
*/
public function getLevel()
{
return $this->level;
}
/**
* Whether to instruct search engines to not index the link to the section
*
* @return bool
*/
public function isNoFollow()
{
return $this->noFollow;
}
/**
* The ID of the chapter the section is part of
*
* @return string
*/
public function getChapterId()
{
return $this->chapterId;
}
/**
* Append content
*
* @param string $content
*/
public function appendContent($content)
{
$this->content[] = $content;
}
/**
* Get the content of the section
*
* @return array
*/
public function getContent()
{
return $this->content;
}
}

View File

@ -0,0 +1,68 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
use Countable;
use RecursiveFilterIterator;
use Icinga\Data\Tree\NodeInterface;
/**
* Recursive iterator over sections that are part of a particular chapter
*/
class SectionFilterIterator extends RecursiveFilterIterator implements Countable
{
/**
* The chapter ID to filter for
*
* @var string
*/
protected $chapterId;
/**
* Create a new SectionFilterIterator
*
* @param NodeInterface $node Node
* @param string $chapterId The chapter ID to filter for
*/
public function __construct(NodeInterface $node, $chapterId)
{
parent::__construct($node);
$this->chapterId = $chapterId;
}
/**
* Accept sections that are part of the given chapter
*
* @return bool Whether the current element of the iterator is acceptable
* through this filter
*/
public function accept()
{
$section = $this->getInnerIterator()->current()->getValue();
/* @var $section \Icinga\Module\Doc\Section */
if ($section->getChapterId() === $this->chapterId) {
return true;
}
return false;
}
/**
* (non-PHPDoc)
* @see RecursiveFilterIterator::getChildren()
*/
public function getChildren()
{
return new static($this->getInnerIterator()->getChildren(), $this->chapterId);
}
/**
* (non-PHPDoc)
* @see Countable::count()
*/
public function count()
{
return iterator_count($this);
}
}

View File

@ -0,0 +1,292 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
require_once 'IcingaVendor/Parsedown/Parsedown.php';
use DOMDocument;
use DOMXPath;
use RecursiveIteratorIterator;
use Parsedown;
use Zend_View_Helper_Url;
use Icinga\Module\Doc\Exception\ChapterNotFoundException;
use Icinga\Web\Url;
use Icinga\Web\View;
/**
* preg_replace_callback helper to replace links
*/
class Callback
{
protected $docTree;
protected $view;
protected $zendUrlHelper;
protected $url;
protected $urlParams;
public function __construct(
DocTree $docTree,
View $view,
Zend_View_Helper_Url $zendUrlHelper,
$url,
array $urlParams)
{
$this->docTree = $docTree;
$this->view = $view;
$this->zendUrlHelper = $zendUrlHelper;
$this->url = $url;
$this->urlParams = $urlParams;
}
public function render($match)
{
$node = $this->docTree->getNode(Renderer::decodeAnchor($match['fragment']));
/* @var $node \Icinga\Data\Tree\Node */
if ($node === null) {
return $match[0];
}
$section = $node->getValue();
/* @var $section \Icinga\Module\Doc\Section */
$path = $this->zendUrlHelper->url(
array_merge(
$this->urlParams,
array(
'chapterId' => SectionRenderer::encodeUrlParam($section->getChapterId())
)
),
$this->url,
false,
false
);
$url = $this->view->url($path);
$url->setAnchor(SectionRenderer::encodeAnchor($section->getId()));
return sprintf(
'<a %s%shref="%s"',
strlen($match['attribs']) ? trim($match['attribs']) . ' ' : '',
$section->isNoFollow() ? 'rel="nofollow" ' : '',
$url->getAbsoluteUrl()
);
}
}
/**
* Section renderer
*/
class SectionRenderer extends Renderer
{
/**
* The documentation tree
*
* @var DocTree
*/
protected $docTree;
protected $tocUrl;
/**
* The URL to replace links with
*
* @var string
*/
protected $url;
/**
* Additional URL parameters
*
* @var array
*/
protected $urlParams;
/**
* Parsedown instance
*
* @var Parsedown
*/
protected $parsedown;
/**
* Content
*
* @var array
*/
protected $content = array();
/**
* Create a new section renderer
*
* @param DocTree $docTree The documentation tree
* @param string|null $chapterId If not null, the chapter ID to filter for
* @param string $tocUrl
* @param string $url The URL to replace links with
* @param array $urlParams Additional URL parameters
*
* @throws ChapterNotFoundException If the chapter to filter for was not found
*/
public function __construct(DocTree $docTree, $chapterId, $tocUrl, $url, array $urlParams)
{
if ($chapterId !== null) {
$filter = new SectionFilterIterator($docTree, $chapterId);
if ($filter->count() === 0) {
throw new ChapterNotFoundException(
sprintf(mt('doc', 'Chapter \'%s\' not found'), $chapterId)
);
}
parent::__construct(
$filter,
RecursiveIteratorIterator::SELF_FIRST
);
} else {
parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST);
}
$this->docTree = $docTree;
$this->tocUrl = $tocUrl;
$this->url = $url;
$this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
$this->parsedown = Parsedown::instance();
}
/**
* Syntax highlighting for PHP code
*
* @param $match
*
* @return string
*/
protected function highlightPhp($match)
{
return '<pre>' . highlight_string(htmlspecialchars_decode($match[1]), true) . '</pre>';
}
/**
* Replace img src tags
*
* @param $match
*
* @return string
*/
protected function replaceImg($match)
{
$doc = new DOMDocument();
$doc->loadHTML($match[0]);
$xpath = new DOMXPath($doc);
$img = $xpath->query('//img[1]')->item(0);
/* @var $img \DOMElement */
$img->setAttribute('src', Url::fromPath($img->getAttribute('src'))->getAbsoluteUrl());
return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>'
}
/**
* Render the section
*
* @param View $view
* @param Zend_View_Helper_Url $zendUrlHelper
* @param bool $renderNavigation
*
* @return string
*/
public function render(View $view, Zend_View_Helper_Url $zendUrlHelper, $renderNavigation = true)
{
$callback = new Callback($this->docTree, $view, $zendUrlHelper, $this->url, $this->urlParams);
$content = array();
foreach ($this as $node) {
$section = $node->getValue();
/* @var $section \Icinga\Module\Doc\Section */
$content[] = sprintf(
'<a name="%1$s"></a><h%2$d>%3$s</h%2$d>',
Renderer::encodeAnchor($section->getId()),
$section->getLevel(),
$view->escape($section->getTitle())
);
$html = preg_replace_callback(
'#<pre><code class="language-php">(.*?)</code></pre>#s',
array($this, 'highlightPhp'),
$this->parsedown->text(implode('', $section->getContent()))
);
$html = preg_replace_callback(
'/<img[^>]+>/',
array($this, 'replaceImg'),
$html
);
$content[] = preg_replace_callback(
'/<a\s+(?P<attribs>[^>]*?\s+)?href="#(?P<fragment>[^"]+)"/',
array($callback, 'render'),
$html
);
}
if ($renderNavigation) {
foreach ($this->docTree as $chapter) {
if ($chapter->getValue()->getId() === $section->getChapterId()) {
$navigation = array('<ul class="navigation">');
$this->docTree->prev();
$prev = $this->docTree->current();
if ($prev !== null) {
$prev = $prev->getValue();
$path = $zendUrlHelper->url(
array_merge(
$this->urlParams,
array(
'chapterId' => $this->encodeUrlParam($prev->getChapterId())
)
),
$this->url,
false,
false
);
$url = $view->url($path);
$url->setAnchor($this->encodeAnchor($prev->getId()));
$navigation[] = sprintf(
'<li class="prev"><a %shref="%s">%s</a></li>',
$prev->isNoFollow() ? 'rel="nofollow" ' : '',
$url->getAbsoluteUrl(),
$view->escape($prev->getTitle())
);
$this->docTree->next();
$this->docTree->next();
} else {
$this->docTree->rewind();
$this->docTree->next();
}
$url = $view->url($this->tocUrl);
$navigation[] = sprintf(
'<li><a href="%s">%s</a></li>',
$url->getAbsoluteUrl(),
mt('doc', 'Index')
);
$next = $this->docTree->current();
if ($next !== null) {
$next = $next->getValue();
$path = $zendUrlHelper->url(
array_merge(
$this->urlParams,
array(
'chapterId' => $this->encodeUrlParam($next->getChapterId())
)
),
$this->url,
false,
false
);
$url = $view->url($path);
$url->setAnchor($this->encodeAnchor($next->getId()));
$navigation[] = sprintf(
'<li class="next"><a %shref="%s">%s</a></li>',
$next->isNoFollow() ? 'rel="nofollow" ' : '',
$url->getAbsoluteUrl(),
$view->escape($next->getTitle())
);
}
$navigation[] = '</ul>';
$content = array_merge($navigation, $content, $navigation);
break;
}
}
}
return implode("\n", $content);
}
}

View File

@ -0,0 +1,109 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
use RecursiveIteratorIterator;
use Zend_View_Helper_Url;
use Icinga\Web\View;
/**
* TOC renderer
*/
class TocRenderer extends Renderer
{
/**
* The URL to replace links with
*
* @var string
*/
protected $url;
/**
* Additional URL parameters
*
* @var array
*/
protected $urlParams;
/**
* Content
*
* @var array
*/
protected $content = array();
/**
* Create a new toc renderer
*
* @param DocTree $docTree The documentation tree
* @param string $url The URL to replace links with
* @param array $urlParams Additional URL parameters
*/
public function __construct(DocTree $docTree, $url, array $urlParams)
{
parent::__construct($docTree, RecursiveIteratorIterator::SELF_FIRST);
$this->url = $url;
$this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
}
public function beginIteration()
{
$this->content[] = '<nav><ul>';
}
public function endIteration()
{
$this->content[] = '</ul></nav>';
}
public function beginChildren()
{
$this->content[] = '<ul>';
}
public function endChildren()
{
$this->content[] = '</ul></li>';
}
/**
* Render the toc
*
* @param View $view
* @param Zend_View_Helper_Url $zendUrlHelper
*
* @return string
*/
public function render(View $view, Zend_View_Helper_Url $zendUrlHelper)
{
foreach ($this as $node) {
$section = $node->getValue();
/* @var $section \Icinga\Module\Doc\Section */
$path = $zendUrlHelper->url(
array_merge(
$this->urlParams,
array(
'chapterId' => $this->encodeUrlParam($section->getChapterId())
)
),
$this->url,
false,
false
);
$url = $view->url($path);
$url->setAnchor($this->encodeAnchor($section->getId()));
$this->content[] = sprintf(
'<li><a %shref="%s">%s</a>',
$section->isNoFollow() ? 'rel="nofollow" ' : '',
$url->getAbsoluteUrl(),
$view->escape($section->getTitle())
);
if (! $this->getInnerIterator()->current()->hasChildren()) {
$this->content[] = '</li>';
}
}
return implode("\n", $this->content);
}
}

View File

@ -0,0 +1,62 @@
// W3C Recommendation <http://www.w3.org/TR/CSS21/sample.html> (except h4)
h1 { font-size: 2em !important; }
h2 { font-size: 1.5em !important; }
h3 { font-size: 1.17em !important; }
h4 { font-size: 1em !important; }
h5 { font-size: .83em !important; }
h6 { font-size: .75em !important; }
div.chapter {
padding-left: 5px;
}
table th {
text-align: left;
}
table th,
table td {
border: solid 1px lightgray;
padding-left: 5px;
padding-right: 5px;
}
code {
width: 100%;
overflow-x: auto;
padding: 0.2em;
display: inline;
}
pre > code {
display: inline-block;
}
div.chapter > ul.navigation {
margin: 0;
padding: 0.4em;
text-align: center;
background-color: #888;
li {
list-style: none;
display: inline;
margin: 0.2em;
padding: 0;
a {
color: #fff;
text-decoration: none;
}
&.prev {
padding-right: 0.6em;
border-right: 2px solid #fff;
}
&.next {
padding-left: 0.6em;
border-left: 2px solid #fff;
}
}
}

50
modules/doc/run.php Normal file
View File

@ -0,0 +1,50 @@
<?php
use \Zend_Controller_Router_Route;
use Icinga\Application\Icinga;
if (Icinga::app()->isCli()) {
return;
}
$docModuleChapter = new Zend_Controller_Router_Route(
'doc/module/:moduleName/chapter/:chapterId',
array(
'controller' => 'module',
'action' => 'chapter',
'module' => 'doc'
)
);
$docIcingaWebChapter = new Zend_Controller_Router_Route(
'doc/icingaweb/chapter/:chapterId',
array(
'controller' => 'icingaweb',
'action' => 'chapter',
'module' => 'doc'
)
);
$docModuleToc = new Zend_Controller_Router_Route(
'doc/module/:moduleName/toc',
array(
'controller' => 'module',
'action' => 'toc',
'module' => 'doc'
)
);
$docModulePdf = new Zend_Controller_Router_Route(
'doc/module/:moduleName/pdf',
array(
'controller' => 'module',
'action' => 'pdf',
'module' => 'doc'
)
);
$this->addRoute('doc/module/chapter', $docModuleChapter);
$this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter);
$this->addRoute('doc/module/toc', $docModuleToc);
$this->addRoute('doc/module/pdf', $docModulePdf);

View File

@ -72,6 +72,7 @@ class ListCommand extends Command
protected function showFormatted($query, $format, $columns) protected function showFormatted($query, $format, $columns)
{ {
$query = $query->getQuery();
switch($format) { switch($format) {
case 'json': case 'json':
echo json_encode($query->fetchAll()); echo json_encode($query->fetchAll());
@ -155,7 +156,7 @@ class ListCommand extends Command
'service_perfdata', 'service_perfdata',
'service_last_state_change' 'service_last_state_change'
); );
$query = $this->getQuery('status', $columns) $query = $this->getQuery('serviceStatus', $columns)
->order('host_name'); ->order('host_name');
echo $this->renderStatusQuery($query); echo $this->renderStatusQuery($query);
} }
@ -167,6 +168,7 @@ class ListCommand extends Command
$screen = $this->screen; $screen = $this->screen;
$utils = new CliUtils($screen); $utils = new CliUtils($screen);
$maxCols = $screen->getColumns(); $maxCols = $screen->getColumns();
$query = $query->getQuery();
$rows = $query->fetchAll(); $rows = $query->fetchAll();
$count = $query->count(); $count = $query->count();
$count = count($rows); $count = count($rows);

View File

@ -87,8 +87,8 @@ class Monitoring_ChartController extends Controller
'services_pending' 'services_pending'
) )
)->getQuery()->fetchAll(); )->getQuery()->fetchAll();
$this->view->height = intval($this->getParam('height', 220)); $this->view->height = intval($this->getParam('height', 500));
$this->view->width = intval($this->getParam('width', 520)); $this->view->width = intval($this->getParam('width', 500));
if (count($query) === 1) { if (count($query) === 1) {
$this->drawGroupPie($query[0]); $this->drawGroupPie($query[0]);
} else { } else {
@ -112,8 +112,8 @@ class Monitoring_ChartController extends Controller
'services_pending' 'services_pending'
) )
)->getQuery()->fetchAll(); )->getQuery()->fetchAll();
$this->view->height = intval($this->getParam('height', 220)); $this->view->height = intval($this->getParam('height', 500));
$this->view->width = intval($this->getParam('width', 520)); $this->view->width = intval($this->getParam('width', 500));
$this->drawServiceGroupChart($query); $this->drawServiceGroupChart($query);
@ -133,7 +133,8 @@ class Monitoring_ChartController extends Controller
} }
$this->view->chart = new GridChart(); $this->view->chart = new GridChart();
$this->view->chart->setAxisLabel('', t('Services')) $this->view->chart->setAxisLabel('', t('Services'))
->setXAxis(new StaticAxis()); ->setXAxis(new StaticAxis())
->setAxisMin(null, 0);
$this->view->chart->drawBars( $this->view->chart->drawBars(
array( array(
@ -183,7 +184,9 @@ class Monitoring_ChartController extends Controller
); );
} }
$this->view->chart = new GridChart(); $this->view->chart = new GridChart();
$this->view->chart->setAxisLabel('', t('Hosts'))->setXAxis(new StaticAxis()); $this->view->chart->setAxisLabel('', t('Hosts'))
->setXAxis(new StaticAxis())
->setAxisMin(null, 0);
$this->view->chart->drawBars( $this->view->chart->drawBars(
array( array(
'label' => t('Up'), 'label' => t('Up'),

View File

@ -94,7 +94,6 @@ class Monitoring_ListController extends Controller
'host_last_check', 'host_last_check',
'host_last_state_change' => $stateChangeColumn, 'host_last_state_change' => $stateChangeColumn,
'host_notifications_enabled', 'host_notifications_enabled',
// 'host_unhandled_service_count',
'host_unhandled_services', 'host_unhandled_services',
'host_action_url', 'host_action_url',
'host_notes_url', 'host_notes_url',
@ -222,6 +221,7 @@ class Monitoring_ListController extends Controller
'author' => 'downtime_author', 'author' => 'downtime_author',
'start' => 'downtime_start', 'start' => 'downtime_start',
'scheduled_start' => 'downtime_scheduled_start', 'scheduled_start' => 'downtime_scheduled_start',
'scheduled_end' => 'downtime_scheduled_end',
'end' => 'downtime_end', 'end' => 'downtime_end',
'duration' => 'downtime_duration', 'duration' => 'downtime_duration',
'is_flexible' => 'downtime_is_flexible', 'is_flexible' => 'downtime_is_flexible',
@ -229,7 +229,9 @@ class Monitoring_ListController extends Controller
'is_in_effect' => 'downtime_is_in_effect', 'is_in_effect' => 'downtime_is_in_effect',
'entry_time' => 'downtime_entry_time', 'entry_time' => 'downtime_entry_time',
'host' => 'downtime_host', 'host' => 'downtime_host',
'service' => 'downtime_service' 'service' => 'downtime_service',
'host_state' => 'downtime_host_state',
'service_state' => 'downtime_service_state'
))->order('downtime_is_in_effect', 'DESC') ))->order('downtime_is_in_effect', 'DESC')
->order('downtime_scheduled_start', 'DESC'); ->order('downtime_scheduled_start', 'DESC');

View File

@ -64,7 +64,7 @@ class Zend_View_Helper_MonitoringCommands extends Zend_View_Helper_Abstract
$out .= '</div>'; $out .= '</div>';
$out .= '<div class="clearfix"></div>'; $out .= '<div style="clear: both;"></div>';
if ($type === Meta::TYPE_FULL) { if ($type === Meta::TYPE_FULL) {
return '<div>'. $out. '</div>'; return '<div>'. $out. '</div>';

View File

@ -21,7 +21,6 @@ class Monitoring_MultiController extends Controller
array( array(
'host_name', 'host_name',
'host_in_downtime', 'host_in_downtime',
'host_unhandled_service_count',
'host_passive_checks_enabled', 'host_passive_checks_enabled',
'host_obsessing', 'host_obsessing',
'host_state', 'host_state',

View File

@ -103,6 +103,40 @@ class Monitoring_ShowController extends Controller
)); ));
} }
public function contactAction()
{
$contact = $this->getParam('contact');
if (! $contact) {
throw new Zend_Controller_Action_Exception(
$this->translate('The parameter `contact\' is required'),
404
);
}
$query = $this->backend->select()->from('contact', array(
'contact_name',
'contact_id',
'contact_alias',
'contact_email',
'contact_pager',
'contact_notify_service_timeperiod',
'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_timeperiod',
'contact_notify_host_recovery',
'contact_notify_host_down',
'contact_notify_host_unreachable',
'contact_notify_host_flapping',
'contact_notify_host_downtime',
));
$query->where('contact_name', $contact);
$this->view->contacts = $query->paginate();
$this->view->contact_name = $contact;
}
/** /**
* Creating tabs for this controller * Creating tabs for this controller
* @return Tabs * @return Tabs

View File

@ -1,5 +1,5 @@
<div style="width:<?= $this->width; ?>px;height:<?= $this->height; ?>px;margin:auto;"> <div style="max-height:548px; margin:auto;">
<?= <?=
$chart->render(); $chart->render();
?> ?>

View File

@ -1,5 +1,5 @@
<div style="width:<?= $this->width; ?>px;height:<?= $this->height; ?>px"> <div style="max-height:548px; margin:auto;">
<?= <?=
$chart->render(); $chart->render();
?> ?>

View File

@ -1,98 +1,114 @@
<div class="controls">
<?= $this->tabs ?>
<div style="margin: 1em">
<?= $this->sortControl->render($this); ?>
</div>
<?= $this->paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?>
</div>
<div class="content">
<table data-base-target="_next" class="action comments">
<tbody>
<?php <?php
$cf = $this->getHelper('CommandForm'); $helper = $this->getHelper('CommandForm');
if (count($comments) === 0) {
echo t('No comments matching the filter');
}
foreach ($comments as $comment):
?> ?>
<tr>
<td style="width: 5em; text-align: center;"> <?php if (false === $this->compact): ?>
<div class="controls">
<?= $this->tabs->render($this); ?>
<div style="margin: 1em" class="dontprint">
<?= $this->translate('Sort by'); ?> <?= $this->sortControl->render($this); ?>
</div>
<?= $this->widget('limiter', array('url' => $this->url, 'max' => $comments->count())); ?>
<?= $this->paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?>
</div>
<?php endif ?>
<div class="content">
<?php if (empty($comments)): ?>
<?= $this->translate('No comments matching the filter') ?>
</div>
<?php return; endif ?>
<table data-base-target="_next" class="action comments">
<tbody>
<?php foreach ($comments as $comment): ?>
<?php <?php
switch ($comment->type) { switch ($comment->type) {
case 'flapping': case 'flapping':
$icon = 'flapping'; $icon = 'flapping';
$tooltip = 'Comment was caused by a flapping host or service.'; $title = $this->translate('Flapping');
$tooltip = $this->translate('Comment was caused by a flapping host or service.');
break; break;
case 'comment': case 'comment':
$icon = 'user'; $icon = 'user';
$tooltip = 'Comment was created by an user.'; $title = $this->translate('User Comment');
$tooltip = $this->translate('Comment was created by an user.');
break; break;
case 'downtime': case 'downtime':
$icon = 'down'; $icon = 'down';
$tooltip = 'Comment was caused by a downtime.'; $title = $this->translate('Downtime');
$tooltip = $this->translate('Comment was caused by a downtime.');
case 'ack': case 'ack':
$icon = 'acknowledgement'; $icon = 'acknowledgement';
$tooltip = 'Comment was caused by an acknowledgement.'; $title = $this->translate('Acknowledgement');
$tooltip = $this->translate('Comment was caused by an acknowledgement.');
} }
?> ?>
<?= $this->icon($icon . '.png', $tooltip) ?><br /> <tr class="state invalid">
<?= $this->timeSince($comment->timestamp) ?> <td class="state" style="width: 12em;">
<?= $this->icon($icon . '.png', $tooltip) ?>
<br>
<strong><?= $this->escape($title); ?></strong>
<br>
<?= $this->prefixedTimeSince($comment->timestamp); ?>
</td> </td>
<td> <td>
<?php if ($comment->objecttype === 'service'): ?><?= $this->icon('service.png', 'Service comment') ?> <?= $this->qlink( <?php if ($comment->objecttype === 'service'): ?>
$comment->service, <?= $this->icon('service.png'); ?> <a href="<?= $this->href('monitoring/show/service', array(
'monitoring/show/service',
array(
'host' => $comment->host, 'host' => $comment->host,
'service' => $comment->service, 'service' => $comment->service,
) )); ?>">
) ?> on<?php else: ?><?= $this->icon('host.png', 'Host comment') ?> <?php endif ?> <?= $this->qlink( <?= $comment->service; ?>
$comment->host, </a>
'monitoring/show/host', <small>
array( <?= $this->translate('on') . ' ' . $comment->host; ?>
</small>
<?php else: ?>
<?= $this->icon('host.png'); ?> <a href="<?= $this->href('monitoring/show/host', array(
'host' => $comment->host 'host' => $comment->host
) )); ?>">
) ?> by <strong><?= $this->escape($comment->author) ?></strong><br /> <?= $comment->host; ?>
<p><?= $this->escape($comment->comment) ?></p> </a>
<?php if ($comment->persistent): ?>Comment is persistent<br /><?php endif ?> <?php endif ?>
<b>Expires: </b> <?= <br>
($comment->expiration) ? <?= $this->icon('comment.png'); ?> <?= isset($comment->author)
$this->timeUntil($comment->expiration) : ? '[' . $comment->author . '] '
'Never' : '';
?><?= $this->escape($comment->comment); ?>
<br>
<?= $comment->persistent
? $this->translate('This comment is persistent.')
: $this->translate('This comment is not persistent.');
?> ?>
<br>
<?= $comment->expiration ? sprintf(
$this->translate('This comment expires on %s at %s.'),
date('d.m.y', $comment-expiration),
date('H:i', $comment->expiration)
) : $this->translate('This comment does not expire.'); ?>
</td> </td>
<td style="width: 2em"> <?php
<?php
$data = array( $data = array(
'commentid' => $comment->id, 'commentid' => $comment->id,
'host' => $comment->host 'host' => $comment->host
); );
if ($comment->objecttype === 'service') { if ($comment->objecttype === 'service') {
$data['service'] = $comment->service; $data['service'] = $comment->service;
} }
?>
// echo $cf->iconSubmitForm( <td style="width: 2em">
// 'img/icons/remove.png', <?= $helper->labelSubmitForm(
echo $cf->labelSubmitForm(
'X', 'X',
'Remove comment', $this->translate('Remove Comment'),
'link-like', 'link-like',
'removecomment', 'removecomment',
$data $data
); ); ?>
?>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -22,7 +22,7 @@ foreach ($groupData as $groupName => $groupInfo): ?>
<div class="box entry"> <div class="box entry">
<img src="<?= $this->href('/static/gravatar', array('email' => $c->contact_email )) ?>" /> <img src="<?= $this->href('/static/gravatar', array('email' => $c->contact_email )) ?>" />
<a href="<?= $this->href( <a href="<?= $this->href(
'monitoring/show/contacts', 'monitoring/show/contact',
array('contact' => $c->contact_name) array('contact' => $c->contact_name)
); ?>"> ); ?>">
<?= $this->escape($c->contact_alias) ?> <?= $this->escape($c->contact_alias) ?>

View File

@ -14,31 +14,41 @@ $contactHelper = $this->getHelper('ContactFlags');
<?php <?php
if (count($contacts) === 0) { if (count($contacts) === 0) {
echo t('No contacts matching the filter'); echo t('No contacts matching the filter');
return;
} }
foreach ($contacts as $contact): foreach ($contacts as $contact): ?>
$periodLink = $this->href('monitoring/show/contacts', array('contact' => $contact->contact_name)); ?> <div class="contact">
<div style="background-color: lightgray; padding: 0.5em; border-radius: 0.5em; overflow: hidden; margin: 0.125em; float: left;"> <img src="<?= $this->href('/static/gravatar', array('email' => $contact->contact_email )) ?>" />
<img src="<?= $this->href('/static/gravatar', array('email' => $contact->contact_email )) ?>"
style="width: 60px; border: 4px solid white; border-radius: 8px; margin-right: 8px; float: left;" />
<a href="<?= $this->href( <a href="<?= $this->href(
'monitoring/show/contact', 'monitoring/show/contact',
array('contact' => $contact->contact_name) array('contact' => $contact->contact_name)
) ?>"><strong><?= $contact->contact_name ?></strong></a> (<?= $contact->contact_alias ?>) ) ?>"><strong><?= $contact->contact_name ?></strong></a> (<?= $contact->contact_alias ?>)
<?php <div><?= sprintf(
foreach (array( '%1$s: <a href="mailto:%2$s">%2$s</a>',
'eMail: <a href="mailto:%1$s">%1$s</a>' => $contact->contact_email, t('Email'),
'Pager: %s' => $contact->contact_pager, $this->escape($contact->contact_email)
'Service notification period: %s' => $contact->contact_notify_service_timeperiod, ) ?></div>
'Host notification period: %s' => $contact->contact_notify_host_timeperiod <?php if ($contact->contact_pager): ?>
) as $format => $value): <div>
if ($value): ?> <?= t('Pager') ?>:
<br /> <?= $this->escape($contact->contact_pager) ?>
<?php
printf($format, htmlspecialchars($value));
endif;
endforeach;
?>
<div class="clearfix"></div>
</div> </div>
<?php endforeach; ?> <?php endif; ?>
<div style="clear: both;"></div>
<div class="notification-periods">
<div>
<?= t('Service notification period') ?>:
<?= $this->escape($contact->contact_notify_service_timeperiod) ?>
</div>
<div>
<?= t('Host notification period') ?>:
<?= $this->escape($contact->contact_notify_host_timeperiod) ?>
</div>
</div>
</div>
<?php
endforeach;
if (true): /* The following piece of HTML MUST be nested in <?php */ ?>
<div style="clear: both;"></div>
<?php endif; ?>
</div> </div>

View File

@ -1,49 +1,101 @@
<?php <?php
$helper = $this->getHelper('CommandForm'); $helper = $this->getHelper('CommandForm');
?> ?>
<div class="controls">
<?= $this->tabs ?>
<div style="margin: 1em">
<?= $this->sortControl->render($this); ?>
</div>
<?= $this->paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?>
</div>
<div class="content downtimes"> <?php if (false === $this->compact): ?>
<table data-base-target="_next" class="action"> <div class="controls">
<tbody> <?= $this->tabs->render($this); ?>
<?php <div style="margin: 1em" class="dontprint">
if (count($downtimes) === 0) { <?= $this->translate('Sort by'); ?> <?= $this->sortControl->render($this); ?>
echo t('No downtimes matching the filter'); </div>
} <?= $this->widget('limiter', array('url' => $this->url, 'max' => $downtimes->count())); ?>
foreach ($this->downtimes as $downtime): ?> <?= $this->paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?>
<tr> </div>
<td> <?php endif ?>
<?= $this->dateFormat()->formatDateTime($downtime->start); ?> -
<?= $this->dateFormat()->formatDateTime($downtime->end); ?> <div class="content">
<br /> <?php if (empty($downtimes)): ?>
<small>Duration: <?= $this->util()->showHourMin($downtime->duration); ?></small> <?= $this->translate('No downtimes matching the filter'); ?>
<br /> </div>
<small>The <?php if($downtime->is_flexible): ?>flexible<?php else: ?>fixed<?php endif; ?> downtime is <?php if(!$downtime->is_in_effect): ?>not <?php endif; ?>in effect</small> <?php return; endif ?>
<table data-base-target="_next" class="action">
<tbody>
<?php foreach ($downtimes as $downtime): ?>
<?php
if (isset($downtime->service)) {
$stateName = strtolower($this->util()->getServiceStateName($downtime->service_state));
} else {
$stateName = strtolower($this->util()->getHostStateName($downtime->host_state));
}
?>
<tr class="state <?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>">
<td class="state">
<strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong>
<br>
<?= $this->prefixedTimeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start); ?>
</td> </td>
<td> <td>
<?php if (isset($downtime->service)): ?> <?php if (isset($downtime->service)): ?>
<a href="<?= $this->href('monitoring/show/service', array( <a href="<?= $this->href('monitoring/show/service', array(
'host' => (string) $downtime->host, 'host' => $downtime->host,
'service' => (string) $downtime->service 'service' => $downtime->service
)); ?>"><?= $downtime->service ?></a> )); ?>">
<small>on <?= $downtime->host ?></small> <?= $downtime->service; ?>
</a>
<small>
<?= $this->translate('on'); ?> <?= $downtime->host; ?>
</small>
<?php else: ?> <?php else: ?>
<a href="<?= $this->href('monitoring/show/host', array( <a href="<?= $this->href('monitoring/show/host', array(
'host' => (string) $downtime->host 'host' => $downtime->host
)); ?>"><?= $downtime->host ?> </a> )); ?>">
<?php endif; ?> <?= $downtime->host; ?>
<br /> </a>
<?= $downtime->author ?>: <?= $downtime->comment ?> <?php endif ?>
<br /> <br>
<small>Entry Time: <?= ($downtime->entry_time) ? $this->dateFormat()->formatDateTime((int) $downtime->entry_time) : ''; ?> <?= $this->icon('comment.png'); ?> [<?= $downtime->author; ?>] <?= $downtime->comment; ?>
<br>
<small>
<?php if ($downtime->is_flexible): ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->translate('This flexible downtime was started on %s at %s and lasts for %s until %s at %s.'),
date('d.m.y', $downtime->start),
date('H:i', $downtime->start),
$this->format()->duration($downtime->duration),
date('d.m.y', $downtime->end),
date('H:i', $downtime->end)
); ?>
<?php else: ?>
<?= sprintf(
$this->translate('This flexible downtime has been scheduled to start between %s - %s and to last for %s.'),
date('d.m.y H:i', $downtime->scheduled_start),
date('d.m.y H:i', $downtime->scheduled_end),
$this->format()->duration($downtime->duration)
); ?>
<?php endif ?>
<?php else: ?>
<?php if ($downtime->is_in_effect): ?>
<?= sprintf(
$this->translate('This fixed downtime was started on %s at %s and expires on %s at %s.'),
date('d.m.y', $downtime->start),
date('H:i', $downtime->start),
date('d.m.y', $downtime->end),
date('H:i', $downtime->end)
); ?>
<?php else: ?>
<?= sprintf(
$this->translate('This fixed downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
date('d.m.y', $downtime->scheduled_start),
date('H:i', $downtime->scheduled_start),
date('d.m.y', $downtime->scheduled_end),
date('H:i', $downtime->scheduled_end)
); ?>
<?php endif ?>
<?php endif ?>
</small> </small>
<td style="width: 2em"> </td>
<?php <?php
$data = array( $data = array(
'downtimeid' => $downtime->id, 'downtimeid' => $downtime->id,
@ -52,19 +104,18 @@ foreach ($this->downtimes as $downtime): ?>
if (isset($downtime->service)) { if (isset($downtime->service)) {
$data['service'] = $downtime->service; $data['service'] = $downtime->service;
} }
// echo $helper->iconSubmitForm( ?>
// 'img/icons/remove.png', <td style="width: 2em">
echo $helper->labelSubmitForm( <?= $helper->labelSubmitForm(
'X', 'X',
'Remove Downtime', 'Remove Downtime',
'link-like', 'link-like',
'removedowntime', 'removedowntime',
$data $data
); ); ?>
?>
</td> </td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -1,122 +1,115 @@
<?php if (false === $this->compact): ?>
<?php if (!$this->compact): ?> <div class="controls">
<div class="controls">
<?= $this->tabs->render($this); ?> <?= $this->tabs->render($this); ?>
<div style="margin: 1em" class="dontprint"> <div style="margin: 1em" class="dontprint">
<?= $this->translate('Sort by') ?> <?= $this->sortControl->render($this); ?> <?= $this->translate('Sort by'); ?> <?= $this->sortControl->render($this); ?>
</div> </div>
<?= $this->widget('limiter', array('url' => $this->url, 'max' => $this->history->count())); ?>
<?= $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?> <?= $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
</div> </div>
<?php endif; ?> <?php endif ?>
<div class="content"> <div class="content">
<?php if (empty($history)): ?> <?php if (empty($history)): ?>
<?= $this->translate('No entries found') ?> <?= $this->translate('No history events matching the filter') ?>
</div> </div>
<?php return; endif ?> <?php return; endif ?>
<table data-base-target="_next" class="action"> <table data-base-target="_next" class="action">
<tbody> <tbody>
<?php
if (count($history) === 0) {
echo t('No history events matching the filter');
}
?>
<?php foreach ($history as $event): ?> <?php foreach ($history as $event): ?>
<?php <?php
$class = null; $stateName = 'invalid';
$isService = false; $isService = isset($event->service);
switch ($event->type) { switch ($event->type) {
case 'notify': case 'notify':
$icon = 'notification'; $icon = 'notification';
$title = 'Notification'; $title = $this->translate('Notification');
$msg = $event->output; $msg = $event->output;
break; break;
case 'comment': case 'comment':
$icon = 'comment'; $icon = 'comment';
$title = 'Comment'; $title = $this->translate('Comment');
$msg = $event->output; $msg = $event->output;
break; break;
case 'ack': case 'ack':
$icon = 'acknowledgement'; $icon = 'acknowledgement';
$title = 'Acknowledgement'; $title = $this->translate('Acknowledgement');
$msg = $event->output; $msg = $event->output;
break; break;
case 'dt_comment': case 'dt_comment':
$icon = 'in-downtime'; $icon = 'in_downtime';
$title = 'In Downtime'; $title = $this->translate('In Downtime');
$msg = $event->output; $msg = $event->output;
break; break;
case 'flapping': case 'flapping':
$icon = 'flapping'; $icon = 'flapping';
$title = 'Flapping'; $title = $this->translate('Flapping');
$msg = $event->output; $msg = $event->output;
break; break;
case 'hard_state': case 'hard_state':
$icon = '{{HARDSTATE_ICON}}'; $icon = $isService ? 'service' : 'host';
$title = 'Hard State'; $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$msg = $event->output . '<br /><small>Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Hard)</small>'; $stateName = (
$class = 'border-status-' . ( $isService
$isService ? ? strtolower($this->util()->getServiceStateName($event->state))
strtolower($this->util()->getServiceStateName($event->state)) : : strtolower($this->util()->getHostStateName($event->state))
strtolower($this->util()->getHostStateName($event->state))
); );
$title = strtoupper($stateName); // TODO: Should be translatable!
break; break;
case 'soft_state': case 'soft_state':
$icon = '{{SOFTSTATE_ICON}}'; $icon = 'softstate';
$title = 'Soft State'; $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$msg = $event->output . '<br /><small>Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Soft)</small>'; $stateName = (
$class = 'border-status-' . ( $isService
$isService ? ? strtolower($this->util()->getServiceStateName($event->state))
strtolower($this->util()->getServiceStateName($event->state)) : : strtolower($this->util()->getHostStateName($event->state))
strtolower($this->util()->getHostStateName($event->state))
); );
$title = strtoupper($stateName); // TODO: Should be translatable!
break; break;
case 'dt_start': case 'dt_start':
$icon = 'downtime-start'; $icon = 'downtime_start';
$title = 'Downtime Start'; $title = $this->translate('Downtime Start');
$msg = $event->output; $msg = $event->output;
break; break;
case 'dt_end': case 'dt_end':
$icon = 'downtime-end'; $icon = 'downtime_end';
$title = 'Downtime End'; $title = $this->translate('Downtime End');
$msg = $event->output; $msg = $event->output;
break; break;
} }
?> ?>
<tr class="state"> <tr class="state <?= $stateName; ?>">
<td style="width: 6em;" <?= !empty($class) ? 'class="' . $class . '"' : '' ?>"> <td class="state">
<strong><?= $this->escape($title); ?></strong>
<br>
<?= date('d.m. H:i', $event->timestamp); ?> <?= date('d.m. H:i', $event->timestamp); ?>
</td> </td>
<td> <td>
<?php if (isset($event->service)): ?> <?php if ($isService): ?>
<a href="<?= $this->href('monitoring/show/service', array( <a href="<?= $this->href('monitoring/show/service', array(
'host' => $event->host, 'host' => $event->host,
'service' => $event->service 'service' => $event->service
)); ?>"> )); ?>">
<?= $event->service ?> <?= $event->service; ?>
</a> </a>
<small> <small>
on <?= $event->host ?> <?= $this->translate('on') . ' ' . $event->host; ?>
</small> </small>
<?php $isService = true; ?>
<?php else: ?> <?php else: ?>
<a href="<?= $this->href('monitoring/show/host', array( <a href="<?= $this->href('monitoring/show/host', array(
'host' => $event->host 'host' => $event->host
)); ?>"> )); ?>">
<?= $event->host ?> <?= $event->host; ?>
</a> </a>
<?php endif; ?> <?php endif ?>
<br /> <br>
<div> <div>
<i title="<?= $title ?>" class="icinga-icon-<?= $icon ?>"></i> <?= $this->icon($icon . '.png', $title); ?> <?= empty($msg) ? '' : $msg; ?>
<?php if (!empty($msg)) { echo $msg; } ?>
</div> </div>
</td> </td>
</tr> </tr>
<? endforeach; ?> <? endforeach ?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -10,28 +10,33 @@ $cf = $this->getHelper('CommandForm');
<div class="content processinfo"> <div class="content processinfo">
<p>Backend <strong><?= $this->backendName; ?></strong> <p>Backend <strong><?= $this->backendName; ?></strong>
<?= $ps->is_currently_running === '1' ? sprintf('has been running with PID %d ', $ps->process_id) . $this->prefixedTimeSince($ps->program_start_time) : 'is not running'; ?>. <?= $ps->is_currently_running === '1'
? sprintf(
$this->translate('has been running with PID %d '),
$ps->process_id
) . $this->prefixedTimeSince($ps->program_start_time)
: $this->translate('is not running'); ?>.
<table class="avp"> <table class="avp">
<tbody> <tbody>
<tr> <tr>
<th>Last status update</th> <th><?= $this->translate('Last status update'); ?></th>
<td><?= $this->timeSince($ps->status_update_time) ?> ago</td> <td><?= $this->timeSince($ps->status_update_time) ?> ago</td>
</tr> </tr>
<tr> <tr>
<th>Last check command</th> <th><?= $this->translate('Last check command'); ?></th>
<td><?= $this->timeSince($ps->last_command_check) ?> ago</td> <td><?= $this->timeSince($ps->last_command_check) ?> ago</td>
</tr> </tr>
<tr> <tr>
<th>Global host event handler</th> <th><?= $this->translate('Global host event handler'); ?></th>
<td><?= $ps->global_host_event_handler ? $ps->global_host_event_handler : 'Not set' ?></td> <td><?= $ps->global_host_event_handler ? $ps->global_host_event_handler : $this->translate('Not set'); ?></td>
</tr> </tr>
<tr> <tr>
<th>Global service event handler</th> <th><?= $this->translate('Global service event handler'); ?></th>
<td><?= $ps->global_service_event_handler ? $ps->global_service_event_handler : 'Not set' ?></td> <td><?= $ps->global_service_event_handler ? $ps->global_service_event_handler : $this->translate('Not set'); ?></td>
</tr> </tr>
<tr> <tr>
<th>Notifications enabled</th> <th><?= $this->translate('Notifications enabled'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->notifications_enabled, $ps->notifications_enabled,
@ -41,15 +46,18 @@ $cf = $this->getHelper('CommandForm');
array('global' => '1') array('global' => '1')
) ?> ) ?>
<?php if ($ps->notifications_enabled === '1'): ?> <?php if ($ps->notifications_enabled === '1'): ?>
<a rel="tooltip" title="Disable notifications for a specific time on a program-wide basis" href="<?= $this->href('monitoring/command/disablenotificationswithexpire') ?>" data-base-target="_next">Temporarily disable</a> <a rel="tooltip" title="<?= $this->translate('Disable notifications for a specific time on a program-wide basis'); ?>"
href="<?= $this->href('monitoring/command/disablenotificationswithexpire') ?>" data-base-target="_next">
<?= $this->translate('Temporarily disable'); ?>
</a>
<?php elseif ($ps->disable_notif_expire_time): ?> <?php elseif ($ps->disable_notif_expire_time): ?>
Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_time) ?></strong> <?= sprintf($this->translate('Will be re-enabled in %s'), '<strong>' . $this->timeUntil($ps->disable_notif_expire_time) . '</strong>'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Execute active service checks</th> <th><?= $this->translate('Execute active service checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->active_service_checks_enabled, $ps->active_service_checks_enabled,
@ -60,7 +68,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Accept passive service checks</th> <th><?= $this->translate('Accept passive service checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->passive_service_checks_enabled, $ps->passive_service_checks_enabled,
@ -71,7 +79,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Execute active host checks</th> <th><?= $this->translate('Execute active host checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->active_host_checks_enabled, $ps->active_host_checks_enabled,
@ -82,7 +90,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Accept passive host checks</th> <th><?= $this->translate('Accept passive host checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->passive_host_checks_enabled, $ps->passive_host_checks_enabled,
@ -93,7 +101,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Eventhandlers enabled</th> <th><?= $this->translate('Eventhandlers enabled'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->event_handlers_enabled, $ps->event_handlers_enabled,
@ -104,7 +112,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Obsessing over host checks</th> <th><?= $this->translate('Obsessing over host checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->obsess_over_hosts, $ps->obsess_over_hosts,
@ -115,7 +123,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Obsessing over service checks</th> <th><?= $this->translate('Obsessing over service checks'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->obsess_over_services, $ps->obsess_over_services,
@ -126,7 +134,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Flap detection enabled</th> <th><?= $this->translate('Flap detection enabled'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->flap_detection_enabled, $ps->flap_detection_enabled,
@ -137,7 +145,7 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Process performance data</th> <th><?= $this->translate('Process performance data'); ?></th>
<td><?= $cf->toggleSubmitForm( <td><?= $cf->toggleSubmitForm(
'', '',
$ps->process_performance_data, $ps->process_performance_data,
@ -148,17 +156,17 @@ Will be re-enabled in <strong><?= $this->timeUntil($ps->disable_notif_expire_tim
) ?></td> ) ?></td>
</tr> </tr>
<tr> <tr>
<th>Monitoring Process</th> <th><?= $this->translate('Monitoring Process'); ?></th>
<td><?= $cf->labelSubmitForm( <td><?= $cf->labelSubmitForm(
'Restart', $this->translate('Restart'),
'Restart the monitoring process', $this->translate('Restart the monitoring process'),
'', '',
'restartprocess' 'restartprocess'
) ?> ) ?>
<!--<?= <!--<?=
$cf->labelSubmitForm( $cf->labelSubmitForm(
'Shutdown monitoring process', $this->translate('Shutdown monitoring process'),
'Shutdown the monitoring process', $this->translate('Shutdown the monitoring process'),
'btn-cta', 'btn-cta',
'shutdownprocess' 'shutdownprocess'
); );

View File

@ -31,12 +31,12 @@ foreach ($object->comments as $comment) {
); );
$list[] = sprintf( $list[] = sprintf(
'<tr><th>%s (%s)</th><td data-base-target="_self">%s (%s) %s</td></tr>', '<tr><th>%s (%s)</th><td><table><tr><td style="vertical-align: top;" data-base-target="_self">%s (%s):</td><td style="padding-left: .5em;">%s</td></tr></table></td></tr>',
$this->escape($comment->author), $this->escape($comment->author),
$this->timeSince($comment->timestamp), $this->timeSince($comment->timestamp),
$iconForm, $iconForm,
ucfirst($comment->type), ucfirst($comment->type),
$text str_replace(array('\r\n', '\n'), '<br>', $text)
); );
} }

View File

@ -42,11 +42,11 @@ foreach ($object->downtimes as $downtime) {
) : $this->escape($downtime->comment); ) : $this->escape($downtime->comment);
$list[] = sprintf( $list[] = sprintf(
'<tr><th>%s</th><td data-base-target="_self">%s %s: %s</td></tr>', '<tr><th>%s</th><td><table><tr><td style="vertical-align: top;" data-base-target="_self">%s %s:</td><td style="padding-left: .5em;">%s</td></tr></table></td></tr>',
$this->escape($downtime->author), $this->escape($downtime->author),
$iconForm, $iconForm,
$state, $state,
$text str_replace(array('\r\n', '\n'), '<br>', $text)
); );
} }

View File

@ -0,0 +1,56 @@
<?php
$contactHelper = $this->getHelper('ContactFlags');
?>
<div style="margin-top: 0.5em;">
<?php foreach ($contacts as $contact): ?>
<table style="border-spacing: 0.25em; border-collapse: separate;">
<thead>
<tr>
<th colspan="2" style="text-align: left">
<?= $this->escape($contact->contact_name) ?><span style="font-weight: normal;"> (<?=
$this->escape($contact->contact_alias)
?>)</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td><?= t('Email') ?></td>
<td><?php printf(
'<a href="mailto:%1$s">%1$s</a>',
$this->escape($contact->contact_email)
); ?></td>
</tr>
<?php if ($contact->contact_pager): ?>
<tr>
<td><?= t('Pager') ?></td>
<td><?= $this->escape($contact->contact_pager) ?></td>
</tr>
<?php endif; ?>
<tr>
<td><?= t('Flags (service)') ?></td>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'service')) ?></td>
</tr>
<tr>
<td><?= t('Flags (host)') ?></td>
<td><?= $this->escape($contactHelper->contactFlags($contact, 'host')) ?></td>
</tr>
<tr>
<td><?= t('Service notification period') ?></td>
<td><?= $this->escape($contact->contact_notify_service_timeperiod) ?></td>
</tr>
<tr>
<td><?= t('Host notification period') ?></td>
<td><?= $this->escape($contact->contact_notify_host_timeperiod) ?></td>
</tr>
</tbody>
</table>
<?php
return;
endforeach;
printf(
'%s: `%s\'',
t('No such contact'), $contact_name
);
?>
</div>

View File

@ -1,149 +1,127 @@
<?php <div class="controls">
<?= $this->render('show/components/header.phtml'); ?>
use Icinga\Module\Monitoring\Object\Service; <h1><?= $this->translate('This Object\'s Event History'); ?></h1>
<?= $this->widget('limiter', array('url' => $url, 'max' => $history->count())); ?>
if ($object instanceof Service) { <?= $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
$title = $object->service_description;
$params = array(
'host' => $object->host_name,
'service' => $object->service_description
);
} else {
$title = $object->host_name;
$params = array('host' => $object->host_name);
}
// TODO: Remove this once we have better helpers
$states = array(
'service' => array(
'ok',
'warning',
'critical',
'unknown',
99 => 'pending',
),
'host' => array(
'up',
'down',
'unreachable',
99 => 'pending',
)
);
?><div class="controls">
<?= $this->render('show/components/header.phtml') ?>
<h1><?= $this->translate("This object's event history") ?></h1>
<?= $this->widget('limiter', array('url' => $this->url, 'max' => $this->history->count())) ?>
<?= $this->paginationControl($this->history, null, null, array('preserve' => $this->preserve)); ?>
</div> </div>
<div class="content"> <div class="content">
<?php if($this->history->count() === 0): ?> <?php if (empty($history)): ?>
<?= $this->translate('No History Available For This Object') ?> <?= $this->translate('No history available for this object'); ?>
</div> </div>
<?php return; endif ?> <?php return; endif ?>
<table data-base-target="_next" class="action objecthistory"> <table data-base-target="_next" class="action objecthistory">
<tbody> <tbody>
<?php foreach ($history as $event): ?>
<?php foreach ($this->history as $event): <?php
if (array_key_exists($event->state, $states[$event->object_type])) { $stateClass = 'invalid';
$state_class = $states[$event->object_type][$event->state]; $isService = isset($event->service_description);
} else { switch ($event->type) {
$state_class = 'invalid';
}
$extraTitle = false;
switch ($event->type) {
case 'notify': case 'notify':
$icon = 'notification'; $icon = 'notification';
$title = $this->translate('Notification'); $title = $this->translate('Notification');
$state = $this->translate('ACK'); $msg = $event->output;
break; break;
case 'comment': case 'comment':
$icon = 'comment'; $icon = 'comment';
$title = $this->translate('Comment'); $title = $this->translate('Comment');
$msg = $event->output;
break; break;
case 'comment_deleted': case 'comment_deleted':
$icon = 'remove'; $icon = 'remove';
$title = $this->translate('Comment deleted'); $title = $this->translate('Comment deleted');
$msg = $event->output;
break; break;
case 'ack': case 'ack':
$icon = 'acknowledgement'; $icon = 'acknowledgement';
$title = $this->translate('Acknowledge'); $title = $this->translate('Acknowledge');
$msg = $event->output;
break; break;
case 'ack_deleted': case 'ack_deleted':
$icon = 'remove'; $icon = 'remove';
$title = $this->translate('Ack removed'); $title = $this->translate('Ack removed');
$msg = $event->output;
break; break;
case 'dt_comment': case 'dt_comment':
$icon = 'in_downtime'; $icon = 'in_downtime';
$title = $this->translate('In Downtime'); $title = $this->translate('In Downtime');
$msg = $event->output;
break; break;
case 'dt_comment_deleted': case 'dt_comment_deleted':
$icon = 'remove'; $icon = 'remove';
$title = $this->translate('Downtime removed'); $title = $this->translate('Downtime removed');
$msg = $event->output;
break; break;
case 'flapping': case 'flapping':
$icon = 'flapping'; $icon = 'flapping';
$title = $this->translate('Flapping'); $title = $this->translate('Flapping');
$msg = $event->output;
break; break;
case 'flapping_deleted': case 'flapping_deleted':
$icon = 'remove'; $icon = 'remove';
$title = $this->translate('Flapping stopped'); $title = $this->translate('Flapping stopped');
$msg = $event->output;
break; break;
case 'hard_state': case 'hard_state':
$icon = 'submit'; $icon = $isService ? 'service' : 'host';
$title = strtoupper($state_class); $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$stateClass = (
$isService
? strtolower($this->util()->getServiceStateName($event->state))
: strtolower($this->util()->getHostStateName($event->state))
);
$title = strtoupper($stateClass); // TODO: Should be translatable!
break; break;
case 'soft_state': case 'soft_state':
$icon = 'softstate'; $icon = 'softstate';
$title = strtoupper($state_class); $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
$extraTitle = $this->translate('Soft State'); $stateClass = (
$state_class .= ' handled'; $isService
? strtolower($this->util()->getServiceStateName($event->state))
: strtolower($this->util()->getHostStateName($event->state))
);
$title = strtoupper($stateClass); // TODO: Should be translatable!
break; break;
case 'dt_start': case 'dt_start':
$icon = 'downtime_start'; $icon = 'downtime_start';
$title = $this->translate('Downtime Start'); $title = $this->translate('Downtime Start');
$msg = $event->output;
break; break;
case 'dt_end': case 'dt_end':
$icon = 'downtime_end'; $icon = 'downtime_end';
$title = $this->translate('Downtime End'); $title = $this->translate('Downtime End');
$msg = $event->output;
break; break;
} }
?>
<tr class="state <?= $stateClass; ?>">
<td class="state">
?> <strong><?= $this->escape($title); ?></strong>
<tr class="state <?= $state_class ?>"> <br>
<td class="state"><strong><?= $this->escape($title) ?></strong><br /><?= $this->prefixedTimeSince($event->timestamp) ?><?= $extraTitle === false ? '' : '<br />' . $this->escape($extraTitle) ?></td> <?= date('d.m. H:i', $event->timestamp); ?>
<td> </td>
<?php <td><?php
$output = $this->tickets ? preg_replace_callback( $output = $this->tickets ? preg_replace_callback(
$this->tickets->getPattern(), $this->tickets->getPattern(),
array($this->tickets, 'createLink'), array($this->tickets, 'createLink'),
$this->escape($event->output) $this->escape($msg)
) : $this->escape($event->output); ) : $this->escape($msg);
echo $this->icon($icon . '.png', $title) . ' ';
if ($object instanceof Host): ?>
<?= $this->escape($event->service_description) ?>
<?php elseif ($object instanceof Service): ?>
<?php else: ?>
<?= $this->escape($event->service_description) ?> on <?= $this->escape($event->host_name) ?>
<?php endif;
if ($event->attempt !== null) {
printf('[ %d/%d ] ', $event->attempt, $event->max_attempts);
}
echo $output;
?> ?>
<?php if ($isService): ?>
<?= $this->escape($event->service_description) . ' ' . $this->translate('on') . ' ' . $this->escape($event->host_name); ?>
<?php else: ?>
<?= $this->escape($event->host_name); ?>
<?php endif ?>
<br>
<div>
<?= $this->icon($icon . '.png', $title); ?> <?= empty($msg) ? '' : $msg; ?>
</div>
</td> </td>
</tr> </tr>
<? endforeach; ?> <? endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -48,25 +48,25 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
<?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?> <?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?>
<div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;"> <div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;">
<div class="outer-circle extrapolated" style="<?= sprintf( <div class="outer-circle extrapolated" style="<?= sprintf(
'width: %4$s; height: %4$s; border-color: %3$s; background-color: %1$s; margin-top: -%2$s;', 'width: %4$s; height: %4$s; border-color: %3$s; background-color: %1$s; margin-top: -%2$Fem;',
Color::changeBrightness($timeInfo[1][$groupName]->getColor(), 0.7), Color::changeBrightness($timeInfo[1][$groupName]->getColor(), 0.7),
((float) substr($extrapolatedCircleWidth, 0, -2) / 2) . 'em', (float) substr($extrapolatedCircleWidth, 0, -2) / 2,
$timeInfo[1][$groupName]->getColor(), $timeInfo[1][$groupName]->getColor(),
$extrapolatedCircleWidth $extrapolatedCircleWidth
); ?>"> ); ?>">
<?php else: ?> <?php else: ?>
<div class="circle-box" style="width: <?= $circleWidth; ?>;"> <div class="circle-box" style="width: <?= $circleWidth; ?>;">
<div class="outer-circle" style="<?= sprintf( <div class="outer-circle" style="<?= sprintf(
'width: %2$s; height: %2$s; margin-top: -%1$s;', 'width: %2$s; height: %2$s; margin-top: -%1$Fem;',
((float) substr($circleWidth, 0, -2) / 2) . 'em', (float) substr($circleWidth, 0, -2) / 2,
$circleWidth $circleWidth
); ?>"> ); ?>">
<?php endif ?> <?php endif ?>
<a class="inner-circle" style="<?= sprintf( <a class="inner-circle" style="<?= sprintf(
'width: %3$s; height: %3$s; background-color: %2$s; margin-top: -%1$s; margin-left: -%1$s;', 'width: %3$s; height: %3$s; background-color: %2$s; margin-top: -%1$Fem; margin-left: -%1$Fem;',
((float) substr($circleWidth, 0, -2) / 2) . 'em', (float) substr($circleWidth, 0, -2) / 2,
$timeInfo[1][$groupName]->getColor(), $timeInfo[1][$groupName]->getColor(),
$circleWidth (string) $circleWidth
); ?>" href="<?= $timeInfo[1][$groupName]->getDetailUrl()->overwriteParams( ); ?>" href="<?= $timeInfo[1][$groupName]->getDetailUrl()->overwriteParams(
array( array(
'timestamp<' => $timeInfo[0]->start->getTimestamp(), 'timestamp<' => $timeInfo[0]->start->getTimestamp(),

View File

@ -24,13 +24,15 @@ class DowntimeQuery extends IdoQuery
'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)", 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)",
'downtime_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', 'downtime_end' => 'CASE WHEN sd.is_fixed THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
'downtime_duration' => 'sd.duration', 'downtime_duration' => 'sd.duration',
'downtime_is_in_effect' => 'sd.is_in_effect', 'downtime_is_in_effect' => 'sd.is_in_effect',
'downtime_internal_id' => 'sd.internal_downtime_id', 'downtime_internal_id' => 'sd.internal_downtime_id',
'downtime_host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci', 'downtime_host' => 'CASE WHEN ho.name1 IS NULL THEN so.name1 ELSE ho.name1 END COLLATE latin1_general_ci',
'downtime_service' => 'so.name2 COLLATE latin1_general_ci', 'downtime_service' => 'so.name2 COLLATE latin1_general_ci',
'downtime_objecttype' => "CASE WHEN ho.object_id IS NOT NULL THEN 'host' ELSE CASE WHEN so.object_id IS NOT NULL THEN 'service' ELSE NULL END END", 'downtime_objecttype' => "CASE WHEN ho.object_id IS NOT NULL THEN 'host' ELSE CASE WHEN so.object_id IS NOT NULL THEN 'service' ELSE NULL END END",
'downtime_host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
'downtime_service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
), ),
); );
@ -53,6 +55,16 @@ class DowntimeQuery extends IdoQuery
'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2', 'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
array() array()
); );
$this->select->joinLeft(
array('hs' => $this->prefix . 'hoststatus'),
'ho.object_id = hs.host_object_id',
array()
);
$this->select->joinLeft(
array('ss' => $this->prefix . 'servicestatus'),
'so.object_id = ss.service_object_id',
array()
);
$this->joinedVirtualTables = array('downtime' => true); $this->joinedVirtualTables = array('downtime' => true);
} }
} }

View File

@ -76,6 +76,11 @@ class HoststatusQuery extends IdoQuery
ELSE 4 ELSE 4
END END
END END
END
+
CASE WHEN hs.state_type = 1
THEN 8
ELSE 0
END' END'
), ),
'hostgroups' => array( 'hostgroups' => array(

View File

@ -130,6 +130,11 @@ class StatusQuery extends IdoQuery
ELSE 4 ELSE 4
END END
END END
END
+
CASE WHEN hs.state_type = 1
THEN 8
ELSE 0
END' END'
), ),
'hostgroups' => array( 'hostgroups' => array(
@ -266,6 +271,11 @@ class StatusQuery extends IdoQuery
END END
END END
END END
END
+
CASE WHEN ss.state_type = 1
THEN 8
ELSE 0
END' END'
), ),
'serviceproblemsummary' => array( 'serviceproblemsummary' => array(

Some files were not shown because too many files have changed in this diff Show More