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/dashboard/dashboard.ini b/.vagrant-puppet/files/etc/icingaweb/dashboard/dashboard.ini
deleted file mode 100644
index 50e5a6373..000000000
--- a/.vagrant-puppet/files/etc/icingaweb/dashboard/dashboard.ini
+++ /dev/null
@@ -1,35 +0,0 @@
-[Incidents]
-title = "Current incidents"
-
-[Incidents.Service Problems]
-url = "monitoring/list/services"
-service_problem = 1
-limit = 10
-sort = service_severity
-
-[Incidents.Recently Recovered Services]
-url = "monitoring/list/services"
-sort = "service_last_state_change"
-service_state = 0
-limit = 10
-dir = "desc"
-
-[Incidents.Host Problems]
-url = "monitoring/list/hosts"
-host_problem = 1
-sort = host_severity
-
-[Landing]
-title = "Landing page"
-
-[Landing.Hostgroups]
-url = "monitoring/chart/hostgroup?height=400&width=500"
-
-[Landing.Servicegroups]
-url = "monitoring/chart/servicegroup?height=360&width=450"
-
-[Landing.Unhandled Problem Services]
-url = "monitoring/list/services?service_handled=0&service_problem=1"
-
-[Landing.Unhandled Problem Hosts]
-url = "monitoring/list/hosts?host_handled=0&host_problem=1"
diff --git a/.vagrant-puppet/files/etc/icingaweb/menu.ini b/.vagrant-puppet/files/etc/icingaweb/menu.ini
deleted file mode 100644
index f01ebaecc..000000000
--- a/.vagrant-puppet/files/etc/icingaweb/menu.ini
+++ /dev/null
@@ -1,30 +0,0 @@
-[Dashboard]
-title = "Dashboard"
-url = "dashboard"
-icon = "img/icons/dashboard.png"
-priority = 10
-
-[System]
-icon = img/icons/configuration.png
-priority = 200
-
-[System.Preferences]
-title = "Preferences"
-url = "preference"
-priority = 200
-
-[System.Configuration]
-title = "Configuration"
-url = "config"
-priority = 300
-
-[System.ApplicationLog]
-title = "Application log"
-url = "list/applicationlog"
-priority = 400
-
-[Logout]
-url = "authentication/logout"
-icon = img/icons/logout.png
-priority = 300
-
diff --git a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini b/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini
deleted file mode 100644
index c66a611a9..000000000
--- a/.vagrant-puppet/files/etc/icingaweb/modules/monitoring/menu.ini
+++ /dev/null
@@ -1,109 +0,0 @@
-
-[Problems]
-priority = 20
-icon = "img/icons/error.png"
-
-[Problems.Unhandled Hosts]
-priority = 40
-url = "monitoring/list/hosts?host_problem=1&host_handled=0"
-
-[Problems.Unhandled Services]
-priority = 40
-url = "monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity"
-
-[Problems.Host Problems]
-priority = 50
-url = "monitoring/list/hosts?host_problem=1&sort=host_severity"
-
-[Problems.Service Problems]
-priority = 50
-url = "monitoring/list/services?service_problem=1&sort=service_severity&dir=desc"
-
-[Problems.Current Downtimes]
-url = "monitoring/list/downtimes?downtime_is_in_effect=1"
-
-[Overview]
-priority = 30
-icon = "img/icons/hostgroup.png"
-
-[Overview.Tactical Overview]
-title = "Tactical Overview"
-url = "monitoring/tactical"
-priority = 40
-
-[Overview.Hosts]
-title = "Hosts"
-url = "monitoring/list/hosts"
-priority = 50
-
-[Overview.Services]
-title = "Services"
-url = "monitoring/list/services"
-priority = 50
-
-[Overview.Servicematrix]
-title = "Servicematrix"
-url = "monitoring/list/servicematrix?service_problem=1"
-priority = 51
-
-[Overview.Servicegroups]
-title = "Servicegroups"
-url = "monitoring/list/servicegroups"
-priority = 60
-
-[Overview.Hostgroups]
-title = "Hostgroups"
-url = "monitoring/list/hostgroups"
-priority = 60
-
-[Overview.Contactgroups]
-title = "Contactgroups"
-url = "monitoring/list/contactgroups"
-priority = 61
-
-[Overview.Downtimes]
-title = "Downtimes"
-url = "monitoring/list/downtimes"
-priority = 70
-
-[Overview.Comments]
-title = "Comments"
-url = "monitoring/list/comments"
-priority = 70
-
-[Overview.Contacts]
-title = "Contacts"
-url = "monitoring/list/contacts"
-priority = 70
-
-[History]
-icon = "img/icons/history.png"
-
-[History.Critical Events]
-title = "Critical Events"
-url = "monitoring/list/statehistorysummary"
-priority = 50
-
-[History.Notifications]
-title = "Notifications"
-url = "monitoring/list/notifications"
-
-[History.Events]
-title = "All Events"
-url = "monitoring/list/eventhistory?timestamp>=-7%20days"
-
-[History.Timeline]
-title = "Timeline"
-url = "monitoring/timeline"
-
-[System.Process Info]
-title = "Process Info"
-url = "monitoring/process/info"
-priority = 120
-
-[System.Performance Info]
-title = "Performance Info"
-url = "monitoring/process/performance"
-priority = 130
-
-
diff --git a/.vagrant-puppet/modules/casperjs/manifests/init.pp b/.vagrant-puppet/modules/casperjs/manifests/init.pp
index 2b9bac3ca..fd54e37f9 100644
--- a/.vagrant-puppet/modules/casperjs/manifests/init.pp
+++ b/.vagrant-puppet/modules/casperjs/manifests/init.pp
@@ -40,7 +40,7 @@ class casperjs(
require => Class['wget']
}
- $tld = inline_template('<%= File.basename(output, ".tar.bz2") %>')
+ $tld = inline_template('<%= File.basename(@output, ".tar.bz2") %>')
$src = "${cwd}/casperjs"
exec { 'extract-casperjs':
diff --git a/.vagrant-puppet/modules/cmmi/manifests/init.pp b/.vagrant-puppet/modules/cmmi/manifests/init.pp
index 64e42eb12..e0116fbc9 100644
--- a/.vagrant-puppet/modules/cmmi/manifests/init.pp
+++ b/.vagrant-puppet/modules/cmmi/manifests/init.pp
@@ -51,7 +51,7 @@ define cmmi(
require => Class['wget']
}
- $tld = inline_template('<%= File.basename(output, ".tar.gz") %>')
+ $tld = inline_template('<%= File.basename(@output, ".tar.gz") %>')
$src = "${cwd}/${name}/${tld}"
exec { "extract-${name}":
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/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php
index 7d4864d5a..4b43ed71b 100644
--- a/application/controllers/AuthenticationController.php
+++ b/application/controllers/AuthenticationController.php
@@ -35,12 +35,17 @@ class AuthenticationController extends ActionController
public function loginAction()
{
$auth = $this->Auth();
- $this->view->form = new LoginForm();
- $this->view->form->setRequest($this->_request);
+ $this->view->form = $form = new LoginForm();
+ $form->setRequest($this->_request);
$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);
@@ -49,13 +54,9 @@ 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.'
)
);
}
@@ -72,12 +73,20 @@ class AuthenticationController extends ActionController
}
}
}
- } elseif ($this->view->form->isSubmittedAndValid()) {
- $user = new User($this->view->form->getValue('username'));
- $password = $this->view->form->getValue('password');
+ } elseif ($form->isSubmittedAndValid()) {
+ $user = new User($form->getValue('username'));
+ $password = $form->getValue('password');
$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;
@@ -97,29 +106,29 @@ 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->addNote(
+ $form->addNote(
$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.'
)
);
}
- $this->view->form->getElement('password')->addError($this->translate('Incorrect username or password'));
+ $form->getElement('password')->addError($this->translate('Incorrect username or password'));
}
} catch (Exception $e) {
$this->view->errorInfo = $e->getMessage();
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
index 31739c30e..45be1d3fd 100644
--- a/application/controllers/ConfigController.php
+++ b/application/controllers/ConfigController.php
@@ -48,9 +48,6 @@ class ConfigController extends BaseConfigController
))->add('logging', array(
'title' => 'Logging',
'url' => 'config/logging'
- ))->add('modules', array(
- 'title' => 'Modules',
- 'url' => 'config/modules'
));
}
@@ -70,7 +67,7 @@ class ConfigController extends BaseConfigController
$form->setConfiguration(IcingaConfig::app());
$form->setRequest($this->_request);
if ($form->isSubmittedAndValid()) {
- if (!$this->writeConfigFile($form->getConfig(), 'config')) {
+ if (!$this->writeConfigFile($form->getConfig(), 'config')) {
return;
}
Notification::success('New configuration has successfully been stored');
@@ -107,6 +104,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/controllers/StaticController.php b/application/controllers/StaticController.php
index df930c3d7..3a15b1b1e 100644
--- a/application/controllers/StaticController.php
+++ b/application/controllers/StaticController.php
@@ -30,8 +30,8 @@ class StaticController extends ActionController
public function gravatarAction()
{
+ header('Content-Type: image/jpg');
$img = file_get_contents('http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->_request->getParam('email')))) . '?s=200&d=mm');
- header('image/jpeg');
echo $img;
}
diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php
index cbe25b623..0eff8006c 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
@@ -16,12 +17,19 @@ class LoginForm extends Form
*/
protected function create()
{
+ $url = Url::fromRequest()->without('renderLayout');
+
$this->setName('form_login');
$this->addElement('text', 'username', array(
'label' => t('Username'),
'placeholder' => t('Please enter your username...'),
'required' => true,
));
+ $redir = $this->addElement('hidden', 'redirect');
+ $redirectUrl = $url->shift('redirect');
+ if ($redirectUrl) {
+ $this->setDefault('redirect', $redirectUrl);
+ }
$this->addElement('password', 'password', array(
'label' => t('Password'),
@@ -34,6 +42,7 @@ class LoginForm extends Form
} else {
$this->getElement('username')->setAttrib('class', 'autofocus');
}
+ $this->setAction((string) $url);
$this->setSubmitLabel('Login');
}
}
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/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/config.ini b/config/modules/monitoring/config.ini
new file mode 100644
index 000000000..9b69fe86f
--- /dev/null
+++ b/config/modules/monitoring/config.ini
@@ -0,0 +1,2 @@
+[security]
+protected_customvars = "*pw*,*pass*,community"
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/config/modules/monitoring/menu.ini.in b/config/modules/monitoring/menu.ini.in
deleted file mode 100644
index ef874f6a7..000000000
--- a/config/modules/monitoring/menu.ini.in
+++ /dev/null
@@ -1,69 +0,0 @@
-[menu]
-;Remove component as of #4583 since it's not working
-;Issues.title = "Issues" ; Extended version
-;Issues.route = "/monitoring/list/services?problems=1&sort=severity" ; Explicit route
-;Issues.key = "issues" ; When this key is set in the controller, the item is active
-
-;Remove component as of #4583 since it's not working
-;Changes.title = "Recent Changes"
-;Changes.route = "/monitoring/list/services?sort=service_last_state_change"
-;_1 = 1 ;Spacer after this section
-
-Hosts.title = "Hosts"
-Hosts.route = "/monitoring/list/hosts"
-Hosts.iconClass = "icinga-icon-host-petrol"
-
-Services.title = "Services"
-Services.route = "/monitoring/list/services"
-Services.iconClass = "icinga-icon-service-petrol"
-
-Downtimes.title = "Downtimes"
-Downtimes.route = "/monitoring/list/downtimes"
-Downtimes.iconClass = "icinga-icon-down-petrol"
-
-Notifications.title = "Notifications"
-Notifications.route = "/monitoring/list/notifications"
-Notifications.iconClass = "icinga-icon-notification-petrol"
-
-Comments.title = "Comments"
-Comments.route = "/monitoring/list/comments"
-Comments.iconClass = "icinga-icon-comment-petrol"
-
-;Contacts = "/monitoring/list/contacts"
-
-;Contact Groups = "/monitoring/list/contactgroups"
-
-Servicegroups.title = "Servicegroups"
-Servicegroups.route = "/monitoring/list/servicegroups"
-Servicegroups.iconClass = "icinga-icon-servicegroup-petrol"
-
-Hostgroups.title = "Hostgroups"
-Hostgroups.route = "/monitoring/list/hostgroups"
-Hostgroups.iconClass = "icinga-icon-hostgroup-petrol"
-
-History.title = "History"
-History.route = "/monitoring/list/eventhistory"
-History.iconClass = "icinga-icon-history-petrol"
-
-Performance.title = "Performance"
-Performance.route ="/monitoring/process/performance"
-
-
-[Hosts]
-; New section
-; Title property unset means title is "Hosts"
-url="#"
-iconClass="icon-hosts"
-priority=1
-
-[Hosts.Problems]
-; New section beneath section hosts
-title="Problem Hosts"
-url="/monitoring/list/hosts?problem=1"
-priority=2
-
-[Hosts.A Link]
-title="Wiki"
-url="https://wiki.somewhere.com"
-priority=1
-icon="https://wiki.somewhere.com/icon.png"
\ No newline at end of file
diff --git a/icingaweb2.spec b/icingaweb2.spec
index 0da22b8ea..cfdcdafc2 100644
--- a/icingaweb2.spec
+++ b/icingaweb2.spec
@@ -40,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
diff --git a/library/Icinga/Application/Benchmark.php b/library/Icinga/Application/Benchmark.php
index c510c61bf..fd69dd510 100644
--- a/library/Icinga/Application/Benchmark.php
+++ b/library/Icinga/Application/Benchmark.php
@@ -142,6 +142,7 @@ class Benchmark
// TODO: Move formatting to CSS file
$html = '
' . "\n" . '';
foreach ($data->columns as & $col) {
+ if ($col->title === 'Time') continue;
$html .= sprintf(
'%s ',
$col->align,
@@ -153,6 +154,7 @@ class Benchmark
foreach ($data->rows as & $row) {
$html .= ' ';
foreach ($data->columns as $key => & $col) {
+ if ($col->title === 'Time') continue;
$html .= sprintf(
'%s ',
$col->align,
diff --git a/library/Icinga/Application/Loader.php b/library/Icinga/Application/Loader.php
index bc31cfbb1..56e59f4b2 100644
--- a/library/Icinga/Application/Loader.php
+++ b/library/Icinga/Application/Loader.php
@@ -40,9 +40,9 @@ class Loader
{
if (!is_dir($directory)) {
throw new ProgrammingError(sprintf(
- 'Namespace directory "%s" for "%s" does not exist',
- $namespace,
- $directory
+ 'Directory "%s" for namespace "%s" does not exist',
+ $directory,
+ $namespace
));
}
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/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/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 @@
+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());
}
}
diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php
index ef5a15aa9..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.
- $fragment = self::stripUrlFragment($url);
$urlParts = parse_url($url);
if (isset($urlParts['path'])) {
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
@@ -144,29 +140,14 @@ class Url
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
}
- if ($fragment) {
- $urlObject->setAnchor($fragment);
+ if (isset($urlParts['fragment'])) {
+ $urlObject->setAnchor($urlParts['fragment']);
}
$urlObject->setParams($params);
return $urlObject;
}
- /**
- * Remove the fragment-part of a given url and return it
- *
- * @param string $url The url to strip its fragment from
- *
- * @return null|string The stripped fragment, without the '#'
- */
- protected static function stripUrlFragment(&$url)
- {
- if (preg_match('@#(.*)$@', $url, $matches)) {
- $url = str_replace('#' . $matches[1], '', $url);
- return $matches[1];
- }
- }
-
/**
* Overwrite the baseUrl
*
@@ -226,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;
}
}
@@ -251,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);
}
/**
@@ -435,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 11fed8333..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,10 +316,10 @@ 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);
}
}
@@ -315,8 +328,11 @@ class UrlParams
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/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/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/controllers/ConfigController.php b/modules/monitoring/application/controllers/ConfigController.php
index c06ded57f..f0982c29a 100644
--- a/modules/monitoring/application/controllers/ConfigController.php
+++ b/modules/monitoring/application/controllers/ConfigController.php
@@ -14,6 +14,7 @@ use Icinga\Module\Monitoring\Form\Config\Backend\EditBackendForm;
use Icinga\Module\Monitoring\Form\Config\Backend\CreateBackendForm;
use Icinga\Module\Monitoring\Form\Config\Instance\EditInstanceForm;
use Icinga\Module\Monitoring\Form\Config\Instance\CreateInstanceForm;
+use Icinga\Module\Monitoring\Form\Config\SecurityForm;
use Icinga\Exception\NotReadableError;
@@ -216,7 +217,7 @@ class Monitoring_ConfigController extends ModuleActionController
/**
* Display a form to remove the instance identified by the 'instance' parameter
*/
- private function writeConfiguration($config, $file)
+ private function writeConfiguration($config, $file = null)
{
$target = $this->Config($file)->getConfigFile();
$writer = new PreservingIniWriter(array('filename' => $target, 'config' => $config));
@@ -258,4 +259,25 @@ class Monitoring_ConfigController extends ModuleActionController
$instanceCfg = $this->Config('instances');
return $instanceCfg && $instanceCfg->get($instance);
}
+
+ public function securityAction()
+ {
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('security');
+
+ $form = new SecurityForm();
+ $form->setConfiguration($this->Config()->get('security'));
+ $form->setRequest($this->getRequest());
+ if ($form->isSubmittedAndValid()) {
+ $config = $this->Config()->toArray();
+ $config['security'] = $form->getConfig();
+ if ($this->writeConfiguration(new Zend_Config($config))) {
+ Notification::success('Configuration modified successfully');
+ $this->redirectNow('monitoring/config/security');
+ } else {
+ $this->render('show-configuration');
+ return;
+ }
+ }
+ $this->view->form = $form;
+ }
}
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/MultiController.php b/modules/monitoring/application/controllers/MultiController.php
index a623dd88b..6459edd59 100644
--- a/modules/monitoring/application/controllers/MultiController.php
+++ b/modules/monitoring/application/controllers/MultiController.php
@@ -24,7 +24,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/forms/Config/SecurityForm.php b/modules/monitoring/application/forms/Config/SecurityForm.php
new file mode 100644
index 000000000..b0749868d
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/SecurityForm.php
@@ -0,0 +1,60 @@
+addElement(
+ 'text',
+ 'protected_customvars',
+ array(
+ 'label' => 'Protected Custom Variables',
+ 'required' => true,
+ 'value' => $this->config ? $this->config->get('protected_customvars', $default) : $default,
+ 'helptext' => 'Comma separated case insensitive list of protected custom variables.'
+ . ' Use * as a placeholder for zero or more wildcard characters.'
+ . ' Existance of those custom variables will be shown, but their values will be masked.'
+ )
+ );
+ $this->setSubmitLabel('Save');
+ }
+
+ /**
+ * Set the configuration to be used for initial population of the form
+ */
+ public function setConfiguration($config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Return the configuration set by this form
+ *
+ * @return Zend_Config The configuration set in this form
+ */
+ public function getConfig()
+ {
+ $values = $this->getValues();
+ return new Zend_Config(array(
+ 'protected_customvars' => $values['protected_customvars']
+ ));
+ }
+}
diff --git a/modules/monitoring/application/views/scripts/config/security.phtml b/modules/monitoring/application/views/scripts/config/security.phtml
new file mode 100644
index 000000000..71f2a341a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/security.phtml
@@ -0,0 +1,6 @@
+
+ = $this->tabs ?>
+
+
+ = $this->form ?>
+
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/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/show/components/customvars.phtml b/modules/monitoring/application/views/scripts/show/components/customvars.phtml
index 9eeb4a0ac..d89260e95 100644
--- a/modules/monitoring/application/views/scripts/show/components/customvars.phtml
+++ b/modules/monitoring/application/views/scripts/show/components/customvars.phtml
@@ -1,16 +1,8 @@
customvars) { return; }
-
foreach ($object->customvars as $name => $value) {
- $name = ucwords(str_replace('_', ' ', strtolower($name)));
- if (preg_match('~(?:pw|pass|community)~', strtolower($name))) {
- $value = '***';
- }
printf(
"%s %s \n",
$this->escape($name),
$this->escape($value)
);
}
-
diff --git a/modules/monitoring/application/views/scripts/show/history.phtml b/modules/monitoring/application/views/scripts/show/history.phtml
index 57832ae94..f2cfacb23 100644
--- a/modules/monitoring/application/views/scripts/show/history.phtml
+++ b/modules/monitoring/application/views/scripts/show/history.phtml
@@ -1,149 +1,127 @@
-service_description;
- $params = array(
- 'host' => $object->host_name,
- 'service' => $object->service_description
- );
-} else {
- $title = $object->host_name;
- $params = array('host' => $object->host_name);
-}
-
-// TODO: Remove this once we have better helpers
-$states = array(
- 'service' => array(
- 'ok',
- 'warning',
- 'critical',
- 'unknown',
- 99 => 'pending',
- ),
- 'host' => array(
- 'up',
- 'down',
- 'unreachable',
- 99 => 'pending',
- )
-);
-
-
-?>
-= $this->render('show/components/header.phtml') ?>
-
= $this->translate("This object's event history") ?>
-= $this->widget('limiter', array('url' => $this->url, 'max' => $this->history->count())) ?>
-= $this->paginationControl($this->history, null, null, array('preserve' => $this->preserve)); ?>
+
+ = $this->render('show/components/header.phtml'); ?>
+
= $this->translate('This Object\'s Event History'); ?>
+ = $this->widget('limiter', array('url' => $url, 'max' => $history->count())); ?>
+ = $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
-history->count() === 0): ?>
-= $this->translate('No History Available For This Object') ?>
+
+ = $this->translate('No history available for this object'); ?>
-
-
-
-history as $event):
-if (array_key_exists($event->state, $states[$event->object_type])) {
- $state_class = $states[$event->object_type][$event->state];
-} else {
- $state_class = 'invalid';
-}
-$extraTitle = false;
-switch ($event->type) {
- case 'notify':
- $icon = 'notification';
- $title = $this->translate('Notification');
- $state = $this->translate('ACK');
- break;
- case 'comment':
- $icon = 'comment';
- $title = $this->translate('Comment');
- break;
- case 'comment_deleted':
- $icon = 'remove';
- $title = $this->translate('Comment deleted');
- break;
- case 'ack':
- $icon = 'acknowledgement';
- $title = $this->translate('Acknowledge');
- break;
- case 'ack_deleted':
- $icon = 'remove';
- $title = $this->translate('Ack removed');
- break;
- case 'dt_comment':
- $icon = 'in_downtime';
- $title = $this->translate('In Downtime');
- break;
- case 'dt_comment_deleted':
- $icon = 'remove';
- $title = $this->translate('Downtime removed');
- break;
- case 'flapping':
- $icon = 'flapping';
- $title = $this->translate('Flapping');
- break;
- case 'flapping_deleted':
- $icon = 'remove';
- $title = $this->translate('Flapping stopped');
- break;
- case 'hard_state':
- $icon = 'submit';
- $title = strtoupper($state_class);
- break;
- case 'soft_state':
- $icon = 'softstate';
- $title = strtoupper($state_class);
- $extraTitle = $this->translate('Soft State');
- $state_class .= ' handled';
- break;
- case 'dt_start':
- $icon = 'downtime_start';
- $title = $this->translate('Downtime Start');
- break;
- case 'dt_end':
- $icon = 'downtime_end';
- $title = $this->translate('Downtime End');
- break;
-}
-
-
-
-?>
-
- = $this->escape($title) ?> = $this->prefixedTimeSince($event->timestamp) ?>= $extraTitle === false ? '' : ' ' . $this->escape($extraTitle) ?>
-
-
+
+
+ service_description);
+ 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 'comment_deleted':
+ $icon = 'remove';
+ $title = $this->translate('Comment deleted');
+ $msg = $event->output;
+ break;
+ case 'ack':
+ $icon = 'acknowledgement';
+ $title = $this->translate('Acknowledge');
+ $msg = $event->output;
+ break;
+ case 'ack_deleted':
+ $icon = 'remove';
+ $title = $this->translate('Ack removed');
+ $msg = $event->output;
+ break;
+ case 'dt_comment':
+ $icon = 'in_downtime';
+ $title = $this->translate('In Downtime');
+ $msg = $event->output;
+ break;
+ case 'dt_comment_deleted':
+ $icon = 'remove';
+ $title = $this->translate('Downtime removed');
+ $msg = $event->output;
+ break;
+ case 'flapping':
+ $icon = 'flapping';
+ $title = $this->translate('Flapping');
+ $msg = $event->output;
+ break;
+ case 'flapping_deleted':
+ $icon = 'remove';
+ $title = $this->translate('Flapping stopped');
+ $msg = $event->output;
+ break;
+ case 'hard_state':
+ $icon = $isService ? 'service' : 'host';
+ $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
+ $stateClass = (
+ $isService
+ ? strtolower($this->util()->getServiceStateName($event->state))
+ : strtolower($this->util()->getHostStateName($event->state))
+ );
+ $title = strtoupper($stateClass); // TODO: Should be translatable!
+ break;
+ case 'soft_state':
+ $icon = 'softstate';
+ $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output;
+ $stateClass = (
+ $isService
+ ? strtolower($this->util()->getServiceStateName($event->state))
+ : strtolower($this->util()->getHostStateName($event->state))
+ );
+ $title = strtoupper($stateClass); // TODO: Should be translatable!
+ break;
+ 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); ?>
+
+ tickets ? preg_replace_callback(
$this->tickets->getPattern(),
array($this->tickets, 'createLink'),
- $this->escape($event->output)
-) : $this->escape($event->output);
-
-echo $this->icon($icon . '.png', $title) . ' ';
-
-if ($object instanceof Host): ?>
- = $this->escape($event->service_description) ?>
-
-
- = $this->escape($event->service_description) ?> on = $this->escape($event->host_name) ?>
-attempt !== null) {
- printf('[ %d/%d ] ', $event->attempt, $event->max_attempts);
-}
-echo $output;
+ $this->escape($msg)
+) : $this->escape($msg);
?>
-
-
+
+ = $this->escape($event->service_description) . ' ' . $this->translate('on') . ' ' . $this->escape($event->host_name); ?>
+
+ = $this->escape($event->host_name); ?>
+
+
+
+ = $this->icon($icon . '.png', $title); ?> = empty($msg) ? '' : $msg; ?>
+
+
+
endforeach; ?>
-
-
+
+
diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml
index a5416614d..1d2b26ab2 100644
--- a/modules/monitoring/application/views/scripts/timeline/index.phtml
+++ b/modules/monitoring/application/views/scripts/timeline/index.phtml
@@ -48,25 +48,25 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g