diff --git a/.gitignore b/.gitignore
index ef44d81c9..1444ecfb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,9 @@ config/preferences/*.ini
# Application logfiles
var/log/*.log
+# Packaging
/debian
+*.tar.gz
+*.komodoproject
diff --git a/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf b/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf
index 412307328..f25df325b 100644
--- a/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf
+++ b/.vagrant-puppet/files/etc/icinga2/conf.d/test-config.conf
@@ -1,6 +1,6 @@
object CheckCommand "dummy-host" {
import "plugin-check-command"
- command = [ PluginDir + "/libexec/test_hostcheck.pl" ]
+ command = [ PluginDir + "/test_hostcheck.pl" ]
arguments = {
"--type" = "$check_type$"
"--failchance" = "$check_failchance$"
@@ -18,7 +18,7 @@ object CheckCommand "dummy-host" {
object CheckCommand "dummy-service" {
import "plugin-check-command"
- command = [ PluginDir + "/libexec/test_servicecheck.pl" ]
+ command = [ PluginDir + "/test_servicecheck.pl" ]
arguments = {
"--total-critical-on-host" = "$check_critical_on_host$"
"--total-warning-on-host" = "$check_warning_on_host$"
diff --git a/.vagrant-puppet/files/etc/icingaweb/menu.ini b/.vagrant-puppet/files/etc/icingaweb/menu.ini
index f01ebaecc..d6f342655 100644
--- a/.vagrant-puppet/files/etc/icingaweb/menu.ini
+++ b/.vagrant-puppet/files/etc/icingaweb/menu.ini
@@ -18,10 +18,15 @@ title = "Configuration"
url = "config"
priority = 300
+[System.Modules]
+title = "Modules"
+url = "config/modules"
+priority = 400
+
[System.ApplicationLog]
title = "Application log"
url = "list/applicationlog"
-priority = 400
+priority = 500
[Logout]
url = "authentication/logout"
diff --git a/.vagrant-puppet/files/etc/icingaweb/modules/doc/menu.ini b/.vagrant-puppet/files/etc/icingaweb/modules/doc/menu.ini
new file mode 100644
index 000000000..86889b239
--- /dev/null
+++ b/.vagrant-puppet/files/etc/icingaweb/modules/doc/menu.ini
@@ -0,0 +1,5 @@
+[Documentation]
+title = "Documentation"
+icon = "img/icons/comment.png"
+url = "doc"
+priority = 80
diff --git a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini b/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini
index c66a611a9..c185fb87d 100644
--- a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini
+++ b/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini
@@ -68,7 +68,7 @@ priority = 70
[Overview.Comments]
title = "Comments"
-url = "monitoring/list/comments"
+url = "monitoring/list/comments?comment_type=(comment|ack)"
priority = 70
[Overview.Contacts]
diff --git a/.vagrant-puppet/manifests/default.pp b/.vagrant-puppet/manifests/default.pp
index 68affd347..86065d8fc 100644
--- a/.vagrant-puppet/manifests/default.pp
+++ b/.vagrant-puppet/manifests/default.pp
@@ -3,10 +3,14 @@ include mysql
include pgsql
include openldap
-Exec { path => '/bin:/usr/bin:/sbin' }
+Exec { path => '/bin:/usr/bin:/sbin:/usr/sbin' }
-$icingaVersion = '1.11.2'
-$icinga2Version = '2.0.0'
+$icingaVersion = '1.11.5'
+$icinga2Version = '2.0.1'
+$pluginVersion = '2.0'
+$livestatusVersion = '1.2.4p5'
+$phantomjsVersion = '1.9.1'
+$casperjsVersion = '1.0.2'
exec { 'create-mysql-icinga-db':
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-cgiurl=/icinga-mysql/cgi-bin \
--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',
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']
}
@@ -98,10 +102,10 @@ cmmi { 'icinga-pgsql':
--with-htmurl=/icinga-pgsql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-pgsql.conf \
--with-cgiurl=/icinga-pgsql/cgi-bin \
--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',
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']
}
@@ -206,20 +210,11 @@ exec { 'icinga-htpasswd':
require => Class['apache']
}
-cmmi { 'icinga-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']
-}
+include monitoring-plugins
cmmi { 'mk-livestatus':
- url => 'http://mathias-kettner.de/download/mk-livestatus-1.2.2p1.tar.gz',
- output => 'mk-livestatus-1.2.2p1.tar.gz',
+ url => "http://mathias-kettner.de/download/mk-livestatus-${livestatusVersion}.tar.gz",
+ output => "mk-livestatus-${livestatusVersion}.tar.gz",
flags => '--prefix=/usr/local/icinga-mysql --exec-prefix=/usr/local/icinga-mysql',
creates => '/usr/local/icinga-mysql/lib/mk-livestatus',
make => 'make && make install',
@@ -262,14 +257,14 @@ exec { 'populate-openldap':
}
class { 'phantomjs':
- url => 'https://phantomjs.googlecode.com/files/phantomjs-1.9.1-linux-x86_64.tar.bz2',
- output => 'phantomjs-1.9.1-linux-x86_64.tar.bz2',
+ url => "https://phantomjs.googlecode.com/files/phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2",
+ output => "phantomjs-${phantomjsVersion}-linux-x86_64.tar.bz2",
creates => '/usr/local/phantomjs'
}
class { 'casperjs':
- url => 'https://github.com/n1k0/casperjs/tarball/1.0.2',
- output => 'casperjs-1.0.2.tar.gz',
+ url => "https://github.com/n1k0/casperjs/tarball/${casperjsVersion}",
+ output => "casperjs-${casperjsVersion}.tar.gz",
creates => '/usr/local/casperjs'
}
@@ -421,11 +416,10 @@ package { 'icinga2-ido-mysql':
exec { 'populate-icinga2-mysql-db':
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'] ]
}
-
file { '/etc/icinga2/features-available/ido-mysql.conf':
source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icinga2/features-available/ido-mysql.conf',
owner => 'icinga',
@@ -574,7 +568,7 @@ populate_monitoring_test_config { ['commands', 'contacts', 'dependencies',
}
define populate_monitoring_test_config_plugins {
- file { "/usr/lib64/nagios/plugins/libexec/${name}":
+ file { "/usr/lib64/nagios/plugins/${name}":
owner => 'icinga',
group => 'icinga',
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']
}
+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',
+}
+
diff --git a/.vagrant-puppet/modules/monitoring-plugins/manifests/init.pp b/.vagrant-puppet/modules/monitoring-plugins/manifests/init.pp
new file mode 100644
index 000000000..6dc7be09d
--- /dev/null
+++ b/.vagrant-puppet/modules/monitoring-plugins/manifests/init.pp
@@ -0,0 +1,9 @@
+class monitoring-plugins {
+ include epel
+
+ # nagios plugins from epel
+ package { 'nagios-plugins-all':
+ ensure => installed,
+ require => Class['epel']
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 5974d5de0..16eb1444a 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,59 @@
## 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
-> **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.
+### General
+
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
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
-> **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
> 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**:
* `/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
@@ -196,12 +244,13 @@ code style issues.
#### 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**:
- cd /usr/local/icinga2
- ./sbin/icinga2 -c etc/icinga2/icinga2.conf.dist
+ /etc/init.d/icinga2 (start|stop|restart|reload)
+
## 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
Have a look at [LDAP example data](#ldap example data) for more accounts.
+
+Using MySQL as backend:
+
+> **Username**: icingaadmin
+> **Password**: icinga
+
diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php
index 8812a21fc..b87779669 100644
--- a/application/controllers/AuthenticationController.php
+++ b/application/controllers/AuthenticationController.php
@@ -34,11 +34,16 @@ class AuthenticationController extends ActionController
public function loginAction()
{
$auth = $this->Auth();
- $this->view->form = new LoginForm();
+ $this->view->form = $form = new LoginForm();
$this->view->title = $this->translate('Icingaweb Login');
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()) {
$this->rerenderLayout()->redirectNow($redirectUrl);
@@ -47,14 +52,10 @@ class AuthenticationController extends ActionController
try {
$config = Config::app('authentication');
} catch (NotReadableError $e) {
- Logger::error(
- new Exception('Cannot load authentication configuration. An exception was thrown:', 0, $e)
- );
throw new ConfigurationError(
- t(
- 'No authentication methods available. Authentication configuration could not be loaded.'
- . ' Please check the system log or Icinga Web 2 log for more information'
- )
+ $this->translate('Could not read your authentiction.ini, no authentication methods are available.'),
+ 0,
+ $e
);
}
@@ -66,6 +67,14 @@ class AuthenticationController extends ActionController
$backendsTried = 0;
$backendsWithError = 0;
+ $redirectUrl = $form->getValue('redirect');
+
+ if ($redirectUrl) {
+ $redirectUrl = Url::fromPath($redirectUrl);
+ } else {
+ $redirectUrl = Url::fromPath('dashboard');
+ }
+
foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) {
continue;
@@ -85,25 +94,25 @@ class AuthenticationController extends ActionController
}
if ($backendsTried === 0) {
throw new ConfigurationError(
- t(
- 'No authentication methods available. It seems that no authentication method has been set'
- . ' up. Please check the system log or Icinga Web 2 log for more information'
- )
+ $this->translate(
+ 'No authentication methods available. Did you create'
+ . ' authentication.ini when installing Icinga Web 2?'
+ )
);
}
if ($backendsTried === $backendsWithError) {
throw new ConfigurationError(
$this->translate(
- 'No authentication methods available. It seems that all set up authentication methods have'
- . ' errors. Please check the system log or Icinga Web 2 log for more information'
+ 'All configured authentication methods failed.'
+ . ' Please check the system log or Icinga Web 2 log for more information.'
)
);
}
if ($backendsWithError) {
$this->view->form->getElement('username')->addError(
$this->translate(
- 'Note that not all authentication backends are available for authentication because they'
- . ' have errors. Please check the system log or Icinga Web 2 log for more information'
+ 'Please note that not all authentication methods where available.'
+ . ' Check the system log or Icinga Web 2 log for more information.'
)
);
}
@@ -131,9 +140,10 @@ class AuthenticationController extends ActionController
public function logoutAction()
{
$auth = $this->Auth();
+ $isRemoteUser = $auth->getUser()->isRemoteUser();
$auth->removeAuthorization();
- if ($auth->isAuthenticatedFromRemoteUser()) {
+ if ($isRemoteUser === true) {
$this->_helper->layout->setLayout('login');
$this->_response->setHttpResponseCode(401);
} else {
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
index 0a699ca99..599e50695 100644
--- a/application/controllers/ConfigController.php
+++ b/application/controllers/ConfigController.php
@@ -6,7 +6,6 @@ use Icinga\Web\Controller\BaseConfigController;
use Icinga\Web\Widget\AlertMessageBox;
use Icinga\Web\Notification;
use Icinga\Application\Modules\Module;
-use Icinga\Web\Url;
use Icinga\Web\Form;
use Icinga\Web\Widget;
use Icinga\Application\Icinga;
@@ -41,9 +40,6 @@ class ConfigController extends BaseConfigController
))->add('logging', array(
'title' => 'Logging',
'url' => 'config/logging'
- ))->add('modules', array(
- 'title' => 'Modules',
- 'url' => 'config/modules'
));
}
@@ -116,6 +112,11 @@ class ConfigController extends BaseConfigController
*/
public function modulesAction()
{
+ $this->view->tabs = Widget::create('tabs')->add('modules', array(
+ 'title' => 'Modules',
+ 'url' => 'config/modules'
+ ));
+
$this->view->tabs->activate('modules');
$this->view->modules = Icinga::app()->getModuleManager()->select()
->from('modules')
diff --git a/application/controllers/LayoutController.php b/application/controllers/LayoutController.php
index 2d1abfb72..8df5c2c21 100644
--- a/application/controllers/LayoutController.php
+++ b/application/controllers/LayoutController.php
@@ -18,7 +18,9 @@ class LayoutController extends ActionController
*/
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()
+ );
}
/**
diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php
index 45e7d3de6..cb127dee7 100644
--- a/application/forms/Authentication/LoginForm.php
+++ b/application/forms/Authentication/LoginForm.php
@@ -5,6 +5,7 @@
namespace Icinga\Form\Authentication;
use Icinga\Web\Form;
+use Icinga\Web\Url;
/**
* Class LoginForm
@@ -44,6 +45,13 @@ class LoginForm extends Form
'placeholder' => t('...and your password'),
'class' => isset($formData['username']) ? 'autofocus' : ''
)
+ ),
+ $this->createElement(
+ 'hidden',
+ 'redirect',
+ array(
+ 'value' => Url::fromRequest()->getParam('redirect')
+ )
)
);
}
diff --git a/application/forms/Config/GeneralForm.php b/application/forms/Config/GeneralForm.php
index ae06dcb9a..4911c9f6b 100644
--- a/application/forms/Config/GeneralForm.php
+++ b/application/forms/Config/GeneralForm.php
@@ -110,7 +110,6 @@ class GeneralForm extends Form
foreach (Translator::getAvailableLocaleCodes() as $language) {
$languages[$language] = $language;
}
- $languages[Translator::DEFAULT_LOCALE] = Translator::DEFAULT_LOCALE;
return $this->createElement(
'select',
diff --git a/application/forms/Preference/GeneralForm.php b/application/forms/Preference/GeneralForm.php
index f16d43342..6ec719f72 100644
--- a/application/forms/Preference/GeneralForm.php
+++ b/application/forms/Preference/GeneralForm.php
@@ -36,7 +36,6 @@ class GeneralForm extends Form
foreach (Translator::getAvailableLocaleCodes() as $language) {
$languages[$language] = $language;
}
- $languages[Translator::DEFAULT_LOCALE] = Translator::DEFAULT_LOCALE;
$useBrowserLanguage = isset($formData['browser_language']) ? $formData['browser_language'] == 1 : true;
$selectOptions = array(
diff --git a/application/layouts/scripts/body.phtml b/application/layouts/scripts/body.phtml
index be9531adc..a40715458 100644
--- a/application/layouts/scripts/body.phtml
+++ b/application/layouts/scripts/body.phtml
@@ -38,7 +38,7 @@ if ($notifications->hasMessages()) {
-
data-icinga-module="= $moduleName ?>" data-icinga-url="= Url::fromRequest() ?>"= $refresh ?> style="display: block">
+
data-icinga-module="= $moduleName ?>" data-icinga-url="= Url::fromRequest()->without('renderLayout') ?>"= $refresh ?> style="display: block">
= $this->render('inline.phtml') ?>
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml
index b4fd504ed..32c4879b4 100644
--- a/application/layouts/scripts/layout.phtml
+++ b/application/layouts/scripts/layout.phtml
@@ -44,6 +44,7 @@ $iframeClass = $isIframe ? ' iframe' : '';
+
diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml
index c689db54a..8b557e32f 100644
--- a/application/layouts/scripts/parts/navigation.phtml
+++ b/application/layouts/scripts/parts/navigation.phtml
@@ -14,5 +14,5 @@ if (! $this->auth()->isAuthenticated()) {
-= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl()); ?>
+= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()); ?>
diff --git a/application/views/scripts/authentication/logout.phtml b/application/views/scripts/authentication/logout.phtml
index eb0d6dc44..9b03d190f 100644
--- a/application/views/scripts/authentication/logout.phtml
+++ b/application/views/scripts/authentication/logout.phtml
@@ -7,10 +7,7 @@
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.
-->
-
-
-
-
+
= t('Logging out...'); ?>
= t(
@@ -19,37 +16,19 @@
'browser session.'
); ?>
-
-
-
-
diff --git a/config/authentication.ini.in b/config/authentication.ini.in
index 554356492..71d9e4402 100644
--- a/config/authentication.ini.in
+++ b/config/authentication.ini.in
@@ -1,7 +1,7 @@
; authentication.ini
;
; 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 user trying to log in is available.
diff --git a/config/dashboard/dashboard.ini b/config/dashboard/dashboard.ini
index 50e5a6373..022b34be5 100644
--- a/config/dashboard/dashboard.ini
+++ b/config/dashboard/dashboard.ini
@@ -23,13 +23,17 @@ sort = host_severity
title = "Landing page"
[Landing.Hostgroups]
-url = "monitoring/chart/hostgroup?height=400&width=500"
+url = "monitoring/chart/hostgroup"
[Landing.Servicegroups]
-url = "monitoring/chart/servicegroup?height=360&width=450"
+url = "monitoring/chart/servicegroup"
[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]
-url = "monitoring/list/hosts?host_handled=0&host_problem=1"
+url = "monitoring/list/hosts"
+host_handled = 0
+host_problem = 1
diff --git a/config/menu.ini b/config/menu.ini
index 65959c93a..075f4230e 100644
--- a/config/menu.ini
+++ b/config/menu.ini
@@ -18,10 +18,15 @@ title = "Configuration"
url = "config"
priority = 300
+[System.Modules]
+title = "Modules"
+url = "config/modules"
+priority = 400
+
[System.ApplicationLog]
title = "Application log"
url = "list/applicationlog"
-priority = 400
+priority = 500
[Logout]
url = "authentication/logout"
diff --git a/config/modules/doc/menu.ini b/config/modules/doc/menu.ini
new file mode 100644
index 000000000..86889b239
--- /dev/null
+++ b/config/modules/doc/menu.ini
@@ -0,0 +1,5 @@
+[Documentation]
+title = "Documentation"
+icon = "img/icons/comment.png"
+url = "doc"
+priority = 80
diff --git a/config/modules/monitoring/menu.ini b/config/modules/monitoring/menu.ini
index c66a611a9..c185fb87d 100644
--- a/config/modules/monitoring/menu.ini
+++ b/config/modules/monitoring/menu.ini
@@ -68,7 +68,7 @@ priority = 70
[Overview.Comments]
title = "Comments"
-url = "monitoring/list/comments"
+url = "monitoring/list/comments?comment_type=(comment|ack)"
priority = 70
[Overview.Contacts]
diff --git a/etc/apache/icingaweb.conf.in b/etc/apache/icingaweb.conf.in
index 69335368e..3884aea5a 100644
--- a/etc/apache/icingaweb.conf.in
+++ b/etc/apache/icingaweb.conf.in
@@ -3,8 +3,19 @@ Alias @web_path@ "@prefix@/public"
Options SymLinksIfOwnerMatch
AllowOverride None
- Order allow,deny
- Allow from all
+
+
+ # Apache 2.4
+
+ Require all granted
+
+
+
+
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+
SetEnv ICINGAWEB_CONFIGDIR @icingaweb_config_path@
diff --git a/icingaweb2.spec b/icingaweb2.spec
index b1cb3d2e5..cfdcdafc2 100644
--- a/icingaweb2.spec
+++ b/icingaweb2.spec
@@ -1,16 +1,37 @@
-# $Id$
-# Authority: The icinga devel team
-# Upstream: The icinga devel team
-# ExcludeDist: el4 el3
+#/**
+# * This file is part of Icinga Web 2.
+# *
+# * 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
+# * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
+# * @author Icinga Development Team
+# *
+# */
-%define revision 0
+%define revision 1
%define configdir %{_sysconfdir}/icingaweb
%define sharedir %{_datadir}/icingaweb
%define prefixdir %{_datadir}/icingaweb
%define logdir %{sharedir}/log
%define usermodparam -a -G
-#%define logdir %{_localstatedir}/log/icingaweb
+%define logdir %{_localstatedir}/log/icingaweb
%if "%{_vendor}" == "suse"
%define phpname php5
@@ -19,11 +40,12 @@
%endif
# SLE 11 = 1110
%if 0%{?suse_version} == 1110
+%define phpname php53
%define apache2modphpname apache2-mod_php53
%define usermodparam -A
%endif
-%if "%{_vendor}" == "redhat" || 0%{?suse_version} == 1110
+%if "%{_vendor}" == "redhat"
%define phpname php
%define phpzendname php-ZendFramework
%endif
@@ -37,15 +59,15 @@
%define apacheconfdir %{_sysconfdir}/apache2/conf.d
%define apacheuser wwwrun
%define apachegroup www
-%define extcmdfile1x %{_localstatedir}/icinga/rw/icinga.cmd
-%define livestatussocket1x %{_localstatedir}/icinga/rw/live
+%define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd
+%define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus
%endif
%if "%{_vendor}" == "redhat"
%define apacheconfdir %{_sysconfdir}/httpd/conf.d
%define apacheuser apache
%define apachegroup apache
-%define extcmdfile-1x %{_localstatedir}/spool/icinga/cmd/icinga.cmd
-%define livestatussocket1x %{_localstatedir}/spool/icinga/cmd/live
+%define extcmdfile %{_localstatedir}/run/icinga2/cmd/icinga.cmd
+%define livestatussocket %{_localstatedir}/run/icinga2/cmd/livestatus
%endif
Summary: Open Source host, service and network monitoring Web UI
@@ -109,8 +131,8 @@ Requires: php-Icinga
%description
-IcingaWeb for Icinga 2 or Icinga 1.x using status data,
-IDOUtils or Livestatus as backend provider.
+Icinga Web 2 for Icinga 2 or Icinga 1.x using multiple backends
+for example DB IDO.
%package -n icingacli
Summary: Icinga CLI
@@ -130,58 +152,54 @@ Requires: %{phpzendname}
%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
-#%setup -q -n %{name}-%{version}
-%setup -q -n %{name}
+#VERSION=0.0.1; git archive --format=tar --prefix=icingaweb2-$VERSION/ HEAD | gzip >icingaweb2-$VERSION.tar.gz
+%setup -q -n %{name}-%{version}
%build
-cat > README.RHEL.SUSE <<"EOF"
-IcingaWeb for RHEL and SUSE
-===========================
-
-Please check ./doc/installation.md
-for requirements and database setup.
-EOF
-
%install
[ "%{buildroot}" != "/" ] && [ -d "%{buildroot}" ] && rm -rf %{buildroot}
# prepare configuration for sub packages
# 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
%{__mkdir} -p %{buildroot}/%{sharedir}
%{__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
%{__cp} -r application library modules public %{buildroot}/%{sharedir}/
-# install index.php
-install -m0644 packages/rhel/usr/share/icingaweb/public/index.php %{buildroot}/%{sharedir}/public/index.php
-
-# use the vagrant config for configuration for now - TODO
-%{__cp} -r .vagrant-puppet/files/etc/icingaweb %{buildroot}/%{_sysconfdir}/
-
-# we use the default 'icinga' database
-sed -i 's/icinga2/icinga/g' %{buildroot}/%{_sysconfdir}/icingaweb/resources.ini
+## config
+# use the default menu.ini for application and monitoring mobule
+install -D -m0644 config/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/menu.ini
+install -D -m0644 config/modules/monitoring/menu.ini %{buildroot}/%{_sysconfdir}/icingaweb/modules/monitoring/menu.ini
+# authentication is db only
+install -D -m0644 packages/rpm/etc/icingaweb/authentication.ini %{buildroot}/%{_sysconfdir}/icingaweb/authentication.ini
+# custom resource paths
+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
ln -s %{sharedir}/modules/monitoring %{buildroot}/%{_sysconfdir}/icingaweb/enabledModules/monitoring
+## config
# install icingacli
-install -D -m0755 bin/icingacli %{buildroot}/usr/bin/icingacli
-
-# install sql schema files as example
-
-# delete all *.in files
+install -D -m0755 packages/rpm/usr/bin/icingacli %{buildroot}/usr/bin/icingacli
%pre
# Add apacheuser in the icingacmd group
@@ -196,9 +214,6 @@ if [ $? -eq 0 ]; then
%{_sbindir}/usermod %{usermodparam} icingacmd %{apacheuser}
fi
-# uncomment if building from git
-# %{__rm} -rf %{buildroot}%{_datadir}/icinga2-web/.git
-
%preun
%post
@@ -209,14 +224,13 @@ fi
%files
# main dirs
%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}/modules
# configs
%defattr(-,root,root)
%config(noreplace) %attr(-,root,root) %{apacheconfdir}/icingaweb.conf
-%dir %{configdir}
-%config(noreplace) %attr(775,%{apacheuser},%{apachegroup}) %{configdir}
+%config(noreplace) %attr(-,%{apacheuser},%{apachegroup}) %{configdir}
# logs
%attr(2775,%{apacheuser},%{apachegroup}) %dir %{logdir}
@@ -228,6 +242,3 @@ fi
%attr(0755,root,root) /usr/bin/icingacli
%changelog
-* Tue May 11 2014 Michael Friedrich - 0.0.1-1
-- initial creation
-
diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php
index 8dd189097..3d5e3752e 100644
--- a/library/Icinga/Application/Modules/Module.php
+++ b/library/Icinga/Application/Modules/Module.php
@@ -5,6 +5,7 @@
namespace Icinga\Application\Modules;
use Exception;
+use Zend_Controller_Router_Route_Abstract;
use Zend_Controller_Router_Route as Route;
use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config;
@@ -135,6 +136,16 @@ class Module
*/
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
*
@@ -166,8 +177,7 @@ class Module
*/
public function register()
{
- $this->registerAutoloader()
- ->registerWebIntegration();
+ $this->registerAutoloader();
try {
$this->launchRunScript();
} catch (Exception $e) {
@@ -179,6 +189,7 @@ class Module
);
return false;
}
+ $this->registerWebIntegration();
return true;
}
@@ -658,24 +669,29 @@ 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()
{
- $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',
new Route(
'js/' . $this->name . '/:file',
array(
'controller' => 'static',
'action' =>'javascript',
- 'module_name' => $this->name
+ 'module_name' => $this->name
)
)
);
- $this->app->getFrontController()->getRouter()->addRoute(
+ $router->addRoute(
$this->name . '_img',
new Route(
'img/' . $this->name . '/:file',
@@ -750,4 +766,19 @@ class Module
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;
+ }
}
diff --git a/library/Icinga/Authentication/Backend/AutoLoginBackend.php b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
index d793b50dd..16373bb6c 100644
--- a/library/Icinga/Authentication/Backend/AutoLoginBackend.php
+++ b/library/Icinga/Authentication/Backend/AutoLoginBackend.php
@@ -53,6 +53,7 @@ class AutoLoginBackend extends UserBackend
{
if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER'];
+ $user->setRemoteUserInformation($username, 'REMOTE_USER');
if ($this->stripUsernameRegexp !== null) {
$stripped = preg_replace($this->stripUsernameRegexp, '', $username);
if ($stripped !== false) {
diff --git a/library/Icinga/Authentication/Manager.php b/library/Icinga/Authentication/Manager.php
index 01964ef00..ec49aa416 100644
--- a/library/Icinga/Authentication/Manager.php
+++ b/library/Icinga/Authentication/Manager.php
@@ -30,12 +30,6 @@ class Manager
*/
private $user;
- /**
- * If the user was authenticated from the REMOTE_USER server variable
- *
- * @var Boolean
- */
- private $fromRemoteUser = false;
private function __construct()
{
@@ -117,6 +111,13 @@ class Manager
public function authenticateFromSession()
{
$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();
}
-
- /**
- * 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;
- }
}
diff --git a/library/Icinga/Chart/Axis.php b/library/Icinga/Chart/Axis.php
index db1a82a83..00a5f5679 100644
--- a/library/Icinga/Chart/Axis.php
+++ b/library/Icinga/Chart/Axis.php
@@ -9,6 +9,7 @@ use Icinga\Chart\Primitive\Drawable;
use Icinga\Chart\Primitive\Line;
use Icinga\Chart\Primitive\Text;
use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Render\Rotator;
use Icinga\Chart\Unit\AxisUnit;
use Icinga\Chart\Unit\CalendarUnit;
use Icinga\Chart\Unit\LinearUnit;
@@ -188,11 +189,11 @@ class Axis implements Drawable
$labelField->setFontSize('2.5em');
}
+ if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
+ $labelField = new Rotator($labelField, 45);
+ }
$labelField = $labelField->toSvg($ctx);
- if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
- $labelField = $this->rotate($ctx, $labelField, 45);
- }
$group->appendChild($labelField);
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
*
@@ -275,9 +248,9 @@ class Axis implements Drawable
if ($this->yLabel) {
$axisLabel = new Text(-8, 50, $this->yLabel);
$axisLabel->setFontSize('2em')
- ->setAdditionalStyle(Text::ORIENTATION_VERTICAL)
->setFontWeight('bold')
->setAlignment(Text::ALIGN_MIDDLE);
+ $axisLabel = new Rotator($axisLabel, 90);
$group->appendChild($axisLabel->toSvg($ctx));
}
diff --git a/library/Icinga/Chart/Graph/BarGraph.php b/library/Icinga/Chart/Graph/BarGraph.php
index 8bde864ce..968e4c0f7 100644
--- a/library/Icinga/Chart/Graph/BarGraph.php
+++ b/library/Icinga/Chart/Graph/BarGraph.php
@@ -69,7 +69,7 @@ class BarGraph extends Styleable implements Drawable
$group = $doc->createElement('g');
$idx = 0;
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->setStrokeWidth($this->strokeWidth);
$rect->setStrokeColor('black');
diff --git a/library/Icinga/Chart/Primitive/Text.php b/library/Icinga/Chart/Primitive/Text.php
index 5521d6e27..72bed552b 100644
--- a/library/Icinga/Chart/Primitive/Text.php
+++ b/library/Icinga/Chart/Primitive/Text.php
@@ -30,16 +30,6 @@ class Text extends Styleable implements Drawable
*/
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
*
diff --git a/library/Icinga/Chart/Render/Rotator.php b/library/Icinga/Chart/Render/Rotator.php
new file mode 100644
index 000000000..934b784b6
--- /dev/null
+++ b/library/Icinga/Chart/Render/Rotator.php
@@ -0,0 +1,86 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/library/Icinga/Chart/Unit/LinearUnit.php b/library/Icinga/Chart/Unit/LinearUnit.php
index bf27486d2..d776fe304 100644
--- a/library/Icinga/Chart/Unit/LinearUnit.php
+++ b/library/Icinga/Chart/Unit/LinearUnit.php
@@ -90,17 +90,52 @@ class LinearUnit implements AxisUnit
}
sort($datapoints);
if (!$this->staticMax) {
- $this->max = max($this->max, $datapoints[count($datapoints)-1]);
+ $this->max = max($this->max, $datapoints[count($datapoints) - 1]);
}
if (!$this->staticMin) {
$this->min = min($this->min, $datapoints[0]);
}
-
+ if (!$this->staticMin || !$this->staticMax) {
+ $this->updateMaxValue();
+ }
$this->currentTick = 0;
$this->currentValue = $this->min;
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
*
@@ -114,7 +149,7 @@ class LinearUnit implements AxisUnit
} elseif ($value > $this->max) {
return 100;
} 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) {
$this->max = $max;
$this->staticMax = true;
+ $this->updateMaxValue();
}
}
@@ -189,6 +225,7 @@ class LinearUnit implements AxisUnit
if ($min !== null) {
$this->min = $min;
$this->staticMin = true;
+ $this->updateMaxValue();
}
}
diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php
index 081be318c..acb2462e4 100644
--- a/library/Icinga/Cli/Command.php
+++ b/library/Icinga/Cli/Command.php
@@ -7,6 +7,7 @@ namespace Icinga\Cli;
use Icinga\Cli\Screen;
use Icinga\Util\Translator;
use Icinga\Cli\Params;
+use Icinga\Application\Config;
use Icinga\Application\ApplicationBootstrap as App;
use Exception;
@@ -23,6 +24,10 @@ abstract class Command
protected $commandName;
protected $actionName;
+ private $config;
+
+ private $configs;
+
protected $defaultActionName = 'default';
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)
{
$this->params = $params;
diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php
index 7fef56efc..3fb74a656 100644
--- a/library/Icinga/Data/Db/DbQuery.php
+++ b/library/Icinga/Data/Db/DbQuery.php
@@ -192,6 +192,24 @@ class DbQuery extends SimpleQuery
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)
{
if ($this->isTimestamp($col)) {
diff --git a/library/Icinga/Data/Identifiable.php b/library/Icinga/Data/Identifiable.php
new file mode 100644
index 000000000..cfa727a1d
--- /dev/null
+++ b/library/Icinga/Data/Identifiable.php
@@ -0,0 +1,18 @@
+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;
+ }
+}
diff --git a/library/Icinga/Data/Tree/NodeInterface.php b/library/Icinga/Data/Tree/NodeInterface.php
new file mode 100644
index 000000000..6953214dc
--- /dev/null
+++ b/library/Icinga/Data/Tree/NodeInterface.php
@@ -0,0 +1,26 @@
+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())
{
if ($command->provideGlobalCommand() === true) {
- $this->transport->send($command->getGlobalCommand());
+ $this->send($command->getGlobalCommand());
} else {
foreach ($objects as $object) {
$objectType = $this->getObjectType($object);
if ($objectType === self::TYPE_SERVICE) {
- $this->transport->send(
- $command->getServiceCommand($object->host_name, $object->service_description)
- );
+ $this->send($command->getServiceCommand($object->host_name, $object->service_description));
} else {
- $this->transport->send($command->getHostCommand($object->host_name));
+ $this->send($command->getHostCommand($object->host_name));
}
}
}
diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php
new file mode 100644
index 000000000..3e0890a57
--- /dev/null
+++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php
@@ -0,0 +1,10 @@
+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;
}
@@ -80,4 +83,4 @@ class Query extends SimpleQuery
{
return $this->filters;
}
-}
\ No newline at end of file
+}
diff --git a/library/Icinga/Protocol/File/Reader.php b/library/Icinga/Protocol/File/Reader.php
index f5677e855..50842bc4a 100644
--- a/library/Icinga/Protocol/File/Reader.php
+++ b/library/Icinga/Protocol/File/Reader.php
@@ -4,38 +4,92 @@
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
- *
- * @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
*/
- 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;
- $this->filename = $config->filename;
+ foreach (array('filename', 'fields') as $key) {
+ 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,27 +102,78 @@ class Reader implements DatasourceInterface
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
*
- * @return array
+ * @param Query $query
+ *
+ * @return array
*/
public function fetchAll(Query $query)
{
$all = array();
foreach ($this->fetchPairs($query) as $index => $value) {
- $all[$index] = new \stdClass();
- foreach ($value as $key => $value_2) {
- $all[$index]->{$key} = $value_2;
- }
+ $all[$index] = (object) $value;
}
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
*
- * @return object
+ * @param Query $query
+ *
+ * @return object
*/
public function fetchRow(Query $query)
{
@@ -82,14 +187,16 @@ class Reader implements DatasourceInterface
/**
* Fetch first result column
*
- * @return array
+ * @param Query $query
+ *
+ * @return array
*/
public function fetchColumn(Query $query)
{
$column = array();
- foreach ($this->fetchPairs($query) as $value) {
- foreach ($value as $value_2) {
- $column[] = $value_2;
+ foreach ($this->fetchPairs($query) as $pair) {
+ foreach ($pair as $value) {
+ $column[] = $value;
break;
}
}
@@ -99,7 +206,9 @@ class Reader implements DatasourceInterface
/**
* Fetch first column value from first result row
*
- * @return mixed
+ * @param Query $query
+ *
+ * @return mixed
*/
public function fetchOne(Query $query)
{
@@ -111,215 +220,4 @@ class Reader implements DatasourceInterface
}
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;
- }
}
diff --git a/library/Icinga/User.php b/library/Icinga/User.php
index 848877850..13f62881a 100644
--- a/library/Icinga/User.php
+++ b/library/Icinga/User.php
@@ -58,6 +58,18 @@ class User
*/
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
*
@@ -401,4 +413,35 @@ class User
{
$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;
+ }
}
diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php
index 111e414ad..fd01812b3 100644
--- a/library/Icinga/Util/Translator.php
+++ b/library/Icinga/Util/Translator.php
@@ -115,7 +115,7 @@ class Translator
*/
public static function getAvailableLocaleCodes()
{
- $codes = array();
+ $codes = array(static::DEFAULT_LOCALE);
foreach (array_values(self::$knownDomains) as $directory) {
$dh = opendir($directory);
while (false !== ($name = readdir($dh))) {
diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php
index b28bddc37..13809e83d 100644
--- a/library/Icinga/Web/Controller/ActionController.php
+++ b/library/Icinga/Web/Controller/ActionController.php
@@ -254,10 +254,27 @@ class ActionController extends Zend_Controller_Action
*
* @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->setParam('redirect', $afterLogin);
+
+ if ($redir) {
+ $url->setParam('redirect', $redir);
+ }
+
$this->rerenderLayout()->redirectNow($url);
}
@@ -273,6 +290,27 @@ class ActionController extends Zend_Controller_Action
return $this->getRequest()->isXmlHttpRequest();
}
+ protected function redirectXhr($url)
+ {
+ if (! $url instanceof Url) {
+ $url = Url::fromPath($url);
+ }
+
+ if ($this->rerenderLayout) {
+ $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
+ }
+ if ($this->reloadCss) {
+ $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
+ }
+
+ $this->getResponse()
+ ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
+ ->sendHeaders();
+
+ // TODO: Session shutdown?
+ exit;
+ }
+
/**
* Redirect to a specific url, updating the browsers URL field
*
@@ -280,26 +318,13 @@ class ActionController extends Zend_Controller_Action
**/
public function redirectNow($url)
{
- if (! $url instanceof Url) {
- $url = Url::fromPath($url);
- }
- $url = preg_replace('~&~', '&', $url);
if ($this->isXhr()) {
- if ($this->rerenderLayout) {
- $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
- }
- if ($this->reloadCss) {
- $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
- }
-
- $this->getResponse()
- ->setHeader('X-Icinga-Redirect', rawurlencode($url))
- ->sendHeaders();
-
- // TODO: Session shutdown?
- exit;
+ $this->redirectXhr($url);
} 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',
rawurlencode($this->view->title . ' :: Icinga Web')
);
+ } else {
+ $resp->setHeader('X-Icinga-Title', rawurlencode('Icinga Web'));
}
if ($this->rerenderLayout) {
diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php
index c9da962be..ed3acf6a7 100644
--- a/library/Icinga/Web/Url.php
+++ b/library/Icinga/Web/Url.php
@@ -127,10 +127,6 @@ class Url
$baseUrl = $request->getBaseUrl();
$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);
if (isset($urlParts['path'])) {
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
@@ -144,44 +140,14 @@ class Url
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
}
- $fragment = self::getUrlFragment($url);
- if ($fragment !== '') {
- $urlObject->setAnchor($fragment);
+ if (isset($urlParts['fragment'])) {
+ $urlObject->setAnchor($urlParts['fragment']);
}
$urlObject->setParams($params);
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
*
@@ -241,12 +207,12 @@ class Url
*
* @return string
*/
- public function getRelativeUrl()
+ public function getRelativeUrl($separator = '&')
{
if ($this->params->isEmpty()) {
return $this->path . $this->anchor;
} else {
- return $this->path . '?' . $this->params->setSeparator('&') . $this->anchor;
+ return $this->path . '?' . $this->params->toString($separator) . $this->anchor;
}
}
@@ -266,9 +232,9 @@ class Url
*
* @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 array An associative key => value array containing all parameters
+ * @return UrlParams An instance of UrlParam containing all parameters
*/
public function getParams()
{
- return $this->params->asArray();
+ return $this->params;
}
/**
@@ -450,6 +416,6 @@ class Url
*/
public function __toString()
{
- return $this->getAbsoluteUrl();
+ return $this->getAbsoluteUrl('&');
}
}
diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php
index 9a8b60929..bdf776401 100644
--- a/library/Icinga/Web/UrlParams.php
+++ b/library/Icinga/Web/UrlParams.php
@@ -114,6 +114,18 @@ class UrlParams
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
*
@@ -127,9 +139,7 @@ class UrlParams
*/
public function add($param, $value = true)
{
- $this->params[] = array($param, $this->cleanupValue($value));
- $this->indexLastOne();
- return $this;
+ return $this->addEncoded($this->urlEncode($param), $this->urlEncode($value));
}
/**
@@ -198,7 +208,7 @@ class UrlParams
*/
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();
return $this;
}
@@ -224,7 +234,10 @@ class UrlParams
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();
return $this;
@@ -243,7 +256,7 @@ class UrlParams
foreach ($this->index[$p] as $key) {
unset($this->params[$key]);
}
- $this->changed = true;
+ $changed = true;
}
}
@@ -303,20 +316,23 @@ class UrlParams
protected function parseQueryStringPart($part)
{
if (strpos($part, '=') === false) {
- $this->add($part, true);
+ $this->addEncoded($part, true);
} else {
list($key, $val) = preg_split('/=/', $part, 2);
- $this->add($key, $val);
+ $this->addEncoded($key, $val);
}
}
- public function asArray()
+ public function toArray()
{
return $this->params;
}
- public function __toString()
+ public function toString($separator = null)
{
+ if ($separator === null) {
+ $separator = $this->separator;
+ }
$parts = array();
foreach ($this->params as $p) {
if ($p[1] === true) {
@@ -325,13 +341,18 @@ class UrlParams
$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)
{
if ($queryString === null) {
- $queryString = $_SERVER['QUERY_STRING'];
+ $queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
}
$params = new static();
$params->parseQueryString($queryString);
diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php
index 8bac34359..2d8517526 100644
--- a/library/Icinga/Web/Widget/Dashboard/Component.php
+++ b/library/Icinga/Web/Widget/Dashboard/Component.php
@@ -126,7 +126,7 @@ EOD;
public function toArray()
{
$array = array('url' => $this->url->getPath());
- foreach ($this->url->getParams() as $param) {
+ foreach ($this->url->getParams()->toArray() as $param) {
$array[$param[0]] = $param[1];
}
return $array;
diff --git a/library/Icinga/Web/Widget/Limiter.php b/library/Icinga/Web/Widget/Limiter.php
index 9c6a726ba..5afa7e4b8 100644
--- a/library/Icinga/Web/Widget/Limiter.php
+++ b/library/Icinga/Web/Widget/Limiter.php
@@ -81,7 +81,7 @@ class Limiter extends AbstractWidget
$this->url->setParam('limit', $limit),
null,
array(
- 'title' => t(sprintf('Show %s rows on one page', $caption))
+ 'title' => sprintf(t('Show %s rows on one page'), $caption)
)
);
}
diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php
index 3d1c97ed0..241b83556 100644
--- a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php
+++ b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php
@@ -28,7 +28,7 @@ class DashboardAction implements Tabextension
'title' => 'Add To Dashboard',
'url' => Url::fromPath('dashboard/addurl'),
'urlParams' => array(
- 'url' => Url::fromRequest()->getRelativeUrl()
+ 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
)
)
);
diff --git a/modules/doc/application/controllers/IcingawebController.php b/modules/doc/application/controllers/IcingawebController.php
new file mode 100644
index 000000000..967a2b768
--- /dev/null
+++ b/modules/doc/application/controllers/IcingawebController.php
@@ -0,0 +1,48 @@
+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');
+ }
+}
diff --git a/modules/doc/application/controllers/IndexController.php b/modules/doc/application/controllers/IndexController.php
index f46fdad87..c83cfabab 100644
--- a/modules/doc/application/controllers/IndexController.php
+++ b/modules/doc/application/controllers/IndexController.php
@@ -2,34 +2,9 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
-use Icinga\Module\Doc\Controller as DocController;
-
-use Icinga\Module\Doc\DocParser;
+use Icinga\Module\Doc\DocController;
class Doc_IndexController extends DocController
{
- protected $parser;
-
-
- 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();
- }
+ public function indexAction() {}
}
diff --git a/modules/doc/application/controllers/ModuleController.php b/modules/doc/application/controllers/ModuleController.php
index 41ba42db6..40913368c 100644
--- a/modules/doc/application/controllers/ModuleController.php
+++ b/modules/doc/application/controllers/ModuleController.php
@@ -2,44 +2,131 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
+use \Zend_Controller_Action_Exception;
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
{
/**
- * Display module documentations index
+ * List modules which are enabled and having the 'doc' directory
*/
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();
- $moduleName = substr($methodName, 0, -6); // Strip 'Action' suffix
- if (!$moduleManager->hasEnabled($moduleName)) {
- // TODO(el): Throw a not found exception once the code has been moved to the moduleAction (see TODO above)
- return parent::__call($methodName, $args);
+ $moduleManager = Icinga::app()->getModuleManager();
+ $modules = array();
+ foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) {
+ $docDir = $moduleManager->getModuleDir($enabledModule, '/doc');
+ 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)
+ );
}
}
diff --git a/modules/doc/application/views/scripts/chapter.phtml b/modules/doc/application/views/scripts/chapter.phtml
new file mode 100644
index 000000000..7657d69fb
--- /dev/null
+++ b/modules/doc/application/views/scripts/chapter.phtml
@@ -0,0 +1,3 @@
+
+ = $sectionRenderer->render($this, $this->getHelper('Url')); ?>
+
diff --git a/modules/doc/application/views/scripts/index/index.phtml b/modules/doc/application/views/scripts/index/index.phtml
index a178cc155..e4218bee2 100644
--- a/modules/doc/application/views/scripts/index/index.phtml
+++ b/modules/doc/application/views/scripts/index/index.phtml
@@ -1,5 +1,6 @@
-Icinga 2 Documentation
-= $this->partial('module/view.phtml', 'doc', array(
- 'toc' => $toc,
- 'html' => $html
-)); ?>
\ No newline at end of file
+
+= $this->translate('Available documentations'); ?>
+
diff --git a/modules/doc/application/views/scripts/index/toc.phtml b/modules/doc/application/views/scripts/index/toc.phtml
deleted file mode 100644
index 9188e21ff..000000000
--- a/modules/doc/application/views/scripts/index/toc.phtml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
Module documentations
-
-
-= $this->partial(
- 'layout/menu.phtml',
- 'default',
- array(
- 'items' => $toc->getChildren(),
- 'sub' => false,
- 'url' => ''
- )
-) ?>
-
diff --git a/modules/doc/application/views/scripts/module/index.phtml b/modules/doc/application/views/scripts/module/index.phtml
index 36f11e15e..cc184016f 100644
--- a/modules/doc/application/views/scripts/module/index.phtml
+++ b/modules/doc/application/views/scripts/module/index.phtml
@@ -1,6 +1,10 @@
-Module documentations
+= $this->translate('Module documentations'); ?>
diff --git a/modules/doc/application/views/scripts/module/view.phtml b/modules/doc/application/views/scripts/module/view.phtml
deleted file mode 100644
index 291947ad7..000000000
--- a/modules/doc/application/views/scripts/module/view.phtml
+++ /dev/null
@@ -1,7 +0,0 @@
-
- No documentation available.
-
-
-= $html ?>
-
-
diff --git a/modules/doc/application/views/scripts/pdf.phtml b/modules/doc/application/views/scripts/pdf.phtml
new file mode 100644
index 000000000..72d77f3c0
--- /dev/null
+++ b/modules/doc/application/views/scripts/pdf.phtml
@@ -0,0 +1,7 @@
+= $docName ?> = $this->translate('Documentation'); ?>
+
+ = $tocRenderer->render($this, $this->getHelper('Url')); ?>
+
+
+ = $sectionRenderer->render($this, $this->getHelper('Url')); ?>
+
diff --git a/modules/doc/application/views/scripts/toc.phtml b/modules/doc/application/views/scripts/toc.phtml
new file mode 100644
index 000000000..ca6283d67
--- /dev/null
+++ b/modules/doc/application/views/scripts/toc.phtml
@@ -0,0 +1,6 @@
+
+
= $title ?>
+
+
+ = $tocRenderer->render($this, $this->getHelper('Url')); ?>
+
diff --git a/modules/doc/library/Doc/Controller.php b/modules/doc/library/Doc/Controller.php
deleted file mode 100644
index 2c5a07d49..000000000
--- a/modules/doc/library/Doc/Controller.php
+++ /dev/null
@@ -1,23 +0,0 @@
-getDocumentation();
- $this->view->html = $html;
- $this->view->toc = $toc;
- }
-}
diff --git a/modules/doc/library/Doc/DocController.php b/modules/doc/library/Doc/DocController.php
new file mode 100644
index 000000000..42f8dce9b
--- /dev/null
+++ b/modules/doc/library/Doc/DocController.php
@@ -0,0 +1,76 @@
+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');
+ }
+}
diff --git a/modules/doc/library/Doc/DocException.php b/modules/doc/library/Doc/DocException.php
deleted file mode 100644
index cb7134045..000000000
--- a/modules/doc/library/Doc/DocException.php
+++ /dev/null
@@ -1,11 +0,0 @@
-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);
+ }
+}
diff --git a/modules/doc/library/Doc/DocParser.php b/modules/doc/library/Doc/DocParser.php
index d4e6875d1..c63532dc1 100644
--- a/modules/doc/library/Doc/DocParser.php
+++ b/modules/doc/library/Doc/DocParser.php
@@ -4,146 +4,65 @@
namespace Icinga\Module\Doc;
-use RecursiveIteratorIterator;
-use RecursiveDirectoryIterator;
-use Parsedown;
-use Icinga\Application\Icinga;
-use Icinga\Web\Menu;
-use Icinga\Web\Url;
-
-require_once 'IcingaVendor/Parsedown/Parsedown.php';
+use SplDoublyLinkedList;
+use Icinga\Exception\NotReadableError;
+use Icinga\Module\Doc\Exception\DocEmptyException;
+use Icinga\Module\Doc\Exception\DocException;
/**
* Parser for documentation written in Markdown
*/
class DocParser
{
- protected $dir;
-
- protected $module;
+ /**
+ * Path to the documentation
+ *
+ * @var string
+ */
+ protected $path;
/**
- * Create a new documentation parser for the given module or the application
+ * Iterator over documentation files
*
- * @param string $module
- *
- * @throws DocException
+ * @var DocIterator
*/
- public function __construct($module = null)
- {
- 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;
- }
+ protected $docIterator;
/**
- * Retrieve table of contents and HTML converted from markdown files sorted by filename
+ * Create a new documentation parser for the given path
*
- * @return array
- * @throws DocException
+ * @param string $path Path to the documentation
+ *
+ * @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(
- new MarkdownFileIterator(
- new RecursiveDirectoryIterator($this->dir)
- )
- );
- $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) {
- throw new DocException('Couldn\'t get the lock');
- }
- $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('.', '.', 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 = ' ' . PHP_EOL . $line;
- }
- $cat[] = $line;
- }
- $fileObject->flock(LOCK_UN);
+ if (! is_dir($path)) {
+ throw new DocException(
+ sprintf(mt('doc', 'Documentation directory \'%s\' does not exist'), $path)
+ );
}
- $html = Parsedown::instance()->parse(implode('', $cat));
- $html = preg_replace_callback(
- '#(.*?)\
#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);
+ 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
+ )
+ );
+ }
+ $this->path = $path;
+ $this->docIterator = $docIterator;
}
/**
@@ -156,28 +75,28 @@ class DocParser
*/
protected function extractHeader($line, $lastLine)
{
- if (!$line) {
+ if (! $line) {
return null;
}
$header = null;
- if ($line &&
- $line[0] === '#' &&
- preg_match('/^#+/', $line, $match) === 1
+ if ($line
+ && $line[0] === '#'
+ && preg_match('/^#+/', $line, $match) === 1
) {
- // Atx-style
+ // Atx
$level = strlen($match[0]);
$header = trim(substr($line, $level));
- if (!$header) {
+ if (! $header) {
return null;
}
} elseif (
- $line &&
- ($line[0] === '=' || $line[0] === '-') &&
- preg_match('/^[=-]+\s*$/', $line, $match) === 1
+ $line
+ && ($line[0] === '=' || $line[0] === '-')
+ && preg_match('/^[=-]+\s*$/', $line, $match) === 1
) {
// Setext
$header = trim($lastLine);
- if (!$header) {
+ if (! $header) {
return null;
}
if ($match[0][0] === '=') {
@@ -189,36 +108,67 @@ class DocParser
if ($header === null) {
return null;
}
- return array($header, $level);
- }
-
- /**
- * 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('#(?:<(?Pa|span) id="(?P.+)">(?P=tag)>)#u', $header, $match)
+ if ($header[0] === '<'
+ && preg_match('#(?:<(?Pa|span) (?:id|name)="(?P.+)">(?P=tag)>)\s*#u', $header, $match)
) {
$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
- * @param int $level
+ * @return DocTree
*/
- protected function reduceToc(array &$toc, $level) {
- while (end($toc)->level >= $level) {
- array_pop($toc);
+ public function getDocTree()
+ {
+ $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;
}
}
diff --git a/modules/doc/library/Doc/DocTree.php b/modules/doc/library/Doc/DocTree.php
new file mode 100644
index 000000000..1b112649c
--- /dev/null
+++ b/modules/doc/library/Doc/DocTree.php
@@ -0,0 +1,80 @@
+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];
+ }
+}
diff --git a/modules/doc/library/Doc/Exception/ChapterNotFoundException.php b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php
new file mode 100644
index 000000000..cd048a162
--- /dev/null
+++ b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php
@@ -0,0 +1,10 @@
+getInnerIterator()->current();
- if (!$current->isFile()) {
+ /* @var $current \SplFileInfo */
+ if (! $current->isFile()) {
return false;
}
$filename = $current->getFilename();
diff --git a/modules/doc/library/Doc/NonEmptyFileIterator.php b/modules/doc/library/Doc/NonEmptyFileIterator.php
new file mode 100644
index 000000000..71bf5acfa
--- /dev/null
+++ b/modules/doc/library/Doc/NonEmptyFileIterator.php
@@ -0,0 +1,31 @@
+getInnerIterator()->current();
+ /* @var $current \SplFileInfo */
+ if (! $current->isFile()
+ || $current->getSize() === 0
+ ) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/modules/doc/library/Doc/Renderer.php b/modules/doc/library/Doc/Renderer.php
new file mode 100644
index 000000000..0aebb89b9
--- /dev/null
+++ b/modules/doc/library/Doc/Renderer.php
@@ -0,0 +1,75 @@
+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;
+ }
+}
diff --git a/modules/doc/library/Doc/SectionFilterIterator.php b/modules/doc/library/Doc/SectionFilterIterator.php
new file mode 100644
index 000000000..e20d80359
--- /dev/null
+++ b/modules/doc/library/Doc/SectionFilterIterator.php
@@ -0,0 +1,68 @@
+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);
+ }
+}
diff --git a/modules/doc/library/Doc/SectionRenderer.php b/modules/doc/library/Doc/SectionRenderer.php
new file mode 100644
index 000000000..938e5ed7b
--- /dev/null
+++ b/modules/doc/library/Doc/SectionRenderer.php
@@ -0,0 +1,292 @@
+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(
+ '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 '' . highlight_string(htmlspecialchars_decode($match[1]), true) . ' ';
+ }
+
+ /**
+ * 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(
+ ' %3$s ',
+ Renderer::encodeAnchor($section->getId()),
+ $section->getLevel(),
+ $view->escape($section->getTitle())
+ );
+ $html = preg_replace_callback(
+ '#(.*?)
#s',
+ array($this, 'highlightPhp'),
+ $this->parsedown->text(implode('', $section->getContent()))
+ );
+ $html = preg_replace_callback(
+ '/ ]+>/',
+ array($this, 'replaceImg'),
+ $html
+ );
+ $content[] = preg_replace_callback(
+ '/[^>]*?\s+)?href="#(?P[^"]+)"/',
+ array($callback, 'render'),
+ $html
+ );
+ }
+ if ($renderNavigation) {
+ foreach ($this->docTree as $chapter) {
+ if ($chapter->getValue()->getId() === $section->getChapterId()) {
+ $navigation = array('');
+ $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(
+ '%s ',
+ $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(
+ '%s ',
+ $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(
+ '%s ',
+ $next->isNoFollow() ? 'rel="nofollow" ' : '',
+ $url->getAbsoluteUrl(),
+ $view->escape($next->getTitle())
+ );
+ }
+ $navigation[] = ' ';
+ $content = array_merge($navigation, $content, $navigation);
+ break;
+ }
+ }
+ }
+ return implode("\n", $content);
+ }
+}
diff --git a/modules/doc/library/Doc/TocRenderer.php b/modules/doc/library/Doc/TocRenderer.php
new file mode 100644
index 000000000..4061e80e3
--- /dev/null
+++ b/modules/doc/library/Doc/TocRenderer.php
@@ -0,0 +1,109 @@
+url = $url;
+ $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
+ }
+
+ public function beginIteration()
+ {
+ $this->content[] = '';
+ }
+
+ public function endIteration()
+ {
+ $this->content[] = ' ';
+ }
+
+ public function beginChildren()
+ {
+ $this->content[] = '';
+ }
+
+ public function endChildren()
+ {
+ $this->content[] = ' ';
+ }
+
+ /**
+ * 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(
+ '%s ',
+ $section->isNoFollow() ? 'rel="nofollow" ' : '',
+ $url->getAbsoluteUrl(),
+ $view->escape($section->getTitle())
+ );
+ if (! $this->getInnerIterator()->current()->hasChildren()) {
+ $this->content[] = ' ';
+ }
+ }
+ return implode("\n", $this->content);
+ }
+}
diff --git a/modules/doc/public/css/module.less b/modules/doc/public/css/module.less
new file mode 100644
index 000000000..d6d0d2a94
--- /dev/null
+++ b/modules/doc/public/css/module.less
@@ -0,0 +1,62 @@
+// W3C Recommendation (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;
+ }
+ }
+}
diff --git a/modules/doc/run.php b/modules/doc/run.php
new file mode 100644
index 000000000..7392e4c22
--- /dev/null
+++ b/modules/doc/run.php
@@ -0,0 +1,50 @@
+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);
+
diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php
index 42a377763..e6b86f4ef 100644
--- a/modules/monitoring/application/clicommands/ListCommand.php
+++ b/modules/monitoring/application/clicommands/ListCommand.php
@@ -72,6 +72,7 @@ class ListCommand extends Command
protected function showFormatted($query, $format, $columns)
{
+ $query = $query->getQuery();
switch($format) {
case 'json':
echo json_encode($query->fetchAll());
@@ -155,7 +156,7 @@ class ListCommand extends Command
'service_perfdata',
'service_last_state_change'
);
- $query = $this->getQuery('status', $columns)
+ $query = $this->getQuery('serviceStatus', $columns)
->order('host_name');
echo $this->renderStatusQuery($query);
}
@@ -167,6 +168,7 @@ class ListCommand extends Command
$screen = $this->screen;
$utils = new CliUtils($screen);
$maxCols = $screen->getColumns();
+ $query = $query->getQuery();
$rows = $query->fetchAll();
$count = $query->count();
$count = count($rows);
diff --git a/modules/monitoring/application/controllers/ChartController.php b/modules/monitoring/application/controllers/ChartController.php
index 4fee6e34f..19ee73a94 100644
--- a/modules/monitoring/application/controllers/ChartController.php
+++ b/modules/monitoring/application/controllers/ChartController.php
@@ -87,8 +87,8 @@ class Monitoring_ChartController extends Controller
'services_pending'
)
)->getQuery()->fetchAll();
- $this->view->height = intval($this->getParam('height', 220));
- $this->view->width = intval($this->getParam('width', 520));
+ $this->view->height = intval($this->getParam('height', 500));
+ $this->view->width = intval($this->getParam('width', 500));
if (count($query) === 1) {
$this->drawGroupPie($query[0]);
} else {
@@ -112,8 +112,8 @@ class Monitoring_ChartController extends Controller
'services_pending'
)
)->getQuery()->fetchAll();
- $this->view->height = intval($this->getParam('height', 220));
- $this->view->width = intval($this->getParam('width', 520));
+ $this->view->height = intval($this->getParam('height', 500));
+ $this->view->width = intval($this->getParam('width', 500));
$this->drawServiceGroupChart($query);
@@ -133,7 +133,8 @@ class Monitoring_ChartController extends Controller
}
$this->view->chart = new GridChart();
$this->view->chart->setAxisLabel('', t('Services'))
- ->setXAxis(new StaticAxis());
+ ->setXAxis(new StaticAxis())
+ ->setAxisMin(null, 0);
$this->view->chart->drawBars(
array(
@@ -183,7 +184,9 @@ class Monitoring_ChartController extends Controller
);
}
$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(
array(
'label' => t('Up'),
diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php
index f0cdb2767..3e02a4757 100644
--- a/modules/monitoring/application/controllers/ListController.php
+++ b/modules/monitoring/application/controllers/ListController.php
@@ -94,7 +94,6 @@ class Monitoring_ListController extends Controller
'host_last_check',
'host_last_state_change' => $stateChangeColumn,
'host_notifications_enabled',
- // 'host_unhandled_service_count',
'host_unhandled_services',
'host_action_url',
'host_notes_url',
@@ -222,6 +221,7 @@ class Monitoring_ListController extends Controller
'author' => 'downtime_author',
'start' => 'downtime_start',
'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
'end' => 'downtime_end',
'duration' => 'downtime_duration',
'is_flexible' => 'downtime_is_flexible',
@@ -229,7 +229,9 @@ class Monitoring_ListController extends Controller
'is_in_effect' => 'downtime_is_in_effect',
'entry_time' => 'downtime_entry_time',
'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_scheduled_start', 'DESC');
diff --git a/modules/monitoring/application/controllers/MonitoringCommands.php b/modules/monitoring/application/controllers/MonitoringCommands.php
index 0a94c17b2..faa04114e 100644
--- a/modules/monitoring/application/controllers/MonitoringCommands.php
+++ b/modules/monitoring/application/controllers/MonitoringCommands.php
@@ -64,7 +64,7 @@ class Zend_View_Helper_MonitoringCommands extends Zend_View_Helper_Abstract
$out .= ' ';
- $out .= '
';
+ $out .= '
';
if ($type === Meta::TYPE_FULL) {
return '
'. $out. '
';
diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php
index 37ea0e061..e8bc18761 100644
--- a/modules/monitoring/application/controllers/MultiController.php
+++ b/modules/monitoring/application/controllers/MultiController.php
@@ -21,7 +21,6 @@ class Monitoring_MultiController extends Controller
array(
'host_name',
'host_in_downtime',
- 'host_unhandled_service_count',
'host_passive_checks_enabled',
'host_obsessing',
'host_state',
diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php
index d7d14ce15..d503ba512 100644
--- a/modules/monitoring/application/controllers/ShowController.php
+++ b/modules/monitoring/application/controllers/ShowController.php
@@ -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
* @return Tabs
diff --git a/modules/monitoring/application/views/scripts/chart/hostgroup.phtml b/modules/monitoring/application/views/scripts/chart/hostgroup.phtml
index 4ad5f61dc..1f98bb8db 100644
--- a/modules/monitoring/application/views/scripts/chart/hostgroup.phtml
+++ b/modules/monitoring/application/views/scripts/chart/hostgroup.phtml
@@ -1,5 +1,5 @@
-
+
=
$chart->render();
?>
diff --git a/modules/monitoring/application/views/scripts/chart/servicegroup.phtml b/modules/monitoring/application/views/scripts/chart/servicegroup.phtml
index ad8a21ab0..1f98bb8db 100644
--- a/modules/monitoring/application/views/scripts/chart/servicegroup.phtml
+++ b/modules/monitoring/application/views/scripts/chart/servicegroup.phtml
@@ -1,5 +1,5 @@
-
+
=
$chart->render();
?>
diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml
index 3ed671fbe..645c35fb6 100644
--- a/modules/monitoring/application/views/scripts/list/comments.phtml
+++ b/modules/monitoring/application/views/scripts/list/comments.phtml
@@ -1,98 +1,114 @@
+getHelper('CommandForm');
+
+?>
+
+compact): ?>
-= $this->tabs ?>
-
-= $this->sortControl->render($this); ?>
-
-= $this->paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?>
+ = $this->tabs->render($this); ?>
+
+ = $this->translate('Sort by'); ?> = $this->sortControl->render($this); ?>
+
+ = $this->widget('limiter', array('url' => $this->url, 'max' => $comments->count())); ?>
+ = $this->paginationControl($comments, null, null, array('preserve' => $this->preserve)); ?>
+
-
+
\ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
index 98661f4e6..c110bf011 100644
--- a/modules/monitoring/application/views/scripts/list/contactgroups.phtml
+++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
@@ -22,7 +22,7 @@ foreach ($groupData as $groupName => $groupInfo): ?>
= $this->escape($c->contact_alias) ?>
diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml
index 642fd7b57..9fc05de8e 100644
--- a/modules/monitoring/application/views/scripts/list/contacts.phtml
+++ b/modules/monitoring/application/views/scripts/list/contacts.phtml
@@ -14,31 +14,41 @@ $contactHelper = $this->getHelper('ContactFlags');
href('monitoring/show/contacts', array('contact' => $contact->contact_name)); ?>
-
-
+ foreach ($contacts as $contact): ?>
+
-
+
+
+
diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml
index 995a66001..babb76074 100644
--- a/modules/monitoring/application/views/scripts/list/downtimes.phtml
+++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml
@@ -1,70 +1,121 @@
getHelper('CommandForm');
?>
-
-= $this->tabs ?>
-
-= $this->sortControl->render($this); ?>
-
-= $this->paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?>
-
-
-
-
-downtimes as $downtime): ?>
-
-
- = $this->dateFormat()->formatDateTime($downtime->start); ?> -
- = $this->dateFormat()->formatDateTime($downtime->end); ?>
-
- Duration: = $this->util()->showHourMin($downtime->duration); ?>
-
- The is_flexible): ?>flexiblefixed downtime is is_in_effect): ?>not in effect
-
-
- service)): ?>
- = $downtime->service ?>
- on = $downtime->host ?>
-
- = $downtime->host ?>
-
-
- = $downtime->author ?>: = $downtime->comment ?>
-
- Entry Time: = ($downtime->entry_time) ? $this->dateFormat()->formatDateTime((int) $downtime->entry_time) : ''; ?>
-
-
- $downtime->id,
- 'host' => $downtime->host
- );
- if (isset($downtime->service)) {
- $data['service'] = $downtime->service;
- }
- // echo $helper->iconSubmitForm(
- // 'img/icons/remove.png',
- echo $helper->labelSubmitForm(
- 'X',
- 'Remove Downtime',
- 'link-like',
- 'removedowntime',
- $data
- );
- ?>
-
-
-
-
-
+compact): ?>
+
+ = $this->tabs->render($this); ?>
+
+ = $this->translate('Sort by'); ?> = $this->sortControl->render($this); ?>
+
+ = $this->widget('limiter', array('url' => $this->url, 'max' => $downtimes->count())); ?>
+ = $this->paginationControl($downtimes, null, null, array('preserve' => $this->preserve)); ?>
+
+
+
+
+
+ = $this->translate('No downtimes matching the filter'); ?>
+
+
+
+
+
+
+ service)) {
+ $stateName = strtolower($this->util()->getServiceStateName($downtime->service_state));
+ } else {
+ $stateName = strtolower($this->util()->getHostStateName($downtime->host_state));
+ }
+ ?>
+
+
+ = $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?>
+
+ = $this->prefixedTimeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start); ?>
+
+
+ service)): ?>
+
+ = $downtime->service; ?>
+
+
+ = $this->translate('on'); ?> = $downtime->host; ?>
+
+
+
+ = $downtime->host; ?>
+
+
+
+ = $this->icon('comment.png'); ?> [= $downtime->author; ?>] = $downtime->comment; ?>
+
+
+ is_flexible): ?>
+ 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)
+ ); ?>
+
+ = 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)
+ ); ?>
+
+
+ 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)
+ ); ?>
+
+ = 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)
+ ); ?>
+
+
+
+
+ $downtime->id,
+ 'host' => $downtime->host
+ );
+ if (isset($downtime->service)) {
+ $data['service'] = $downtime->service;
+ }
+ ?>
+
+ = $helper->labelSubmitForm(
+ 'X',
+ 'Remove Downtime',
+ 'link-like',
+ 'removedowntime',
+ $data
+ ); ?>
+
+
+
+
+
diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
index 55257bae6..56c2198e9 100644
--- a/modules/monitoring/application/views/scripts/list/eventhistory.phtml
+++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
@@ -1,122 +1,115 @@
-
-compact): ?>
-
- = $this->tabs->render($this); ?>
-
- = $this->translate('Sort by') ?> = $this->sortControl->render($this); ?>
-
- = $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
-
-
+compact): ?>
+
+ = $this->tabs->render($this); ?>
+
+ = $this->translate('Sort by'); ?> = $this->sortControl->render($this); ?>
+
+ = $this->widget('limiter', array('url' => $this->url, 'max' => $this->history->count())); ?>
+ = $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
+
+
-= $this->translate('No entries found') ?>
+ = $this->translate('No history events matching the filter') ?>
-
-
-
-
- type) {
- case 'notify':
- $icon = 'notification';
- $title = 'Notification';
- $msg = $event->output;
- break;
- case 'comment':
- $icon = 'comment';
- $title = 'Comment';
- $msg = $event->output;
- break;
- case 'ack':
- $icon = 'acknowledgement';
- $title = 'Acknowledgement';
- $msg = $event->output;
- break;
- case 'dt_comment':
- $icon = 'in-downtime';
- $title = 'In Downtime';
- $msg = $event->output;
- break;
- case 'flapping':
- $icon = 'flapping';
- $title = 'Flapping';
- $msg = $event->output;
- break;
- case 'hard_state':
- $icon = '{{HARDSTATE_ICON}}';
- $title = 'Hard State';
- $msg = $event->output . 'Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Hard) ';
- $class = 'border-status-' . (
- $isService ?
- strtolower($this->util()->getServiceStateName($event->state)) :
- strtolower($this->util()->getHostStateName($event->state))
- );
- break;
- case 'soft_state':
- $icon = '{{SOFTSTATE_ICON}}';
- $title = 'Soft State';
- $msg = $event->output . 'Attempt ' . $event->attempt . '/' . $event->max_attempts . ' (Soft) ';
- $class = 'border-status-' . (
- $isService ?
- strtolower($this->util()->getServiceStateName($event->state)) :
- strtolower($this->util()->getHostStateName($event->state))
- );
- break;
- case 'dt_start':
- $icon = 'downtime-start';
- $title = 'Downtime Start';
- $msg = $event->output;
- break;
- case 'dt_end':
- $icon = 'downtime-end';
- $title = 'Downtime End';
- $msg = $event->output;
- break;
- }
- ?>
-
- ">
- = date('d.m. H:i', $event->timestamp); ?>
-
-
- service)): ?>
-
- = $event->service ?>
-
-
- on = $event->host ?>
-
-
-
-
- = $event->host ?>
-
-
-
-
-
-
-
-
+
+
+
+ service);
+ switch ($event->type) {
+ case 'notify':
+ $icon = 'notification';
+ $title = $this->translate('Notification');
+ $msg = $event->output;
+ break;
+ case 'comment':
+ $icon = 'comment';
+ $title = $this->translate('Comment');
+ $msg = $event->output;
+ break;
+ case 'ack':
+ $icon = 'acknowledgement';
+ $title = $this->translate('Acknowledgement');
+ $msg = $event->output;
+ break;
+ case 'dt_comment':
+ $icon = 'in_downtime';
+ $title = $this->translate('In Downtime');
+ $msg = $event->output;
+ break;
+ case 'flapping':
+ $icon = 'flapping';
+ $title = $this->translate('Flapping');
+ $msg = $event->output;
+ break;
+ case 'hard_state':
+ $icon = $isService ? 'service' : 'host';
+ $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
+ $stateName = (
+ $isService
+ ? strtolower($this->util()->getServiceStateName($event->state))
+ : strtolower($this->util()->getHostStateName($event->state))
+ );
+ $title = strtoupper($stateName); // TODO: Should be translatable!
+ break;
+ case 'soft_state':
+ $icon = 'softstate';
+ $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
+ $stateName = (
+ $isService
+ ? strtolower($this->util()->getServiceStateName($event->state))
+ : strtolower($this->util()->getHostStateName($event->state))
+ );
+ $title = strtoupper($stateName); // TODO: Should be translatable!
+ break;
+ case 'dt_start':
+ $icon = 'downtime_start';
+ $title = $this->translate('Downtime Start');
+ $msg = $event->output;
+ break;
+ case 'dt_end':
+ $icon = 'downtime_end';
+ $title = $this->translate('Downtime End');
+ $msg = $event->output;
+ break;
+ }
+ ?>
+
+
+ = $this->escape($title); ?>
+
+ = date('d.m. H:i', $event->timestamp); ?>
+
+
+
+
+ = $event->service; ?>
+
+
+ = $this->translate('on') . ' ' . $event->host; ?>
+
+
+
+ = $event->host; ?>
+
+
+
+
+ = $this->icon($icon . '.png', $title); ?> = empty($msg) ? '' : $msg; ?>
+
+
- endforeach; ?>
-
-
-
+ endforeach ?>
+
+
-
diff --git a/modules/monitoring/application/views/scripts/process/info.phtml b/modules/monitoring/application/views/scripts/process/info.phtml
index 350966c49..8fb9bd0e3 100644
--- a/modules/monitoring/application/views/scripts/process/info.phtml
+++ b/modules/monitoring/application/views/scripts/process/info.phtml
@@ -10,28 +10,33 @@ $cf = $this->getHelper('CommandForm');
Backend = $this->backendName; ?>
-= $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'); ?>.
- Last status update
+ = $this->translate('Last status update'); ?>
= $this->timeSince($ps->status_update_time) ?> ago
- Last check command
+ = $this->translate('Last check command'); ?>
= $this->timeSince($ps->last_command_check) ?> ago
- Global host event handler
- = $ps->global_host_event_handler ? $ps->global_host_event_handler : 'Not set' ?>
+ = $this->translate('Global host event handler'); ?>
+ = $ps->global_host_event_handler ? $ps->global_host_event_handler : $this->translate('Not set'); ?>
- Global service event handler
- = $ps->global_service_event_handler ? $ps->global_service_event_handler : 'Not set' ?>
+ = $this->translate('Global service event handler'); ?>
+ = $ps->global_service_event_handler ? $ps->global_service_event_handler : $this->translate('Not set'); ?>
- Notifications enabled
+ = $this->translate('Notifications enabled'); ?>
= $cf->toggleSubmitForm(
'',
$ps->notifications_enabled,
@@ -41,15 +46,18 @@ $cf = $this->getHelper('CommandForm');
array('global' => '1')
) ?>
notifications_enabled === '1'): ?>
- Temporarily disable
+
+ = $this->translate('Temporarily disable'); ?>
+
disable_notif_expire_time): ?>
-Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_time) ?>
+= sprintf($this->translate('Will be re-enabled in %s'), '' . $this->timeUntil($ps->disable_notif_expire_time) . ' '); ?>
- Execute active service checks
+ = $this->translate('Execute active service checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->active_service_checks_enabled,
@@ -60,7 +68,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Accept passive service checks
+ = $this->translate('Accept passive service checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->passive_service_checks_enabled,
@@ -71,7 +79,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Execute active host checks
+ = $this->translate('Execute active host checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->active_host_checks_enabled,
@@ -82,7 +90,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Accept passive host checks
+ = $this->translate('Accept passive host checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->passive_host_checks_enabled,
@@ -93,7 +101,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Eventhandlers enabled
+ = $this->translate('Eventhandlers enabled'); ?>
= $cf->toggleSubmitForm(
'',
$ps->event_handlers_enabled,
@@ -104,7 +112,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Obsessing over host checks
+ = $this->translate('Obsessing over host checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->obsess_over_hosts,
@@ -115,7 +123,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Obsessing over service checks
+ = $this->translate('Obsessing over service checks'); ?>
= $cf->toggleSubmitForm(
'',
$ps->obsess_over_services,
@@ -126,7 +134,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Flap detection enabled
+ = $this->translate('Flap detection enabled'); ?>
= $cf->toggleSubmitForm(
'',
$ps->flap_detection_enabled,
@@ -137,7 +145,7 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Process performance data
+ = $this->translate('Process performance data'); ?>
= $cf->toggleSubmitForm(
'',
$ps->process_performance_data,
@@ -148,17 +156,17 @@ Will be re-enabled in = $this->timeUntil($ps->disable_notif_expire_tim
) ?>
- Monitoring Process
+ = $this->translate('Monitoring Process'); ?>
= $cf->labelSubmitForm(
- 'Restart',
- 'Restart the monitoring process',
+ $this->translate('Restart'),
+ $this->translate('Restart the monitoring process'),
'',
'restartprocess'
) ?>